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