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;
019
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Iterator;
023import java.util.List;
024import java.util.Map;
025import java.util.Properties;
026
027import org.apache.commons.configuration2.ex.ConfigurationRuntimeException;
028
029/**
030 * <p>
031 * A Map based Configuration.
032 * </p>
033 * <p>
034 * This implementation of the {@code Configuration} interface is initialized with a {@link java.util.Map}. The methods
035 * of the {@code Configuration} interface are implemented on top of the content of this map. The following storage
036 * scheme is used:
037 * </p>
038 * <p>
039 * Property keys are directly mapped to map keys, i.e. the {@code getProperty()} method directly performs a
040 * {@code get()} on the map. Analogously, {@code setProperty()} or {@code addProperty()} operations write new data into
041 * the map. If a value is added to an existing property, a {@link java.util.List} is created, which stores the values of
042 * this property.
043 * </p>
044 * <p>
045 * An important use case of this class is to treat a map as a {@code Configuration} allowing access to its data through
046 * the richer interface. This can be a bit problematic in some cases because the map may contain values that need not
047 * adhere to the default storage scheme used by typical configuration implementations, e.g. regarding lists. In such
048 * cases care must be taken when manipulating the data through the {@code Configuration} interface, e.g. by calling
049 * {@code addProperty()}; results may be different than expected.
050 * </p>
051 * <p>
052 * The handling of list delimiters is a bit different for this configuration implementation: When a property of type
053 * String is queried, it is passed to the current {@link org.apache.commons.configuration2.convert.ListDelimiterHandler
054 * ListDelimiterHandler} which may generate multiple values. Note that per default a list delimiter handler is set which
055 * does not do any list splitting, so this feature is disabled. It can be enabled by setting a properly configured
056 * {@code ListDelimiterHandler} implementation, e.g. a
057 * {@link org.apache.commons.configuration2.convert.DefaultListDelimiterHandler DefaultListDelimiterHandler} object.
058 * </p>
059 * <p>
060 * Notice that list splitting is only performed for single string values. If a property has multiple values, the single
061 * values are not split even if they contain the list delimiter character.
062 * </p>
063 * <p>
064 * As the underlying {@code Map} is directly used as store of the property values, the thread-safety of this
065 * {@code Configuration} implementation depends on the map passed to the constructor.
066 * </p>
067 * <p>
068 * Notes about type safety: For properties with multiple values this implementation creates lists of type {@code Object}
069 * and stores them. If a property is assigned another value, the value is added to the list. This can cause problems if
070 * the map passed to the constructor already contains lists of other types. This should be avoided, otherwise it cannot
071 * be guaranteed that the application might throw {@code ClassCastException} exceptions later.
072 * </p>
073 *
074 * @since 1.1
075 */
076public class MapConfiguration extends AbstractConfiguration implements Cloneable {
077    /** The Map decorated by this configuration. */
078    protected Map<String, Object> map;
079
080    /** A flag whether trimming of property values should be disabled. */
081    private boolean trimmingDisabled;
082
083    /**
084     * Create a Configuration decorator around the specified Map. The map is used to store the configuration properties, any
085     * change will also affect the Map.
086     *
087     * @param map the map
088     */
089    public MapConfiguration(final Map<String, ?> map) {
090        this.map = (Map<String, Object>) map;
091    }
092
093    /**
094     * Creates a new instance of {@code MapConfiguration} which uses the specified {@code Properties} object as its data
095     * store. All changes of this configuration affect the given {@code Properties} object and vice versa. Note that while
096     * {@code Properties} actually implements {@code Map<Object, Object>}, we expect it to contain only string keys. Other
097     * key types will lead to {@code ClassCastException} exceptions on certain methods.
098     *
099     * @param props the {@code Properties} object defining the content of this configuration
100     * @since 1.8
101     */
102    public MapConfiguration(final Properties props) {
103        map = toMap(props);
104    }
105
106    /**
107     * Gets the Map decorated by this configuration.
108     *
109     * @return the map this configuration is based onto
110     */
111    public Map<String, Object> getMap() {
112        return map;
113    }
114
115    /**
116     * Returns the flag whether trimming of property values is disabled.
117     *
118     * @return <b>true</b> if trimming of property values is disabled; <b>false</b> otherwise
119     * @since 1.7
120     */
121    public boolean isTrimmingDisabled() {
122        return trimmingDisabled;
123    }
124
125    /**
126     * Sets a flag whether trimming of property values is disabled. This flag is only evaluated if list splitting is
127     * enabled. Refer to the header comment for more information about list splitting and trimming.
128     *
129     * @param trimmingDisabled a flag whether trimming of property values should be disabled
130     * @since 1.7
131     */
132    public void setTrimmingDisabled(final boolean trimmingDisabled) {
133        this.trimmingDisabled = trimmingDisabled;
134    }
135
136    @Override
137    protected Object getPropertyInternal(final String key) {
138        final Object value = map.get(key);
139        if (value instanceof String) {
140            final Collection<String> list = getListDelimiterHandler().split((String) value, !isTrimmingDisabled());
141            return list.size() > 1 ? list : list.iterator().next();
142        }
143        return value;
144    }
145
146    @Override
147    protected void addPropertyDirect(final String key, final Object value) {
148        final Object previousValue = getProperty(key);
149
150        if (previousValue == null) {
151            map.put(key, value);
152        } else if (previousValue instanceof List) {
153            // the value is added to the existing list
154            // Note: This is problematic. See header comment!
155            ((List<Object>) previousValue).add(value);
156        } else {
157            // the previous value is replaced by a list containing the previous value and the new value
158            final List<Object> list = new ArrayList<>();
159            list.add(previousValue);
160            list.add(value);
161
162            map.put(key, list);
163        }
164    }
165
166    @Override
167    protected boolean isEmptyInternal() {
168        return map.isEmpty();
169    }
170
171    @Override
172    protected boolean containsKeyInternal(final String key) {
173        return map.containsKey(key);
174    }
175
176    @Override
177    protected void clearPropertyDirect(final String key) {
178        map.remove(key);
179    }
180
181    @Override
182    protected Iterator<String> getKeysInternal() {
183        return map.keySet().iterator();
184    }
185
186    @Override
187    protected int sizeInternal() {
188        return map.size();
189    }
190
191    /**
192     * Returns a copy of this object. The returned configuration will contain the same properties as the original. Event
193     * listeners are not cloned.
194     *
195     * @return the copy
196     * @since 1.3
197     */
198    @Override
199    public Object clone() {
200        try {
201            final MapConfiguration copy = (MapConfiguration) super.clone();
202            // Safe because ConfigurationUtils returns a map of the same types.
203            @SuppressWarnings("unchecked")
204            final Map<String, Object> clonedMap = (Map<String, Object>) ConfigurationUtils.clone(map);
205            copy.map = clonedMap;
206            copy.cloneInterpolator(this);
207            return copy;
208        } catch (final CloneNotSupportedException cex) {
209            // cannot happen
210            throw new ConfigurationRuntimeException(cex);
211        }
212    }
213
214    /**
215     * Helper method for converting the type of the {@code Properties} object to a supported map type. As stated by the
216     * comment of the constructor, we expect the {@code Properties} object to contain only String key; therefore, it is safe
217     * to do this cast.
218     *
219     * @param props the {@code Properties} to be copied
220     * @return a newly created map with all string keys of the properties
221     */
222    @SuppressWarnings("unchecked")
223    private static Map<String, Object> toMap(final Properties props) {
224        @SuppressWarnings("rawtypes")
225        final Map map = props;
226        return map;
227    }
228
229    /**
230     * Converts this object to a String suitable for debugging and logging.
231     *
232     * @since 2.3
233     */
234    @Override
235    public String toString() {
236        return getClass().getSimpleName() + " [map=" + map + ", trimmingDisabled=" + trimmingDisabled + "]";
237    }
238}