/*
 * @(#)ContentProducerRegistry.java
 *
 * Project:		JHotdraw - a GUI framework for technical drawings
 *				http://www.jhotdraw.org
 *				http://jhotdraw.sourceforge.net
 * Copyright:	� by the original author(s) and all contributors
 * License:		Lesser GNU Public License (LGPL)
 *				http://www.opensource.org/licenses/lgpl-license.html
 */
package org.jhotdraw.contrib.html;

import java.io.Serializable;
import java.net.URL;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

/**
 * ContentProducerRegistry acts as a repository for ContentProducers. It allows
 * for registries to be organized in a hierarchy, so that a specific producer
 * request will travel upwards in the hierarchy until eventually a suitable
 * producer is found.<br>
 * Producers are registered associated with the class of the object they are suitable
 * to produce contents for, but ContentProducerRegistry allows for class hierarchy
 * searches. What this means is that for two classes A and B, B being a subclass
 * of A, if a producer is registered for class B it will be used, otherwise the
 * producer registered for class A will be used instead. ContentProducerRegistry will
 * always select the super class closest in the class hierarchy, so if several
 * producers are registered for classes in a derivation hierarchy, the producer
 * registered for the class closest to the requested class will be selected.
 *
 * @author  Eduardo Francos - InContext
 * @created 7 mai 2002
 * @version <$CURRENT_VERSION$>
 */
/*
 * AJHD: refactored persistence - the interface extends Storable
 * through introductions/declare parents from the PersistentContentProviders aspect. 
 */
public class ContentProducerRegistry implements Serializable/*, Storable */{

	/** producers registered with this registry */
	private Hashtable fContentProducers = new Hashtable();

	/** parent registry for hierarchical searches */
	private transient ContentProducerRegistry fParent;

	/** Application global producers */
	private static ContentProducerRegistry fDefaultRegistry =
			new ContentProducerRegistry(null);

	// initialize the application wide default content producers
	static {
		fDefaultRegistry.registerContentProducer(URL.class, new URLContentProducer());
	}

	/**Constructor for the ContentProducerRegistry object */
	public ContentProducerRegistry() {
		setParent(fDefaultRegistry);
	}

	/**
	 *Constructor for the ContentProducerRegistry object
	 *
	 * @param parent  the parent for this producer
	 */
	public ContentProducerRegistry(ContentProducerRegistry parent) {
		setParent(parent);
	}

	/**
	 * Sets the autonomous attribute of the ContentProducerRegistry object.
	 * Similar to setting the parent to null<br>
	 * An autonomous registry does not have a parent hierarchy so resolution of
	 * search requests stop do not propagate.
	 *
	 * @see   #setParent(ContentProducerRegistry)
	 */
	public void setAutonomous() {
		setParent(null);
	}

	/**
	 * Gets the autonomous status of the ContentProducerRegistry object
	 *
	 * @return   The autonomous value
	 */
	public boolean isAutonomous() {
		return (getParent() == null);
	}

	/**
	 * Sets the parent attribute of the ContentProducerRegistry object
	 *
	 * @param newParent  The new parent value
	 * @see              #setAutonomous()
	 */
	public void setParent(ContentProducerRegistry newParent) {
		fParent = newParent;
	}

	/**
	 * Gets the parent attribute of the ContentProducerRegistry object
	 *
	 * @return   The parent value
	 */
	public ContentProducerRegistry getParent() {
		return fParent;
	}

	/**
	 * Registers an application global producer
	 *
	 * @param producer     the registered producer
	 * @param targetClass  The class associated with the producer
	 * @return             the previous producer registered for the target class
	 */
	public static ContentProducer registerDefaultContentProducer(Class targetClass, ContentProducer producer) {
		return fDefaultRegistry.registerContentProducer(targetClass, producer);
	}

	/**
	 * Unregisters ie: removes a registered producer for a target class.<br>
	 * A check is made to ensure the removed producer is the current producer
	 * for the target class. If not, the request is ignored.
	 *
	 * @param producer     the producer to unregister
	 * @param targetClass  the target class
	 */
	public static void unregisterDefaultContentProducer(Class targetClass, ContentProducer producer) {
		fDefaultRegistry.unregisterContentProducer(targetClass, producer);
	}

	/**
	 * Gets the ContentProducer attribute of the HTMLTextAreaFigure object
	 *
	 * @param targetClass  the target class
	 * @return             the previous producer registered for the target class
	 */
	public static ContentProducer getDefaultContentProducer(Class targetClass) {
		return fDefaultRegistry.getContentProducer(targetClass);
	}

	/**
	 * Gets the exact application global Producer for the target class, ie:
	 * no class hierarchy search
	 *
	 * @param targetClass  the target class
	 * @return             The producer
	 */
	public static ContentProducer getExactDefaultContentProducer(Class targetClass) {
		return fDefaultRegistry.getExactContentProducer(targetClass);
	}

