1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 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 BeanInfo bi = Introspector.getBeanInfo(_beanClass);
138 PropertyDescriptor[] pds = bi.getPropertyDescriptors();
139 if (pds!=null) {
140 for (int i = 0; eq && i<pds.length; i++) {
141 Method pReadMethod = pds[i].getReadMethod();
142 if (pReadMethod!=null &&
143 pReadMethod.getDeclaringClass()!=Object.class &&
144 pReadMethod.getParameterTypes().length==0) {
145 Object value1 = pReadMethod.invoke(bean1, NO_PARAMS);
146 Object value2 = pReadMethod.invoke(bean2, NO_PARAMS);
147 eq = doEquals(value1, value2);
148 }
149 }
150 }
151 }
152 catch (Exception ex) {
153 throw new RuntimeException("Could not execute equals()", ex);
154 }
155 }
156 }
157 return eq;
158 }
159
160 /***
161 * Returns the hashcode for this object.
162 * <p>
163 * It follows the contract defined by the Object hashCode() method.
164 * <p>
165 * The hashcode is calculated by getting the hashcode of the Bean String representation.
166 * <p>
167 * To be used by classes extending EqualsBean. Although it works also for classes using
168 * EqualsBean in a delegation pattern, for correctness those classes should use the
169 * @see #beanHashCode() beanHashCode method.
170 * <p>
171 * @return the hashcode of the bean object.
172 *
173 */
174 public int hashCode() {
175 return beanHashCode();
176 }
177
178 /***
179 * Returns the hashcode for the object passed in the constructor.
180 * <p>
181 * It follows the contract defined by the Object hashCode() method.
182 * <p>
183 * The hashcode is calculated by getting the hashcode of the Bean String representation.
184 * <p>
185 * To be used by classes using EqualsBean in a delegation pattern,
186 * @see #EqualsBean(Class,Object) constructor.
187 * <p>
188 * @return the hashcode of the bean object.
189 *
190 */
191 public int beanHashCode() {
192 return _obj.toString().hashCode();
193 }
194
195
196 private boolean doEquals(Object obj1, Object obj2) {
197 boolean eq = obj1==obj2;
198 if (!eq && obj1!=null && obj2!=null) {
199 Class classObj1 = obj1.getClass();
200 Class classObj2 = obj2.getClass();
201 if (classObj1.isArray() && classObj2.isArray()) {
202 eq = equalsArray(obj1, obj2);
203 }
204 else {
205 eq = obj1.equals(obj2);
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