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}