1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 &&
144 pReadMethod.getDeclaringClass()!=Object.class &&
145 pReadMethod.getParameterTypes().length==0) {
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