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.configuration2; 019 020import java.util.Iterator; 021 022import org.apache.commons.configuration2.convert.ListDelimiterHandler; 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 */ 033public class SubsetConfiguration extends AbstractConfiguration 034{ 035 /** The parent configuration. */ 036 protected Configuration parent; 037 038 /** The prefix used to select the properties. */ 039 protected String prefix; 040 041 /** The prefix delimiter */ 042 protected String delimiter; 043 044 /** 045 * Create a subset of the specified configuration 046 * 047 * @param parent The parent configuration (must not be <b>null</b>) 048 * @param prefix The prefix used to select the properties 049 * @throws IllegalArgumentException if the parent configuration is <b>null</b> 050 */ 051 public SubsetConfiguration(final Configuration parent, final String prefix) 052 { 053 this(parent, prefix, null); 054 } 055 056 /** 057 * Create a subset of the specified configuration 058 * 059 * @param parent The parent configuration (must not be <b>null</b>) 060 * @param prefix The prefix used to select the properties 061 * @param delimiter The prefix delimiter 062 * @throws IllegalArgumentException if the parent configuration is <b>null</b> 063 */ 064 public SubsetConfiguration(final Configuration parent, final String prefix, final String delimiter) 065 { 066 if (parent == null) 067 { 068 throw new IllegalArgumentException( 069 "Parent configuration must not be null!"); 070 } 071 072 this.parent = parent; 073 this.prefix = prefix; 074 this.delimiter = delimiter; 075 initInterpolator(); 076 } 077 078 /** 079 * Return the key in the parent configuration associated to the specified 080 * key in this subset. 081 * 082 * @param key The key in the subset. 083 * @return the key as to be used by the parent 084 */ 085 protected String getParentKey(final String key) 086 { 087 if ("".equals(key) || key == null) 088 { 089 return prefix; 090 } 091 return delimiter == null ? prefix + key : prefix + delimiter + key; 092 } 093 094 /** 095 * Return the key in the subset configuration associated to the specified 096 * key in the parent configuration. 097 * 098 * @param key The key in the parent configuration. 099 * @return the key in the context of this subset configuration 100 */ 101 protected String getChildKey(final String key) 102 { 103 if (!key.startsWith(prefix)) 104 { 105 throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset."); 106 } 107 String modifiedKey = null; 108 if (key.length() == prefix.length()) 109 { 110 modifiedKey = ""; 111 } 112 else 113 { 114 final int i = prefix.length() + (delimiter != null ? delimiter.length() : 0); 115 modifiedKey = key.substring(i); 116 } 117 118 return modifiedKey; 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(final String prefix) 147 { 148 this.prefix = prefix; 149 } 150 151 @Override 152 public Configuration subset(final String prefix) 153 { 154 return parent.subset(getParentKey(prefix)); 155 } 156 157 @Override 158 protected boolean isEmptyInternal() 159 { 160 return !getKeysInternal().hasNext(); 161 } 162 163 @Override 164 protected boolean containsKeyInternal(final String key) 165 { 166 return parent.containsKey(getParentKey(key)); 167 } 168 169 @Override 170 public void addPropertyDirect(final String key, final Object value) 171 { 172 parent.addProperty(getParentKey(key), value); 173 } 174 175 @Override 176 protected void clearPropertyDirect(final String key) 177 { 178 parent.clearProperty(getParentKey(key)); 179 } 180 181 @Override 182 protected Object getPropertyInternal(final String key) 183 { 184 return parent.getProperty(getParentKey(key)); 185 } 186 187 @Override 188 protected Iterator<String> getKeysInternal(final String prefix) 189 { 190 return new SubsetIterator(parent.getKeys(getParentKey(prefix))); 191 } 192 193 @Override 194 protected Iterator<String> getKeysInternal() 195 { 196 return new SubsetIterator(parent.getKeys(prefix)); 197 } 198 199 /** 200 * {@inheritDoc} 201 * 202 * Change the behavior of the parent configuration if it supports this feature. 203 */ 204 @Override 205 public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) 206 { 207 if (parent instanceof AbstractConfiguration) 208 { 209 ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing); 210 } 211 else 212 { 213 super.setThrowExceptionOnMissing(throwExceptionOnMissing); 214 } 215 } 216 217 /** 218 * {@inheritDoc} 219 * 220 * The subset inherits this feature from its parent if it supports this feature. 221 */ 222 @Override 223 public boolean isThrowExceptionOnMissing() 224 { 225 if (parent instanceof AbstractConfiguration) 226 { 227 return ((AbstractConfiguration) parent).isThrowExceptionOnMissing(); 228 } 229 return super.isThrowExceptionOnMissing(); 230 } 231 232 /** 233 * {@inheritDoc} If the parent configuration extends 234 * {@link AbstractConfiguration}, the list delimiter handler is obtained 235 * from there. 236 */ 237 @Override 238 public ListDelimiterHandler getListDelimiterHandler() 239 { 240 return parent instanceof AbstractConfiguration ? ((AbstractConfiguration) parent) 241 .getListDelimiterHandler() : super.getListDelimiterHandler(); 242 } 243 244 /** 245 * {@inheritDoc} If the parent configuration extends 246 * {@link AbstractConfiguration}, the list delimiter handler is passed to 247 * the parent. 248 */ 249 @Override 250 public void setListDelimiterHandler( 251 final ListDelimiterHandler listDelimiterHandler) 252 { 253 if (parent instanceof AbstractConfiguration) 254 { 255 ((AbstractConfiguration) parent) 256 .setListDelimiterHandler(listDelimiterHandler); 257 } 258 else 259 { 260 super.setListDelimiterHandler(listDelimiterHandler); 261 } 262 } 263 264 /** 265 * Initializes the {@code ConfigurationInterpolator} for this sub configuration. 266 * This is a standard {@code ConfigurationInterpolator} which also references 267 * the {@code ConfigurationInterpolator} of the parent configuration. 268 */ 269 private void initInterpolator() 270 { 271 getInterpolator().setParentInterpolator(getParent().getInterpolator()); 272 } 273 274 /** 275 * A specialized iterator to be returned by the {@code getKeys()} 276 * methods. This implementation wraps an iterator from the parent 277 * configuration. The keys returned by this iterator are correspondingly 278 * transformed. 279 */ 280 private class SubsetIterator implements Iterator<String> 281 { 282 /** Stores the wrapped iterator. */ 283 private final Iterator<String> parentIterator; 284 285 /** 286 * Creates a new instance of {@code SubsetIterator} and 287 * initializes it with the parent iterator. 288 * 289 * @param it the iterator of the parent configuration 290 */ 291 public SubsetIterator(final Iterator<String> it) 292 { 293 parentIterator = it; 294 } 295 296 /** 297 * Checks whether there are more elements. Delegates to the parent 298 * iterator. 299 * 300 * @return a flag whether there are more elements 301 */ 302 @Override 303 public boolean hasNext() 304 { 305 return parentIterator.hasNext(); 306 } 307 308 /** 309 * Returns the next element in the iteration. This is the next key from 310 * the parent configuration, transformed to correspond to the point of 311 * view of this subset configuration. 312 * 313 * @return the next element 314 */ 315 @Override 316 public String next() 317 { 318 return getChildKey(parentIterator.next()); 319 } 320 321 /** 322 * Removes the current element from the iteration. Delegates to the 323 * parent iterator. 324 */ 325 @Override 326 public void remove() 327 { 328 parentIterator.remove(); 329 } 330 } 331}