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; 023import org.apache.commons.lang3.StringUtils; 024 025/** 026 * <p> 027 * A subset of another configuration. The new Configuration object contains every key from the parent Configuration that 028 * starts with prefix. The prefix is removed from the keys in the subset. 029 * </p> 030 * <p> 031 * It is usually not necessary to use this class directly. Instead the {@link Configuration#subset(String)} method 032 * should be used, which will return a correctly initialized instance. 033 * </p> 034 */ 035public class SubsetConfiguration extends AbstractConfiguration { 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 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 if (parent == null) { 066 throw new IllegalArgumentException("Parent configuration must not be null!"); 067 } 068 069 this.parent = parent; 070 this.prefix = prefix; 071 this.delimiter = delimiter; 072 initInterpolator(); 073 } 074 075 /** 076 * Gets the key in the parent configuration associated to the specified key in this subset. 077 * 078 * @param key The key in the subset. 079 * @return the key as to be used by the parent 080 */ 081 protected String getParentKey(final String key) { 082 if (StringUtils.isEmpty(key)) { 083 return prefix; 084 } 085 return delimiter == null ? prefix + key : prefix + delimiter + key; 086 } 087 088 /** 089 * Gets the key in the subset configuration associated to the specified key in the parent configuration. 090 * 091 * @param key The key in the parent configuration. 092 * @return the key in the context of this subset configuration 093 */ 094 protected String getChildKey(final String key) { 095 if (!key.startsWith(prefix)) { 096 throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset."); 097 } 098 String modifiedKey = null; 099 if (key.length() == prefix.length()) { 100 modifiedKey = ""; 101 } else { 102 final int i = prefix.length() + (delimiter != null ? delimiter.length() : 0); 103 modifiedKey = key.substring(i); 104 } 105 106 return modifiedKey; 107 } 108 109 /** 110 * Gets the parent configuration for this subset. 111 * 112 * @return the parent configuration 113 */ 114 public Configuration getParent() { 115 return parent; 116 } 117 118 /** 119 * Gets the prefix used to select the properties in the parent configuration. 120 * 121 * @return the prefix used by this subset 122 */ 123 public String getPrefix() { 124 return prefix; 125 } 126 127 /** 128 * Sets the prefix used to select the properties in the parent configuration. 129 * 130 * @param prefix the prefix 131 */ 132 public void setPrefix(final String prefix) { 133 this.prefix = prefix; 134 } 135 136 @Override 137 public Configuration subset(final String prefix) { 138 return parent.subset(getParentKey(prefix)); 139 } 140 141 @Override 142 protected boolean isEmptyInternal() { 143 return !getKeysInternal().hasNext(); 144 } 145 146 @Override 147 protected boolean containsKeyInternal(final String key) { 148 return parent.containsKey(getParentKey(key)); 149 } 150 151 @Override 152 public void addPropertyDirect(final String key, final Object value) { 153 parent.addProperty(getParentKey(key), value); 154 } 155 156 @Override 157 protected void clearPropertyDirect(final String key) { 158 parent.clearProperty(getParentKey(key)); 159 } 160 161 @Override 162 protected Object getPropertyInternal(final String key) { 163 return parent.getProperty(getParentKey(key)); 164 } 165 166 @Override 167 protected Iterator<String> getKeysInternal(final String prefix) { 168 return new SubsetIterator(parent.getKeys(getParentKey(prefix))); 169 } 170 171 @Override 172 protected Iterator<String> getKeysInternal() { 173 return new SubsetIterator(parent.getKeys(prefix, delimiter)); 174 } 175 176 /** 177 * {@inheritDoc} 178 * 179 * Change the behavior of the parent configuration if it supports this feature. 180 */ 181 @Override 182 public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) { 183 if (parent instanceof AbstractConfiguration) { 184 ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing); 185 } else { 186 super.setThrowExceptionOnMissing(throwExceptionOnMissing); 187 } 188 } 189 190 /** 191 * {@inheritDoc} 192 * 193 * The subset inherits this feature from its parent if it supports this feature. 194 */ 195 @Override 196 public boolean isThrowExceptionOnMissing() { 197 if (parent instanceof AbstractConfiguration) { 198 return ((AbstractConfiguration) parent).isThrowExceptionOnMissing(); 199 } 200 return super.isThrowExceptionOnMissing(); 201 } 202 203 /** 204 * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is 205 * obtained from there. 206 */ 207 @Override 208 public ListDelimiterHandler getListDelimiterHandler() { 209 return parent instanceof AbstractConfiguration ? ((AbstractConfiguration) parent).getListDelimiterHandler() : super.getListDelimiterHandler(); 210 } 211 212 /** 213 * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is passed 214 * to the parent. 215 */ 216 @Override 217 public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) { 218 if (parent instanceof AbstractConfiguration) { 219 ((AbstractConfiguration) parent).setListDelimiterHandler(listDelimiterHandler); 220 } else { 221 super.setListDelimiterHandler(listDelimiterHandler); 222 } 223 } 224 225 /** 226 * Initializes the {@code ConfigurationInterpolator} for this sub configuration. This is a standard 227 * {@code ConfigurationInterpolator} which also references the {@code ConfigurationInterpolator} of the parent 228 * configuration. 229 */ 230 private void initInterpolator() { 231 getInterpolator().setParentInterpolator(getParent().getInterpolator()); 232 } 233 234 /** 235 * A specialized iterator to be returned by the {@code getKeys()} methods. This implementation wraps an iterator from 236 * the parent configuration. The keys returned by this iterator are correspondingly transformed. 237 */ 238 private final class SubsetIterator implements Iterator<String> { 239 /** Stores the wrapped iterator. */ 240 private final Iterator<String> parentIterator; 241 242 /** 243 * Creates a new instance of {@code SubsetIterator} and initializes it with the parent iterator. 244 * 245 * @param it the iterator of the parent configuration 246 */ 247 public SubsetIterator(final Iterator<String> it) { 248 parentIterator = it; 249 } 250 251 /** 252 * Checks whether there are more elements. Delegates to the parent iterator. 253 * 254 * @return a flag whether there are more elements 255 */ 256 @Override 257 public boolean hasNext() { 258 return parentIterator.hasNext(); 259 } 260 261 /** 262 * Returns the next element in the iteration. This is the next key from the parent configuration, transformed to 263 * correspond to the point of view of this subset configuration. 264 * 265 * @return the next element 266 */ 267 @Override 268 public String next() { 269 return getChildKey(parentIterator.next()); 270 } 271 272 /** 273 * Removes the current element from the iteration. Delegates to the parent iterator. 274 */ 275 @Override 276 public void remove() { 277 parentIterator.remove(); 278 } 279 } 280}