Hi guys,

Thanks again for inspecting this problem with me and hashing out my various
options.  I have taken the liberty of making some modifications to 2 classes
in Commons Configuration (attached) in order to get it to do what I wanted.

Is it possible to contribute this to Commons Configuration?  I've tried to
do a good job in this code change by providing the test case.  I plan to use
commons configuration for as long as I can think and would hate to have to
make this change for every new version I get.

The summary of these changes is that I added a Boolean flag to tell the
interpolation algorithm whether to substitute using the entire multi-valued
property or just the first value.  I.e., an if statement determining whether
getString or getPoroperty are used.  I added a test case for this in what I
think is the appropriate place.

My exact changes are as follows:

TestBaseConfiguration.java(592): test case for making sure flag switches
behaviour properly.

        public void testMultiValuedPropertyInterpolation() throws Exception
        {
                config.setProperty("multi", "value1");
                config.addProperty("multi", "value2");
                config.setProperty("interpolated", "${multi}");
                
                config.setInterpolateAllValues(false);
                String expectedValue = "value1";        
                assertEquals(config.getString("interpolated"),
expectedValue);
                
                config.setInterpolateAllValues(true);
                expectedValue = "" + config.getProperty("multi");
                
                assertEquals(config.getString("interpolated"),
expectedValue);
        }

Abstractconfiguration.java(59): added flag

        /**
         * Determines whether all values of a property are used or just 
         *the first when 
         * substituting tokens.
         */
        private boolean interpolateAllValues = true;

Abstractconfiguration.java(209): the if statement in interpolateHelper() to
use getString or getProperty:

        Object value;
        if (isInterpolateAllValues())
        {
                value = getProperty(variable);
        }
        else
        {
                value = getString(variable);
        }

Abstractconfiguration.java(977): setter and getter for flag:

        public void setInterpolateAllValues(boolean b)
        {
                this.interpolateAllValues = b;
        }

        public boolean isInterpolateAllValues()
        {
                return interpolateAllValues;
        }