	/**
	 * Registers a producer
	 *
	 * @param producer     the producer to register
	 * @param targetClass  the target class
	 * @return             the previous producer registered for the target class
	 */
	public ContentProducer registerContentProducer(Class targetClass, ContentProducer producer) {
		ContentProducer previousProducer = getContentProducer(targetClass);
		fContentProducers.put(targetClass, producer);
		return previousProducer;
	}

	/**
	 * Unregisters a producer
	 *
	 * @param producer     the producer to unregister
	 * @param targetClass  the target class
	 */
	public void unregisterContentProducer(Class targetClass, ContentProducer producer) {
		// first check that the producer being removed is the current producer
		// for the target class
		ContentProducer currentProducer = getContentProducer(targetClass);
		if (currentProducer == producer) {
			fContentProducers.remove(targetClass);
		}
	}

	/**
	 * Finds the most appropriate producer for the target class. Will search
	 * first for exact producers, ie: no class hierarchy search, then if none found,
	 * will do a class compatible search
	 *
	 * @param targetClass  The target class
	 * @return             The producer
	 */
	public ContentProducer getContentProducer(Class targetClass) {
		// first try an exact producer
		ContentProducer producer = getExactContentProducer(targetClass);
		if (producer != null) {
			return producer;
		}

		// none defined, try finding one for the nearest super class
		// of the target class. Note this shouldn't return null because there is
		// a default producer defined for the Object class
		return getSuperClassContentProducer(targetClass, null);
	}

	/**
	 * Finds the exact producer for the target class, ie: no class hierarchy search
	 *
	 * @param targetClass  The target class
	 * @return             The producer
	 */
	public ContentProducer getExactContentProducer(Class targetClass) {
		// first try our own
		ContentProducer producer = (ContentProducer)fContentProducers.get(targetClass);
		if (producer != null) {
			return producer;
		}

		// none defined, try our parent's
		// unless we are autonomous (ie: we have no parent)
		if (!this.isAutonomous()) {
			return getParent().getExactContentProducer(targetClass);
		}

		// none defined
		return null;
	}

	/**
	 * Gets the producers for the closest super class of the target class
	 *
	 * @param targetClass  The target class
	 * @return             The producer
	 */
	protected ContentProducer getSuperClassContentProducer(Class targetClass, Class closestClass) {
		Map.Entry entry = null;
		Class entryClass = null;
		ContentProducer closestProducer = null;

		Iterator iter = fContentProducers.entrySet().iterator();
		while (iter.hasNext()) {
			entry = (Map.Entry)iter.next();
			entryClass = (Class)entry.getKey();
			if (entryClass.isAssignableFrom(targetClass)) {
				if (closestClass != null && closestClass.isAssignableFrom(entryClass)) {
					closestClass = entryClass;
					closestProducer = (ContentProducer)entry.getValue();
				}
			}
		}

		// whether we found one or not ask our parent to see if a more
		// exact producer is defined
		// unless we are autonomous (ie: we have no parent)
		if (!this.isAutonomous()) {
			ContentProducer parentProducer =
					getParent().getSuperClassContentProducer(targetClass, closestClass);
			if (parentProducer != null) {
				closestProducer = parentProducer;
			}
		}

		return closestProducer;
	}

//	AJHD: refactored persistence - the persistence-specific methods,
//	write/read, are introduced via inter-type declarations from the
//	persistence.PersistentContentProducers aspect.
//	/**
//	 * Storable write support
//	 *
//	 * @param dw  the storable output
//	 */
//	public void write(StorableOutput dw) {
//		dw.writeInt(fContentProducers.size());
//		Map.Entry producerEntry;
//		Iterator iter = fContentProducers.entrySet().iterator();
//		while (iter.hasNext()) {
//			producerEntry = (Map.Entry)iter.next();
//			dw.writeString(((Class)producerEntry.getKey()).getName());
//			dw.writeStorable((Storable)producerEntry.getKey());
//		}
//	}
//
//	/**
//	 * Storable inoput support
//	 *
//	 * @param dr               storable input
//	 * @exception IOException  thrown by called methods
//	 */
//	public void read(StorableInput dr) throws IOException {
//		// read the default content producers, count first
//		int prodCount = dr.readInt();
//		String prodClass;
//		ContentProducer producer;
//		for (int cnt = 0; cnt < prodCount; cnt++) {
//			prodClass = dr.readString();
//			producer = (ContentProducer)dr.readStorable();
//			try {
//				registerContentProducer(Class.forName(prodClass), producer);
//			}
//			catch (ClassNotFoundException ex) {
//				// the class does not exist in this application
//				// cannot do much about it so ignore it, the entities of
//				// this class will get their toString() value instead
//			}
//		}
//
//	}
}
