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