FileDocCategorySizeDatePackage
MutableObject.javaAPI DocExample8222Sun Dec 14 22:47:40 GMT 2003oreilly.hcj.datamodeling

MutableObject.java

/*
 *     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 ########## */