// ========================================================================
// Copyright 2004-2005 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ========================================================================

package org.mortbay.xml;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;

import org.mortbay.component.LifeCycle;
import org.mortbay.log.Log;
import org.mortbay.resource.Resource;
import org.mortbay.util.LazyList;
import org.mortbay.util.Loader;
import org.mortbay.util.TypeUtil;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/* ------------------------------------------------------------ */
/**
 * Configure Objects from XML. This class reads an XML file conforming to the configure.dtd DTD and
 * uses it to configure and object by calling set, put or other methods on the object.
 *
 * @author Greg Wilkins (gregw)
 */
public class XmlConfiguration
{

    private static Class[] __primitives = { Boolean.TYPE, Character.TYPE, Byte.TYPE, Short.TYPE,
            Integer.TYPE, Long.TYPE, Float.TYPE, Double.TYPE, Void.TYPE};

    private static Class[] __primitiveHolders = { Boolean.class, Character.class, Byte.class,
            Short.class, Integer.class, Long.class, Float.class, Double.class, Void.class};
    private static final Integer ZERO=new Integer(0);
    
    /* ------------------------------------------------------------ */
    private static XmlParser __parser;
    private XmlParser.Node _config;
    private Map _idMap = new HashMap();
    private Map _propertyMap = new HashMap();

