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