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 * https://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 */ 017package org.apache.commons.configuration2; 018 019import java.io.IOException; 020import java.io.Reader; 021import java.io.Writer; 022import java.math.BigDecimal; 023import java.math.BigInteger; 024import java.util.Collection; 025import java.util.Collections; 026import java.util.Iterator; 027import java.util.List; 028import java.util.Objects; 029import java.util.Properties; 030 031import org.apache.commons.configuration2.event.Event; 032import org.apache.commons.configuration2.event.EventListener; 033import org.apache.commons.configuration2.event.EventType; 034import org.apache.commons.configuration2.ex.ConfigurationException; 035import org.apache.commons.configuration2.io.FileBased; 036import org.apache.commons.configuration2.tree.ExpressionEngine; 037import org.apache.commons.configuration2.tree.ImmutableNode; 038 039/** 040 * Wraps a BaseHierarchicalConfiguration and allows subtrees to be accessed via a configured path with replaceable 041 * tokens derived from the ConfigurationInterpolator. When used with injection frameworks such as Spring it allows 042 * components to be injected with subtrees of the configuration. 043 * 044 * @since 1.6 045 */ 046public class PatternSubtreeConfigurationWrapper extends BaseHierarchicalConfiguration implements FileBasedConfiguration { 047 048 /** The wrapped configuration */ 049 private final HierarchicalConfiguration<ImmutableNode> config; 050 051 /** The path to the subtree */ 052 private final String path; 053 054 /** True if the path ends with '/', false otherwise */ 055 private final boolean trailing; 056 057 /** True if the constructor has finished */ 058 private final boolean init; 059 060 /** 061 * Constructs a new instance. 062 * 063 * @param config The Configuration to be wrapped. 064 * @param path The base path pattern. 065 */ 066 public PatternSubtreeConfigurationWrapper(final HierarchicalConfiguration<ImmutableNode> config, final String path) { 067 this.config = Objects.requireNonNull(config, "config"); 068 this.path = path; 069 this.trailing = path.endsWith("/"); 070 this.init = true; 071 } 072 073 @Override 074 public <T extends Event> void addEventListener(final EventType<T> eventType, final EventListener<? super T> listener) { 075 getConfig().addEventListener(eventType, listener); 076 } 077 078 @Override 079 protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) { 080 getConfig().addNodes(key, nodes); 081 } 082 083 @Override 084 protected void addPropertyInternal(final String key, final Object value) { 085 config.addProperty(makePath(key), value); 086 } 087 088 @Override 089 public void clearErrorListeners() { 090 getConfig().clearErrorListeners(); 091 } 092 093 @Override 094 public void clearEventListeners() { 095 getConfig().clearEventListeners(); 096 } 097 098 @Override 099 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}