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}