/*
 * JBoss, Home of Professional Open Source
 * Copyright 2006, Red Hat Middleware LLC, and individual contributors 
 * as indicated by the @author tags. 
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors. 
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, 
 * MA  02110-1301, USA.
 * 
 * (C) 2005-2006,
 * @author JBoss Inc.
 */
package com.arjuna.common.internal.util.propertyservice;

import com.arjuna.common.util.propertyservice.PropertyManager;
import com.arjuna.common.util.propertyservice.Environment;
import com.arjuna.common.util.exceptions.LoadPropertiesException;
import com.arjuna.common.util.exceptions.ManagementPluginException;
import com.arjuna.common.util.propertyservice.plugins.PropertyManagerIOPlugin;
import com.arjuna.common.util.propertyservice.plugins.PropertyManagementPlugin;
import com.arjuna.common.util.propertyservice.propertycontainer.PropertyManagerPluginInterface;
import com.arjuna.common.util.exceptions.SavePropertiesException;

import java.util.*;
import java.io.IOException;

/*
 * Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003
 *
 * Arjuna Technologies Ltd.
 * Newcastle upon Tyne,
 * Tyne and Wear,
 * UK.
 *
 * $Id: PropertyManagerImpl.java 2342 2006-03-30 13:06:17Z  $
 */

/**
 * Implementation of the Property Manager.
 *
 * @author Richard A. Begg (richard.begg@arjuna.com)
 */
public class PropertyManagerImpl implements PropertyManagerPluginInterface
{
    /** Specifies whether the property manager is in verbose mode **/
	private static boolean		_verbose = false;

    /** This is the top level property manager **/
	private PropertyManagerImpl _topLevelPropertyManager = this;

    /** The parent and children of this property manager **/
	private HashSet		_parentPropertyManagers;
    private HashSet		_childPropertyManagers;

    /** Properties stored within this property manager **/
	private Properties	_properties;

    /** The name of this property manager **/
	private String		_name;

    /** The uri associated with this property manager **/
    private String      _associatedUri;
	private String		_associatedPluginClassname;

	public PropertyManagerImpl(String name)
	{
		_name = name;
		_parentPropertyManagers = new HashSet();
    	_childPropertyManagers = new HashSet();

		_properties = new Properties();
        _associatedUri = null;
	}

	public PropertyManagerImpl(String name, PropertyManagerImpl parent)
	{
		this(name);

		_topLevelPropertyManager = parent;
	}

    public PropertyManagerPluginInterface getTopLevelPropertyManager()
    {
        return _topLevelPropertyManager;
    }

    /**
     * Retrieve the name of this property manager
     * @return
     */
	public String getName()
	{
		return _name;
	}

    /**
     * Retrieves the URI associated with this property manager
     * @return
     */
    public String getUri()
    {
        return _associatedUri;
    }

    /**
     * Set the URI associated with this property manager
     * @param uri
     */
    public void setUri(String uri)
    {
        _associatedUri = uri;
    }

	public String getIOPluginClassname()
	{
		return _associatedPluginClassname;
	}

	public void setIOPluginClassname(String classname)
	{
		_associatedPluginClassname = classname;
	}

	/**
	 * Get the value of the property with the name <code>name</code>
	 *
	 * @param name The name of the property to retrieve the value of.
	 * @return The value of the property.
	 */
	public String getProperty(String name)
	{
		/** Get the system property **/
		String value = System.getProperty(name);

		/** If the system property is null look in this property managers sub-tree **/
		value = value != null ? value : getPropertyFromSubTree(name);

		return value;
	}

    /**
     * Get the property from the sub-tree without checking the system
     * properties.
     * @param name
     * @return
     */
    private String getPropertyFromSubTree(String name)
    {
        /** look in this property managers properties **/
        String value = _properties.getProperty(name);

        if ( value == null )
        {
            /** Else search upwards **/
            Iterator itr = _parentPropertyManagers.iterator();
            while (itr.hasNext() && value == null)
            {
                PropertyManagerImpl pm = (PropertyManagerImpl)itr.next();
                value = pm.getPropertyFromSubTree(name);
            }
        }

        return value;
    }

	/**
	 * Get the value of the property with the name <code>name</code>.
	 * If the property cannot be found return the value <code>defaultValue</code>.
	 *
	 * @param name The name of the property to retrieve the value of.
	 * @param defaultValue The default value to return if the property cannot be found.
	 * @return The value of the property (or defaultValue).
	 */
	public String getProperty(String name, String defaultValue)
	{
		String value = getProperty(name);

		return value == null ? defaultValue : value;
	}

