/*
* file: MutableObject.java
* package: oreilly.hcj.datamodeling
*
* This software is granted under the terms of the Common Public License,
* CPL, which may be found at the following URL:
* http://www-124.ibm.com/developerworks/oss/CPLv1.0.htm
*
* Copyright(c) 2003-2005 by the authors indicated in the @author tags.
* All Rights are Reserved by the various authors.
*
########## DO NOT EDIT ABOVE THIS LINE ########## */
package oreilly.hcj.datamodeling;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyChangeListener;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import oreilly.hcj.datamodeling.constraints.ObjectConstraint;
import oreilly.hcj.references.PropertyChangeSupport;
/**
* Base class for data model objects that are mutable.
*
* <p>
* All data model objects that are not constant descend from this class. It provides
* services for handling these objects such as firing property change events.
* </p>
*
* @author <a href="mailto:worderisor@yahoo.com">Robert Simmons jr.</a>
* @version $Revision: 1.10 $
*/
public abstract class MutableObject implements Serializable {
/** Use serialVersionUID for interoperability. */
private static final long serialVersionUID = 7902971632143872731L;
/**
* Holds a cache of constraints asked for. The cache is a structure of a Map of Maps.
* The outter map is keyed by class and contains a value which is another map.
* These maps are keyed by property name and have the constraint instances as
* values.
*/
private static final Map CONSTRAINT_CACHE = new HashMap();
/** Utility field used by bound properties. */
protected final transient PropertyChangeSupport propertyChangeSupport =
new PropertyChangeSupport(this);
/**
* Creates a new instance of MutableObject
*/
protected MutableObject() {
}
/**
* Fetch the named constraint for the given data type.
*
* @param dataType The class type for which to look up the constraint.
* @param name The name of the constraitn to look up.
*
* @return The named constraint or null.
*/
public static ObjectConstraint getConstraint(final Class dataType, final String name) {
Map constraintMap = getConstraintMap(dataType);
return (ObjectConstraint)constraintMap.get(name);
}
/**
* Return the cached constraints or index the constraints of a given class if they
* aren't already indexed.
*
* @param dataType The class type for which to index the constraints.
*
* @return The constraitn map for the given data type.
*
* @throws RuntimeException If there is a problem indexing the constraints. Please
* see contained exception if this occurs.
*/
public static final Map getConstraintMap(final Class dataType)
throws RuntimeException {
try {
Map constraintMap = (Map)CONSTRAINT_CACHE.get(dataType);
if (constraintMap == null) {
constraintMap = buildConstraintMap(dataType);
CONSTRAINT_CACHE.put(dataType, constraintMap);
return constraintMap;
}
return Collections.unmodifiableMap(constraintMap);
} catch (final IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
/**
* Adds a PropertyChangeListener to the listener list.
*
* @param listener The listener to add.
*/
public void addPropertyChangeListener(final PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
/**
* Adds a PropertyChangeListener to the listener list for a specific property.
*
* @param property The property to listen to.
* @param listener The listener to add.
*/
public void addPropertyChangeListener(final String property,
final PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(property, listener);
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public abstract boolean equals(Object obj);
/**
* @see java.lang.Object#hashCode()
*/
public abstract int hashCode();
/**
* Removes a PropertyChangeListener to the listener list.
*
* @param listener The listener to add.
*/
public void removePropertyChangeListener(final PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
/**
* Removes a PropertyChangeListener to the listener list for a specific property.
*
* @param property The property to listen to.
* @param listener The listener to add.
*/
public void removePropertyChangeListener(final String property,
final PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(property, listener);
}
/**
* {@inheritDoc} Creates as string containing the class name and the readable
* properties of the object.
*/
public String toString() {
try {
final BeanInfo info = Introspector.getBeanInfo(this.getClass(), Object.class);
final PropertyDescriptor[] props = info.getPropertyDescriptors();
final StringBuffer buf = new StringBuffer(500);
Object value = null;
buf.append(getClass().getName());
buf.append("@"); //$NON-NLS-1$
buf.append(hashCode());
buf.append("={"); //$NON-NLS-1$
for (int idx = 0; idx < props.length; idx++) {
if (idx != 0) {
buf.append(", "); //$NON-NLS-1$
}
buf.append(props[idx].getName());
buf.append("="); //$NON-NLS-1$
if (props[idx].getReadMethod() != null) {
value = props[idx].getReadMethod()
.invoke(this, null);
if (value instanceof MutableObject) {
buf.append("@"); //$NON-NLS-1$
buf.append(value.hashCode());
} else if (value instanceof Collection) {
buf.append("{"); //$NON-NLS-1$
for (Iterator iter = ((Collection)value).iterator();
iter.hasNext();) {
Object element = iter.next();
if (element instanceof MutableObject) {
buf.append("@"); //$NON-NLS-1$
buf.append(element.hashCode());
} else {
buf.append(element.toString());
}
}
buf.append("}"); //$NON-NLS-1$
} else if (value instanceof Map) {
buf.append("{"); //$NON-NLS-1$
Map map = (Map)value;
for (Iterator iter = map.keySet()
.iterator(); iter.hasNext();) {
Object key = iter.next();
Object element = map.get(key);
buf.append(key.toString() + "=");
if (element instanceof MutableObject) {
buf.append("@"); //$NON-NLS-1$
buf.append(element.hashCode());
} else {
buf.append(element.toString());
}
}
buf.append("}"); //$NON-NLS-1$
} else {
buf.append(value);
}
}
}
buf.append("}"); //$NON-NLS-1$
return buf.toString();
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
/**
* Creates a constraint map for the given class.
*
* @param dataType The data type to create the map for.
*
* @return The constructed unmodifiable constraint map.
*
* @throws IllegalAccessException If there is a security violation.
*/
protected static final Map buildConstraintMap(final Class dataType)
throws IllegalAccessException {
final int modifiers = Modifier.PUBLIC | Modifier.FINAL | Modifier.STATIC;
// --
Map constraintMap = new HashMap();
final Field[] fields = dataType.getFields();
Object value = null;
for (int idx = 0; idx < fields.length; idx++) {
if ((fields[idx].getModifiers() & modifiers) == modifiers) {
value = fields[idx].get(null);
if (value instanceof ObjectConstraint) {
constraintMap.put(((ObjectConstraint)value).getName(), value);
}
}
}
return Collections.unmodifiableMap(constraintMap);
}
}
/* ########## End of File ########## */
|