View Javadoc

1   /*
2    * Copyright 2004 Sun Microsystems, Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   */
17  package com.sun.syndication.common;
18  
19  import java.beans.BeanInfo;
20  import java.beans.Introspector;
21  import java.beans.PropertyDescriptor;
22  import java.lang.reflect.Array;
23  import java.lang.reflect.Method;
24  import java.io.Serializable;
25  
26  /***
27   * Provides deep <b>Bean</b> equals() and hashCode() functionality for Java Beans.
28   * <p>
29   * It works on all read/write properties, recursively. It support all primitive types, Strings, Collections,
30   * bean-like objects and multi-dimensional arrays of any of them.
31   * <p>
32   * The hashcode is calculated by getting the hashcode of the Bean String representation.
33   * <p>
34   * @author Alejandro Abdelnur
35   *
36   */
37  public class EqualsBean implements Serializable {
38  
39      private static final Object[] NO_PARAMS = new Object[0];
40  
41      private Object _obj;
42  
43      /***
44       * Default constructor.
45       * <p>
46       * To be used by classes extending EqualsBean only.
47       * <p>
48       *
49       */
50      protected EqualsBean() {
51          _obj = this;
52      }
53  
54      /***
55       * Creates a EqualsBean to be used in a delegation pattern.
56       * <p>
57       * For example:
58       * <p>
59       * <code>
60       *   public class Foo  {
61       *       private EqualsBean _equalsBean;
62       *
63       *       public Foo() {
64       *           _equalsBean = new EqualsBean();
65       *       }
66       *
67       *       public boolean equals(Object obj) {
68       *           return _equalsBean.beanEquals(obj);
69       *       }
70       *
71       *       public int hashCode() {
72       *           return _equalsBean.beanHashCode();
73       *       }
74       *
75       *   }
76       * </code>
77       * <p>
78       * @param obj object bean to test equality.
79       *
80       */
81      public EqualsBean(Object obj) {
82          _obj = obj;
83      }
84  
85      /***
86       * Indicates whether some other object is "equal to" this object as defined by the Object equals() method.
87       * <p>
88       * To be used by classes extending EqualsBean. Although it works also for classes using
89       * EqualsBean in a delegation pattern, for correctness those classes should use the
90       * @see #beanEquals(Object) beanEquals method.
91       * <p>
92       * @param obj he reference object with which to compare.
93       * @return <b>true</b> if 'this' object is equal to the 'other' object.
94       *
95       */
96      public boolean equals(Object obj) {
97          return beanEquals(obj);
98      }
99  
100     /***
101      * Indicates whether some other object is "equal to" the object passed in the constructor,
102      * as defined by the Object equals() method.
103      * <p>
104      * To be used by classes using EqualsBean in a delegation pattern,
105      * @see #EqualsBean(Object) constructor.
106      * <p>
107      * @param obj he reference object with which to compare.
108      * @return <b>true</b> if the object passed in the constructor is equal to the 'obj' object.
109      *
110      */
111     public boolean beanEquals(Object obj) {
112         Object bean1 = _obj;
113         Object bean2 = obj;
114         boolean eq;
115         if (bean1==null && bean2==null) {
116             eq = true;
117         }
118         else
119             if (bean1==null || bean2==null) {
120                 eq = false;
121             }
122             else {
123                 Class bean1Class = bean1.getClass();
124                 Class bean2Class = bean2.getClass();
125                 if (!bean1Class.equals(bean2Class)) {
126                     eq = false;
127                 }
128                 else {
129                     eq = true;
130                     Class beanClass = bean1Class;
131                     try {
132                         BeanInfo bi = Introspector.getBeanInfo(beanClass);
133                         PropertyDescriptor[] pds = bi.getPropertyDescriptors();
134                         if (pds!=null) {
135                             for (int i = 0; eq && i<pds.length; i++) {
136                                 Method pReadMethod = pds[i].getReadMethod();
137                                 if (pReadMethod!=null && // ensure it has a getter method
138                                         pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
139                                         pReadMethod.getParameterTypes().length==0) {     // filter getter methods that take parameters
140                                     Object value1 = pReadMethod.invoke(bean1, NO_PARAMS);
141                                     Object value2 = pReadMethod.invoke(bean2, NO_PARAMS);
142                                     eq = doEquals(value1, value2);
143                                 }
144                             }
145                         }
146                     }
147                     catch (Exception ex) {
148                         throw new RuntimeException("Could not execute equals()", ex);
149                     }
150                 }
151             }
152         return eq;
153     }
154 
155     /***
156      * Returns the hashcode for this object.
157      * <p>
158      * It follows the contract defined by the Object hashCode() method.
159      * <p>
160      * The hashcode is calculated by getting the hashcode of the Bean String representation.
161      * <p>
162      * To be used by classes extending EqualsBean. Although it works also for classes using
163      * EqualsBean in a delegation pattern, for correctness those classes should use the
164      * @see #beanHashCode() beanHashCode method.
165      * <p>
166      * @return the hashcode of the bean object.
167      *
168      */
169     public int hashCode() {
170         return beanHashCode();
171     }
172 
173     /***
174      * Returns the hashcode for the object passed in the constructor.
175      * <p>
176      * It follows the contract defined by the Object hashCode() method.
177      * <p>
178      * The hashcode is calculated by getting the hashcode of the Bean String representation.
179      * <p>
180      * To be used by classes using EqualsBean in a delegation pattern,
181      * @see #EqualsBean(Object) constructor.
182      * <p>
183      * @return the hashcode of the bean object.
184      *
185      */
186     public int beanHashCode() {
187         return _obj.toString().hashCode();
188     }
189 
190 
191     private boolean doEquals(Object obj1, Object obj2) {
192         boolean eq = obj1==obj2;
193         if (!eq && obj1!=null && obj2!=null) {
194             Class classObj1 = obj1.getClass();
195             Class classObj2 = obj2.getClass();
196             if (classObj1.equals(classObj2)) {
197                 if (classObj1.isArray()) {
198                     eq = equalsArray(obj1, obj2);
199                 }
200                 else {
201                     eq = obj1.equals(obj2);
202                 }
203             }
204             else {
205                 eq = false;
206             }
207         }
208         return eq;
209     }
210 
211     private boolean equalsArray(Object array1, Object array2) {
212         boolean eq;
213         int length1 = Array.getLength(array1);
214         int length2 = Array.getLength(array2);
215         if (length1==length2) {
216             eq = true;
217             for (int i = 0; eq && i<length1; i++) {
218                 Object e1 = Array.get(array1, i);
219                 Object e2 = Array.get(array2, i);
220                 eq = doEquals(e1, e2);
221             }
222         }
223         else {
224             eq = false;
225         }
226         return eq;
227     }
228 
229 }
230