	/**
	 * Set the value of the property <code>name</code> to <code>value</code>.
	 *
	 * @param name The name of the property to set.
	 * @param value The value of the property being set.
	 * @return The previous value of the property.
	 */
	public String setProperty(String name, String value)
	{
		return setProperty(name, value, true);
	}

	/**
	 * Set the value of the property <code>name</code> to <code>value</code>.
	 *
	 * @param name The name of the property to set.
	 * @param value The value of the property being set.
	 * @param setSystemProperty True - set the system property if it has a value
	 * @return
	 */
	public String setProperty(String name, String value, boolean setSystemProperty)
	{
		/** If the property exists in the system properties ensure that is set also **/
		if ( setSystemProperty && System.getProperty(name) != null )
		{
			System.setProperty(name, value);
		}

		String oldValue = setLocalProperty(name, value);

		if ( oldValue == null )
		{
			_properties.setProperty(name, value);
		}

		return oldValue;
	}

	/**
	 * Removes the property from the property manager.
	 * @param name The name of the property to remove.
	 * @return
	 */
	public String removeProperty(String name)
	{
		/** If the property exists in the system properties ensure that is removed also **/
		if ( System.getProperty(name) != null )
		{
			System.getProperties().remove(name);
		}

		String oldValue = removeLocalProperty(name);

		return oldValue;
	}

	private String removeLocalProperty(String name)
	{
		PropertyManagerImpl pm = this;
		String oldValue = _properties.getProperty(name);

		if ( oldValue != null )
		{
			_properties.remove(name);
		}

		Iterator itr = _parentPropertyManagers.iterator();
		while ( oldValue == null && itr.hasNext() )
		{
			pm = (PropertyManagerImpl)itr.next();
			oldValue = pm.removeLocalProperty(name);
		}

		return oldValue;
	}

	private String setLocalProperty(String name, String value)
	{
		PropertyManagerImpl pm = this;
		String oldValue = _properties.getProperty(name);

		if ( oldValue != null )
		{
			_properties.setProperty(name, value);
		}

		Iterator itr = _parentPropertyManagers.iterator();
		while ( oldValue == null && itr.hasNext() )
		{
			pm = (PropertyManagerImpl)itr.next();
			oldValue = pm.setLocalProperty(name, value);
		}

		return oldValue;
	}

	/**
	 * Get the properties stored in this property manager only.
	 * @return
	 */
	public Properties getLocalProperties()
	{
		return _properties;
	}

	/**
	 * Get all the properties stored in this property manager and it's parents.
     * It also includes the system properties.
	 *
	 * @return
	 */
	public Properties getProperties()
	{
		Properties returnProps = getAllProperties();

        returnProps.putAll(System.getProperties());

        return returnProps;
    }

    /**
     * This is an internal method which retrieves all the properties stored
     * in this property manager and it's parents.  But it does not include
     * the properties stored in system.
     *
     * @return
     */
    public Properties getAllProperties()
    {
        Properties returnProps = new Properties();
		Iterator itr = _parentPropertyManagers.iterator();
		while (itr.hasNext())
		{
			PropertyManagerImpl pm = (PropertyManagerImpl)itr.next();

			returnProps.putAll(pm.getAllProperties());
		}

		returnProps.putAll(_properties);

		return returnProps;
	}

	/**
	 * Returns an enumeration of the property names
	 * @return
	 */
	public Enumeration propertyNames()
	{
		return getProperties().keys();
	}

	/**
	 * Loads properties from a given URI using the given property manager plugin.
	 * This plugin can be overridden by setting the system property 'propertyservice.plugin'.
	 *
	 * @param pluginClassname The classname of the plugin in to use.  This plugin is loaded
	 * using the current thread context classloader.
	 * @param uri The URI to load.
	 */
	public synchronized void load(String pluginClassname, String uri) throws IOException, ClassNotFoundException, LoadPropertiesException
	{
		try
		{
			/** Check to see if the system property has been set **/
			String classname = System.getProperty(Environment.OVERRIDING_PLUGIN_CLASSNAME);
			classname = classname == null ? pluginClassname : classname;

			PropertyManagerIOPlugin plugin = (PropertyManagerIOPlugin)Thread.currentThread().getContextClassLoader().loadClass(classname).newInstance();

			plugin.load(uri, _topLevelPropertyManager, _verbose);
		}
		catch (java.io.IOException e)
		{
			throw e;
		}
        catch (Exception e)
		{
			throw new LoadPropertiesException("Failed to instantiate plugin: "+e);
		}
	}

