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.configuration.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.configuration.Configuration;
027import org.apache.commons.configuration.ConfigurationMap;
028import org.apache.commons.configuration.SubsetConfiguration;
029import org.apache.commons.logging.Log;
030import org.apache.commons.logging.LogFactory;
031
032/**
033 * The <tt>ConfigurationDynaBean</tt> dynamically reads and writes
034 * configurations properties from a wrapped configuration-collection
035 * {@link org.apache.commons.configuration.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.configuration.Configuration#subset}
042 * method. Similarly, indexed properties reference lists of configuration
043 * properties using the
044 * {@link org.apache.commons.configuration.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 1366932 2012-07-29 20:06:31Z 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    public void set(String name, 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            Collection<?> collection = (Collection<?>) value;
094            for (Object v : collection)
095            {
096                getConfiguration().addProperty(name, v);
097            }
098        }
099        else if (value.getClass().isArray())
100        {
101            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    public Object get(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            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    public boolean contains(String name, String key)
145    {
146        Configuration subset = getConfiguration().subset(name);
147        if (subset == null)
148        {
149            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
150        }
151
152        return subset.containsKey(key);
153    }
154
155    public Object get(String name, int index)
156    {
157        if (!checkIndexedProperty(name))
158        {
159            throw new IllegalArgumentException("Property '" + name
160                    + "' is not indexed.");
161        }
162
163        List<Object> list = getConfiguration().getList(name);
164        return list.get(index);
165    }
166
167    public Object get(String name, String key)
168    {
169        Configuration subset = getConfiguration().subset(name);
170        if (subset == null)
171        {
172            throw new IllegalArgumentException("Mapped property '" + name + "' does not exist.");
173        }
174
175        return subset.getProperty(key);
176    }
177
178    public DynaClass getDynaClass()
179    {
180        return new ConfigurationDynaClass(getConfiguration());
181    }
182
183    public void remove(String name, String key)
184    {
185        Configuration subset = new SubsetConfiguration(getConfiguration(), name, PROPERTY_DELIMITER);
186        subset.setProperty(key, null);
187    }
188
189    public void set(String name, int index, Object value)
190    {
191        if (!checkIndexedProperty(name) && index > 0)
192        {
193            throw new IllegalArgumentException("Property '" + name
194                    + "' is not indexed.");
195        }
196
197        Object property = getConfiguration().getProperty(name);
198
199        if (property instanceof List)
200        {
201            // This is safe because multiple values of a configuration property
202            // are always stored as lists of type Object.
203            @SuppressWarnings("unchecked")
204            List<Object> list = (List<Object>) property;
205            list.set(index, value);
206            getConfiguration().setProperty(name, list);
207        }
208        else if (property.getClass().isArray())
209        {
210            Array.set(property, index, value);
211        }
212        else if (index == 0)
213        {
214            getConfiguration().setProperty(name, value);
215        }
216    }
217
218    public void set(String name, String key, Object value)
219    {
220        getConfiguration().setProperty(name + "." + key, value);
221    }
222
223    /**
224     * Tests whether the given name references an indexed property. This
225     * implementation tests for properties of type list or array. If the
226     * property does not exist, an exception is thrown.
227     *
228     * @param name the name of the property to check
229     * @return a flag whether this is an indexed property
230     * @throws IllegalArgumentException if the property does not exist
231     */
232    private boolean checkIndexedProperty(String name)
233    {
234        Object property = getConfiguration().getProperty(name);
235
236        if (property == null)
237        {
238            throw new IllegalArgumentException("Property '" + name
239                    + "' does not exist.");
240        }
241
242        return (property instanceof List) || property.getClass().isArray();
243    }
244}