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