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 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 /** The wrapped configuration */ 048 private final HierarchicalConfiguration<ImmutableNode> config; 049 050 /** The path to the subtree */ 051 private final String path; 052 053 /** True if the path ends with '/', false otherwise */ 054 private final boolean trailing; 055 056 /** True if the constructor has finished */ 057 private final boolean init; 058 059 /** 060 * Constructor 061 * 062 * @param config The Configuration to be wrapped. 063 * @param path The base path pattern. 064 */ 065 public PatternSubtreeConfigurationWrapper(final HierarchicalConfiguration<ImmutableNode> config, final String path) { 066 this.config = Objects.requireNonNull(config, "config"); 067 this.path = path; 068 this.trailing = path.endsWith("/"); 069 this.init = true; 070 } 071 072 @Override 073 protected void addPropertyInternal(final String key, final Object value) { 074 config.addProperty(makePath(key), value); 075 } 076 077 @Override 078 protected void clearInternal() { 079 getConfig().clear(); 080 } 081 082 @Override 083 protected void clearPropertyDirect(final String key) { 084 config.clearProperty(makePath(key)); 085 } 086 087 @Override 088 protected boolean containsKeyInternal(final String key) { 089 return config.containsKey(makePath(key)); 090 } 091 092 @Override 093 public BigDecimal getBigDecimal(final String key, final BigDecimal defaultValue) { 094 return config.getBigDecimal(makePath(key), defaultValue); 095 } 096 097 @Override 098 public BigDecimal getBigDecimal(final String key) { 099 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}