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