package jade.util;

import jade.core.Specifier;
import jade.util.leap.ArrayList;
import jade.util.leap.Iterator;
import jade.util.leap.List;
import jade.util.leap.Map;
import jade.util.leap.HashMap;
import jade.util.leap.Properties;

import java.util.Vector;

//#J2ME_EXCLUDE_FILE
//#APIDOC_EXCLUDE_FILE

/**
 * Utility class to manage instances of classes with attached properties i.e. classes specified in the form<br>
 * foo.Bar[key1=value1,key2=value2...]
 * This class allows registering "Loaders" for given types of objects (e.g. agents) and successively load such 
 * objects using them
 */
public class ObjectManager {
	public static final String CLASS_NAME = "name";

	// Predefined object types
	/**
	 * The constant representing the "agent" type
	 */
	public static final String AGENT_TYPE = "agent";
	
	private static Map loaders = new HashMap();
	
	/**
	 * The interface to be implemented by classes that can be registered to load objects of a given type
	 */
	public static interface Loader {
		Object load(String className, Properties pp) throws ClassNotFoundException, IllegalAccessException, InstantiationException;
	}
	
	/**
	 * Convert a class-name, possibly with attached properties, into a Properties object 
	 * The actual class-name will be available as the value of the <code>CLASS_NAME</code> property
	 */
	private static Properties getClassProperties(String str) {
		Properties pp = new Properties();
		int index = str.indexOf('[');
		if (index < 0) {
			pp.setProperty(CLASS_NAME, str);
		}
		else {
			pp.setProperty(CLASS_NAME, str.substring(0, index));
			int index1 = str.indexOf(']');
			String propsStr = str.substring(index+1, index1);
			Vector propsList = Specifier.parseList(propsStr, ',');
			for (int i = 0; i < propsList.size(); ++i) {
				String ps = (String) propsList.get(i);
				int k = ps.indexOf('=');
				if (k > 0) {
					String name = ps.substring(0, k);
					String value = ps.substring(k+1);
					pp.setProperty(name, value);
				}
			}
		}
		return pp;
	}
	
	/**
	 * Register a <code>Loader</code> for a given type of object. Note that more than one <code>Loader</code>
	 * can be associated to a given type of object.
	 * @param type The type of object the registered <code>Loader</code> is associated to
	 * @param loader The <code>Loader</code> instance.
	 */
	public synchronized static void addLoader(String type, Loader loader) {
		List l = (List) loaders.get(type);
		if (l == null) {
			l = new ArrayList();
			loaders.put(type, l);
		}
		l.add(loader);
	}
	
	public synchronized static boolean removeLoader(String type, Loader loader) {
		List l = (List) loaders.get(type);
		if (l != null) {
			return l.remove(loader);
		}
		return false;
	}
	
	/**
	 * Try to load an object of a given type by means of the loaders (if any) associated to that type.  
	 * @param extendedClassName The class of the object to load in the form foo.Bar[key1=value1;key2=value2...]
	 * @param type The type of object to load 
	 * @return The loaded object or null if no loader is suitable to load the object.
	 * @throws ClassNotFoundException
	 * @throws IllegalAccessException
	 * @throws InstantiationException
	 */
	public synchronized static Object load(String extendedClassName, String type) throws ClassNotFoundException, IllegalAccessException, InstantiationException{
		if (extendedClassName == null) {
			throw new IllegalArgumentException("Null class name");
		}
		List l = (List) loaders.get(type);
		if (l != null && !l.isEmpty()) {
			// If we have loaders for this type of object, try to use them
			Properties pp = getClassProperties(extendedClassName);
			String className = pp.getProperty(ObjectManager.CLASS_NAME);
			Iterator it = l.iterator();
			while (it.hasNext()) {
				Loader loader = (Loader) it.next();
				Object obj = loader.load(className, pp);
				if (obj != null) {
					return obj;
				}
			}
		}
		return null;
	}
}
