package org.jacorb.config;

/*
 *        JacORB - a free Java ORB
 *
 *   Copyright (C) 1997-2014 Gerald Brose / The JacORB Team.
 *
 *   This library is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU Library General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This library is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *   Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this library; if not, write to the Free
 *   Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import org.jacorb.orb.ORB;
import org.jacorb.util.ObjectUtil;
import org.jacorb.util.Version;
import org.omg.CORBA.NO_IMPLEMENT;
import org.slf4j.Logger;

/**
 * The Class JacORBConfiguration.
 *
 * @author Gerald Brose
 */
public class JacORBConfiguration implements Configuration
{
    /**
     * The Constant fileSuffix.
     */
    private static final String fileSuffix = ".properties";

    /**
     * The Constant COMMON_PROPS.
     */
    private static final String COMMON_PROPS = "orb" + fileSuffix;

    /**
     * The Constant TRUE.
     */
    private static final String TRUE = "true";

    /**
     * The Constant ON.
     */
    private static final String ON = "on";

    /**
     * The Constant ATTR_LOGGING_INITIALIZER.
     */
    private static final String ATTR_LOGGING_INITIALIZER = "jacorb.log.initializer";


   /**
    * <code>useTCCL</code> controls which class loader policy JacORB should use throughout
    * the codebase. By default it will attempt to load using the Thread Context Class Loader.
    * To support integration in some deployment scenarios it is also possible to use Class.forName.
    * This may be set by setting jacorb.classloaderpolicy system property to either tccl or forname.
    *
    * Note that this is duplicated within org.omg.CORBA.ORBSingleton
    * (to avoid cross-dependencies)
    */
    public static final boolean useTCCL;

    static
    {
        String clpolicy = System.getProperty ("jacorb.classloaderpolicy", "tccl");
        if (clpolicy.equalsIgnoreCase ("forname"))
        {
            useTCCL = false;
        }
        else
        {
            useTCCL = true;
        }
    }


    /**
     * Contains the actual configuration data.
     *
     * To speed up the access of frequently requested configuration values a set
     * of dedicated hashmaps provide String, Boolean and Number storage.
     */
    private ConcurrentHashMap<String,String> stringAttributes = new ConcurrentHashMap<String,String>(16, 0.9f, 1);

    /**
     * The boolean attributes.
     */
    private ConcurrentHashMap<String,Boolean> booleanAttributes = new ConcurrentHashMap<String,Boolean>(16, 0.9f, 1);

    /**
     * The number attributes.
     */
    private ConcurrentHashMap<String, Number> numberAttributes = new ConcurrentHashMap<String, Number> (16, 0.9f, 1);

    /**
     * The orb.
     */
    private final ORB orb;

    /**
     * The logger.
     */
    private Logger logger;

    /**
     * The li.
     */
    private LoggingInitializer li;


    /**
     * Factory method.
     *
     * @param props the props
     * @param orb the orb
     * @param isApplet the is applet
     * @return the configuration
     * @throws ConfigurationException the configuration exception
     */
    public static Configuration getConfiguration(Properties props,
                                                 org.omg.CORBA.ORB orb,
                                                 boolean isApplet)
        throws ConfigurationException
    {
        if ( ! (orb instanceof ORB))
        {
            throw new ConfigurationException("ORB must not be null and must be a JacORB ORB " + (orb == null ? "<null>" : orb.getClass().getName()));
        }

        return getConfiguration (props, (org.jacorb.orb.ORB)orb, null, isApplet);
    }

    /**
     * Factory method.
     *
     * @param props the props
     * @param orb the orb
     * @param orbid  the orb_id (if set)
     * @param isApplet the is applet
     * @return the configuration
     * @throws ConfigurationException the configuration exception
     */
    public static Configuration getConfiguration(Properties props,
                                                 ORB orb,
                                                 String orbid,
                                                 boolean isApplet)
        throws ConfigurationException
    {
        // determine the ORBId, if set, so we can locate the corresponding
        // configuration
        String orbID = Version.orbId; // default id
        String myOrbID = null;

        if ( !isApplet )
        {
            try
            {
                myOrbID = System.getProperty("ORBid");
            }
            catch ( SecurityException e )
            {
                isApplet = true;
                System.err.println ("Could not access system property 'ORBid' - will use default...");
            }
        }

        // ORB props and args override system props.
        if ( orbid != null && orbid.length () > 0)
        {
            myOrbID = orbid;
        }

        if (myOrbID != null && !myOrbID.equals(""))
        {
            // check for legal values
            if (myOrbID.equals("orb") || myOrbID.equals(Version.orbId))
            {
                throw new ConfigurationException("Illegal orbID, <" +
                                                  myOrbID + "> is reserved");
            }
            orbID = myOrbID;
        }
        return new JacORBConfiguration(orbID, props, orb, isApplet);
    }


