    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 * @author <a href="mailto:ricardo.gladwell@btinternet.com">Ricardo Gladwell</a>
    053 * @since 1.0-rc1
    054 */
    055public class ConfigurationDynaBean extends ConfigurationMap implements DynaBean
    056{
    057    /** Constant for the property delimiter.*/
    058    private static final String PROPERTY_DELIMITER = ".";
    059
    060    /** The logger.*/
    061    private static final Log LOG = LogFactory.getLog(ConfigurationDynaBean.class);
    062
    063    /**
    064     * Creates a new instance of {@code ConfigurationDynaBean} and sets
    065     * the configuration this bean is associated with.
    066     *
    067     * @param configuration the configuration
    068     */
    069    public ConfigurationDynaBean(final Configuration configuration)
    070    {
    071        super(configuration);
    072        if (LOG.isTraceEnabled())
    073        {
    074            LOG.trace("ConfigurationDynaBean(" + configuration + ")");
    075        }
    076    }
    077
    078    @Override
    079    public void set(final String name, final Object value)
    080    {
    081        if (LOG.isTraceEnabled())
    082        {
    083            LOG.trace("set(" + name + "," + value + ")");
    084        }
    085
    086        if (value == null)
    087        {
    088            throw new NullPointerException("Error trying to set property to null.");
    089        }
    090
    091        if (value instanceof Collection)
    092        {
    093            final Collection<?> collection = (Collection<?>) value;
    094            for (final Object v : collection)
    095            {
    096                getConfiguration().addProperty(name, v);
    097            }
    098        }
    099        else if (value.getClass().isArray())
    100        {
    101            final int length = Array.getLength(value);
    102            for (int i = 0; i < length; i++)
    103            {
    104                getConfiguration().addProperty(name, Array.get(value, i));
    105            }
    106        }
    107        else
    108        {
    109            getConfiguration().setProperty(name, value);
    110        }
    111    }
    112
    113    @Override
    114    public Object get(final String name)
    115    {
    116        if (LOG.isTraceEnabled())
    117        {
    118            LOG.trace("get(" + name + ")");
    119        }
    120
    121        // get configuration property
    122        Object result = getConfiguration().getProperty(name);
    123        if (result == null)
    124        {
    125            // otherwise attempt to create bean from configuration subset
    126            final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    127            if (!subset.isEmpty())
    128            {
    129                result = new ConfigurationDynaBean(subset);
    130            }
    131        }
    132
    133        if (LOG.isDebugEnabled())
    134        {
    135            LOG.debug(name + "=[" + result + "]");
    136        }
    137
    138        if (result == null)
    139        {
    140            throw new IllegalArgumentException("Property '" + name + "' does not exist.");
    141        }
    142        return result;
    143    }
    144
    145    @Override
    146    public boolean contains(final String name, final String key)
    147    {
    148        final Configuration subset = getConfiguration().subset(name);
    149        if (subset == null)
    150        {
    151            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    152        }
    153
    154        return subset.containsKey(key);
    155    }
    156
    157    @Override
    158    public Object get(final String name, final int index)
    159    {
    160        if (!checkIndexedProperty(name))
    161        {
    162            throw new IllegalArgumentException("Property '" + name
    163                    + "' is not indexed.");
    164        }
    165
    166        final List<Object> list = getConfiguration().getList(name);
    167        return list.get(index);
    168    }
    169
    170    @Override
    171    public Object get(final String name, final String key)
    172    {
    173        final Configuration subset = getConfiguration().subset(name);
    174        if (subset == null)
    175        {
    176            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
    177        }
    178
    179        return subset.getProperty(key);
    180    }
    181
    182    @Override
    183    public DynaClass getDynaClass()
    184    {
    185        return new ConfigurationDynaClass(getConfiguration());
    186    }
    187
    188    @Override
    189    public void remove(final String name, final String key)
    190    {
    191        final Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
    192        subset.setProperty(key, null);
    193    }
    194
    195    @Override
    196    public void set(final String name, final int index, final Object value)
    197    {
    198        if (!checkIndexedProperty(name) && index > 0)
    199        {
    200            throw new IllegalArgumentException("Property '" + name
    201                    + "' is not indexed.");
    202        }
    203
    204        final Object property = getConfiguration().getProperty(name);
    205
    206        if (property instanceof List)
    207        {
    208            // This is safe because multiple values of a configuration property
    209            // are always stored as lists of type Object.
    210            @SuppressWarnings("unchecked")
    211            final
    212            List<Object> list = (List<Object>) property;
    213            list.set(index, value);
    214            getConfiguration().setProperty(name, list);
    215        }
    216        else if (property.getClass().isArray())
    217        {
    218            Array.set(property, index, value);
    219        }
    220        else if (index == 0)
    221        {
    222            getConfiguration().setProperty(name, value);
    223        }
    224    }
    225
    226    @Override
    227    public void set(final String name, final String key, final Object value)
    228    {
    229        getConfiguration().setProperty(name + "." + key, value);
    230    }
    231
    232    /**
    233     * Tests whether the given name references an indexed property. This
    234     * implementation tests for properties of type list or array. If the
    235     * property does not exist, an exception is thrown.
    236     *
    237     * @param name the name of the property to check
    238     * @return a flag whether this is an indexed property
    239     * @throws IllegalArgumentException if the property does not exist
    240     */
    241    private boolean checkIndexedProperty(final String name)
    242    {
    243        final Object property = getConfiguration().getProperty(name);
    244
    245        if (property == null)
    246        {
    247            throw new IllegalArgumentException("Property '" + name
    248                    + "' does not exist.");
    249        }
    250
    251        return (property instanceof List) || property.getClass().isArray();
    252    }
    253}