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 1624601 2014-09-12 18:04:36Z oheger $
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(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(String name, 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            Collection<?> collection = (Collection<?>) value;
095            for (Object v : collection)
096            {
097                getConfiguration().addProperty(name, v);
098            }
099        }
100        else if (value.getClass().isArray())
101        {
102            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(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            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(String name, String key)
148    {
149        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(String name, int index)
160    {
161        if (!checkIndexedProperty(name))
162        {
163            throw new IllegalArgumentException("Property '" + name
164                    + "' is not indexed.");
165        }
166
167        List<Object> list = getConfiguration().getList(name);
168        return list.get(index);
169    }
170
171    @Override
172    public Object get(String name, String key)
173    {
174        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(String name, String key)
191    {
192        Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
193        subset.setProperty(key, null);
194    }
195
196    @Override
197    public void set(String name, int index, Object value)
198    {
199        if (!checkIndexedProperty(name) && index > 0)
200        {
201            throw new IllegalArgumentException("Property '" + name
202                    + "' is not indexed.");
203        }
204
205        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            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(String name, String key, 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(String name)
242    {
243        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}