    /**
     * Create a configuration using the properties passed
     * into ORB.init()
     *
     * @param name the name
     * @param orbProperties the orb properties
     * @param orb the orb
     * @param isApplet the is applet
     * @throws ConfigurationException the configuration exception
     */

    private JacORBConfiguration(String name,
                                Properties orbProperties,
                                ORB orb,
                                boolean isApplet)
        throws ConfigurationException
    {
        super();
        this.orb = orb;
        LinkedHashMap<Level,String> delayedLogging = new LinkedHashMap<Level,String> ();
        if (isApplet)
        {
           initApplet(delayedLogging, name, orbProperties);
        }
        else
        {
           init(delayedLogging, name, orbProperties);
        }

        initLogging();

        logger = getLogger ("org.jacorb.config");

        // This delays logging out any information about the loading of the properties
        // or configuration until any logging subsystem has been setup.
        for (Entry<Level, String> e : delayedLogging.entrySet())
        {
           if (e.getKey () == Level.INFO)
           {
              logger.info (e.getValue ());
           }
           else if (e.getKey () == Level.WARNING)
           {
              logger.warn (e.getValue ());
           }
           else if (e.getKey () == Level.FINE)
           {
              logger.debug (e.getValue ());
           }
           else
           {
              throw new NO_IMPLEMENT("Only info/warn delayed logging implemented.");
           }
        }
    }


    /**
     * loads properties from files.
     *
     * Properties are loaded in the following order, with later
     * properties overriding earlier ones: 1) System properties
     * (incl. command line), to get properties that affect further
     * property loading 2) orb.properties file 3) specific
     * configuration file for the ORB (if any) 4) System properties
     * again, so that command line args take precedence over the above
     * 5) the ORB properties set in the client code and passed in
     * through ORB.init().
     * (Note that these will thus always take effect!)
     *
     * @param delayedLogging the delayed logging
     * @param name the name for the ORB instance, may not be null.
     * @param orbProperties the orb properties
     * @throws ConfigurationException the configuration exception
     */

