001: /*
002: * Copyright 2004 Sun Microsystems, Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: *
016: */
017: package com.sun.syndication.feed.impl;
018:
019: import java.beans.PropertyDescriptor;
020: import java.lang.reflect.Array;
021: import java.lang.reflect.Method;
022: import java.io.Serializable;
023:
024: /**
025: * Provides deep <b>Bean</b> equals() and hashCode() functionality for Java Beans.
026: * <p>
027: * It works on all read/write properties, recursively. It support all primitive types, Strings, Collections,
028: * bean-like objects and multi-dimensional arrays of any of them.
029: * <p>
030: * The hashcode is calculated by getting the hashcode of the Bean String representation.
031: * <p>
032: * @author Alejandro Abdelnur
033: *
034: */
035: public class EqualsBean implements Serializable {
036:
037: private static final Object[] NO_PARAMS = new Object[0];
038:
039: private Class _beanClass;
040: private Object _obj;
041:
042: /**
043: * Default constructor.
044: * <p>
045: * To be used by classes extending EqualsBean only.
046: * <p>
047: * @param beanClass the class/interface to be used for property scanning.
048: *
049: */
050: protected EqualsBean(Class beanClass) {
051: _beanClass = beanClass;
052: _obj = this ;
053: }
054:
055: /**
056: * Creates a EqualsBean to be used in a delegation pattern.
057: * <p>
058: * For example:
059: * <p>
060: * <code>
061: * public class Foo implements FooI {
062: * private EqualsBean _equalsBean;
063: *
064: * public Foo() {
065: * _equalsBean = new EqualsBean(FooI.class);
066: * }
067: *
068: * public boolean equals(Object obj) {
069: * return _equalsBean.beanEquals(obj);
070: * }
071: *
072: * public int hashCode() {
073: * return _equalsBean.beanHashCode();
074: * }
075: *
076: * }
077: * </code>
078: * <p>
079: * @param beanClass the class/interface to be used for property scanning.
080: * @param obj object bean to test equality.
081: *
082: */
083: public EqualsBean(Class beanClass, Object obj) {
084: if (!beanClass.isInstance(obj)) {
085: throw new IllegalArgumentException(obj.getClass()
086: + " is not instance of " + beanClass);
087: }
088: _beanClass = beanClass;
089: _obj = obj;
090: }
091:
092: /**
093: * Indicates whether some other object is "equal to" this object as defined by the Object equals() method.
094: * <p>
095: * To be used by classes extending EqualsBean. Although it works also for classes using
096: * EqualsBean in a delegation pattern, for correctness those classes should use the
097: * @see #beanEquals(Object) beanEquals method.
098: * <p>
099: * @param obj he reference object with which to compare.
100: * @return <b>true</b> if 'this' object is equal to the 'other' object.
101: *
102: */
103: public boolean equals(Object obj) {
104: return beanEquals(obj);
105: }
106:
107: /**
108: * Indicates whether some other object is "equal to" the object passed in the constructor,
109: * as defined by the Object equals() method.
110: * <p>
111: * To be used by classes using EqualsBean in a delegation pattern,
112: * @see #EqualsBean(Class,Object) constructor.
113: * <p>
114: * @param obj he reference object with which to compare.
115: * @return <b>true</b> if the object passed in the constructor is equal to the 'obj' object.
116: *
117: */
118: public boolean beanEquals(Object obj) {
119: Object bean1 = _obj;
120: Object bean2 = obj;
121: boolean eq;
122: if (bean1 == null && bean2 == null) {
123: eq = true;
124: } else if (bean1 == null || bean2 == null) {
125: eq = false;
126: } else {
127: if (!_beanClass.isInstance(bean2)) {
128: eq = false;
129: } else {
130: eq = true;
131: try {
132: PropertyDescriptor[] pds = BeanIntrospector
133: .getPropertyDescriptors(_beanClass);
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: && // ensure it has a getter method
139: pReadMethod.getDeclaringClass() != Object.class
140: && // filter Object.class getter methods
141: pReadMethod.getParameterTypes().length == 0) { // filter getter methods that take parameters
142: Object value1 = pReadMethod.invoke(
143: bean1, NO_PARAMS);
144: Object value2 = pReadMethod.invoke(
145: bean2, NO_PARAMS);
146: eq = doEquals(value1, value2);
147: }
148: }
149: }
150: } catch (Exception ex) {
151: throw new RuntimeException(
152: "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: private boolean doEquals(Object obj1, Object obj2) {
195: boolean eq = obj1 == obj2;
196: if (!eq && obj1 != null && obj2 != null) {
197: Class classObj1 = obj1.getClass();
198: Class classObj2 = obj2.getClass();
199: if (classObj1.isArray() && classObj2.isArray()) {
200: eq = equalsArray(obj1, obj2);
201: } else {
202: eq = obj1.equals(obj2);
203: }
204: }
205: return eq;
206: }
207:
208: private boolean equalsArray(Object array1, Object array2) {
209: boolean eq;
210: int length1 = Array.getLength(array1);
211: int length2 = Array.getLength(array2);
212: if (length1 == length2) {
213: eq = true;
214: for (int i = 0; eq && i < length1; i++) {
215: Object e1 = Array.get(array1, i);
216: Object e2 = Array.get(array2, i);
217: eq = doEquals(e1, e2);
218: }
219: } else {
220: eq = false;
221: }
222: return eq;
223: }
224:
225: }
|