package org.treesched;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Modifier;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;


/**
 * Create a schedule from a name. Useful for building
 * shcedules from configuration files.
 */
public class ScheduleFactory {

	private final Hashtable classDefinitions = new Hashtable();
	private static ScheduleFactory instance = null;

	/**
	 * Private constructor.
	 */
	private ScheduleFactory() {		
	}

	/**
	 * Get the singleton instance of the factory.
	 * 
	 * @return the ScheduleFactory instance.
	 */
	public static ScheduleFactory getInstance() {

		if (instance == null) {
			instance = new ScheduleFactory();
			instance.addResource("schedule.properties");
		}
		return instance;		
	}
	
	/**
	 * Add a resource properties file of class definitions to the factory.
	 * 
	 * @param classDefFileName A property file of class names.
	 */
	public void addResource(String classDefFileName) {
		Properties props = new Properties();
		try {
			InputStream in = this.getClass().getResourceAsStream(classDefFileName);
	
			if (in == null) {
					throw new ScheduleFactoryException("Can't load class def list");
			}
			props.load(in);
			in.close();
		}
		catch (IOException e) {
			throw new ScheduleFactoryException("Failed to load class def list.", e);	
		}
		addProperties(props);
	}
		
	/**
	 * Add a property file of class definitions to the factory.
	 * 
	 * @param file A property file.
	 */	
	public void addFile(File file) {
		Properties props = new Properties();
		try {
			InputStream in = new FileInputStream(file);
			if (in == null) {
					throw new ScheduleFactoryException("Can't load class def list");
			}
			props.load(in);
			in.close();
		}
		catch (IOException e) {
			throw new ScheduleFactoryException("Failed to load class def list.", e);	
		}
		addProperties(props);
	}
	
	/**
	 * Add a property set of class definitions to the factory.
	 * 
	 * @param props Property class definitions.
	 */
	public void addProperties(Properties props) {
		
		Enumeration enum = props.propertyNames();
		while (enum.hasMoreElements()) {
			String key = (String) enum.nextElement();
			String value = props.getProperty(key);
			try {
				Class taskClass = Class.forName(value);
				addFactoryDefinition(key, taskClass);
			} catch (NoClassDefFoundError ncdfe) {
				throw new ScheduleFactoryException("Could not load a dependent class ("
						+ ncdfe.getMessage() + ") for task " + key, ncdfe);
			} catch (ClassNotFoundException cnfe) {
				throw new ScheduleFactoryException("Could not load class (" + value
						+ ") for task " + key, cnfe);
			}
		}
	}

	/**
	 * Adds a new schedule class definition to the factory.
	 *
	 * @param name The name of the schedule to add.
	 *                 Must not be <code>null</code>.
	 * @param class The full name of the class implementing the shcedule.
	 *                  Must not be <code>null</code>.
	 *
	 * @exception ScheduleFactoryException if the class is unsuitable for being a
	 *                           schedule.
	 */

	public void addFactoryDefinition(String objName, Class objClass) throws ScheduleFactoryException {

		checkTaskClass(objClass);
		classDefinitions.put(objName, objClass);
	}

	/**
	 * Checks whether or not a class is suitable for being a schedule.
	 *
	 * @param theClass The class to be checked.
	 *                  Must not be <code>null</code>.
	 *
	 * @exception ScheduleFactoryException if the class is unsuitable for being an Ant
	 *                           task. An error level message is logged before
	 *                           this exception is thrown.
	 */

	public void checkTaskClass(final Class theClass) throws ScheduleFactoryException {

		if (!Modifier.isPublic(theClass.getModifiers())) {
			final String message = theClass + " is not public";
			throw new ScheduleFactoryException(message);
		}
		if (Modifier.isAbstract(theClass.getModifiers())) {
			final String message = theClass + " is abstract";
			throw new ScheduleFactoryException(message);
		}
		try {
			theClass.getConstructor(null);
			// don't have to check for public, since
			// getConstructor finds public constructors only.
		} catch (NoSuchMethodException e) {
			final String message = "No public no-arg constructor in "
				+ theClass;
			throw new ScheduleFactoryException(message);
		}
	}

	/**
	 * The factory method.
	 * 
	 * @param name The name of the scheudle.
	 * @return The schedule.
	 * @throws ScheduleFactoryException If the schedule couldn't be created.
	 */
	public Schedule createSchedule(String name) throws ScheduleFactoryException {

		Class c = (Class) classDefinitions.get(name);
		Object o = null;
		try {
			o = c.newInstance();
		}
		catch (Throwable t) {
			String msg = "Could not create schedule of type: "
				 + name + " due to " + t;
			throw new ScheduleFactoryException(msg, t);
		}
		return (Schedule)o;
	}
}