    private void init(LinkedHashMap<Level, String> delayedLogging, String name, Properties orbProperties)
        throws ConfigurationException
   {
       if( name == null )
       {
           throw new ConfigurationException("Illegal null value for ORB name!");
       }

       String separator = System.getProperty("file.separator");
       String home = System.getProperty("user.home");
       String lib = System.getProperty("java.home");
       boolean loaded = false;

       // 1) load system properties to grab any command line properties
       //    that will influence further property loading
       setAttributes(System.getProperties());

       // 1.5) load passed-in properties that might influence further
       // property loading.  This will allow me to pass in jacorb.home,
       // jacorb.config.dir, and jacorb.config.log.verbosity.
       if (orbProperties != null)
       {
           loaded = true;
           setAttributes(orbProperties);
       }
       // 2) look for orb.properties
       // look for common properties files in java.home/lib first
       String propFile = lib + separator + "lib" + separator + COMMON_PROPS;
       Properties commonProps = loadPropertiesFromFile (propFile);
       if (commonProps!= null)
       {
            setAttributes(commonProps);
            loaded = true;
            delayedLogging.put (Level.FINE, "base configuration loaded from file " + propFile);
       }

       // look for common properties files in user.home next
       propFile = home + separator + COMMON_PROPS;
       commonProps = loadPropertiesFromFile (propFile);
       if (commonProps!= null)
       {
           setAttributes(commonProps);
           loaded = true;
           delayedLogging.put (Level.FINE, "base configuration loaded from file " + propFile);
       }

       // look for common properties files on the classpath next
       commonProps = loadPropertiesFromClassPath( COMMON_PROPS );
       if (commonProps!= null)
       {
           loaded = true;
           setAttributes(commonProps);
           delayedLogging.put (Level.FINE, "base configuration loaded from classpath " + COMMON_PROPS);
       }

       // 3) look for specific properties file
       String configDir = getAttribute("jacorb.config.dir", "");

       if (configDir.length() == 0)
       {
           configDir = getAttribute ("jacorb.home", "");

           if (configDir.length() != 0 )
           {
               configDir += separator + "etc";
           }
           else
           {
               delayedLogging.put (Level.FINE, "jacorb.home unset! Will use '.'");
               configDir = ".";
           }
       }

       propFile = configDir + separator + name + fileSuffix;

       // now load properties file from file system
       Properties orbConfig = loadPropertiesFromFile (propFile);
       if (orbConfig!= null)
       {
           setAttributes(orbConfig);
           loaded = true;

           delayedLogging.put (Level.FINE, "configuration " + name + " loaded from file " + propFile +
                               (orb == null ? " for ORBSingleton" : " for " + orb));
       }

       // now load properties file from classpath
       orbConfig = loadPropertiesFromClassPath (name + fileSuffix);
       if (orbConfig!= null)
       {
           setAttributes(orbConfig);
           loaded = true;
           delayedLogging.put (Level.FINE, "configuration " + name + " loaded from classpath" +
                               (orb == null ? " for ORBSingleton" : " for " + orb));
       }

       // 4) look for additional custom properties files
       List<String> customPropFileNames = getAttributeList("custom.props");

       if (!customPropFileNames.isEmpty())
       {
           for (String fileName : customPropFileNames)
           {
                Properties customProps = loadPropertiesFromFile(fileName);
                if (customProps!= null)
                {
                    setAttributes(customProps);
                    loaded = true;
                    delayedLogging.put (Level.FINE, "custom properties loaded from file " + fileName);
                }
                else
                {
                    delayedLogging.put (Level.WARNING, "custom properties not found in " + fileName);
                }
           }
       }

       // 5) load system properties again, so that
       //    command line args override properties from files
       setAttributes( System.getProperties() );

       // 6) load properties passed to ORB.init() again, these will override any
       // settings in config files or system properties!
       if (orbProperties != null)
       {
           loaded = true;
           setAttributes(orbProperties);

           // This is in case ORBClass/ORBSingleton are not in system properties
           // and have been specified via ORB.init props. If this is not done
           // and JacORB jars are not in bootclasspath/endorsed then it is possible
           // that a Sun Singleton is created by mistake in the config init.
           if (orbProperties.containsKey ("org.omg.CORBA.ORBClass") &&
               System.getProperty ("org.omg.CORBA.ORBClass") == null)
           {
               System.setProperty
               (
                   "org.omg.CORBA.ORBClass",
                   orbProperties.getProperty ("org.omg.CORBA.ORBClass")
               );
           }
           if (orbProperties.containsKey ("org.omg.CORBA.ORBSingletonClass") &&
               System.getProperty ("org.omg.CORBA.ORBSingletonClass") == null)
           {
               System.setProperty
               (
                   "org.omg.CORBA.ORBSingletonClass",
                   orbProperties.getProperty ("org.omg.CORBA.ORBSingletonClass")
               );
           }
       }

       if (!loaded)
       {
           delayedLogging.put (Level.FINE, "no properties found for configuration " + name);
       }
    }

    /**
     * loads properties via classloader.
     *
     * Properties are loaded in the following order, with later properties
     * overriding earlier ones: 1) Properties from ORB.init(), to get
     * properties that affect further property loading 2) orb.properties 3)
     * specific configuration file for the ORB (if any) 4) the ORB properties
     * set in the client code and passed in through ORB.init().  (Note that
     * these will thus always take effect!)
     *
     * @param delayedLogging the delayed logging
     * @param name the name for the ORB instance, may not be null.
     * @param orbProperties the orb properties
     * @throws ConfigurationException the configuration exception
     */

