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}