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 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 &&
138 pReadMethod.getDeclaringClass()!=Object.class &&
139 pReadMethod.getParameterTypes().length==0) {
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