    private void initApplet(LinkedHashMap<Level, String> delayedLogging, String name, Properties orbProperties)
        throws ConfigurationException
   {
       if( name == null )
       {
           throw new ConfigurationException("Illegal null value for ORB name!");
       }
       boolean loaded = false;

       // 1) load system properties to grab any command line properties
       //    that will influence further property loading
       if ( orbProperties != null )
       {
           setAttributes(orbProperties);
       }

       // 2) look for orb.properties
       // look for common properties files on the classpath next
       Properties commonProps = loadPropertiesFromClassPath (COMMON_PROPS);

       if (commonProps!= null)
       {
           setAttributes(commonProps);
           loaded = true;
           delayedLogging.put (Level.FINE, "base configuration loaded from classpath " + COMMON_PROPS);
       }

       // 3) look for specific properties file
       String propFile = name + fileSuffix;
       Properties orbConfig = loadPropertiesFromClassPath (propFile);

       if (orbConfig!= null)
       {
           setAttributes(orbConfig);
           loaded = true;
           delayedLogging.put (Level.FINE, "configuration " + name + " loaded from classpath " + propFile);
       }
       else
       {
           delayedLogging.put (Level.WARNING, "File " + propFile +
                   " for configuration " + name +
                   " not found in classpath");
       }

       // 4) look for additional custom properties files
       List<String> customPropFileNames = getAttributeList("custom.props");

       if (!customPropFileNames.isEmpty())
       {
           for (String fileName : customPropFileNames)
           {
                Properties customProps = loadPropertiesFromClassPath(fileName);
                if (customProps!= null)
                {
                    setAttributes(customProps);
                    loaded = true;
                    delayedLogging.put (Level.FINE, "custom properties loaded from classpath " + fileName);
                }
                else
                {
                    delayedLogging.put (Level.WARNING, "custom properties " + fileName + " not found in classpath");
                }
           }
       }

       // 5) load properties passed to ORB.init(), these will override any
       // settings in config files or system properties!
       if (orbProperties != null)
       {
           loaded = true;
           setAttributes(orbProperties);
       }

       if (!loaded)
       {
           delayedLogging.put (Level.WARNING, "no properties found for configuration " + name);
       }
    }


    /**
     * Sets the value of a single attribute.
     *
     * @param key the key
     * @param value the value
     */
    @Override
    public void setAttribute(String key, String value)
    {
        stringAttributes.put(key, value);
    }

    /* (non-Javadoc)
     * @see org.jacorb.config.Configuration#setAttribute(java.lang.String, int)
     */
    @Override
    public void setAttribute(String key, int value)
    {
        numberAttributes.put(key, value);
    }


    /**
     * set attributes of this configuration using properties.
     *
     * @param properties the new attributes
     */

    @Override
    public void setAttributes(Properties properties)
    {
        // Some lunatics illegally put non String objects into System props
        // as keys / values - we ignore them.
        Iterator<String> keyIt = properties.stringPropertyNames().iterator();
        while (keyIt.hasNext())
        {
            String key = keyIt.next();
            setAttribute(key, properties.getProperty(key));
        }
    }


    /**
     * Loads properties from a file.
     *
     * @param fileName the name of a properties file
     * @return a properties object or null, if fileName not found
     */

    private Properties loadPropertiesFromFile(String fileName)
    {
        try
        {
            InputStream stream = new FileInputStream(fileName);
            try
            {
                Properties result = new Properties();
                result.load(stream);
                return result;
            }
            finally
            {
                stream.close();
            }
        }
        catch (java.io.FileNotFoundException e)
        {
            // It's okay to ignore this silently: There was just no
            // config file there, so the caller is going to look elsewhere.
            return null;
        }
        catch (java.io.IOException e)
        {
            // This is probably a more severe problem with the config file.
            // Write to the terminal, because we have no logging yet.
            System.err.println("could not read config file: " + fileName);
            e.printStackTrace();
            return null;
        }
    }


    /**
     * load properties file from classpath.
     *
     * @param name the name of the properties file.
     * @return a properties object or null, if name not found
     */

    private Properties loadPropertiesFromClassPath(String name)
    {
        Properties result = null;
        try
        {
            final ClassLoader clazzLoader;

            if (Thread.currentThread().getContextClassLoader() != null)
            {
                clazzLoader = Thread.currentThread().getContextClassLoader();
            }
            else
            {
                clazzLoader = JacORBConfiguration.class.getClassLoader();
            }

            java.net.URL url = clazzLoader.getResource(name);
            if (url!=null)
            {
                result = new Properties();
                final InputStream stream = url.openStream();
                try
                {
                    result.load(stream);
                }
                finally
                {
                    stream.close();
                }
            }
        }
        catch (FileNotFoundException ex)
        {
            // It's okay to ignore this silently: the caller will just look
            // elsewhere.
        }
        catch (java.io.IOException ioe)
        {
            // This is a more severe problem: write to the terminal, because
            // we have no logging yet.
            System.err.println("could not read config file: " + name);
            ioe.printStackTrace();
        }
        return result;
    }

