    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *     http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.configuration2.beanutils;
    019
    020import java.lang.reflect.Array;
    021import java.util.Collection;
    022import java.util.List;
    023
    024import org.apache.commons.beanutils.DynaBean;
    025import org.apache.commons.beanutils.DynaClass;
    026import org.apache.commons.configuration2.Configuration;
    027import org.apache.commons.configuration2.ConfigurationMap;
    028import org.apache.commons.configuration2.SubsetConfiguration;
    029import org.apache.commons.logging.Log;
    030import org.apache.commons.logging.LogFactory;
    031
    032/**
    033 * The {@code ConfigurationDynaBean} dynamically reads and writes
    034 * configurations properties from a wrapped configuration-collection
    035 * {@link org.apache.commons.configuration2.Configuration} instance. It also
    036 * implements a {@link java.util.Map} interface so that it can be used in
    037 * JSP 2.0 Expression Language expressions.
    038 *
    039 * <p>The {@code ConfigurationDynaBean} maps nested and mapped properties
    040 * to the appropriate {@code Configuration} subset using the
    041 * {@link org.apache.commons.configuration2.Configuration#subset}
    042 * method. Similarly, indexed properties reference lists of configuration
    043 * properties using the
    044 * {@link org.apache.commons.configuration2.Configuration#getList(String)}
    045 * method. Setting an indexed property is supported, too.</p>
    046 *
    047 * <p>Note: Some of the methods expect that a dot (&quot;.&quot;) is used as
    048 * property delimiter for the wrapped configuration. This is true for most of
    049 * the default configurations. Hierarchical configurations, for which a specific
    050 * expression engine is set, may cause problems.</p>
    051 *
    052 * @since 1.0-rc1
    053 */
    054public class ConfigurationDynaBean extends ConfigurationMap implements DynaBean
    055{
    056    /** Constant for the property delimiter.*/
    057    private static final String PROPERTY_DELIMITER = ".";
    058
    059    /** The logger.*/
    060    private static final Log LOG = LogFactory.getLog(ConfigurationDynaBean.class);
    061
    062    /**
    063     * Creates a new instance of {@code ConfigurationDynaBean} and sets
    064     * the configuration this bean is associated with.
    065     *
    066     * @param configuration the configuration
    067     */
    068    public ConfigurationDynaBean(final Configuration configuration)
    069    {
    070        super(configuration);
    071        if (LOG.isTraceEnabled())
    072        {
    073            LOG.trace("ConfigurationDynaBean(" + configuration + ")");
    074        }
    075    }
    076
    077    @Override
    078    public void set(final String name, final Object value)
    079    {
    080        if (LOG.isTraceEnabled())
    081        {
    082            LOG.trace("set(" + name + "," + value + ")");
    083        }
    084
    085        if (value == null)
    086        {
    087            throw new NullPointerException("Error trying to set property to null.");
    088        }
    089
    090        if (value instanceof Collection)
    091        {
    092            final Collection<?> collection = (Collection<?>) value;
    093            for (final Object v : collection)
    094            {
    095                getConfiguration().addProperty(name, v);
    096            }
    097        }
    098        else if (value.getClass().isArray())
    099        {
    100            final int length = Array.getLength(value);
    101            for (int i = 0; i < length; i++)
    102            {
    103                getConfiguration().addProperty(name, Array.get(value, i));
    104            }
    105        }
    106        else
    107        {
    108            getConfiguration().setProperty(name, value);
    109        }
    110    }
    111
    112    @Override
    113    public Object get(final String name)
    114    {
    115        if (LOG.isTraceEnabled())
    116        {
    117            LOG.trace("get(" + name + ")");
    118        }
    119
    120        // get configuration property
    121        Object result = getConfiguration().getProperty(name);
    122        if (result == null)
    123        {
    124            // otherwise attempt to create bean from configuration subset
    125            final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    126            if (!subset.isEmpty())
    127            {
    128                result = new ConfigurationDynaBean(subset);
    129            }
    130        }
    131
    132        if (LOG.isDebugEnabled())
    133        {
    134            LOG.debug(name + "=[" + result + "]");
    135        }
    136
    137        if (result == null)
    138        {
    139            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
    140        }
    141        return result;
    142    }
    143
    144    @Override
    145    public boolean contains(final String name, final String key)
    146    {
    147        final Configuration subset = getConfiguration().subset(name);
    148        if (subset == null)
    149        {
    150            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    151        }
    152
    153        return subset.containsKey(key);
    154    }
    155
    156    @Override
    157    public Object get(final String name, final int index)
    158    {
    159        if (!checkIndexedProperty(name))
    160        {
    161            throw new IllegalArgumentException("Property '" + name
    162                    + "' is not indexed.");
    163        }
    164
    165        final List<Object> list = getConfiguration().getList(name);
    166        return list.get(index);
    167    }
    168
    169    @Override
    170    public Object get(final String name, final String key)
    171    {
    172        final Configuration subset = getConfiguration().subset(name);
    173        if (subset == null)
    174        {
    175            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    176        }
    177
    178        return subset.getProperty(key);
    179    }
    180
    181    @Override
    182    public DynaClass getDynaClass()
    183    {
    184        return new ConfigurationDynaClass(getConfiguration());
    185    }
    186
    187    @Override
    188    public void remove(final String name, final String key)
    189    {
    190        final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    191        subset.setProperty(key, null);
    192    }
    193
    194    @Override
    195    public void set(final String name, final int index, final Object value)
    196    {
    197        if (!checkIndexedProperty(name) && index > 0)
    198        {
    199            throw new IllegalArgumentException("Property '" + name
    200                    + "' is not indexed.");
    201        }
    202
    203        final Object property = getConfiguration().getProperty(name);
    204
    205        if (property instanceof List)
    206        {
    207            // This is safe because multiple values of a configuration property
    208            // are always stored as lists of type Object.
    209            @SuppressWarnings("unchecked")
    210            final
    211            List<Object> list = (List<Object>) property;
    212            list.set(index, value);
    213            getConfiguration().setProperty(name, list);
    214        }
    215        else if (property.getClass().isArray())
    216        {
    217            Array.set(property, index, value);
    218        }
    219        else if (index == 0)
    220        {
    221            getConfiguration().setProperty(name, value);
    222        }
    223    }
    224
    225    @Override
    226    public void set(final String name, final String key, final Object value)
    227    {
    228        getConfiguration().setProperty(name + "." + key, value);
    229    }
    230
    231    /**
    232     * Tests whether the given name references an indexed property. This
    233     * implementation tests for properties of type list or array. If the
    234     * property does not exist, an exception is thrown.
    235     *
    236     * @param name the name of the property to check
    237     * @return a flag whether this is an indexed property
    238     * @throws IllegalArgumentException if the property does not exist
    239     */
    240    private boolean checkIndexedProperty(final String name)
    241    {
    242        final Object property = getConfiguration().getProperty(name);
    243
    244        if (property == null)
    245        {
    246            throw new IllegalArgumentException("Property '" + name
    247                    + "' does not exist.");
    248        }
    249
    250        return property instanceof List || property.getClass().isArray();
    251    }
    252}