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 */ 017 018package org.apache.commons.configuration; 019 020import java.util.Iterator; 021 022import org.apache.commons.configuration.interpol.ConfigurationInterpolator; 023 024/** 025 * <p>A subset of another configuration. The new Configuration object contains 026 * every key from the parent Configuration that starts with prefix. The prefix 027 * is removed from the keys in the subset.</p> 028 * <p>It is usually not necessary to use this class directly. Instead the 029 * {@link Configuration#subset(String)} method should be used, 030 * which will return a correctly initialized instance.</p> 031 * 032 * @author Emmanuel Bourg 033 * @version $Id: SubsetConfiguration.java 1210202 2011-12-04 20:30:46Z oheger $ 034 */ 035public class SubsetConfiguration extends AbstractConfiguration 036{ 037 /** The parent configuration. */ 038 protected Configuration parent; 039 040 /** The prefix used to select the properties. */ 041 protected String prefix; 042 043 /** The prefix delimiter */ 044 protected String delimiter; 045 046 /** 047 * Create a subset of the specified configuration 048 * 049 * @param parent The parent configuration 050 * @param prefix The prefix used to select the properties 051 */ 052 public SubsetConfiguration(Configuration parent, String prefix) 053 { 054 this.parent = parent; 055 this.prefix = prefix; 056 } 057 058 /** 059 * Create a subset of the specified configuration 060 * 061 * @param parent The parent configuration 062 * @param prefix The prefix used to select the properties 063 * @param delimiter The prefix delimiter 064 */ 065 public SubsetConfiguration(Configuration parent, String prefix, String delimiter) 066 { 067 this.parent = parent; 068 this.prefix = prefix; 069 this.delimiter = delimiter; 070 } 071 072 /** 073 * Return the key in the parent configuration associated to the specified 074 * key in this subset. 075 * 076 * @param key The key in the subset. 077 * @return the key as to be used by the parent 078 */ 079 protected String getParentKey(String key) 080 { 081 if ("".equals(key) || key == null) 082 { 083 return prefix; 084 } 085 else 086 { 087 return delimiter == null ? prefix + key : prefix + delimiter + key; 088 } 089 } 090 091 /** 092 * Return the key in the subset configuration associated to the specified 093 * key in the parent configuration. 094 * 095 * @param key The key in the parent configuration. 096 * @return the key in the context of this subset configuration 097 */ 098 protected String getChildKey(String key) 099 { 100 if (!key.startsWith(prefix)) 101 { 102 throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset."); 103 } 104 else 105 { 106 String modifiedKey = null; 107 if (key.length() == prefix.length()) 108 { 109 modifiedKey = ""; 110 } 111 else 112 { 113 int i = prefix.length() + (delimiter != null ? delimiter.length() : 0); 114 modifiedKey = key.substring(i); 115 } 116 117 return modifiedKey; 118 } 119 } 120 121 /** 122 * Return the parent configuration for this subset. 123 * 124 * @return the parent configuration 125 */ 126 public Configuration getParent() 127 { 128 return parent; 129 } 130 131 /** 132 * Return the prefix used to select the properties in the parent configuration. 133 * 134 * @return the prefix used by this subset 135 */ 136 public String getPrefix() 137 { 138 return prefix; 139 } 140 141 /** 142 * Set the prefix used to select the properties in the parent configuration. 143 * 144 * @param prefix the prefix 145 */ 146 public void setPrefix(String prefix) 147 { 148 this.prefix = prefix; 149 } 150 151 @Override 152 public Configuration subset(String prefix) 153 { 154 return parent.subset(getParentKey(prefix)); 155 } 156 157 public boolean isEmpty() 158 { 159 return !getKeys().hasNext(); 160 } 161 162 public boolean containsKey(String key) 163 { 164 return parent.containsKey(getParentKey(key)); 165 } 166 167 @Override 168 public void addPropertyDirect(String key, Object value) 169 { 170 parent.addProperty(getParentKey(key), value); 171 } 172 173 @Override 174 protected void clearPropertyDirect(String key) 175 { 176 parent.clearProperty(getParentKey(key)); 177 } 178 179 public Object getProperty(String key) 180 { 181 return parent.getProperty(getParentKey(key)); 182 } 183 184 @Override 185 public Iterator<String> getKeys(String prefix) 186 { 187 return new SubsetIterator(parent.getKeys(getParentKey(prefix))); 188 } 189 190 public Iterator<String> getKeys() 191 { 192 return new SubsetIterator(parent.getKeys(prefix)); 193 } 194 195 @Override 196 protected Object interpolate(Object base) 197 { 198 if (delimiter == null && "".equals(prefix)) 199 { 200 return super.interpolate(base); 201 } 202 else 203 { 204 SubsetConfiguration config = new SubsetConfiguration(parent, ""); 205 ConfigurationInterpolator interpolator = config.getInterpolator(); 206 getInterpolator().registerLocalLookups(interpolator); 207 if (parent instanceof AbstractConfiguration) 208 { 209 interpolator.setParentInterpolator(((AbstractConfiguration) parent).getInterpolator()); 210 } 211 return config.interpolate(base); 212 } 213 } 214 215 @Override 216 protected String interpolate(String base) 217 { 218 return super.interpolate(base); 219 } 220 221 /** 222 * {@inheritDoc} 223 * 224 * Change the behavior of the parent configuration if it supports this feature. 225 */ 226 @Override 227 public void setThrowExceptionOnMissing(boolean throwExceptionOnMissing) 228 { 229 if (parent instanceof AbstractConfiguration) 230 { 231 ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing); 232 } 233 else 234 { 235 super.setThrowExceptionOnMissing(throwExceptionOnMissing); 236 } 237 } 238 239 /** 240 * {@inheritDoc} 241 * 242 * The subset inherits this feature from its parent if it supports this feature. 243 */ 244 @Override 245 public boolean isThrowExceptionOnMissing() 246 { 247 if (parent instanceof AbstractConfiguration) 248 { 249 return ((AbstractConfiguration) parent).isThrowExceptionOnMissing(); 250 } 251 else 252 { 253 return super.isThrowExceptionOnMissing(); 254 } 255 } 256 257 /** 258 * Returns the list delimiter. This property will be fetched from the parent 259 * configuration if supported. 260 * 261 * @return the list delimiter 262 * @since 1.4 263 */ 264 @Override 265 public char getListDelimiter() 266 { 267 return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent) 268 .getListDelimiter() 269 : super.getListDelimiter(); 270 } 271 272 /** 273 * Sets the list delimiter. If the parent configuration supports this 274 * feature, the delimiter will be set at the parent. 275 * 276 * @param delim the new list delimiter 277 * @since 1.4 278 */ 279 @Override 280 public void setListDelimiter(char delim) 281 { 282 if (parent instanceof AbstractConfiguration) 283 { 284 ((AbstractConfiguration) parent).setListDelimiter(delim); 285 } 286 else 287 { 288 super.setListDelimiter(delim); 289 } 290 } 291 292 /** 293 * Returns a flag whether string properties should be checked for list 294 * delimiter characters. This implementation ensures that this flag is kept 295 * in sync with the parent configuration if this object supports this 296 * feature. 297 * 298 * @return the delimiter parsing disabled flag 299 * @since 1.4 300 */ 301 @Override 302 public boolean isDelimiterParsingDisabled() 303 { 304 return (parent instanceof AbstractConfiguration) ? ((AbstractConfiguration) parent) 305 .isDelimiterParsingDisabled() 306 : super.isDelimiterParsingDisabled(); 307 } 308 309 /** 310 * Sets a flag whether list parsing is disabled. This implementation will 311 * also set the flag at the parent configuration if this object supports 312 * this feature. 313 * 314 * @param delimiterParsingDisabled the delimiter parsing disabled flag 315 * @since 1.4 316 */ 317 @Override 318 public void setDelimiterParsingDisabled(boolean delimiterParsingDisabled) 319 { 320 if (parent instanceof AbstractConfiguration) 321 { 322 ((AbstractConfiguration) parent) 323 .setDelimiterParsingDisabled(delimiterParsingDisabled); 324 } 325 else 326 { 327 super.setDelimiterParsingDisabled(delimiterParsingDisabled); 328 } 329 } 330 331 332 /** 333 * A specialized iterator to be returned by the {@code getKeys()} 334 * methods. This implementation wraps an iterator from the parent 335 * configuration. The keys returned by this iterator are correspondingly 336 * transformed. 337 */ 338 private class SubsetIterator implements Iterator<String> 339 { 340 /** Stores the wrapped iterator. */ 341 private final Iterator<String> parentIterator; 342 343 /** 344 * Creates a new instance of {@code SubsetIterator} and 345 * initializes it with the parent iterator. 346 * 347 * @param it the iterator of the parent configuration 348 */ 349 public SubsetIterator(Iterator<String> it) 350 { 351 parentIterator = it; 352 } 353 354 /** 355 * Checks whether there are more elements. Delegates to the parent 356 * iterator. 357 * 358 * @return a flag whether there are more elements 359 */ 360 public boolean hasNext() 361 { 362 return parentIterator.hasNext(); 363 } 364 365 /** 366 * Returns the next element in the iteration. This is the next key from 367 * the parent configuration, transformed to correspond to the point of 368 * view of this subset configuration. 369 * 370 * @return the next element 371 */ 372 public String next() 373 { 374 return getChildKey(parentIterator.next()); 375 } 376 377 /** 378 * Removes the current element from the iteration. Delegates to the 379 * parent iterator. 380 */ 381 public void remove() 382 { 383 parentIterator.remove(); 384 } 385 } 386}