    /**
     * Configures the external logging backend from within JacORB,
     * but only if the backend is JDK, and if one of the legacy logging
     * properties, jacorb.log.default.verbosity or jacorb.logfile, is set.
     * This is only meant to ease the transition as we move to SLF4J.
     * Normally, configuration of the logging backend is completely
     * external to JacORB and left to the user.
     *
     * @throws ConfigurationException the configuration exception
     */
    private void initLogging() throws ConfigurationException
    {
        li = (LoggingInitializer)getAttributeAsObject
        (
            ATTR_LOGGING_INITIALIZER,
            "org.jacorb.config.JdkLoggingInitializer"
        );
        li.init (this);
    }


    /**
     * Calls shutdown on the logging sub-system. This may be a no-op
     * depending upon the logging backend.
     */
    public void shutdownLogging ()
    {
        li.shutdownLogging ();
    }


    /**
     * Gets the ORB.
     *
     * @return the ORB for which this configuration was created
     */

    @Override
    public org.jacorb.orb.ORB getORB()
    {
        return orb;
    }


    /**
     * Returns a Logger that logs JacORB system messages.  This uses
     * the SLF4J logging facade.  The actual logging backend is chosen
     * at deployment time, by putting a corresponding SLF4J adapter
     * jar on the classpath.
     * <p>
     * The JacORB root logger is named "jacorb".  Sublogger names all
     * start with this prefix.  If the property jacorb.implname is set,
     * and the property jacorb.log.split_on_implname is true, then
     * all loggers for that particular ORB instance are rooted in
     * <code>{@literal jacorb.<implname>}</code>
     * <p>
     * Here's a guideline how to use logging levels in the code:
     * <p>
     * error Conditions that indicate a bug in JacORB or user code,
     * or a wrong configuration.  This includes, but is not
     * limited to, errors that will lead to termination of the
     * program (fatal errors).
     * <p>
     * warn  Conditions that demand attention, but are handled properly
     * according to the CORBA spec.  For example, abnormal termination
     * of a connection, reaching of a resource limit (queue full).
     * <p>
     * info  Start/stop of subsystems, establishing and closing of connections,
     * registering objects with a POA.
     * <p>
     * debug Information that might be needed for finding bugs in JacORB
     * or user code.  Anything that relates to the normal processing
     * of individual messages should come under this level.  For each
     * CORBA message, there should at least one debug message when
     * subsystem boundaries are crossed (e.g. GIOPConnection -&gt; POA
     * -&gt; User Code).
     *
     * @param name the name
     * @return the logger
     */
    @Override
    public org.slf4j.Logger getLogger (String name)
    {
        String loggerName = name;
        if (getAttributeAsBoolean ("jacorb.log.split_on_implname", false))
        {
           String implName = getAttribute ("jacorb.implname", null);

           if (implName != null && implName.length() > 0)
           {
              if (name.equals (Version.orbId))
              {
                 loggerName = LoggingInitializer.ATTR_LOG_NAME + '.' + implName;
              }
              else if (name.startsWith (LoggingInitializer.ATTR_LOG_NAME + '.'))
              {
                 loggerName = LoggingInitializer.ATTR_LOG_NAME + '.' + implName + "." + name.substring (11);
              }
           }
        }
        return org.slf4j.LoggerFactory.getLogger (loggerName);
    }

    @Override
    public boolean isAttributeSet(String key) throws ConfigurationException
    {
        return stringAttributes.containsKey(key);
    }

    /**
     * Gets the attribute
     *
     * @param key the key
     * @return the attribute
     * @throws ConfigurationException the configuration exception
     * @see org.jacorb.config.Configuration#getAttribute(java.lang.String)
     */
    @Override
    public String getAttribute(String key) throws ConfigurationException
    {
       String result = getAttribute (key, null);

       if (result == null)
       {
           throw new ConfigurationException
           (
               "attribute " + key + " is not defined"
           );
       }

       return result;
    }

    /**
     * getAttribute
     * @see org.jacorb.config.Configuration#getAttribute(java.lang.String, java.lang.String)
     */
    @Override
    public String getAttribute(String key, String defaultValue)
    {
       String result = stringAttributes.get (key);

       if (result == null && defaultValue != null)
       {
          stringAttributes.put (key, defaultValue);

          result = defaultValue;
       }
       else if ( result != null )
       {
           result = result.trim();
       }

       return result;
   }