> -----Original Message-----
> From: Emmanuel Bourg [mailto:[EMAIL PROTECTED]
> Sent: Tuesday, August 23, 2005 8:49 AM
> To: Jakarta Commons Users List
> Subject: Re: [configuration] Property Substitution Policy
> 
> Hi Moran,
> 
> Moran Ben-David wrote:
> 
> > I was actually trying to get the substitution to only use the 1st value
> in a
> > multi-valued property.  For example, a file like
> >
> >     V1 = value1
> >     V1 = value2
> >     Myprop = ${V1}
> >
> > Would result in a configuration having
> >
> >     Myprop = value1
> >
> > This way, when I do config.getString("Myprop") I'd get "value1" and not
> > "[value1, value2]".
> 
> I tend to agree on this, the interpolated value should be a scalar, this
> would be consistent with the getString() method called on a multi-valued
> property.
> 
> However regarding your use case I agree with Oliver, using a
> CompositeConfiguration is the right solution. You can build it
> programatically, or use the ConfigurationFactory with a configuration
> descriptor.
> 
> Emmanuel Bourg
> 
> ---------------------------------------------------------------------
> To unsubscribe, e-mail: [EMAIL PROTECTED]
> For additional commands, e-mail: [EMAIL PROTECTED]
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * 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.apache.commons.configuration;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Properties;

import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.iterators.FilterIterator;
import org.apache.commons.lang.BooleanUtils;

/**
 * Abstract configuration class. Provide basic functionality but does not store
 * any data. If you want to write your own Configuration class then you should
 * implement only abstract methods from this class.
 * 
 * @author <a href="mailto:[EMAIL PROTECTED]">Konstantin Shaposhnikov </a>
 * @author <a href="mailto:[EMAIL PROTECTED]">Oliver Heger </a>
 * @author <a href="mailto:[EMAIL PROTECTED]">Henning P. Schmiedehausen </a>
 * @version $Id: AbstractConfiguration.java,v 1.29 2004/12/02 22:05:52 ebourg
 * Exp $
 */
public abstract class AbstractConfiguration implements Configuration
{
    /** start token */
    protected static final String START_TOKEN = "${";

    /** end token */
    protected static final String END_TOKEN = "}";

    /** The property delimiter used while parsing (a comma). */
    private static char DELIMITER = ',';

    /**
     * Whether the configuration should throw NoSuchElementExceptions or simply
     * return null when a property does not exist. Defaults to return null.
     */
    private boolean throwExceptionOnMissing = false;
	
	/**
	 * Determines whether all values of a property are used or just the first when 
	 * substituting tokens.
	 */
	private boolean interpolateAllValues = true;

    /**
     * For configurations extending AbstractConfiguration, allow them to change
     * the delimiter from the default comma (",").
     * 
     * @param delimiter The new delimiter
     */
    public static void setDelimiter(char delimiter)
    {
        AbstractConfiguration.DELIMITER = delimiter;
    }

    /**
     * Retrieve the current delimiter. By default this is a comma (",").
     * 
     * @return The delimiter in use
     */
    public static char getDelimiter()
    {
        return AbstractConfiguration.DELIMITER;
    }

    /**
     * If set to false, missing elements return null if possible (for objects).
     * 
     * @param throwExceptionOnMissing The new value for the property
     */
    public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
    {
        this.throwExceptionOnMissing = throwExceptionOnMissing;
    }

    /**
     * Returns true if missing values throw Exceptions.
     * 
     * @return true if missing values throw Exceptions
     */
    public boolean isThrowExceptionOnMissing()
    {
        return throwExceptionOnMissing;
    }

    /**
     * [EMAIL PROTECTED]
     */
    public void addProperty(String key, Object value)
    {
        Iterator it = PropertyConverter.toIterator(value, DELIMITER);
        while (it.hasNext())
        {
            addPropertyDirect(key, it.next());
        }
    }

    /**
     * Adds a key/value pair to the Configuration. Override this method to
     * provide write acces to underlying Configuration store.
     * 
     * @param key key to use for mapping
     * @param obj object to store
     */
    protected abstract void addPropertyDirect(String key, Object obj);

    /**
     * interpolate key names to handle ${key} stuff
     * 
     * @param base string to interpolate
     * 
     * @return returns the key name with the ${key} substituted
     */
    protected String interpolate(String base)
    {
        return interpolateHelper(base, null);
    }

    /**
     * Recursive handler for multple levels of interpolation.
     * 
     * When called the first time, priorVariables should be null.
     * 
     * @param base string with the ${key} variables
     * @param priorVariables serves two purposes: to allow checking for loops,
     * and creating a meaningful exception message should a loop occur. It's
     * 0'th element will be set to the value of base from the first call. All
     * subsequent interpolated variables are added afterward.
     * 
     * @return the string with the interpolation taken care of
     */
    protected String interpolateHelper(String base, List priorVariables)
    {
        if (base == null)
        {
            return null;
        }

        // on the first call initialize priorVariables
        // and add base as the first element
        if (priorVariables == null)
        {
            priorVariables = new ArrayList();
            priorVariables.add(base);
        }

        int begin = -1;
        int end = -1;
        int prec = 0 - END_TOKEN.length();
        String variable = null;
        StringBuffer result = new StringBuffer();

        // FIXME: we should probably allow the escaping of the start token
        while (((begin = base.indexOf(START_TOKEN, prec + END_TOKEN.length())) > -1)
                && ((end = base.indexOf(END_TOKEN, begin)) > -1))
        {
            result.append(base.substring(prec + END_TOKEN.length(), begin));
            variable = base.substring(begin + START_TOKEN.length(), end);

            // if we've got a loop, create a useful exception message and throw
            if (priorVariables.contains(variable))
            {
                String initialBase = priorVariables.remove(0).toString();
                priorVariables.add(variable);
                StringBuffer priorVariableSb = new StringBuffer();

                // create a nice trace of interpolated variables like so:
                // var1->var2->var3
                for (Iterator it = priorVariables.iterator(); it.hasNext();)
                {
                    priorVariableSb.append(it.next());
                    if (it.hasNext())
                    {
                        priorVariableSb.append("->");
                    }
                }

                throw new IllegalStateException("infinite loop in property interpolation of " + initialBase + ": "
                        + priorVariableSb.toString());
            }
            // otherwise, add this variable to the interpolation list.
            else
            {
                priorVariables.add(variable);
            }

			
            Object value;
            if (isInterpolateAllValues())
			{
				value = getProperty(variable);
			}
            else
			{
				value = getString(variable);
			}
			
            if (value != null)
            {
                result.append(interpolateHelper(value.toString(), priorVariables));

                // pop the interpolated variable off the stack
                // this maintains priorVariables correctness for
                // properties with multiple interpolations, e.g.
                // prop.name=${some.other.prop1}/blahblah/${some.other.prop2}
                priorVariables.remove(priorVariables.size() - 1);
            }
            else
            {
                //variable not defined - so put it back in the value
                result.append(START_TOKEN).append(variable).append(END_TOKEN);
            }

            prec = end;
        }
        result.append(base.substring(prec + END_TOKEN.length(), base.length()));
        return result.toString();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Configuration subset(String prefix)
    {
        return new SubsetConfiguration(this, prefix, ".");
    }

    /**
     * [EMAIL PROTECTED]
     */
    public abstract boolean isEmpty();

    /**
     * [EMAIL PROTECTED]
     */
    public abstract boolean containsKey(String key);

    /**
     * [EMAIL PROTECTED]
     */
    public void setProperty(String key, Object value)
    {
        clearProperty(key);
        addProperty(key, value);
    }

    /**
     * [EMAIL PROTECTED]
     */
    public abstract void clearProperty(String key);

    /**
     * [EMAIL PROTECTED]
     */
    public void clear()
    {
        Iterator it = getKeys();
        while (it.hasNext())
        {
            String key = (String) it.next();
            it.remove();

            if (containsKey(key))
            {
                // workaround for Iterators that do not remove the property on calling remove()
                clearProperty(key);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public abstract Iterator getKeys();

    /**
     * [EMAIL PROTECTED]
     */
    public Iterator getKeys(final String prefix)
    {
        return new FilterIterator(getKeys(), new Predicate()
        {
            public boolean evaluate(Object obj)
            {
                String key = (String) obj;
                return key.startsWith(prefix + ".") || key.equals(prefix);
            }
        });
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Properties getProperties(String key)
    {
        return getProperties(key, null);
    }

    /**
     * Get a list of properties associated with the given configuration key.
     * 
     * @param key The configuration key.
     * @param defaults Any default values for the returned
     * <code>Properties</code> object. Ignored if <code>null</code>.
     * 
     * @return The associated properties if key is found.
     * 
     * @throws ConversionException is thrown if the key maps to an object that
     * is not a String/List of Strings.
     * 
     * @throws IllegalArgumentException if one of the tokens is malformed (does
     * not contain an equals sign).
     */
    public Properties getProperties(String key, Properties defaults)
    {
        /*
         * Grab an array of the tokens for this key.
         */
        String[] tokens = getStringArray(key);

        /*
         * Each token is of the form 'key=value'.
         */
        Properties props = defaults == null ? new Properties() : new Properties(defaults);
        for (int i = 0; i < tokens.length; i++)
        {
            String token = tokens[i];
            int equalSign = token.indexOf('=');
            if (equalSign > 0)
            {
                String pkey = token.substring(0, equalSign).trim();
                String pvalue = token.substring(equalSign + 1).trim();
                props.put(pkey, pvalue);
            }
            else if (tokens.length == 1 && "".equals(token))
            {
                // Semantically equivalent to an empty Properties
                // object.
                break;
            }
            else
            {
                throw new IllegalArgumentException('\'' + token + "' does not contain an equals sign");
            }
        }
        return props;
    }

    /**
     * [EMAIL PROTECTED]
     */
    public boolean getBoolean(String key)
    {
        Boolean b = getBoolean(key, null);
        if (b != null)
        {
            return b.booleanValue();
        }
        else
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public boolean getBoolean(String key, boolean defaultValue)
    {
        return getBoolean(key, BooleanUtils.toBooleanObject(defaultValue)).booleanValue();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Boolean getBoolean(String key, Boolean defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toBoolean(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a Boolean object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public byte getByte(String key)
    {
        Byte b = getByte(key, null);
        if (b != null)
        {
            return b.byteValue();
        }
        else
        {
            throw new NoSuchElementException('\'' + key + " doesn't map to an existing object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public byte getByte(String key, byte defaultValue)
    {
        return getByte(key, new Byte(defaultValue)).byteValue();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Byte getByte(String key, Byte defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toByte(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a Byte object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public double getDouble(String key)
    {
        Double d = getDouble(key, null);
        if (d != null)
        {
            return d.doubleValue();
        }
        else
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public double getDouble(String key, double defaultValue)
    {
        return getDouble(key, new Double(defaultValue)).doubleValue();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Double getDouble(String key, Double defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toDouble(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a Double object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public float getFloat(String key)
    {
        Float f = getFloat(key, null);
        if (f != null)
        {
            return f.floatValue();
        }
        else
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public float getFloat(String key, float defaultValue)
    {
        return getFloat(key, new Float(defaultValue)).floatValue();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Float getFloat(String key, Float defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toFloat(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a Float object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public int getInt(String key)
    {
        Integer i = getInteger(key, null);
        if (i != null)
        {
            return i.intValue();
        }
        else
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public int getInt(String key, int defaultValue)
    {
        Integer i = getInteger(key, null);

        if (i == null)
        {
            return defaultValue;
        }

        return i.intValue();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Integer getInteger(String key, Integer defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toInteger(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to an Integer object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public long getLong(String key)
    {
        Long l = getLong(key, null);
        if (l != null)
        {
            return l.longValue();
        }
        else
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public long getLong(String key, long defaultValue)
    {
        return getLong(key, new Long(defaultValue)).longValue();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Long getLong(String key, Long defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toLong(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a Long object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public short getShort(String key)
    {
        Short s = getShort(key, null);
        if (s != null)
        {
            return s.shortValue();
        }
        else
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public short getShort(String key, short defaultValue)
    {
        return getShort(key, new Short(defaultValue)).shortValue();
    }

    /**
     * [EMAIL PROTECTED]
     */
    public Short getShort(String key, Short defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toShort(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a Short object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public BigDecimal getBigDecimal(String key)
    {
        BigDecimal number = getBigDecimal(key, null);
        if (number != null)
        {
            return number;
        }
        else if (isThrowExceptionOnMissing())
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
        else
        {
            return null;
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toBigDecimal(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public BigInteger getBigInteger(String key)
    {
        BigInteger number = getBigInteger(key, null);
        if (number != null)
        {
            return number;
        }
        else if (isThrowExceptionOnMissing())
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
        else
        {
            return null;
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public BigInteger getBigInteger(String key, BigInteger defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value == null)
        {
            return defaultValue;
        }
        else
        {
            try
            {
                return PropertyConverter.toBigInteger(value);
            }
            catch (ConversionException e)
            {
                throw new ConversionException('\'' + key + "' doesn't map to a BigDecimal object", e);
            }
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public String getString(String key)
    {
        String s = getString(key, null);
        if (s != null)
        {
            return s;
        }
        else if (isThrowExceptionOnMissing())
        {
            throw new NoSuchElementException('\'' + key + "' doesn't map to an existing object");
        }
        else
        {
            return null;
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public String getString(String key, String defaultValue)
    {
        Object value = resolveContainerStore(key);

        if (value instanceof String)
        {
            return interpolate((String) value);
        }
        else if (value == null)
        {
            return interpolate(defaultValue);
        }
        else
        {
            throw new ConversionException('\'' + key + "' doesn't map to a String object");
        }
    }

    /**
     * [EMAIL PROTECTED]
     */
    public String[] getStringArray(String key)
    {
        Object value = getProperty(key);

        String[] array;

        if (value instanceof String)
        {
            array = new String[1];

            array[0] = interpolate((String) value);
        }
        else if (value instanceof List)
        {
            List list = (List) value;
            array = new String[list.size()];

            for (int i = 0; i < array.length; i++)
            {
                array[i] = interpolate((String) list.get(i));
            }
        }
        else if (value == null)
        {
            array = new String[0];
        }
        else
        {
            throw new ConversionException('\'' + key + "' doesn't map to a String/List object");
        }
        return array;
    }

    /**
     * [EMAIL PROTECTED]
     */
    public List getList(String key)
    {
        return getList(key, new ArrayList());
    }

    /**
     * [EMAIL PROTECTED]
     */
    public List getList(String key, List defaultValue)
    {
        Object value = getProperty(key);
        List list = null;

        if (value instanceof String)
        {
            list = new ArrayList(1);
            list.add(value);
        }
        else if (value instanceof List)
        {
            list = (List) value;
        }
        else if (value == null)
        {
            list = defaultValue;
        }
        else
        {
            throw new ConversionException('\'' + key + "' doesn't map to a List object: " + value + ", a "
                    + value.getClass().getName());
        }
        return list;
    }

    /**
     * Returns an object from the store described by the key. If the value is a
     * List object, replace it with the first object in the list.
     * 
     * @param key The property key.
     * 
     * @return value Value, transparently resolving a possible List dependency.
     */
    protected Object resolveContainerStore(String key)
    {
        Object value = getProperty(key);
        if (value != null)
        {
            if (value instanceof List)
            {
                List list = (List) value;
                value = list.isEmpty() ? null : list.get(0);
            }
            else if (value instanceof Object[])
            {
                Object[] array = (Object[]) value;
                value = array.length == 0 ? null : array[0];
            }
            else if (value instanceof boolean[])
            {
                boolean[] array = (boolean[]) value;
                value = array.length == 0 ? null : new Boolean(array[0]);
            }
            else if (value instanceof byte[])
            {
                byte[] array = (byte[]) value;
                value = array.length == 0 ? null : new Byte(array[0]);
            }
            else if (value instanceof short[])
            {
                short[] array = (short[]) value;
                value = array.length == 0 ? null : new Short(array[0]);
            }
            else if (value instanceof int[])
            {
                int[] array = (int[]) value;
                value = array.length == 0 ? null : new Integer(array[0]);
            }
            else if (value instanceof long[])
            {
                long[] array = (long[]) value;
                value = array.length == 0 ? null : new Long(array[0]);
            }
            else if (value instanceof float[])
            {
                float[] array = (float[]) value;
                value = array.length == 0 ? null : new Float(array[0]);
            }
            else if (value instanceof double[])
            {
                double[] array = (double[]) value;
                value = array.length == 0 ? null : new Double(array[0]);
            }
        }

        return value;
    }
	
	public void setInterpolateAllValues(boolean b)
	{
		this.interpolateAllValues = b;
	}

	public boolean isInterpolateAllValues()
	{
		return interpolateAllValues;
	}

}
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * 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.apache.commons.configuration;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Properties;
import java.util.StringTokenizer;

import junit.framework.TestCase;
import junitx.framework.ObjectAssert;

/**
 * Tests some basic functions of the BaseConfiguration class. Missing keys will
 * throw Exceptions
 *
 * @version $Id: TestBaseConfiguration.java 158491 2005-03-21 17:58:56Z ebourg $
 */
public class TestBaseConfiguration extends TestCase
{
    protected BaseConfiguration config = null;

    protected static Class missingElementException = NoSuchElementException.class;
    protected static Class incompatibleElementException = ConversionException.class;

    protected void setUp() throws Exception
    {
        config = new BaseConfiguration();
        config.setThrowExceptionOnMissing(true);
    }

    public void testThrowExceptionOnMissing()
    {
        assertTrue("Throw Exception Property is not set!", config.isThrowExceptionOnMissing());
    }

    public void testGetProperty()
    {
        /* should be empty and return null */
        assertEquals("This returns null", config.getProperty("foo"), null);

        /* add a real value, and get it two different ways */
        config.setProperty("number", "1");
        assertEquals("This returns '1'", config.getProperty("number"), "1");
        assertEquals("This returns '1'", config.getString("number"), "1");
    }

    public void testGetByte()
    {
        config.setProperty("number", "1");
        byte oneB = 1;
        byte twoB = 2;
        assertEquals("This returns 1(byte)", oneB, config.getByte("number"));
        assertEquals("This returns 1(byte)", oneB, config.getByte("number", twoB));
        assertEquals("This returns 2(default byte)", twoB, config.getByte("numberNotInConfig", twoB));
        assertEquals("This returns 1(Byte)", new Byte(oneB), config.getByte("number", new Byte("2")));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getByte("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getByte("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetShort()
    {
        config.setProperty("numberS", "1");
        short oneS = 1;
        short twoS = 2;
        assertEquals("This returns 1(short)", oneS, config.getShort("numberS"));
        assertEquals("This returns 1(short)", oneS, config.getShort("numberS", twoS));
        assertEquals("This returns 2(default short)", twoS, config.getShort("numberNotInConfig", twoS));
        assertEquals("This returns 1(Short)", new Short(oneS), config.getShort("numberS", new Short("2")));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getShort("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getShort("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetLong()
    {
        config.setProperty("numberL", "1");
        long oneL = 1;
        long twoL = 2;
        assertEquals("This returns 1(long)", oneL, config.getLong("numberL"));
        assertEquals("This returns 1(long)", oneL, config.getLong("numberL", twoL));
        assertEquals("This returns 2(default long)", twoL, config.getLong("numberNotInConfig", twoL));
        assertEquals("This returns 1(Long)", new Long(oneL), config.getLong("numberL", new Long("2")));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getLong("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getLong("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetFloat()
    {
        config.setProperty("numberF", "1.0");
        float oneF = 1;
        float twoF = 2;
        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF"), 0);
        assertEquals("This returns 1(float)", oneF, config.getFloat("numberF", twoF), 0);
        assertEquals("This returns 2(default float)", twoF, config.getFloat("numberNotInConfig", twoF), 0);
        assertEquals("This returns 1(Float)", new Float(oneF), config.getFloat("numberF", new Float("2")));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getFloat("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getFloat("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetDouble()
    {
        config.setProperty("numberD", "1.0");
        double oneD = 1;
        double twoD = 2;
        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD"), 0);
        assertEquals("This returns 1(double)", oneD, config.getDouble("numberD", twoD), 0);
        assertEquals("This returns 2(default double)", twoD, config.getDouble("numberNotInConfig", twoD), 0);
        assertEquals("This returns 1(Double)", new Double(oneD), config.getDouble("numberD", new Double("2")));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getDouble("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getDouble("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetBigDecimal()
    {
        config.setProperty("numberBigD", "123.456");
        BigDecimal number = new BigDecimal("123.456");
        BigDecimal defaultValue = new BigDecimal("654.321");

        assertEquals("Existing key", number, config.getBigDecimal("numberBigD"));
        assertEquals("Existing key with default value", number, config.getBigDecimal("numberBigD", defaultValue));
        assertEquals("Missing key with default value", defaultValue, config.getBigDecimal("numberNotInConfig", defaultValue));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getBigDecimal("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getBigDecimal("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetBigInteger()
    {
        config.setProperty("numberBigI", "1234567890");
        BigInteger number = new BigInteger("1234567890");
        BigInteger defaultValue = new BigInteger("654321");

        assertEquals("Existing key", number, config.getBigInteger("numberBigI"));
        assertEquals("Existing key with default value", number, config.getBigInteger("numberBigI", defaultValue));
        assertEquals("Missing key with default value", defaultValue, config.getBigInteger("numberNotInConfig", defaultValue));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getBigInteger("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getBigInteger("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetString()
    {
        config.setProperty("testString", "The quick brown fox");
        String string = new String("The quick brown fox");
        String defaultValue = new String("jumps over the lazy dog");

        assertEquals("Existing key", string, config.getString("testString"));
        assertEquals("Existing key with default value", string, config.getString("testString", defaultValue));
        assertEquals("Missing key with default value", defaultValue, config.getString("stringNotInConfig", defaultValue));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getString("stringNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);
    }

    public void testGetBoolean()
    {
        config.setProperty("boolA", Boolean.TRUE);
        boolean boolT = true, boolF = false;
        assertEquals("This returns true", boolT, config.getBoolean("boolA"));
        assertEquals("This returns true, not the default", boolT, config.getBoolean("boolA", boolF));
        assertEquals("This returns false(default)", boolF, config.getBoolean("boolNotInConfig", boolF));
        assertEquals("This returns true(Boolean)", new Boolean(boolT), config.getBoolean("boolA", new Boolean(boolF)));

        // missing key without default value
        Throwable t = null;
        try
        {
            config.getBoolean("numberNotInConfig");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for missing keys", t);
        ObjectAssert.assertInstanceOf("Exception thrown for missing keys", missingElementException, t);

        // existing key with an incompatible value
        config.setProperty("test.empty", "");
        t = null;
        try
        {
            config.getBoolean("test.empty");
        }
        catch (Throwable T)
        {
            t = T;
        }
        assertNotNull("No exception thrown for incompatible values", t);
        ObjectAssert.assertInstanceOf("Exception thrown for incompatible values", incompatibleElementException, t);
    }

    public void testGetList()
    {
        config.addProperty("number", "1");
        config.addProperty("number", "2");
        List list = config.getList("number");
        assertNotNull("The list is null", list);
        assertEquals("List size", 2, list.size());
        assertTrue("The number 1 is missing from the list", list.contains("1"));
        assertTrue("The number 2 is missing from the list", list.contains("2"));

        /*
         *  now test dan's new fix where we get the first scalar
         *  when we access a list valued property
         */
        try
        {
            config.getString("number");
        }
        catch (NoSuchElementException nsse)
        {
            fail("Should return a string");
        }
    }

    public void testCommaSeparatedString()
    {
        String prop = "hey, that's a test";
        config.setProperty("prop.string", prop);
        try
        {
            config.getList("prop.string");
        }
        catch (NoSuchElementException nsse)
        {
            fail("Should return a list");
        }

        String prop2 = "hey\\, that's a test";
        config.clearProperty("prop.string");
        config.setProperty("prop.string", prop2);
        try
        {
            config.getString("prop.string");
        }
        catch (NoSuchElementException nsse)
        {
            fail("Should return a list");
        }

    }
    
    public void testAddProperty() throws Exception
    {
        Collection props = new ArrayList();
        props.add("one");
        props.add("two,three,four");
        props.add(new String[] { "5.1", "5.2", "5.3,5.4", "5.5" });
        props.add("six");
        config.addProperty("complex.property", props);
        
        Object val = config.getProperty("complex.property");
        assertTrue(val instanceof Collection);
        Collection col = (Collection) val;
        assertEquals(10, col.size());
        
        props = new ArrayList();
        props.add("quick");
        props.add("brown");
        props.add("fox,jumps");
        Object[] data = new Object[] {
                "The", props, "over,the", "lazy", "dog."
        };
        config.setProperty("complex.property", data);
        val = config.getProperty("complex.property");
        assertTrue(val instanceof Collection);
        col = (Collection) val;
        Iterator it = col.iterator();
        StringTokenizer tok = new StringTokenizer("The quick brown fox jumps over the lazy dog.", " ");
        while(tok.hasMoreTokens())
        {
            assertTrue(it.hasNext());
            assertEquals(tok.nextToken(), it.next());
        }
        assertFalse(it.hasNext());
        
        config.setProperty("complex.property", null);
        assertFalse(config.containsKey("complex.property"));
    }

    public void testPropertyAccess()
    {
        config.clearProperty("prop.properties");
        config.setProperty("prop.properties", "");
        assertEquals(
                "This returns an empty Properties object",
                config.getProperties("prop.properties"),
                new Properties());
        config.clearProperty("prop.properties");
        config.setProperty("prop.properties", "foo=bar, baz=moo, seal=clubber");

        Properties p = new Properties();
        p.setProperty("foo", "bar");
        p.setProperty("baz", "moo");
        p.setProperty("seal", "clubber");
        assertEquals(
                "This returns a filled in Properties object",
                config.getProperties("prop.properties"),
                p);
    }

    public void testSubset()
    {
        /*
         * test subset : assure we don't reprocess the data elements
         * when generating the subset
         */

        String prop = "hey, that's a test";
        String prop2 = "hey\\, that's a test";
        config.setProperty("prop.string", prop2);
        config.setProperty("property.string", "hello");

        Configuration subEprop = config.subset("prop");

        assertEquals(
                "Returns the full string",
                prop,
                subEprop.getString("string"));
        try
        {
            subEprop.getString("string");
        }
        catch (NoSuchElementException nsse)
        {
            fail("Should return a string");
        }
        try
        {
            subEprop.getList("string");
        }
        catch (NoSuchElementException nsse)
        {
            fail("Should return a list");
        }

        Iterator it = subEprop.getKeys();
        it.next();
        assertFalse(it.hasNext());

        subEprop = config.subset("prop.");
        it = subEprop.getKeys();
        assertFalse(it.hasNext());
    }

    public void testInterpolation() throws Exception
    {
        config.setProperty("applicationRoot", "/home/applicationRoot");
        config.setProperty("db", "${applicationRoot}/db/hypersonic");
        String unInterpolatedValue = "${applicationRoot2}/db/hypersonic";
        config.setProperty("dbFailedInterpolate", unInterpolatedValue);
        String dbProp = "/home/applicationRoot/db/hypersonic";

        //construct a new config, using config as the defaults config for it.
        BaseConfiguration superProp = config;

        assertEquals(
                "Checking interpolated variable", dbProp,
                superProp.getString("db"));
        assertEquals(
                "lookup fails, leave variable as is",
                superProp.getString("dbFailedInterpolate"),
                unInterpolatedValue);

        superProp.setProperty("arrayInt", "${applicationRoot}/1");
        String[] arrayInt = superProp.getStringArray("arrayInt");
        assertEquals(
                "check first entry was interpolated",
                "/home/applicationRoot/1",
                arrayInt[0]);
    }

    public void testMultipleInterpolation() throws Exception
    {
        config.setProperty("test.base-level", "/base-level");
        config.setProperty("test.first-level", "${test.base-level}/first-level");
        config.setProperty(
                "test.second-level",
                "${test.first-level}/second-level");
        config.setProperty(
                "test.third-level",
                "${test.second-level}/third-level");

        String expectedValue =
                "/base-level/first-level/second-level/third-level";

        assertEquals(config.getString("test.third-level"), expectedValue);
    }
	
	public void testMultiValuedPropertyInterpolation() throws Exception
	{
		config.setProperty("multi", "value1");
		config.addProperty("multi", "value2");
		config.setProperty("interpolated", "${multi}");
		
		config.setInterpolateAllValues(false);
		String expectedValue = "value1";	
		assertEquals(config.getString("interpolated"), expectedValue);
		
		config.setInterpolateAllValues(true);
		expectedValue = "" + config.getProperty("multi");
		
		assertEquals(config.getString("interpolated"), expectedValue);
	}

    public void testInterpolationLoop() throws Exception
    {
        config.setProperty("test.a", "${test.b}");
        config.setProperty("test.b", "${test.a}");

        try
        {
            config.getString("test.a");
        }
        catch (IllegalStateException e)
        {
            return;
        }

        fail("IllegalStateException should have been thrown for looped property references");
    }

    public void testGetHexadecimalValue()
    {
        config.setProperty("number", "0xFF");
        assertEquals("byte value", (byte) 0xFF, config.getByte("number"));

        config.setProperty("number", "0xFFFF");
        assertEquals("short value", (short) 0xFFFF, config.getShort("number"));

        config.setProperty("number", "0xFFFFFFFF");
        assertEquals("int value", 0xFFFFFFFF, config.getInt("number"));

        config.setProperty("number", "0xFFFFFFFFFFFFFFFF");
        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getLong("number"));

        assertEquals("long value", 0xFFFFFFFFFFFFFFFFL, config.getBigInteger("number").longValue());
    }

    public void testResolveContainerStore()
    {
        AbstractConfiguration config = new BaseConfiguration();

        // array of objects
        config.addPropertyDirect("array", new String[] { "foo", "bar" });

        assertEquals("first element of the 'array' property", "foo", config.resolveContainerStore("array"));

        // list of objects
        List list = new ArrayList();
        list.add("foo");
        list.add("bar");
        config.addPropertyDirect("list", list);

        assertEquals("first element of the 'list' property", "foo", config.resolveContainerStore("list"));

        // arrays of primitives
        config.addPropertyDirect("array.boolean", new boolean[] { true, false });
        assertEquals("first element of the 'array.boolean' property", true, config.getBoolean("array.boolean"));

        config.addPropertyDirect("array.byte", new byte[] { 1, 2 });
        assertEquals("first element of the 'array.byte' property", 1, config.getByte("array.byte"));

        config.addPropertyDirect("array.short", new short[] { 1, 2 });
        assertEquals("first element of the 'array.short' property", 1, config.getShort("array.short"));

        config.addPropertyDirect("array.int", new int[] { 1, 2 });
        assertEquals("first element of the 'array.int' property", 1, config.getInt("array.int"));

        config.addPropertyDirect("array.long", new long[] { 1, 2 });
        assertEquals("first element of the 'array.long' property", 1, config.getLong("array.long"));

        config.addPropertyDirect("array.float", new float[] { 1, 2 });
        assertEquals("first element of the 'array.float' property", 1, config.getFloat("array.float"), 0);

        config.addPropertyDirect("array.double", new double[] { 1, 2 });
        assertEquals("first element of the 'array.double' property", 1, config.getDouble("array.double"), 0);
    }

}

---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]

Reply via email to