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