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 */ 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 041 * replaceable tokens derived from the ConfigurationInterpolator. When used with injection frameworks 042 * such as Spring it allows components to be injected with subtrees of the configuration. 043 * @since 1.6 044 * @author <a 045 * href="http://commons.apache.org/configuration/team-list.html">Commons 046 * Configuration team</a> 047 * @version $Id: PatternSubtreeConfigurationWrapper.java 1842194 2018-09-27 22:24:23Z ggregory $ 048 */ 049public class PatternSubtreeConfigurationWrapper extends BaseHierarchicalConfiguration 050 implements FileBasedConfiguration 051{ 052 /** The wrapped configuration */ 053 private final HierarchicalConfiguration<ImmutableNode> config; 054 055 /** The path to the subtree */ 056 private final String path; 057 058 /** True if the path ends with '/', false otherwise */ 059 private final boolean trailing; 060 061 /** True if the constructor has finished */ 062 private final boolean init; 063 064 /** 065 * Constructor 066 * @param config The Configuration to be wrapped. 067 * @param path The base path pattern. 068 */ 069 public PatternSubtreeConfigurationWrapper( 070 final HierarchicalConfiguration<ImmutableNode> config, final String path) 071 { 072 this.config = config; 073 this.path = path; 074 this.trailing = path.endsWith("/"); 075 this.init = true; 076 } 077 078 @Override 079 protected void addPropertyInternal(final String key, final Object value) 080 { 081 config.addProperty(makePath(key), value); 082 } 083 084 @Override 085 protected void clearInternal() 086 { 087 getConfig().clear(); 088 } 089 090 @Override 091 protected void clearPropertyDirect(final String key) 092 { 093 config.clearProperty(makePath(key)); 094 } 095 096 @Override 097 protected boolean containsKeyInternal(final String key) 098 { 099 return config.containsKey(makePath(key)); 100 } 101 102 @Override 103 public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) 104 { 105 return config.getBigDecimal(makePath(key), defaultValue); 106 } 107 108 @Override 109 public BigDecimal getBigDecimal(final String key) 110 { 111 return config.getBigDecimal(makePath(key)); 112 } 113 114 @Override 115 public BigInteger getBigInteger(final String key, final BigInteger defaultValue) 116 { 117 return config.getBigInteger(makePath(key), defaultValue); 118 } 119 120 @Override 121 public BigInteger getBigInteger(final String key) 122 { 123 return config.getBigInteger(makePath(key)); 124 } 125 126 @Override 127 public boolean getBoolean(final String key, final boolean defaultValue) 128 { 129 return config.getBoolean(makePath(key), defaultValue); 130 } 131 132 @Override 133 public Boolean getBoolean(final String key, final Boolean defaultValue) 134 { 135 return config.getBoolean(makePath(key), defaultValue); 136 } 137 138 @Override 139 public boolean getBoolean(final String key) 140 { 141 return config.getBoolean(makePath(key)); 142 } 143 144 @Override 145 public byte getByte(final String key, final byte defaultValue) 146 { 147 return config.getByte(makePath(key), defaultValue); 148 } 149 150 @Override 151 public Byte getByte(final String key, final Byte defaultValue) 152 { 153 return config.getByte(makePath(key), defaultValue); 154 } 155 156 @Override 157 public byte getByte(final String key) 158 { 159 return config.getByte(makePath(key)); 160 } 161 162 @Override 163 public double getDouble(final String key, final double defaultValue) 164 { 165 return config.getDouble(makePath(key), defaultValue); 166 } 167 168 @Override 169 public Double getDouble(final String key, final Double defaultValue) 170 { 171 return config.getDouble(makePath(key), defaultValue); 172 } 173 174 @Override 175 public double getDouble(final String key) 176 { 177 return config.getDouble(makePath(key)); 178 } 179 180 @Override 181 public float getFloat(final String key, final float defaultValue) 182 { 183 return config.getFloat(makePath(key), defaultValue); 184 } 185 186 @Override 187 public Float getFloat(final String key, final Float defaultValue) 188 { 189 return config.getFloat(makePath(key), defaultValue); 190 } 191 192 @Override 193 public float getFloat(final String key) 194 { 195 return config.getFloat(makePath(key)); 196 } 197 198 @Override 199 public int getInt(final String key, final int defaultValue) 200 { 201 return config.getInt(makePath(key), defaultValue); 202 } 203 204 @Override 205 public int getInt(final String key) 206 { 207 return config.getInt(makePath(key)); 208 } 209 210 @Override 211 public Integer getInteger(final String key, final Integer defaultValue) 212 { 213 return config.getInteger(makePath(key), defaultValue); 214 } 215 216 @Override 217 protected Iterator<String> getKeysInternal() 218 { 219 return config.getKeys(makePath()); 220 } 221 222 @Override 223 protected Iterator<String> getKeysInternal(final String prefix) 224 { 225 return config.getKeys(makePath(prefix)); 226 } 227 228 @Override 229 public List<Object> getList(final String key, final List<?> defaultValue) 230 { 231 return config.getList(makePath(key), defaultValue); 232 } 233 234 @Override 235 public List<Object> getList(final String key) 236 { 237 return config.getList(makePath(key)); 238 } 239 240 @Override 241 public long getLong(final String key, final long defaultValue) 242 { 243 return config.getLong(makePath(key), defaultValue); 244 } 245 246 @Override 247 public Long getLong(final String key, final Long defaultValue) 248 { 249 return config.getLong(makePath(key), defaultValue); 250 } 251 252 @Override 253 public long getLong(final String key) 254 { 255 return config.getLong(makePath(key)); 256 } 257 258 @Override 259 public Properties getProperties(final String key) 260 { 261 return config.getProperties(makePath(key)); 262 } 263 264 @Override 265 protected Object getPropertyInternal(final String key) 266 { 267 return config.getProperty(makePath(key)); 268 } 269 270 @Override 271 public short getShort(final String key, final short defaultValue) 272 { 273 return config.getShort(makePath(key), defaultValue); 274 } 275 276 @Override 277 public Short getShort(final String key, final Short defaultValue) 278 { 279 return config.getShort(makePath(key), defaultValue); 280 } 281 282 @Override 283 public short getShort(final String key) 284 { 285 return config.getShort(makePath(key)); 286 } 287 288 @Override 289 public String getString(final String key, final String defaultValue) 290 { 291 return config.getString(makePath(key), defaultValue); 292 } 293 294 @Override 295 public String getString(final String key) 296 { 297 return config.getString(makePath(key)); 298 } 299 300 @Override 301 public String[] getStringArray(final String key) 302 { 303 return config.getStringArray(makePath(key)); 304 } 305 306 @Override 307 protected boolean isEmptyInternal() 308 { 309 return getConfig().isEmpty(); 310 } 311 312 @Override 313 protected void setPropertyInternal(final String key, final Object value) 314 { 315 getConfig().setProperty(key, value); 316 } 317 318 @Override 319 public Configuration subset(final String prefix) 320 { 321 return getConfig().subset(prefix); 322 } 323 324 @Override 325 public ExpressionEngine getExpressionEngine() 326 { 327 return config.getExpressionEngine(); 328 } 329 330 @Override 331 public void setExpressionEngine(final ExpressionEngine expressionEngine) 332 { 333 if (init) 334 { 335 config.setExpressionEngine(expressionEngine); 336 } 337 else 338 { 339 super.setExpressionEngine(expressionEngine); 340 } 341 } 342 343 @Override 344 protected void addNodesInternal(final String key, final Collection<? extends ImmutableNode> nodes) 345 { 346 getConfig().addNodes(key, nodes); 347 } 348 349 @Override 350 public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key, final boolean supportUpdates) 351 { 352 return config.configurationAt(makePath(key), supportUpdates); 353 } 354 355 @Override 356 public HierarchicalConfiguration<ImmutableNode> configurationAt(final String key) 357 { 358 return config.configurationAt(makePath(key)); 359 } 360 361 @Override 362 public List<HierarchicalConfiguration<ImmutableNode>> configurationsAt(final String key) 363 { 364 return config.configurationsAt(makePath(key)); 365 } 366 367 @Override 368 protected Object clearTreeInternal(final String key) 369 { 370 config.clearTree(makePath(key)); 371 return Collections.emptyList(); 372 } 373 374 @Override 375 protected int getMaxIndexInternal(final String key) 376 { 377 return config.getMaxIndex(makePath(key)); 378 } 379 380 @Override 381 public Configuration interpolatedConfiguration() 382 { 383 return getConfig().interpolatedConfiguration(); 384 } 385 386 @Override 387 public <T extends Event> void addEventListener(final EventType<T> eventType, 388 final EventListener<? super T> listener) 389 { 390 getConfig().addEventListener(eventType, listener); 391 } 392 393 @Override 394 public <T extends Event> boolean removeEventListener( 395 final EventType<T> eventType, final EventListener<? super T> listener) 396 { 397 return getConfig().removeEventListener(eventType, listener); 398 } 399 400 @Override 401 public <T extends Event> Collection<EventListener<? super T>> getEventListeners( 402 final EventType<T> eventType) 403 { 404 return getConfig().getEventListeners(eventType); 405 } 406 407 @Override 408 public void clearEventListeners() 409 { 410 getConfig().clearEventListeners(); 411 } 412 413 @Override 414 public void clearErrorListeners() 415 { 416 getConfig().clearErrorListeners(); 417 } 418 419 @Override 420 public void write(final Writer writer) throws ConfigurationException, IOException 421 { 422 fetchFileBased().write(writer); 423 } 424 425 @Override 426 public void read(final Reader reader) throws ConfigurationException, IOException 427 { 428 fetchFileBased().read(reader); 429 } 430 431 private BaseHierarchicalConfiguration getConfig() 432 { 433 return (BaseHierarchicalConfiguration) config.configurationAt(makePath()); 434 } 435 436 private String makePath() 437 { 438 final String pathPattern = trailing ? path.substring(0, path.length() - 1) : path; 439 return substitute(pathPattern); 440 } 441 442 /* 443 * Resolve the root expression and then add the item being retrieved. Insert a 444 * separator character as required. 445 */ 446 private String makePath(final String item) 447 { 448 String pathPattern; 449 if ((item.length() == 0 || item.startsWith("/")) && trailing) 450 { 451 pathPattern = path.substring(0, path.length() - 1); 452 } 453 else if (!item.startsWith("/") || !trailing) 454 { 455 pathPattern = path + "/"; 456 } 457 else 458 { 459 pathPattern = path; 460 } 461 return substitute(pathPattern) + item; 462 } 463 464 /** 465 * Uses this configuration's {@code ConfigurationInterpolator} to perform 466 * variable substitution on the given pattern string. 467 * 468 * @param pattern the pattern string 469 * @return the string with variables replaced 470 */ 471 private String substitute(final String pattern) 472 { 473 return Objects.toString(getInterpolator().interpolate(pattern), null); 474 } 475 476 /** 477 * Returns the wrapped configuration as a {@code FileBased} object. If this 478 * cast is not possible, an exception is thrown. 479 * 480 * @return the wrapped configuration as {@code FileBased} 481 * @throws ConfigurationException if the wrapped configuration does not 482 * implement {@code FileBased} 483 */ 484 private FileBased fetchFileBased() throws ConfigurationException 485 { 486 if (!(config instanceof FileBased)) 487 { 488 throw new ConfigurationException( 489 "Wrapped configuration does not implement FileBased!" 490 + " No I/O operations are supported."); 491 } 492 return (FileBased) config; 493 } 494}