View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration2;
18  
19  import java.io.IOException;
20  import java.io.Reader;
21  import java.io.Writer;
22  import java.math.BigDecimal;
23  import java.math.BigInteger;
24  import java.util.Collection;
25  import java.util.Collections;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Objects;
29  import java.util.Properties;
30  
31  import org.apache.commons.configuration2.event.Event;
32  import org.apache.commons.configuration2.event.EventListener;
33  import org.apache.commons.configuration2.event.EventType;
34  import org.apache.commons.configuration2.ex.ConfigurationException;
35  import org.apache.commons.configuration2.io.FileBased;
36  import org.apache.commons.configuration2.tree.ExpressionEngine;
37  import org.apache.commons.configuration2.tree.ImmutableNode;
38  
39  /**
40   * Wraps a BaseHierarchicalConfiguration and allows subtrees to be accessed via a configured path with replaceable
41   * tokens derived from the ConfigurationInterpolator. When used with injection frameworks such as Spring it allows
42   * components to be injected with subtrees of the configuration.
43   *
44   * @since 1.6
45   */
46  public class PatternSubtreeConfigurationWrapper extends BaseHierarchicalConfiguration implements FileBasedConfiguration {
47      /** The wrapped configuration */
48      private final HierarchicalConfiguration<ImmutableNode> config;
49  
50      /** The path to the subtree */
51      private final String path;
52  
53      /** True if the path ends with '/', false otherwise */
54      private final boolean trailing;
55  
56      /** True if the constructor has finished */
57      private final boolean init;
58  
59      /**
60       * Constructor
61       *
62       * @param config The Configuration to be wrapped.
63       * @param path The base path pattern.
64       */
65      public PatternSubtreeConfigurationWrapper(final HierarchicalConfiguration<ImmutableNode> config, final String path) {
66          this.config = Objects.requireNonNull(config, "config");
67          this.path = path;
68          this.trailing = path.endsWith("/");
69          this.init = true;
70      }
71  
72      @Override
73      public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
74          getConfig().addEventListener(eventType, listener);
75      }
76  
77      @Override
78      protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
79          getConfig().addNodes(key, nodes);
80      }
81  
82      @Override
83      protected void addPropertyInternal(final String key, final Object value) {
84          config.addProperty(makePath(key), value);
85      }
86  
87      @Override
88      public void clearErrorListeners() {
89          getConfig().clearErrorListeners();
90      }
91  
92      @Override
93      public void clearEventListeners() {
94          getConfig().clearEventListeners();
95      }
96  
97      @Override
98      protected void clearInternal() {
99          getConfig().clear();
100     }
101 
102     @Override
103     protected void clearPropertyDirect(final String key) {
104         config.clearProperty(makePath(key));
105     }
106 
107     @Override
108     protected Object clearTreeInternal(final String key) {
109         config.clearTree(makePath(key));
110         return Collections.emptyList();
111     }
112 
113     @Override
114     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
115         return config.configurationAt(makePath(key));
116     }
117 
118     @Override
119     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
120         return config.configurationAt(makePath(key), supportUpdates);
121     }
122 
123     @Override
124     public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
125         return config.configurationsAt(makePath(key));
126     }
127 
128     @Override
129     protected boolean containsKeyInternal(final String key) {
130         return config.containsKey(makePath(key));
131     }
132 
133     /**
134      * Tests whether this configuration contains one or more matches to this value. This operation stops at first
135      * match but may be more expensive than the containsKey method.
136      * @since 2.11.0
137      */
138     @Override
139     protected boolean containsValueInternal(final Object value) {
140         return config.containsValue(value);
141     }
142 
143     /**
144      * Returns the wrapped configuration as a {@code FileBased} object. If this cast is not possible, an exception is
145      * thrown.
146      *
147      * @return the wrapped configuration as {@code FileBased}
148      * @throws ConfigurationException if the wrapped configuration does not implement {@code FileBased}
149      */
150     private FileBased fetchFileBased() throws ConfigurationException {
151         if (!(config instanceof FileBased)) {
152             throw new ConfigurationException("Wrapped configuration does not implement FileBased!" + " No I/O operations are supported.");
153         }
154         return (FileBased) config;
155     }
156 
157     @Override
158     public BigDecimal getBigDecimal(final String key) {
159         return config.getBigDecimal(makePath(key));
160     }
161 
162     @Override
163     public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
164         return config.getBigDecimal(makePath(key), defaultValue);
165     }
166 
167     @Override
168     public BigInteger getBigInteger(final String key) {
169         return config.getBigInteger(makePath(key));
170     }
171 
172     @Override
173     public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
174         return config.getBigInteger(makePath(key), defaultValue);
175     }
176 
177     @Override
178     public boolean getBoolean(final String key) {
179         return config.getBoolean(makePath(key));
180     }
181 
182     @Override
183     public boolean getBoolean(final String key, final boolean defaultValue) {
184         return config.getBoolean(makePath(key), defaultValue);
185     }
186 
187     @Override
188     public Boolean getBoolean(final String key, final Boolean defaultValue) {
189         return config.getBoolean(makePath(key), defaultValue);
190     }
191 
192     @Override
193     public byte getByte(final String key) {
194         return config.getByte(makePath(key));
195     }
196 
197     @Override
198     public byte getByte(final String key, final byte defaultValue) {
199         return config.getByte(makePath(key), defaultValue);
200     }
201 
202     @Override
203     public Byte getByte(final String key, final Byte defaultValue) {
204         return config.getByte(makePath(key), defaultValue);
205     }
206 
207     private BaseHierarchicalConfiguration getConfig() {
208         return (BaseHierarchicalConfiguration) config.configurationAt(makePath());
209     }
210 
211     @Override
212     public double getDouble(final String key) {
213         return config.getDouble(makePath(key));
214     }
215 
216     @Override
217     public double getDouble(final String key, final double defaultValue) {
218         return config.getDouble(makePath(key), defaultValue);
219     }
220 
221     @Override
222     public Double getDouble(final String key, final Double defaultValue) {
223         return config.getDouble(makePath(key), defaultValue);
224     }
225 
226     @Override
227     public <T extends Event> Collection<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
228         return getConfig().getEventListeners(eventType);
229     }
230 
231     @Override
232     public ExpressionEngine getExpressionEngine() {
233         return config.getExpressionEngine();
234     }
235 
236     @Override
237     public float getFloat(final String key) {
238         return config.getFloat(makePath(key));
239     }
240 
241     @Override
242     public float getFloat(final String key, final float defaultValue) {
243         return config.getFloat(makePath(key), defaultValue);
244     }
245 
246     @Override
247     public Float getFloat(final String key, final Float defaultValue) {
248         return config.getFloat(makePath(key), defaultValue);
249     }
250 
251     @Override
252     public int getInt(final String key) {
253         return config.getInt(makePath(key));
254     }
255 
256     @Override
257     public int getInt(final String key, final int defaultValue) {
258         return config.getInt(makePath(key), defaultValue);
259     }
260 
261     @Override
262     public Integer getInteger(final String key, final Integer defaultValue) {
263         return config.getInteger(makePath(key), defaultValue);
264     }
265 
266     @Override
267     protected Iterator<String> getKeysInternal() {
268         return config.getKeys(makePath());
269     }
270 
271     @Override
272     protected Iterator<String> getKeysInternal(final String prefix) {
273         return config.getKeys(makePath(prefix));
274     }
275 
276     @Override
277     public List<Object> getList(final String key) {
278         return config.getList(makePath(key));
279     }
280 
281     @Override
282     public List<Object> getList(final String key, final List<?> defaultValue) {
283         return config.getList(makePath(key), defaultValue);
284     }
285 
286     @Override
287     public long getLong(final String key) {
288         return config.getLong(makePath(key));
289     }
290 
291     @Override
292     public long getLong(final String key, final long defaultValue) {
293         return config.getLong(makePath(key), defaultValue);
294     }
295 
296     @Override
297     public Long getLong(final String key, final Long defaultValue) {
298         return config.getLong(makePath(key), defaultValue);
299     }
300 
301     @Override
302     protected int getMaxIndexInternal(final String key) {
303         return config.getMaxIndex(makePath(key));
304     }
305 
306     @Override
307     public Properties getProperties(final String key) {
308         return config.getProperties(makePath(key));
309     }
310 
311     @Override
312     protected Object getPropertyInternal(final String key) {
313         return config.getProperty(makePath(key));
314     }
315 
316     @Override
317     public short getShort(final String key) {
318         return config.getShort(makePath(key));
319     }
320 
321     @Override
322     public short getShort(final String key, final short defaultValue) {
323         return config.getShort(makePath(key), defaultValue);
324     }
325 
326     @Override
327     public Short getShort(final String key, final Short defaultValue) {
328         return config.getShort(makePath(key), defaultValue);
329     }
330 
331     @Override
332     public String getString(final String key) {
333         return config.getString(makePath(key));
334     }
335 
336     @Override
337     public String getString(final String key, final String defaultValue) {
338         return config.getString(makePath(key), defaultValue);
339     }
340 
341     @Override
342     public String[] getStringArray(final String key) {
343         return config.getStringArray(makePath(key));
344     }
345 
346     @Override
347     public Configuration interpolatedConfiguration() {
348         return getConfig().interpolatedConfiguration();
349     }
350 
351     @Override
352     protected boolean isEmptyInternal() {
353         return getConfig().isEmpty();
354     }
355 
356     private String makePath() {
357         final String pathPattern = trailing ? path.substring(0, path.length() - 1) : path;
358         return substitute(pathPattern);
359     }
360 
361     /*
362      * Resolve the root expression and then add the item being retrieved. Insert a separator character as required.
363      */
364     private String makePath(final String item) {
365         final String pathPattern;
366         if ((item.isEmpty() || item.startsWith("/")) && trailing) {
367             pathPattern = path.substring(0, path.length() - 1);
368         } else if (!item.startsWith("/") || !trailing) {
369             pathPattern = path + "/";
370         } else {
371             pathPattern = path;
372         }
373         return substitute(pathPattern) + item;
374     }
375 
376     @Override
377     public void read(final Reader reader) throws ConfigurationException, IOException {
378         fetchFileBased().read(reader);
379     }
380 
381     @Override
382     public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
383         return getConfig().removeEventListener(eventType, listener);
384     }
385 
386     @Override
387     public void setExpressionEngine(final ExpressionEngine expressionEngine) {
388         if (init) {
389             config.setExpressionEngine(expressionEngine);
390         } else {
391             super.setExpressionEngine(expressionEngine);
392         }
393     }
394 
395     @Override
396     protected void setPropertyInternal(final String key, final Object value) {
397         getConfig().setProperty(key, value);
398     }
399 
400     @Override
401     public Configuration subset(final String prefix) {
402         return getConfig().subset(prefix);
403     }
404 
405     /**
406      * Uses this configuration's {@code ConfigurationInterpolator} to perform variable substitution on the given pattern
407      * string.
408      *
409      * @param pattern the pattern string
410      * @return the string with variables replaced
411      */
412     private String substitute(final String pattern) {
413         return Objects.toString(getInterpolator().interpolate(pattern), null);
414     }
415 
416     @Override
417     public void write(final Writer writer) throws ConfigurationException, IOException {
418         fetchFileBased().write(writer);
419     }
420 }