package org.jvnet.lafplugin;

import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import javax.swing.UIDefaults;

/**
 * Plugin manager for look-and-feels.
 * 
 * @author Kirill Grouchnikov
 * @author Erik Vickroy
 * @author Robert Beeger
 * @author Frederic Lavigne
 * @author Pattrick Gotthardt
 */
public class PluginManager {
	private String mainTag;

	private String pluginTag;

	private String xmlName;

	private Set plugins;

	/**
	 * Simple constructor.
	 * 
	 * @param xmlName
	 *            The name of XML file that contains plugin configuration.
	 * @param mainTag
	 *            The main tag in the XML configuration file.
	 * @param pluginTag
	 *            The tag that corresponds to a single plugin kind. Specifies
	 *            the plugin kind that will be located in
	 *            {@link #getAvailablePlugins(boolean)}.
	 */
	public PluginManager(String xmlName, String mainTag, String pluginTag) {
		this.xmlName = xmlName;
		this.mainTag = mainTag;
		this.pluginTag = pluginTag;
		this.plugins = null;
	}

	// protected String getPluginClass(URL pluginUrl) {
	// InputStream is = null;
	// try {
	// DocumentBuilder builder = DocumentBuilderFactory.newInstance()
	// .newDocumentBuilder();
	// is = pluginUrl.openStream();
	// Document doc = builder.parse(is);
	// Node root = doc.getFirstChild();
	// if (!this.mainTag.equals(root.getNodeName()))
	// return null;
	// NodeList children = root.getChildNodes();
	// for (int i = 0; i < children.getLength(); i++) {
	// Node child = children.item(i);
	// if (!this.pluginTag.equals(child.getNodeName()))
	// continue;
	// if (child.getChildNodes().getLength() != 1)
	// return null;
	// Node text = child.getFirstChild();
	// if (text.getNodeType() != Node.TEXT_NODE)
	// return null;
	// return text.getNodeValue();
	// }
	// return null;
	// } catch (Exception exc) {
	// return null;
	// } finally {
	// if (is != null) {
	// try {
	// is.close();
	// } catch (Exception e) {
	// }
	// }
	// }
	// }
	//
	protected String getPluginClass(URL pluginUrl) {
		InputStream is = null;
		InputStreamReader isr = null;
		try {
			XMLElement xml = new XMLElement();
			is = pluginUrl.openStream();
			isr = new InputStreamReader(is);
			xml.parseFromReader(isr);
			if (!this.mainTag.equals(xml.getName()))
				return null;
			Enumeration children = xml.enumerateChildren();
			while (children.hasMoreElements()) {
				XMLElement child = (XMLElement) children.nextElement();
				if (!this.pluginTag.equals(child.getName()))
					continue;
				if (child.countChildren() != 0)
					return null;
				return child.getContent();
			}
			return null;
		} catch (Exception exc) {
			return null;
		} finally {
			if (isr != null) {
				try {
					isr.close();
				} catch (Exception e) {
				}
			}
			if (is != null) {
				try {
					is.close();
				} catch (Exception e) {
				}
			}
		}
	}

	protected Object getPlugin(URL pluginUrl) throws Exception {
		String pluginClassName = this.getPluginClass(pluginUrl);
		if (pluginClassName == null)
			return null;
		Class pluginClass = Class.forName(pluginClassName);
		if (pluginClass == null)
			return null;
		Object pluginInstance = pluginClass.newInstance();
		if (pluginInstance == null)
			return null;
		return pluginInstance;
	}

	/**
	 * Returns a collection of all available plugins.
	 * 
	 * @return Collection of all available plugins. The classpath is scanned
	 *         only once.
	 * @see #getAvailablePlugins(boolean)
	 */
	public Set getAvailablePlugins() {
		return this.getAvailablePlugins(false);
	}

	/**
	 * Returns a collection of all available plugins. The parameter specifies
	 * whether the classpath should be rescanned or whether to return the
	 * already found plugins (after first-time scan).
	 * 
	 * @param toReload
	 *            If <code>true</code>, the classpath is scanned for
	 *            available plugins every time <code>this</code> function is
	 *            called. If <code>false</code>, the classpath scan is
	 *            performed only once. The consecutive calls return the cached
	 *            result.
	 * @return Collection of all available plugins.
	 */
	public Set getAvailablePlugins(boolean toReload) {
		if (toReload && (this.plugins != null))
			return this.plugins;

		this.plugins = new HashSet();

		ClassLoader cl = PluginManager.class.getClassLoader();
		try {
			Enumeration urls = cl.getResources(this.xmlName);
			while (urls.hasMoreElements()) {
				URL pluginUrl = (URL) urls.nextElement();
				Object pluginInstance = this.getPlugin(pluginUrl);
				if (pluginInstance != null)
					this.plugins.add(pluginInstance);

			}
		} catch (Exception exc) {
			return null;
		}

		return plugins;
	}
}