    /* ------------------------------------------------------------ */
    private synchronized static void initParser() throws IOException
    {
        if (__parser != null) return;

        __parser = new XmlParser();
        try
        {
            URL configURL = Loader.getResource(XmlConfiguration.class, "org/mortbay/xml/configure_6_0.dtd", true);
            __parser.redirectEntity("configure.dtd", configURL);
            __parser.redirectEntity("configure_1_3.dtd", configURL);
            __parser.redirectEntity("http://jetty.mortbay.org/configure.dtd", configURL);
            __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure//EN", configURL);
            __parser.redirectEntity("http://jetty.mortbay.org/configure_1_3.dtd", configURL);
            __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.3//EN", configURL);
            __parser.redirectEntity("configure_1_2.dtd", configURL);
            __parser.redirectEntity("http://jetty.mortbay.org/configure_1_2.dtd", configURL);
            __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.2//EN", configURL);
            __parser.redirectEntity("configure_1_1.dtd", configURL);
            __parser.redirectEntity("http://jetty.mortbay.org/configure_1_1.dtd", configURL);
            __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.1//EN", configURL);
            __parser.redirectEntity("configure_1_0.dtd", configURL);
            __parser.redirectEntity("http://jetty.mortbay.org/configure_1_0.dtd", configURL);
            __parser.redirectEntity("-//Mort Bay Consulting//DTD Configure 1.0//EN", configURL);
        }
        catch (ClassNotFoundException e)
        {
            Log.warn(e.toString());
            Log.debug(e);
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Constructor. Reads the XML configuration file.
     *
     * @param configuration
     */
    public XmlConfiguration(URL configuration) throws SAXException, IOException
    {
        initParser();
        synchronized (__parser)
        {
            _config = __parser.parse(configuration.toString());
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Constructor.
     *
     * @param configuration String of XML configuration commands excluding the normal XML preamble.
     *            The String should start with a " <Configure ...." element.
     * @exception SAXException
     * @exception IOException
     */
    public XmlConfiguration(String configuration) throws SAXException, IOException
    {
        initParser();
        configuration = "<?xml version=\"1.0\"  encoding=\"ISO-8859-1\"?>\n<!DOCTYPE Configure PUBLIC \"-//Mort Bay Consulting//DTD Configure 1.2//EN\" \"http://jetty.mortbay.org/configure_1_2.dtd\">"
                + configuration;
        InputSource source = new InputSource(new StringReader(configuration));
        synchronized (__parser)
        {
            _config = __parser.parse(source);
        }
    }

    /* ------------------------------------------------------------ */
    /**
     * Constructor.
     *
     * @param configuration An input stream containing a complete e.g. configuration file
     * @exception SAXException
     * @exception IOException
     */
    public XmlConfiguration(InputStream configuration) throws SAXException, IOException
    {
        initParser();
        InputSource source = new InputSource(configuration);
        synchronized (__parser)
        {
            _config = __parser.parse(source);
        }
    }

    /* ------------------------------------------------------------ */
    public Map getIdMap()
    {
        return _idMap;
    }
    
    /* ------------------------------------------------------------ */
    public void setIdMap(Map map)
    {
        _idMap=map;
    }

    /* ------------------------------------------------------------ */
    public void setProperties (Map map)
    {
        _propertyMap = map;
    }

    /* ------------------------------------------------------------ */
    public Map getProperties ()
    {
        return _propertyMap;
    }
    
    /* ------------------------------------------------------------ */
    /**
     * Configure an object. If the object is of the approprate class, the XML configuration script
     * is applied to the object.
     *
     * @param obj The object to be configured.
     * @exception Exception
     */
    public void configure(Object obj) throws Exception
    {
        //Check the class of the object
        Class oClass = nodeClass(_config);
        if (!oClass.isInstance(obj))
                throw new IllegalArgumentException("Object is not of type " + oClass);
        configure(obj, _config, 0);
    }

    /* ------------------------------------------------------------ */
    /**
     * Configure an object.  If the configuration has an ID, an object is looked up
     * by ID and it's type check.  Otherwise a new object is created.
     * 
     * @return The newly created configured object.
     * @exception Exception
     */
    public Object configure() throws Exception
    {
        Class oClass = nodeClass(_config);
        
        String id = _config.getAttribute("id");
        Object obj = id==null?null:_idMap.get(id);
        
        if (obj==null && oClass !=null)
            obj = oClass.newInstance();
        
        if (oClass!=null && !oClass.isInstance(obj))
            throw new ClassCastException(oClass.toString());
        
        configure(obj, _config, 0);
        return obj;
    }

    /* ------------------------------------------------------------ */
    private Class nodeClass(XmlParser.Node node) throws ClassNotFoundException
    {
        String className = node.getAttribute("class");
        if (className == null) return null;

        return Loader.loadClass(XmlConfiguration.class, className,true);
    }

    /* ------------------------------------------------------------ */
    /*
     * Recursive configuration step. This method applies the remaining Set, Put and Call elements to
     * the current object. @param obj @param cfg @param i @exception Exception
     */
    private void configure(Object obj, XmlParser.Node cfg, int i) throws Exception
    {
        String id = cfg.getAttribute("id");
        if (id!=null)
            _idMap.put(id,obj);

        for (; i < cfg.size(); i++)
        {
            Object o = cfg.get(i);
            if (o instanceof String) continue;
            XmlParser.Node node = (XmlParser.Node) o;

            try
            {
                String tag = node.getTag();
                if ("Set".equals(tag))
                    set(obj, node);
                else if ("Put".equals(tag))
                    put(obj, node);
                else if ("Call".equals(tag))
                    call(obj, node);
                else if ("Get".equals(tag))
                    get(obj, node);
                else if ("New".equals(tag))
                    newObj(obj, node);
                else if ("Array".equals(tag))
                    newArray(obj, node);
                else if ("Ref".equals(tag))
                    refObj(obj, node);
                else if ("Property".equals(tag))
                    propertyObj(obj, node);
                else
                    throw new IllegalStateException("Unknown tag: " + tag);
            }
            catch (Exception e)
            {
                Log.warn("Config error at " + node, e.toString());
                throw e;
            }
        }
    }

    /* ------------------------------------------------------------ */
    /*
     * Call a set method. This method makes a best effort to find a matching set method. The type of
     * the value is used to find a suitable set method by 1. Trying for a trivial type match. 2.
     * Looking for a native type match. 3. Trying all correctly named methods for an auto
     * conversion. 4. Attempting to construct a suitable value from original value. @param obj
     * @param node
     */
    private void set(Object obj, XmlParser.Node node) throws Exception
    {
        String attr = node.getAttribute("name");
        String name = "set" + attr.substring(0, 1).toUpperCase() + attr.substring(1);
        Object value = value(obj, node);
        Object[] arg = { value};

        Class oClass = nodeClass(node);
        if (oClass != null)
            obj = null;
        else
            oClass = obj.getClass();

        Class[] vClass = { Object.class};
        if (value != null) vClass[0] = value.getClass();

        if (Log.isDebugEnabled())
                Log.debug("XML "+(obj!=null?obj.toString():oClass.getName()) + "." + name + "(" + value + ")");

        // Try for trivial match
        try
        {
            Method set = oClass.getMethod(name, vClass);
            set.invoke(obj, arg);
            return;
        }
        catch (IllegalArgumentException e)
        {
            Log.ignore(e);
        }
        catch (IllegalAccessException e)
        {
            Log.ignore(e);
        }
        catch (NoSuchMethodException e)
        {
            Log.ignore(e);
        }

        // Try for native match
        try
        {
            Field type = vClass[0].getField("TYPE");
            vClass[0] = (Class) type.get(null);
            Method set = oClass.getMethod(name, vClass);
            set.invoke(obj, arg);
            return;
        }
        catch (NoSuchFieldException e)
        {
            Log.ignore(e);
        }
        catch (IllegalArgumentException e)
        {
            Log.ignore(e);
        }
        catch (IllegalAccessException e)
        {
            Log.ignore(e);
        }
        catch (NoSuchMethodException e)
        {
            Log.ignore(e);
        }

        // Try a field
        try
        {
            Field field = oClass.getField(attr);
            if (Modifier.isPublic(field.getModifiers()))
            {
                field.set(obj, value);
                return;
            }
        }
        catch (NoSuchFieldException e)
        {
            Log.ignore(e);
        }

        // Search for a match by trying all the set methods
        Method[] sets = oClass.getMethods();
        Method set = null;
        for (int s = 0; sets != null && s < sets.length; s++)
        {
            if (name.equals(sets[s].getName()) && sets[s].getParameterTypes().length == 1)
            {
                // lets try it
                try
                {
                    set = sets[s];
                    sets[s].invoke(obj, arg);
                    return;
                }
                catch (IllegalArgumentException e)
                {
                    Log.ignore(e);
                }
                catch (IllegalAccessException e)
                {
                    Log.ignore(e);
                }
            }
        }

        // Try converting the arg to the last set found.
        if (set != null)
        {
            try
            {
                Class sClass = set.getParameterTypes()[0];
                if (sClass.isPrimitive())
                {
                    for (int t = 0; t < __primitives.length; t++)
                    {
                        if (sClass.equals(__primitives[t]))
                        {
                            sClass = __primitiveHolders[t];
                            break;
                        }
                    }
                }
                Constructor cons = sClass.getConstructor(vClass);
                arg[0] = cons.newInstance(arg);
                set.invoke(obj, arg);
                return;
            }
            catch (NoSuchMethodException e)
            {
                Log.ignore(e);
            }
            catch (IllegalAccessException e)
            {
                Log.ignore(e);
            }
            catch (InstantiationException e)
            {
                Log.ignore(e);
            }
        }

        // No Joy
        throw new NoSuchMethodException(oClass + "." + name + "(" + vClass[0] + ")");
    }

    /* ------------------------------------------------------------ */
    /*
     * Call a put method.
     *
     * @param obj @param node
     */
    private void put(Object obj, XmlParser.Node node) throws Exception
    {
        if (!(obj instanceof Map))
                throw new IllegalArgumentException("Object for put is not a Map: " + obj);
        Map map = (Map) obj;

        String name = node.getAttribute("name");
        Object value = value(obj, node);
        map.put(name, value);
        if (Log.isDebugEnabled()) Log.debug("XML "+obj + ".put(" + name + "," + value + ")");
    }

    /* ------------------------------------------------------------ */
    /*
     * Call a get method. Any object returned from the call is passed to the configure method to
     * consume the remaining elements. @param obj @param node @return @exception Exception
     */
    private Object get(Object obj, XmlParser.Node node) throws Exception
    {
        Class oClass = nodeClass(node);
        if (oClass != null)
            obj = null;
        else
            oClass = obj.getClass();

        String name = node.getAttribute("name");
        String id = node.getAttribute("id");
        if (Log.isDebugEnabled()) Log.debug("XML get " + name);

        try
        {
            // try calling a getXxx method.
            Method method = oClass.getMethod("get" + name.substring(0, 1).toUpperCase()
                    + name.substring(1), (java.lang.Class[]) null);
            obj = method.invoke(obj, (java.lang.Object[]) null);
            configure(obj, node, 0);
        }
        catch (NoSuchMethodException nsme)
        {
            try
            {
                Field field = oClass.getField(name);
                obj = field.get(obj);
                configure(obj, node, 0);
            }
            catch (NoSuchFieldException nsfe)
            {
                throw nsme;
            }
        }
        if (id != null) _idMap.put(id, obj);
        return obj;
    }

    /* ------------------------------------------------------------ */
    /*
     * Call a method. A method is selected by trying all methods with matching names and number of
     * arguments. Any object returned from the call is passed to the configure method to consume the
     * remaining elements. Note that if this is a static call we consider only methods declared
     * directly in the given class. i.e. we ignore any static methods in superclasses. @param obj
     * @param node @return @exception Exception
     */
    private Object call(Object obj, XmlParser.Node node) throws Exception
    {
        String id = node.getAttribute("id");
        Class oClass = nodeClass(node);
        if (oClass != null)
            obj = null;
        else if (obj != null) oClass = obj.getClass();
        if (oClass == null) throw new IllegalArgumentException(node.toString());

        int size = 0;
        int argi = node.size();
        for (int i = 0; i < node.size(); i++)
        {
            Object o = node.get(i);
            if (o instanceof String) continue;
            if (!((XmlParser.Node) o).getTag().equals("Arg"))
            {
                argi = i;
                break;
            }
            size++;
        }

        Object[] arg = new Object[size];
        for (int i = 0, j = 0; j < size; i++)
        {
            Object o = node.get(i);
            if (o instanceof String) continue;
            arg[j++] = value(obj, (XmlParser.Node) o);
        }

        String method = node.getAttribute("name");
        if (Log.isDebugEnabled()) Log.debug("XML call " + method);

        // Lets just try all methods for now
        Method[] methods = oClass.getMethods();
        for (int c = 0; methods != null && c < methods.length; c++)
        {
            if (!methods[c].getName().equals(method)) continue;
            if (methods[c].getParameterTypes().length != size) continue;
            if (Modifier.isStatic(methods[c].getModifiers()) != (obj == null)) continue;
            if ((obj == null) && methods[c].getDeclaringClass() != oClass) continue;

            Object n = null;
            boolean called = false;
            try
            {
                n = methods[c].invoke(obj, arg);
                called = true;
            }
            catch (IllegalAccessException e)
            {
                Log.ignore(e);
            }
            catch (IllegalArgumentException e)
            {
                Log.ignore(e);
            }
            if (called)
            {
                if (id != null) _idMap.put(id, n);
                configure(n, node, argi);
                return n;
            }
        }

        throw new IllegalStateException("No Method: " + node + " on " + oClass);
    }

    /* ------------------------------------------------------------ */
    /*
     * Create a new value object.
     *
     * @param obj @param node @return @exception Exception
     */
    private Object newObj(Object obj, XmlParser.Node node) throws Exception
    {
        Class oClass = nodeClass(node);
        String id = node.getAttribute("id");
        int size = 0;
        int argi = node.size();
        for (int i = 0; i < node.size(); i++)
        {
            Object o = node.get(i);
            if (o instanceof String) continue;
            if (!((XmlParser.Node) o).getTag().equals("Arg"))
            {
                argi = i;
                break;
            }
            size++;
        }

        Object[] arg = new Object[size];
        for (int i = 0, j = 0; j < size; i++)
        {
            Object o = node.get(i);
            if (o instanceof String) continue;
            arg[j++] = value(obj, (XmlParser.Node) o);
        }

        if (Log.isDebugEnabled()) Log.debug("XML new " + oClass);

        // Lets just try all constructors for now
        Constructor[] constructors = oClass.getConstructors();
        for (int c = 0; constructors != null && c < constructors.length; c++)
        {
            if (constructors[c].getParameterTypes().length != size) continue;

            Object n = null;
            boolean called = false;
            try
            {
                n = constructors[c].newInstance(arg);
                called = true;
            }
            catch (IllegalAccessException e)
            {
                Log.ignore(e);
            }
            catch (InstantiationException e)
            {
                Log.ignore(e);
            }
            catch (IllegalArgumentException e)
            {
                Log.ignore(e);
            }
            if (called)
            {
                if (id != null) _idMap.put(id, n);
                configure(n, node, argi);
                return n;
            }
        }

        throw new IllegalStateException("No Constructor: " + node + " on " + obj);
    }

    /* ------------------------------------------------------------ */
    /*
     * Reference an id value object.
     *
     * @param obj @param node @return @exception NoSuchMethodException @exception
     * ClassNotFoundException @exception InvocationTargetException
     */
    private Object refObj(Object obj, XmlParser.Node node) throws Exception
    {
        String id = node.getAttribute("id");
        obj = _idMap.get(id);
        if (obj == null) throw new IllegalStateException("No object for id=" + id);
        configure(obj, node, 0);
        return obj;
    }


    /* ------------------------------------------------------------ */
    /*
     * Create a new array object.
     *
     */
    private Object newArray(Object obj, XmlParser.Node node) throws Exception
    {

        // Get the type
        Class aClass = java.lang.Object.class;
        String type = node.getAttribute("type");
        final String id = node.getAttribute("id");
        if (type != null)
        {
            aClass = TypeUtil.fromName(type);
            if (aClass == null)
            {
                if ("String".equals(type))
                    aClass = java.lang.String.class;
                else if ("URL".equals(type))
                    aClass = java.net.URL.class;
                else if ("InetAddress".equals(type))
                    aClass = java.net.InetAddress.class;
                else
                    aClass = Loader.loadClass(XmlConfiguration.class, type,true);
            }
        }

        Object al=null;
        
        Iterator iter = node.iterator("Item");
        while(iter.hasNext())
        {
            XmlParser.Node item= (XmlParser.Node)iter.next();
            String nid = item.getAttribute("id");
            Object v = value(obj, item);
            al=LazyList.add(al,(v==null&&aClass.isPrimitive())?ZERO:v);
            if (nid != null) 
                _idMap.put(nid, v);
        }
        
        Object array =  LazyList.toArray(al,aClass);
        if (id != null) 
            _idMap.put(id, array);
        return array; 
    }
    
    /* ------------------------------------------------------------ */
    /*
     * Create a new map object.
     *
     */
    private Object newMap(Object obj, XmlParser.Node node) throws Exception
    {
        String id = node.getAttribute("id");

        Map map = new HashMap();
        if (id != null) _idMap.put(id, map);

        for (int i = 0; i < node.size(); i++)
        {
            Object o = node.get(i);
            if (o instanceof String) continue;
            XmlParser.Node entry = (XmlParser.Node) o;
            if (!entry.getTag().equals("Entry")) throw new IllegalStateException("Not an Entry");
            
            
            XmlParser.Node key=null;
            XmlParser.Node value=null;

            for (int j = 0; j < entry.size(); j++)
            {
                o = entry.get(j);
                if (o instanceof String) continue;
                XmlParser.Node item = (XmlParser.Node) o;
                if (!item.getTag().equals("Item")) throw new IllegalStateException("Not an Item");
                if (key==null) 
                    key=item;
                else
                    value=item;
            }
            
            if (key==null || value==null)
                throw new IllegalStateException("Missing Item in Entry");
            String kid = key.getAttribute("id");
            String vid = value.getAttribute("id");
             
            Object k = value(obj, key);
            Object v = value(obj, value);
            map.put(k,v);
            
            if (kid != null) _idMap.put(kid, k);
            if (vid != null) _idMap.put(vid, v);
        }

        return map;
    }

    /* ------------------------------------------------------------ */
    /*
     * Create a new value object.
     *
     * @param obj @param node @return @exception Exception
     */
    private Object propertyObj(Object obj, XmlParser.Node node) throws Exception
    {
        String id = node.getAttribute("id");
        String name = node.getAttribute("name");
        Object defval = node.getAttribute("default");
        Object prop=null;
        if (_propertyMap!=null && _propertyMap.containsKey(name))
        {
            prop=_propertyMap.get(name);
        }
        else if (defval != null)
            prop=defval;

        if (id != null) 
            _idMap.put(id, prop);
        if (prop!=null)
            configure(prop, node, 0);
        return prop;
    }
    
    /* ------------------------------------------------------------ */
    /*
     * Get the value of an element. If no value type is specified, then white space is trimmed out
     * of the value. If it contains multiple value elements they are added as strings before being
     * converted to any specified type. @param node
     */
    private Object value(Object obj, XmlParser.Node node) throws Exception
    {
        Object value = null;

        // Get the type
        String type = node.getAttribute("type");

        // Try a ref lookup
        String ref = node.getAttribute("ref");
        if (ref != null)
        {
            value = _idMap.get(ref);
        }
        else
        {
            // handle trivial case
            if (node.size() == 0)
            {
                if ("String".equals(type)) return "";
                return null;
            }

            // Trim values
            int first = 0;
            int last = node.size() - 1;

            // Handle default trim type
            if (type == null || !"String".equals(type))
            {
                // Skip leading white
                Object item = null;
                while (first <= last)
                {
                    item = node.get(first);
                    if (!(item instanceof String)) break;
                    item = ((String) item).trim();
                    if (((String) item).length() > 0) break;
                    first++;
                }

                // Skip trailing white
                while (first < last)
                {
                    item = node.get(last);
                    if (!(item instanceof String)) break;
                    item = ((String) item).trim();
                    if (((String) item).length() > 0) break;
                    last--;
                }

                // All white, so return null
                if (first > last) return null;
            }

            if (first == last)
                //  Single Item value
                value = itemValue(obj, node.get(first));
            else
            {
                // Get the multiple items as a single string
                StringBuffer buf = new StringBuffer();
                synchronized (buf)
                {
                    for (int i = first; i <= last; i++)
                    {
                        Object item = node.get(i);
                        buf.append(itemValue(obj, item));
                    }
                    value = buf.toString();
                }
            }
        }

        // Untyped or unknown
        if (value == null)
        {
            if ("String".equals(type)) return "";
            return null;
        }

        // Try to type the object
        if (type == null)
        {
            if (value != null && value instanceof String) return ((String) value).trim();
            return value;
        }

        if ("String".equals(type) || "java.lang.String".equals(type)) return value.toString();

        Class pClass = TypeUtil.fromName(type);
        if (pClass != null) return TypeUtil.valueOf(pClass, value.toString());

        if ("URL".equals(type) || "java.net.URL".equals(type))
        {
            if (value instanceof URL) return value;
            try
            {
                return new URL(value.toString());
            }
            catch (MalformedURLException e)
            {
                throw new InvocationTargetException(e);
            }
        }

        if ("InetAddress".equals(type) || "java.net.InetAddress".equals(type))
        {
            if (value instanceof InetAddress) return value;
            try
            {
                return InetAddress.getByName(value.toString());
            }
            catch (UnknownHostException e)
            {
                throw new InvocationTargetException(e);
            }
        }

        throw new IllegalStateException("Unknown type " + type);
    }

    /* ------------------------------------------------------------ */
    /*
     * Get the value of a single element. @param obj @param item @return @exception Exception
     */
    private Object itemValue(Object obj, Object item) throws Exception
    {
        // String value
        if (item instanceof String) return item;

        XmlParser.Node node = (XmlParser.Node) item;
        String tag = node.getTag();
        if ("Call".equals(tag)) return call(obj, node);
        if ("Get".equals(tag)) return get(obj, node);
        if ("New".equals(tag)) return newObj(obj, node);
        if ("Ref".equals(tag)) return refObj(obj, node);
        if ("Array".equals(tag)) return newArray(obj, node);
        if ("Map".equals(tag)) return newMap(obj, node);
        if ("Property".equals(tag)) return propertyObj(obj,node);

        if ("SystemProperty".equals(tag))
        {
            String name = node.getAttribute("name");
            String defaultValue = node.getAttribute("default");
            return System.getProperty(name, defaultValue);
        }
        
        Log.warn("Unknown value tag: " + node, new Throwable());
        return null;
    }

    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /* ------------------------------------------------------------ */
    /** 
     * Run the XML configurations as a main application.
     * The command line is used to obtain properties files (must be named '*.properties') and XmlConfiguration
     * files.
     * <p>
     * Any property file on the command line is added to a combined Property instance that is passed to
     * each configuration file via {@link XmlConfiguration#setProperties(Map)}.
     * <p>
     * Each configuration file on the command line is used to create a new XmlConfiguration instance and the
     * {@link XmlConfiguration#configure()} method is used to create the configured object.  If the resulting 
     * object is an instance of {@link LifeCycle}, then it is started.
     * <p>
     * Any IDs created in a configuration are passed to the next configuration file on the command line using
     * {@link #getIdMap()} and {@link #setIdMap(Map)}. This allows objects with IDs created in one config file to
     * be referenced in subsequent config files on the command line.
     * 
     * @param args array of property and xml configuration filenames or {@link Resource}s.
     */
    public static void main(String[] args)
    {
        try
        {
            Properties properties=new Properties();
            XmlConfiguration last=null;
            Object[] obj = new Object[args.length];
            for (int i = 0; i < args.length; i++)
            {
                if (args[i].toLowerCase().endsWith(".properties"))
                {
                    properties.load(Resource.newResource(args[i]).getInputStream());
                }
                else
                {
                    XmlConfiguration configuration = new XmlConfiguration(Resource.newResource(args[i]).getURL());
                    if (last!=null)
                        configuration.getIdMap().putAll(last.getIdMap());
                    if (properties.size()>0)
                        configuration.setProperties(properties);
                    obj[i] = configuration.configure();
                    last=configuration;
                }
            }

            for (int i = 0; i < args.length; i++)
            {
                if (obj[i] instanceof LifeCycle)
                {
                    LifeCycle lc = (LifeCycle)obj[i];
                    if (!lc.isRunning())
                        lc.start();
                }
            }
        }
        catch (Exception e)
        {
            Log.warn(Log.EXCEPTION, e);
        }
        
    }
    
}

