/* * Copyright 2009-2011 Collaborative Research Centre SFB 632 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package annis.model; import java.io.Serializable; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Field; import java.util.SortedMap; import java.util.TreeMap; import org.apache.commons.lang.builder.EqualsBuilder; import org.apache.commons.lang.builder.HashCodeBuilder; import org.apache.log4j.Logger; /** * Base implementation of a class that only holds data values. * * <p> * Overrides equals() so that all fields in this class and its * superclasses are checked for equality. hashCode() is overridden * accordingly. * * <p> * The following fields are skipped: * <ul> * <li>fields annotated with @Transient</li> * <li>Logger instances</i> * <li>serialVersionUID</i> * </ul> */ public class DataObject implements Serializable { // this class is sent to the front end private static final long serialVersionUID = -800334837282048108L; @Retention(RetentionPolicy.RUNTIME) @Target(value=ElementType.FIELD) public static @interface Transient { } private static Logger log = Logger.getLogger(DataObject.class); // encapsulates the actions for each field in equals() and hashCode() private interface FieldCallBack { void doForField(Field field) throws IllegalAccessException; } // walks the class hierarchy down to DataObject and applies // fieldCallBack.doForField() for each field in each class private void forEachFieldDo(FieldCallBack fieldCallBack) { Class<?> clazz = this.getClass(); while (true) { Field[] fields = clazz.getDeclaredFields(); try { for (Field field : fields) { // FIXME: test skipped cases // skip Logger object if (field.getType() == Logger.class) continue; // skip fields annotated with @Transient if (field.getAnnotation(Transient.class) != null) continue; // skip serialVersionUID if ("serialVersionUID".equals(field.getName())) continue; field.setAccessible(true); fieldCallBack.doForField(field); } } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } if (clazz.getName().equals(DataObject.class.getName())) break; clazz = clazz.getSuperclass(); } } @Override public boolean equals(final Object obj) { // enforce equals contract (turn on debugging for this class to see the bug) if (obj == null) return false; if (this.getClass() != obj.getClass()) return false; final EqualsBuilder equalsBuilder = new EqualsBuilder(); final Object _this = this; forEachFieldDo(new FieldCallBack() { public void doForField(Field field) throws IllegalAccessException { Object thisValue = field.get(_this); Object otherValue = field.get(obj); if (log.isDebugEnabled()) { String fieldName = field.getDeclaringClass().getSimpleName() + "." + field.getName(); try { boolean equal = thisValue != null && thisValue.equals(otherValue) || thisValue == null && otherValue == null; log.debug(fieldName + ": " + thisValue + " " + (equal ? "=" : "!=") + " " + otherValue); } catch (RuntimeException e) { log.error("Exception while comparing " + fieldName + "(" + thisValue + ", " + otherValue + ")"); throw e; } } equalsBuilder.append(thisValue, otherValue); } }); return equalsBuilder.isEquals(); } @Override public int hashCode() { // sort fields by name for predictable results final SortedMap<String, Object> fieldValues = new TreeMap<String, Object>(); final Object _this = this; forEachFieldDo(new FieldCallBack() { public void doForField(Field field) throws IllegalAccessException { fieldValues.put(field.getName(), field.get(_this)); } }); HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); for (Object fieldValue : fieldValues.values()) hashCodeBuilder.append(fieldValue); return hashCodeBuilder.toHashCode(); } }