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    
018    package org.apache.commons.configuration;
019    
020    import java.util.Iterator;
021    
022    import org.apache.commons.collections.Transformer;
023    import org.apache.commons.collections.iterators.TransformIterator;
024    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
025    
026    /**
027     * <p>A subset of another configuration. The new Configuration object contains
028     * every key from the parent Configuration that starts with prefix. The prefix
029     * is removed from the keys in the subset.</p>
030     * <p>It is usually not necessary to use this class directly. Instead the
031     * <code>{@link Configuration#subset(String)}</code> method should be used,
032     * which will return a correctly initialized instance.</p>
033     *
034     * @author Emmanuel Bourg
035     * @version $Revision: 930693 $, $Date: 2010-04-04 16:02:59 +0200 (So, 04. Apr 2010) $
036     */
037    public class SubsetConfiguration extends AbstractConfiguration
038    {
039        /** The parent configuration. */
040        protected Configuration parent;
041    
042        /** The prefix used to select the properties. */
043        protected String prefix;
044    
045        /** The prefix delimiter */
046        protected String delimiter;
047    
048        /**
049         * Create a subset of the specified configuration
050         *
051         * @param parent The parent configuration
052         * @param prefix The prefix used to select the properties
053         */
054        public SubsetConfiguration(Configuration parent, String prefix)
055        {
056            this.parent = parent;
057            this.prefix = prefix;
058        }
059    
060        /**
061         * Create a subset of the specified configuration
062         *
063         * @param parent    The parent configuration
064         * @param prefix    The prefix used to select the properties
065         * @param delimiter The prefix delimiter
066         */
067        public SubsetConfiguration(Configuration parent, String prefix, String delimiter)
068        {
069            this.parent = parent;
070            this.prefix = prefix;
071            this.delimiter = delimiter;
072        }
073    
074        /**
075         * Return the key in the parent configuration associated to the specified
076         * key in this subset.
077         *
078         * @param key The key in the subset.
079         * @return the key as to be used by the parent
080         */
081        protected String getParentKey(String key)
082        {
083            if ("".equals(key) || key == null)
084            {
085                return prefix;
086            }
087            else
088            {
089                return delimiter == null ? prefix + key : prefix + delimiter + key;
090            }
091        }
092    
093        /**
094         * Return the key in the subset configuration associated to the specified
095         * key in the parent configuration.
096         *
097         * @param key The key in the parent configuration.
098         * @return the key in the context of this subset configuration
099         */
100        protected String getChildKey(String key)
101        {
102            if (!key.startsWith(prefix))
103            {
104                throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
105            }
106            else
107            {
108                String modifiedKey = null;
109                if (key.length() == prefix.length())
110                {
111                    modifiedKey = "";
112                }
113                else
114                {
115                    int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
116                    modifiedKey = key.substring(i);
117                }
118    
119                return modifiedKey;
120            }
121        }
122    
123        /**
124         * Return the parent configuration for this subset.
125         *
126         * @return the parent configuration
127         */
128        public Configuration getParent()
129        {
130            return parent;
131        }
132    
133        /**
134         * Return the prefix used to select the properties in the parent configuration.
135         *
136         * @return the prefix used by this subset
137         */
138        public String getPrefix()
139        {
140            return prefix;
141        }
142    
143        /**
144         * Set the prefix used to select the properties in the parent configuration.
145         *
146         * @param prefix the prefix
147         */
148        public void setPrefix(String prefix)
149        {
150            this.prefix = prefix;
151        }
152    
153        public Configuration subset(String prefix)
154        {
155            return parent.subset(getParentKey(prefix));
156        }
157    
158        public boolean isEmpty()
159        {
160            return !getKeys().hasNext();
161        }
162    
163        public boolean containsKey(String key)
164        {
165            return parent.containsKey(getParentKey(key));
166        }
167    
168        public void addPropertyDirect(String key, Object value)
169        {
170            parent.addProperty(getParentKey(key), value);
171        }
172    
173        protected void clearPropertyDirect(String key)
174        {
175            parent.clearProperty(getParentKey(key));
176        }
177    
178        public Object getProperty(String key)
179        {
180            return parent.getProperty(getParentKey(key));
181        }
182    
183        public Iterator getKeys(String prefix)
184        {
185            return new TransformIterator(parent.getKeys(getParentKey(prefix)), new Transformer()
186            {
187                public Object transform(Object obj)
188                {
189                    return getChildKey((String) obj);
190                }
191            });
192        }
193    
194        public Iterator getKeys()
195        {
196            return new TransformIterator(parent.getKeys(prefix), new Transformer()
197            {
198                public Object transform(Object obj)
199                {
200                    return getChildKey((String) obj);
201                }
202            });
203        }
204    
205        protected Object interpolate(Object base)
206        {
207            if (delimiter == null && "".equals(prefix))
208            {
209                return super.interpolate(base);
210            }
211            else
212            {
213                SubsetConfiguration config = new SubsetConfiguration(parent, "");
214                ConfigurationInterpolator interpolator = config.getInterpolator();
215                getInterpolator().registerLocalLookups(interpolator);
216                if (parent instanceof AbstractConfiguration)
217                {
218                    interpolator.setParentInterpolator(((AbstractConfiguration) parent).getInterpolator());
219                }
220                return config.interpolate(base);
221            }
222        }
223    
224        protected String interpolate(String base)
225        {
226            return super.interpolate(base);
227        }
228    
229        /**
230         * {@inheritDoc}
231         *
232         * Change the behaviour of the parent configuration if it supports this feature.
233         */
234        public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing)
235        {
236            if (parent instanceof AbstractConfiguration)
237            {
238                ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
239            }
240            else
241            {
242                super.setThrowExceptionOnMissing(throwExceptionOnMissing);
243            }
244        }
245    
246        /**
247         * {@inheritDoc}
248         *
249         * The subset inherits this feature from its parent if it supports this feature.
250         */
251        public boolean isThrowExceptionOnMissing()
252        {
253            if (parent instanceof AbstractConfiguration)
254            {
255                return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
256            }
257            else
258            {
259                return super.isThrowExceptionOnMissing();
260            }
261        }
262    
263        /**
264         * Returns the list delimiter. This property will be fetched from the parent
265         * configuration if supported.
266         *
267         * @return the list delimiter
268         * @since 1.4
269         */
270        public char getListDelimiter()
271        {
272            return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
273                    .getListDelimiter()
274                    : super.getListDelimiter();
275        }
276    
277        /**
278         * Sets the list delimiter. If the parent configuration supports this
279         * feature, the delimiter will be set at the parent.
280         *
281         * @param delim the new list delimiter
282         * @since 1.4
283         */
284        public void setListDelimiter(char delim)
285        {
286            if (parent instanceof AbstractConfiguration)
287            {
288                ((AbstractConfiguration) parent).setListDelimiter(delim);
289            }
290            else
291            {
292                super.setListDelimiter(delim);
293            }
294        }
295    
296        /**
297         * Returns a flag whether string properties should be checked for list
298         * delimiter characters. This implementation ensures that this flag is kept
299         * in sync with the parent configuration if this object supports this
300         * feature.
301         *
302         * @return the delimiter parsing disabled flag
303         * @since 1.4
304         */
305        public boolean isDelimiterParsingDisabled()
306        {
307            return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent)
308                    .isDelimiterParsingDisabled()
309                    : super.isDelimiterParsingDisabled();
310        }
311    
312        /**
313         * Sets a flag whether list parsing is disabled. This implementation will
314         * also set the flag at the parent configuration if this object supports
315         * this feature.
316         *
317         * @param delimiterParsingDisabled the delimiter parsing disabled flag
318         * @since 1.4
319         */
320        public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled)
321        {
322            if (parent instanceof AbstractConfiguration)
323            {
324                ((AbstractConfiguration) parent)
325                        .setDelimiterParsingDisabled(delimiterParsingDisabled);
326            }
327            else
328            {
329                super.setDelimiterParsingDisabled(delimiterParsingDisabled);
330            }
331        }
332    }