/*
* Copyright 2007 The Fornax Project Team, including the original
* author or authors.
*
* 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 org.sculptor.framework.domain;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.builder.ReflectionToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* Base class for all Domain Objects. It provides stuff like toString, equals
* and hashCode.
*
*/
public abstract class AbstractDomainObject implements Serializable, Cloneable {
private static final long serialVersionUID = -7580523094174795539L;
// acceptedToStringTypes contains Class objects or String names of types
// accepted by toString
private static Set<Object> acceptedToStringTypes = new HashSet<Object>();
static {
acceptedToStringTypes.add(int.class);
acceptedToStringTypes.add(Integer.class);
acceptedToStringTypes.add(long.class);
acceptedToStringTypes.add(Long.class);
acceptedToStringTypes.add(double.class);
acceptedToStringTypes.add(Double.class);
acceptedToStringTypes.add(BigDecimal.class);
acceptedToStringTypes.add(boolean.class);
acceptedToStringTypes.add(Boolean.class);
acceptedToStringTypes.add(String.class);
acceptedToStringTypes.add(Date.class);
acceptedToStringTypes.add(float.class);
acceptedToStringTypes.add(Float.class);
acceptedToStringTypes.add(short.class);
acceptedToStringTypes.add(Short.class);
// avoid runtime dependency to joda, since it is optional
acceptedToStringTypes.add("org.joda.time.LocalDate");
acceptedToStringTypes.add("org.joda.time.DateTime");
}
/**
* Only "simple" types will be included in toString. Relations to other
* domain objects can not be included since we don't know if they are
* fetched and we don't wont toString to fetch them.
* <p>
* More intelligent implementation can be done in subclasses if needed.
* Subclasses may also override {@link #acceptToString(Field)}.
* <p>
* The
*/
@Override
public String toString() {
try {
ReflectionToStringBuilder builder = new ReflectionToStringBuilder(this, toStringStyle()) {
@Override
protected boolean accept(Field f) {
if (super.accept(f)) {
return acceptToString(f);
} else {
return false;
}
}
};
builder.setAppendStatics(false);
builder.setAppendTransients(true);
return builder.toString();
} catch (RuntimeException e) {
// toString is only used for debug purpose and
// eventual exceptions should not be "ignored"
return "RuntimeException in " + getClass().getName() + ".toString():" + e.getMessage();
}
}
/**
* Subclass may override to define the style to use for toString, e.g.
* ToStringStyle.SHORT_PREFIX_STYLE.
*/
protected ToStringStyle toStringStyle() {
return null;
}
/**
* Subclasses may override this method to include or exclude some properties
* in toString. By default only "simple" types will be included in toString.
* Relations to other domain objects can not be included since we don't know
* if they are fetched and we don't wont toString to fetch them.
*
* @param field
* the field to include in toString if this method returns true
* @return true to include the field in toString, or false to exclude it
*/
protected boolean acceptToString(Field field) {
return acceptedToStringTypes.contains(field.getType())
|| acceptedToStringTypes.contains(field.getType().getName());
}
/**
* Subclass will typically override this method and return the real natural
* key or UUID key. If it is not overridden this default implementation
* returns null, which means that equals and hashCode will be implemented by
* java.lang.Object (if those methods are not overridden in subclass).
*/
protected Object getKey() {
return null;
}
@Override
public boolean equals(Object obj) {
if (getKey() == null) {
return super.equals(obj);
}
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!isSameClassHierarchy(obj.getClass())) {
return false;
}
AbstractDomainObject other = (AbstractDomainObject) obj;
Object key = getKey();
if (key instanceof BigDecimal) {
return ((BigDecimal) key).compareTo((BigDecimal) other.getKey()) == 0;
}
return key.equals(other.getKey());
}
protected boolean isSameClassHierarchy(Class<?> otherClass) {
Class<?> clz = getClass();
if (clz == otherClass) {
return true;
}
Class<?> potentialRoot;
do {
potentialRoot = clz;
clz = clz.getSuperclass();
} while (clz != AbstractDomainObject.class);
return potentialRoot.isAssignableFrom(otherClass);
}
@Override
public int hashCode() {
if (getKey() == null) {
return super.hashCode();
}
return getKey().hashCode();
}
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError();
}
}
}