    /**
     * Gets the attribute as integer.
     *
     * @param key the key
     * @param defaultValue the default value
     * @return the attribute as integer
     * @throws ConfigurationException the configuration exception
     * @see org.jacorb.config.Configuration#getAttributeAsInteger(java.lang.String, int, int)
     */
    @Override
    public int getAttributeAsInteger(String key, int defaultValue) throws ConfigurationException
    {
        return getAttributeAsInteger (key, defaultValue, 10);
    }

    /**
     * Returns the integer value of the specified key.
     *
     * @param key the key
     * @param defaultValue the default value
     * @param radix the radix
     * @return the attribute as integer
     * @throws ConfigurationException the configuration exception
     */
    @Override
    public int getAttributeAsInteger(String key, int defaultValue, int radix) throws ConfigurationException
    {
       Number result = numberAttributes.get (key);

       if (result == null)
       {
           // This used to be .remove but removing it from the string
           // cache can cause inconsistency if another caller uses
           // getAttribute.
          String value = stringAttributes.get (key);

          if (value == null)
          {
             result = Integer.valueOf (defaultValue);
          }
          else if (value.trim().length() < 1)
          {
              // treat empty values as non-defined (null)
              result = Integer.valueOf (defaultValue);
          }
          else
          {
             try
             {
                result = Integer.parseInt (value.trim(), radix);
             }
             catch (NumberFormatException ex)
             {
                throw new ConfigurationException
                (
                 "value for attribute " + key + " is not numeric: " + value
                );
             }
          }
          numberAttributes.put (key, result);

       }

       return result.intValue ();
    }


    /**
     * Validates the key exists and then returns the value.
     *
     * @param key the key
     * @return the attribute as integer
     * @throws ConfigurationException the configuration exception
     */
    @Override
    public int getAttributeAsInteger(String key) throws ConfigurationException
    {
        if ( ! stringAttributes.containsKey (key) && ! numberAttributes.containsKey (key))
        {
            throw new ConfigurationException
            (
                "Value for attribute " + key + " is not set"
            );
        }
        return getAttributeAsInteger (key, -1, 10);
    }


    /**
     * Gets the attribute as long.
     *
     * @param key the key
     * @param defaultValue the default value
     * @return the attribute as long
     * @throws ConfigurationException the configuration exception
     * @see org.jacorb.config.Configuration#getAttributeAsLong(java.lang.String, long)
     */
    @Override
    public long getAttributeAsLong(String key, long defaultValue) throws ConfigurationException
    {
       Number result = numberAttributes.get (key);

       if (result == null)
       {
           // This used to be .remove but removing it from the string
           // cache can cause inconsistency if another caller uses
           // getAttribute.
          String value = stringAttributes.get (key);

          if (value == null)
          {
             result = Long.valueOf (defaultValue);
          }
          else if (value.trim().length() < 1)
          {
              // treat empty values as non-defined (null)
              result = Long.valueOf (defaultValue);
          }
          else
          {
             try
             {
                result = Long.parseLong (value.trim());
             }
             catch (NumberFormatException ex)
             {
                throw new ConfigurationException
                (
                 "value for attribute " + key + " is not numeric: " + value
                );
             }
          }
          numberAttributes.put (key, result);
       }

       return result.longValue ();
    }


    /**
     * Gets the attribute list.
     *
     * @param key the key
     * @return the attribute list
     * @see org.jacorb.config.Configuration#getAttributeList(java.lang.String)
     */
    @Override
    public List<String> getAttributeList(String key)
    {
        List<String> result = new ArrayList<String>();
        String value = null;

        try
        {
            value = getAttribute(key);
        }
        catch( ConfigurationException ce)
        {
            // ignore
        }

        if (value != null)
        {
            StringTokenizer tok = new StringTokenizer(value, ",");
            while (tok.hasMoreTokens())
            {
                result.add(tok.nextToken().trim());
            }
        }
        return result;
    }

    @Override
    public String[] getAttributeAsStringsArray(String key)
    {
        String value = null;

        try
        {
            value = getAttribute(key);
        }
        catch( ConfigurationException ce)
        {
            // ignore
        }

        if (value == null)
        {
            return null;
        }

        List<String> values = getAttributeList (key);

        // Return null if key is defined but has empty value
        if (values.size () < 1)
        {
            return null;
        }

        return values.toArray (new String[values.size ()]);
    }


