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