	/**
	 * Saves the properties stored in this property manager using the given
	 * property manager plugin.  This plugin can be overridden by setting the
	 * system property 'propertyservice.plugin'
	 *
	 * @param pluginClassname The classname of the plugin to use.  This plugin is loaded
	 * using the current thread context classloader.  If null is provided the plugin used to
	 * load the properties is also used to save them.
	 * @param uri The URI to save to.  If null is provided the uri used to load the properties
	 * is also used to save them.
	 * @throws IOException
	 * @throws ClassNotFoundException
	 * @throws SavePropertiesException
	 */
	public synchronized void save(String pluginClassname, String uri) throws IOException, ClassNotFoundException, SavePropertiesException
	{
		try
		{
			if ( pluginClassname == null )
			{
				pluginClassname = _associatedPluginClassname;
			}

			/** Check to see if the system property has been set **/
			String classname = System.getProperty(Environment.OVERRIDING_PLUGIN_CLASSNAME);
			classname = classname == null ? pluginClassname : classname;

			PropertyManagerIOPlugin plugin = (PropertyManagerIOPlugin)Thread.currentThread().getContextClassLoader().loadClass(pluginClassname).newInstance();

			plugin.save(uri, this);
		}
		catch (java.io.IOException e)
		{
			throw e;
		}
        catch (Exception e)
		{
			e.printStackTrace(System.err);
			throw new SavePropertiesException("Failed to instantiate plugin: "+e);
		}
	}

	public boolean verbose()
	{
		return _verbose;
	}

	/**
	 * Adds the given property manager as a child of this property manager
	 *
	 * @param pm The property manager to add as a child
	 */
	public void addChild(PropertyManager pm)
	{
		if ( !_childPropertyManagers.contains(pm) )
		{
			_childPropertyManagers.add(pm);
			((PropertyManagerImpl)pm).addParent(this);
		}
	}

	/**
	 * This method adds a parent property manager.
	 * @param pm The property manager to add as a parent.
	 */
	public void addParent(PropertyManager pm)
	{
		if ( !_parentPropertyManagers.contains(pm) )
		{
			_parentPropertyManagers.add(pm);
			((PropertyManagerImpl)pm).addChild(this);
		}
	}

	/**
	 * This method creates a property manager with the name <code>moduleName</code>.
	 * @param modulename
	 * @return
	 */
	public PropertyManagerPluginInterface createPropertyManager(String modulename)
	{
		return new PropertyManagerImpl(modulename, _topLevelPropertyManager);
	}

	/**
	 * This method returns an array of parents to the property manager.
	 * @return
	 */
	public PropertyManagerPluginInterface[] getParents()
	{
		PropertyManagerPluginInterface[] pms = new PropertyManagerPluginInterface[_parentPropertyManagers.size()];
		_parentPropertyManagers.toArray(pms);
		return pms;
	}

	/**
	 * This method returns an array of the children of the current property manager.
	 * @return
	 */
	public PropertyManagerPluginInterface[] getChildren()
	{
		PropertyManagerPluginInterface[] pms = new PropertyManagerPluginInterface[_childPropertyManagers.size()];
		_childPropertyManagers.toArray(pms);
		return pms;
	}

	/**
	 * This adds a management plugin to this property manager.
	 *
	 * @param plugin The proeprty management plugin to plugin.
	 * @throws IOException
	 * @throws ManagementPluginException
	 */
	public void addManagementPlugin(PropertyManagementPlugin plugin) throws IOException, ManagementPluginException
	{
		try
		{
			plugin.initialise(this);
		}
		catch (java.io.IOException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new ManagementPluginException(e.toString());
		}
	}

	public PropertyManagerPluginInterface getChild(String moduleName)
	{
		PropertyManagerPluginInterface returnPm = null;
    	Iterator itr = _childPropertyManagers.iterator();

		while ( returnPm == null && itr.hasNext() )
		{
			PropertyManagerImpl pm = (PropertyManagerImpl)itr.next();

			if ( pm.getName().equals(moduleName) )
			{
				returnPm = pm;
			}
		}

		if ( returnPm == null )
		{
			addChild(returnPm = createPropertyManager(moduleName));
		}

		return returnPm;
	}

	static
	{
		String verboseSetting = System.getProperty(Environment.VERBOSE_PROPERTY_MANAGER);

		if ( verboseSetting != null && verboseSetting.equalsIgnoreCase("ON") )
		{
			_verbose = true;
		}
	}
}
