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 com.sun.syndication.common.impl.BeanIntrospector;
20  
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 Class _beanClass;
42      private Object _obj;
43  
44      /***
45       * Default constructor.
46       * <p>
47       * To be used by classes extending EqualsBean only.
48       * <p>
49       * @param beanClass the class/interface to be used for property scanning.
50       *
51       */
52      protected EqualsBean(Class beanClass) {
53          _beanClass = beanClass;
54          _obj = this;
55      }
56  
57      /***
58       * Creates a EqualsBean to be used in a delegation pattern.
59       * <p>
60       * For example:
61       * <p>
62       * <code>
63       *   public class Foo  implements FooI {
64       *       private EqualsBean _equalsBean;
65       *
66       *       public Foo() {
67       *           _equalsBean = new EqualsBean(FooI.class);
68       *       }
69       *
70       *       public boolean equals(Object obj) {
71       *           return _equalsBean.beanEquals(obj);
72       *       }
73       *
74       *       public int hashCode() {
75       *           return _equalsBean.beanHashCode();
76       *       }
77       *
78       *   }
79       * </code>
80       * <p>
81       * @param beanClass the class/interface to be used for property scanning.
82       * @param obj object bean to test equality.
83       *
84       */
85      public EqualsBean(Class beanClass,Object obj) {
86          if (!beanClass.isInstance(obj)) {
87              throw new IllegalArgumentException(obj.getClass()+" is not instance of "+beanClass);
88          }
89          _beanClass = beanClass;
90          _obj = obj;
91      }
92  
93      /***
94       * Indicates whether some other object is "equal to" this object as defined by the Object equals() method.
95       * <p>
96       * To be used by classes extending EqualsBean. Although it works also for classes using
97       * EqualsBean in a delegation pattern, for correctness those classes should use the
98       * @see #beanEquals(Object) beanEquals method.
99       * <p>
100      * @param obj he reference object with which to compare.
101      * @return <b>true</b> if 'this' object is equal to the 'other' object.
102      *
103      */
104     public boolean equals(Object obj) {
105         return beanEquals(obj);
106     }
107 
108     /***
109      * Indicates whether some other object is "equal to" the object passed in the constructor,
110      * as defined by the Object equals() method.
111      * <p>
112      * To be used by classes using EqualsBean in a delegation pattern,
113      * @see #EqualsBean(Class,Object) constructor.
114      * <p>
115      * @param obj he reference object with which to compare.
116      * @return <b>true</b> if the object passed in the constructor is equal to the 'obj' object.
117      *
118      */
119     public boolean beanEquals(Object obj) {
120         Object bean1 = _obj;
121         Object bean2 = obj;
122         boolean eq;
123         if (bean1==null && bean2==null) {
124             eq = true;
125         }
126         else
127             if (bean1==null || bean2==null) {
128                 eq = false;
129             }
130             else {
131                 if (!_beanClass.isInstance(bean2)) {
132                     eq = false;
133                 }
134                 else {
135                     eq = true;
136                     try {
137                         PropertyDescriptor[] pds = BeanIntrospector.getPropertyDescriptors(_beanClass);
138                         if (pds!=null) {
139                             for (int i = 0; eq && i<pds.length; i++) {
140                                 Method pReadMethod = pds[i].getReadMethod();
141                                 if (pReadMethod!=null && // ensure it has a getter method
142                                         pReadMethod.getDeclaringClass()!=Object.class && // filter Object.class getter methods
143                                         pReadMethod.getParameterTypes().length==0) {     // filter getter methods that take parameters
144                                     Object value1 = pReadMethod.invoke(bean1, NO_PARAMS);
145                                     Object value2 = pReadMethod.invoke(bean2, NO_PARAMS);
146                                     eq = doEquals(value1, value2);
147                                 }
148                             }
149                         }
150                     }
151                     catch (Exception ex) {
152                         throw new RuntimeException("Could not execute equals()", ex);
153                     }
154                 }
155             }
156         return eq;
157     }
158 
159     /***
160      * Returns the hashcode for this object.
161      * <p>
162      * It follows the contract defined by the Object hashCode() method.
163      * <p>
164      * The hashcode is calculated by getting the hashcode of the Bean String representation.
165      * <p>
166      * To be used by classes extending EqualsBean. Although it works also for classes using
167      * EqualsBean in a delegation pattern, for correctness those classes should use the
168      * @see #beanHashCode() beanHashCode method.
169      * <p>
170      * @return the hashcode of the bean object.
171      *
172      */
173     public int hashCode() {
174         return beanHashCode();
175     }
176 
177     /***
178      * Returns the hashcode for the object passed in the constructor.
179      * <p>
180      * It follows the contract defined by the Object hashCode() method.
181      * <p>
182      * The hashcode is calculated by getting the hashcode of the Bean String representation.
183      * <p>
184      * To be used by classes using EqualsBean in a delegation pattern,
185      * @see #EqualsBean(Class,Object) constructor.
186      * <p>
187      * @return the hashcode of the bean object.
188      *
189      */
190     public int beanHashCode() {
191         return _obj.toString().hashCode();
192     }
193 
194 
195     private boolean doEquals(Object obj1, Object obj2) {
196         boolean eq = obj1==obj2;
197         if (!eq && obj1!=null && obj2!=null) {
198             Class classObj1 = obj1.getClass();
199             Class classObj2 = obj2.getClass();
200             if (classObj1.isArray() && classObj2.isArray()) {
201                 eq = equalsArray(obj1, obj2);
202             }
203             else {
204                 eq = obj1.equals(obj2);
205             }
206         }
207         return eq;
208     }
209 
210     private boolean equalsArray(Object array1, Object array2) {
211         boolean eq;
212         int length1 = Array.getLength(array1);
213         int length2 = Array.getLength(array2);
214         if (length1==length2) {
215             eq = true;
216             for (int i = 0; eq && i<length1; i++) {
217                 Object e1 = Array.get(array1, i);
218                 Object e2 = Array.get(array2, i);
219                 eq = doEquals(e1, e2);
220             }
221         }
222         else {
223             eq = false;
224         }
225         return eq;
226     }
227 
228 }
229