    /**
     * New instance.
     *
     * @param key the key
     * @param className the class name
     * @return the object
     * @throws ConfigurationException the configuration exception
     */
    private Object newInstance(String key, String className) throws ConfigurationException
    {
        try
        {
            Class<?> clazz = ObjectUtil.classForName(className);
            final Object instance = clazz.newInstance();

            if (instance instanceof Configurable)
            {
                ((Configurable)instance).configure(this);
            }
            return instance;
        }
        catch( Exception e )
        {
            throw new ConfigurationException( "Unable to build class from key >" +
                                              key +"<: " + e );
        }
    }


    /**
     * Gets the attribute as object.
     *
     * @param key the key
     * @return the attribute as object
     * @throws ConfigurationException the configuration exception
     * @see org.jacorb.config.Configuration#getAttributeAsObject(java.lang.String)
     */
    @Override
    public Object getAttributeAsObject( String key )
        throws ConfigurationException
    {
        return getAttributeAsObject(key, "");
    }

    /**
     * Gets the attribute as object.
     *
     * @param key the key
     * @param defaultClass the default class
     * @return the attribute as object
     * @throws ConfigurationException the configuration exception
     * @see org.jacorb.config.Configuration#getAttributeAsObject(java.lang.String, java.lang.String)
     */
    @Override
    public Object getAttributeAsObject(String key, String defaultClass) throws ConfigurationException
    {
       Object result = null;
       String classname = getAttribute (key, "");

       if (classname.length() > 0 )
       {
          result = newInstance(key, classname);
       }
       else if (defaultClass != null && defaultClass.length () > 0)
       {
          result = newInstance("default", defaultClass);
       }

       return result;
    }

    /**
     * Return the attribute as a boolean. A default value must be supplied and the
     * key/value is cached.
     *
     * @param key the key
     * @param defaultValue the default value
     * @return the attribute as boolean
     */
    @Override
    public boolean getAttributeAsBoolean(String key, boolean defaultValue)
    {
       Boolean result = booleanAttributes.get (key);

       if (result == null)
       {
           // This used to be .remove but removing it from the string
           // cache can cause inconsistency if another caller uses
           // getAttribute.
          String value = stringAttributes.get (key);

          if (value == null)
          {
             result = Boolean.valueOf (defaultValue);
          }
          else if (value.trim().length() < 1)
          {
              // treat empty values as non-defined (null)
              result = Boolean.valueOf (defaultValue);
          }
          else
          {
             value = value.trim().toLowerCase();
             result = Boolean.valueOf ((ON.equals(value) || TRUE.equals(value)));
          }
          booleanAttributes.put (key, result);
       }

       return result;
    }


    /**
     * Gets the attribute names.
     *
     * @return the attribute names
     */
    private String[] getAttributeNames()
    {
        return (stringAttributes.keySet().toArray (new String[]{}));
    }

    /**
     * Gets the attribute names with prefix.
     *
     * @param prefix the prefix
     * @return the attribute names with prefix
     * @see org.jacorb.config.Configuration#getAttributeNamesWithPrefix(java.lang.String)
     */
    @Override
    public List<String> getAttributeNamesWithPrefix(String prefix)
    {
        final ArrayList<String> attributesWithPrefix = new ArrayList<String>();

        final String[] allAttributes = getAttributeNames();

        for (int x = 0; x < allAttributes.length; ++x)
        {
            if (allAttributes[x].startsWith(prefix))
            {
                attributesWithPrefix.add(allAttributes[x]);
            }
        }

        return Collections.unmodifiableList(attributesWithPrefix);
    }


    /* (non-Javadoc)
     * @see org.jacorb.config.Configuration#getAttributeAsFloat(java.lang.String, double)
     */
    @Override
    public double getAttributeAsFloat (String key, double defaultValue) throws ConfigurationException
    {
       Number result = numberAttributes.get (key);

       if (result == null)
       {
           // This used to be .remove but removing it from the string
           // cache can cause inconsistency if another caller uses
           // getAttribute.
          String value = stringAttributes.get (key);

          if (value == null)
          {
             result = Double.valueOf (defaultValue);
          }
          else if (value.trim().length() < 1)
          {
              // treat empty values as non-defined (null)
              result = Double.valueOf (defaultValue);
          }
          else
          {
             try
             {
                result = Double.parseDouble (value.trim());
             }
             catch (NumberFormatException ex)
             {
                throw new ConfigurationException
                (
                 "value for attribute " + key + " is not numeric: " + value
                );
             }
          }
          numberAttributes.put (key, result);
       }

       return result.doubleValue ();
    }
}
