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