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