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