import java.io.*;

import java.lang.reflect.*;

import java.util.*;


/**
 * Adapted (genericised and simplified) from code
 * found at http://www.koders.com/java/fid05CDA0D5C0A416F30AFEA2731C4E59A4D97EA53E.aspx?s=cdef%3Ajava
 */

public class Factory {
    public static final String PROPERTY_FILE_KEY = "com.technojeeves.factory";
    protected static Factory instance; //the singleton Factory instance
    protected Map<String, Class> classes = new TreeMap<String, Class>();
    protected Map<String, Object> objects = new TreeMap<String, Object>(); //singleton instances
    protected Properties keysToClassNames;

    private Factory() {
	try {
	    initFromSystemProperties();
	} catch (Exception ex) {
	    ex.printStackTrace();
	    throw new RuntimeException(ex.getClass().getName() + ": " +
		    ex.getMessage());
	}
    }

    private Factory(Map<String, Class> mappings) {
	classes = mappings;
    }

    public static Factory getFactory() {
	if (instance == null) {
	    instance = new Factory();
	}

	return instance;
    }

    public static Factory getFactory(Map<String, Class> mappings) {
	if (instance == null) {
	    instance = new Factory(mappings);
	}

	return instance;
    }

    public Object newInstance(String abstractName, Class<?>[] paramTypes,
	    Object[] params) {
	Object obj = null;

	try {
	    Class<?> cls = classes.get(abstractName);

	    if (cls == null) {
		throw new RuntimeException("No class registered under " +
			abstractName);
	    }

	    Constructor<?> ctor = cls.getConstructor(paramTypes);
	    obj = ctor.newInstance(params);
	} catch (Exception ex) {
	    ex.printStackTrace();
	}

	return obj;
    }

    public Object newInstance(String abstractName) {
	return newInstance(abstractName, new Class[] {  }, new Object[] {  });
    }

    /**
     * Returns a singleton instance of the class
     */
    public Object getInstance(String abstractName, Class[] paramTypes,
	    Object[] params) {
	Object obj = objects.get(abstractName);

	if (obj == null) {
	    obj = newInstance(abstractName, paramTypes, params);
	    objects.put(abstractName, obj);
	}

	return obj;
    }

    public Object getInstance(String abstractName) {
	return getInstance(abstractName, new Class[] {  }, new Object[] {  });
    }

    protected void initFromSystemProperties() throws Exception {
	String fileName = System.getProperty(PROPERTY_FILE_KEY);

	if (fileName == null) {
	    throw new RuntimeException("No system property " +
		    PROPERTY_FILE_KEY + " found");
	}

	keysToClassNames = new Properties();
	FileInputStream fi = new FileInputStream(fileName);
	keysToClassNames.load(fi);
	fi.close();

	Iterator it = keysToClassNames.entrySet().iterator();

	while (it.hasNext()) {
	    Map.Entry entry = (Map.Entry) it.next();
	    String abstractName = (String) entry.getKey();
	    String className = (String) entry.getValue();
	    Class cls = Class.forName(className);
	    classes.put(abstractName, cls);
	}
    }
}
