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      protected void addPropertyInternal(final String key, final Object value) {
74          config.addProperty(makePath(key), value);
75      }
76  
77      @Override
78      protected void clearInternal() {
79          getConfig().clear();
80      }
81  
82      @Override
83      protected void clearPropertyDirect(final String key) {
84          config.clearProperty(makePath(key));
85      }
86  
87      @Override
88      protected boolean containsKeyInternal(final String key) {
89          return config.containsKey(makePath(key));
90      }
91  
92      @Override
93      public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) {
94          return config.getBigDecimal(makePath(key), defaultValue);
95      }
96  
97      @Override
98      public BigDecimal getBigDecimal(final String key) {
99          return config.getBigDecimal(makePath(key));
100     }
101 
102     @Override
103     public BigInteger getBigInteger(final String key, final BigInteger defaultValue) {
104         return config.getBigInteger(makePath(key), defaultValue);
105     }
106 
107     @Override
108     public BigInteger getBigInteger(final String key) {
109         return config.getBigInteger(makePath(key));
110     }
111 
112     @Override
113     public boolean getBoolean(final String key, final boolean defaultValue) {
114         return config.getBoolean(makePath(key), defaultValue);
115     }
116 
117     @Override
118     public Boolean getBoolean(final String key, final Boolean defaultValue) {
119         return config.getBoolean(makePath(key), defaultValue);
120     }
121 
122     @Override
123     public boolean getBoolean(final String key) {
124         return config.getBoolean(makePath(key));
125     }
126 
127     @Override
128     public byte getByte(final String key, final byte defaultValue) {
129         return config.getByte(makePath(key), defaultValue);
130     }
131 
132     @Override
133     public Byte getByte(final String key, final Byte defaultValue) {
134         return config.getByte(makePath(key), defaultValue);
135     }
136 
137     @Override
138     public byte getByte(final String key) {
139         return config.getByte(makePath(key));
140     }
141 
142     @Override
143     public double getDouble(final String key, final double defaultValue) {
144         return config.getDouble(makePath(key), defaultValue);
145     }
146 
147     @Override
148     public Double getDouble(final String key, final Double defaultValue) {
149         return config.getDouble(makePath(key), defaultValue);
150     }
151 
152     @Override
153     public double getDouble(final String key) {
154         return config.getDouble(makePath(key));
155     }
156 
157     @Override
158     public float getFloat(final String key, final float defaultValue) {
159         return config.getFloat(makePath(key), defaultValue);
160     }
161 
162     @Override
163     public Float getFloat(final String key, final Float defaultValue) {
164         return config.getFloat(makePath(key), defaultValue);
165     }
166 
167     @Override
168     public float getFloat(final String key) {
169         return config.getFloat(makePath(key));
170     }
171 
172     @Override
173     public int getInt(final String key, final int defaultValue) {
174         return config.getInt(makePath(key), defaultValue);
175     }
176 
177     @Override
178     public int getInt(final String key) {
179         return config.getInt(makePath(key));
180     }
181 
182     @Override
183     public Integer getInteger(final String key, final Integer defaultValue) {
184         return config.getInteger(makePath(key), defaultValue);
185     }
186 
187     @Override
188     protected Iterator<String> getKeysInternal() {
189         return config.getKeys(makePath());
190     }
191 
192     @Override
193     protected Iterator<String> getKeysInternal(final String prefix) {
194         return config.getKeys(makePath(prefix));
195     }
196 
197     @Override
198     public List<Object> getList(final String key, final List<?> defaultValue) {
199         return config.getList(makePath(key), defaultValue);
200     }
201 
202     @Override
203     public List<Object> getList(final String key) {
204         return config.getList(makePath(key));
205     }
206 
207     @Override
208     public long getLong(final String key, final long defaultValue) {
209         return config.getLong(makePath(key), defaultValue);
210     }
211 
212     @Override
213     public Long getLong(final String key, final Long defaultValue) {
214         return config.getLong(makePath(key), defaultValue);
215     }
216 
217     @Override
218     public long getLong(final String key) {
219         return config.getLong(makePath(key));
220     }
221 
222     @Override
223     public Properties getProperties(final String key) {
224         return config.getProperties(makePath(key));
225     }
226 
227     @Override
228     protected Object getPropertyInternal(final String key) {
229         return config.getProperty(makePath(key));
230     }
231 
232     @Override
233     public short getShort(final String key, final short defaultValue) {
234         return config.getShort(makePath(key), defaultValue);
235     }
236 
237     @Override
238     public Short getShort(final String key, final Short defaultValue) {
239         return config.getShort(makePath(key), defaultValue);
240     }
241 
242     @Override
243     public short getShort(final String key) {
244         return config.getShort(makePath(key));
245     }
246 
247     @Override
248     public String getString(final String key, final String defaultValue) {
249         return config.getString(makePath(key), defaultValue);
250     }
251 
252     @Override
253     public String getString(final String key) {
254         return config.getString(makePath(key));
255     }
256 
257     @Override
258     public String[] getStringArray(final String key) {
259         return config.getStringArray(makePath(key));
260     }
261 
262     @Override
263     protected boolean isEmptyInternal() {
264         return getConfig().isEmpty();
265     }
266 
267     @Override
268     protected void setPropertyInternal(final String key, final Object value) {
269         getConfig().setProperty(key, value);
270     }
271 
272     @Override
273     public Configuration subset(final String prefix) {
274         return getConfig().subset(prefix);
275     }
276 
277     @Override
278     public ExpressionEngine getExpressionEngine() {
279         return config.getExpressionEngine();
280     }
281 
282     @Override
283     public void setExpressionEngine(final ExpressionEngine expressionEngine) {
284         if (init) {
285             config.setExpressionEngine(expressionEngine);
286         } else {
287             super.setExpressionEngine(expressionEngine);
288         }
289     }
290 
291     @Override
292     protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) {
293         getConfig().addNodes(key, nodes);
294     }
295 
296     @Override
297     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) {
298         return config.configurationAt(makePath(key), supportUpdates);
299     }
300 
301     @Override
302     public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) {
303         return config.configurationAt(makePath(key));
304     }
305 
306     @Override
307     public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) {
308         return config.configurationsAt(makePath(key));
309     }
310 
311     @Override
312     protected Object clearTreeInternal(final String key) {
313         config.clearTree(makePath(key));
314         return Collections.emptyList();
315     }
316 
317     @Override
318     protected int getMaxIndexInternal(final String key) {
319         return config.getMaxIndex(makePath(key));
320     }
321 
322     @Override
323     public Configuration interpolatedConfiguration() {
324         return getConfig().interpolatedConfiguration();
325     }
326 
327     @Override
328     public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
329         getConfig().addEventListener(eventType, listener);
330     }
331 
332     @Override
333     public <T extends Event> boolean removeEventListener(final EventType<T> eventType, final EventListener<? super T> listener) {
334         return getConfig().removeEventListener(eventType, listener);
335     }
336 
337     @Override
338     public <T extends Event> Collection<EventListener<? super T>> getEventListeners(final EventType<T> eventType) {
339         return getConfig().getEventListeners(eventType);
340     }
341 
342     @Override
343     public void clearEventListeners() {
344         getConfig().clearEventListeners();
345     }
346 
347     @Override
348     public void clearErrorListeners() {
349         getConfig().clearErrorListeners();
350     }
351 
352     @Override
353     public void write(final Writer writer) throws ConfigurationException, IOException {
354         fetchFileBased().write(writer);
355     }
356 
357     @Override
358     public void read(final Reader reader) throws ConfigurationException, IOException {
359         fetchFileBased().read(reader);
360     }
361 
362     private BaseHierarchicalConfiguration getConfig() {
363         return (BaseHierarchicalConfiguration) config.configurationAt(makePath());
364     }
365 
366     private String makePath() {
367         final String pathPattern = trailing ? path.substring(0, path.length() - 1) : path;
368         return substitute(pathPattern);
369     }
370 
371     /*
372      * Resolve the root expression and then add the item being retrieved. Insert a separator character as required.
373      */
374     private String makePath(final String item) {
375         final String pathPattern;
376         if ((item.isEmpty() || item.startsWith("/")) && trailing) {
377             pathPattern = path.substring(0, path.length() - 1);
378         } else if (!item.startsWith("/") || !trailing) {
379             pathPattern = path + "/";
380         } else {
381             pathPattern = path;
382         }
383         return substitute(pathPattern) + item;
384     }
385 
386     /**
387      * Uses this configuration's {@code ConfigurationInterpolator} to perform variable substitution on the given pattern
388      * string.
389      *
390      * @param pattern the pattern string
391      * @return the string with variables replaced
392      */
393     private String substitute(final String pattern) {
394         return Objects.toString(getInterpolator().interpolate(pattern), null);
395     }
396 
397     /**
398      * Returns the wrapped configuration as a {@code FileBased} object. If this cast is not possible, an exception is
399      * thrown.
400      *
401      * @return the wrapped configuration as {@code FileBased}
402      * @throws ConfigurationException if the wrapped configuration does not implement {@code FileBased}
403      */
404     private FileBased fetchFileBased() throws ConfigurationException {
405         if (!(config instanceof FileBased)) {
406             throw new ConfigurationException("Wrapped configuration does not implement FileBased!" + " No I/O operations are supported.");
407         }
408         return (FileBased) config;
409     }
410 }