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