SubsetConfiguration.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.configuration2;

  18. import java.util.Iterator;
  19. import java.util.Objects;

  20. import org.apache.commons.configuration2.convert.ListDelimiterHandler;
  21. import org.apache.commons.lang3.StringUtils;

  22. /**
  23.  * <p>
  24.  * A subset of another configuration. The new Configuration object contains every key from the parent Configuration that
  25.  * starts with prefix. The prefix is removed from the keys in the subset.
  26.  * </p>
  27.  * <p>
  28.  * It is usually not necessary to use this class directly. Instead the {@link Configuration#subset(String)} method
  29.  * should be used, which will return a correctly initialized instance.
  30.  * </p>
  31.  */
  32. public class SubsetConfiguration extends AbstractConfiguration {
  33.     /**
  34.      * A specialized iterator to be returned by the {@code getKeys()} methods. This implementation wraps an iterator from
  35.      * the parent configuration. The keys returned by this iterator are correspondingly transformed.
  36.      */
  37.     private final class SubsetIterator implements Iterator<String> {
  38.         /** Stores the wrapped iterator. */
  39.         private final Iterator<String> parentIterator;

  40.         /**
  41.          * Creates a new instance of {@code SubsetIterator} and initializes it with the parent iterator.
  42.          *
  43.          * @param it the iterator of the parent configuration
  44.          */
  45.         public SubsetIterator(final Iterator<String> it) {
  46.             parentIterator = it;
  47.         }

  48.         /**
  49.          * Checks whether there are more elements. Delegates to the parent iterator.
  50.          *
  51.          * @return a flag whether there are more elements
  52.          */
  53.         @Override
  54.         public boolean hasNext() {
  55.             return parentIterator.hasNext();
  56.         }

  57.         /**
  58.          * Returns the next element in the iteration. This is the next key from the parent configuration, transformed to
  59.          * correspond to the point of view of this subset configuration.
  60.          *
  61.          * @return the next element
  62.          */
  63.         @Override
  64.         public String next() {
  65.             return getChildKey(parentIterator.next());
  66.         }

  67.         /**
  68.          * Removes the current element from the iteration. Delegates to the parent iterator.
  69.          */
  70.         @Override
  71.         public void remove() {
  72.             parentIterator.remove();
  73.         }
  74.     }

  75.     /** The parent configuration. */
  76.     protected Configuration parent;

  77.     /** The prefix used to select the properties. */
  78.     protected String prefix;

  79.     /** The prefix delimiter */
  80.     protected String delimiter;

  81.     /**
  82.      * Create a subset of the specified configuration
  83.      *
  84.      * @param parent The parent configuration (must not be <strong>null</strong>)
  85.      * @param prefix The prefix used to select the properties
  86.      * @throws IllegalArgumentException if the parent configuration is <strong>null</strong>
  87.      */
  88.     public SubsetConfiguration(final Configuration parent, final String prefix) {
  89.         this(parent, prefix, null);
  90.     }

  91.     /**
  92.      * Create a subset of the specified configuration
  93.      *
  94.      * @param parent The parent configuration (must not be <strong>null</strong>)
  95.      * @param prefix The prefix used to select the properties
  96.      * @param delimiter The prefix delimiter
  97.      * @throws NullPointerException if the parent configuration is <strong>null</strong>
  98.      */
  99.     public SubsetConfiguration(final Configuration parent, final String prefix, final String delimiter) {
  100.         this.parent = Objects.requireNonNull(parent, "parent");
  101.         this.prefix = prefix;
  102.         this.delimiter = delimiter;
  103.         initInterpolator();
  104.     }

  105.     @Override
  106.     public void addPropertyDirect(final String key, final Object value) {
  107.         parent.addProperty(getParentKey(key), value);
  108.     }

  109.     @Override
  110.     protected void clearPropertyDirect(final String key) {
  111.         parent.clearProperty(getParentKey(key));
  112.     }

  113.     @Override
  114.     protected boolean containsKeyInternal(final String key) {
  115.         return parent.containsKey(getParentKey(key));
  116.     }

  117.     /**
  118.      * Tests whether this configuration contains one or more matches to this value. This operation stops at first match
  119.      * but may be more expensive than the containsKey method.
  120.      * @since 2.11.0
  121.      */
  122.     @Override
  123.     protected boolean containsValueInternal(final Object value) {
  124.         return parent.containsValue(value);
  125.     }

  126.     /**
  127.      * Gets the key in the subset configuration associated to the specified key in the parent configuration.
  128.      *
  129.      * @param key The key in the parent configuration.
  130.      * @return the key in the context of this subset configuration
  131.      */
  132.     protected String getChildKey(final String key) {
  133.         if (!key.startsWith(prefix)) {
  134.             throw new IllegalArgumentException("The parent key '" + key + "' is not in the subset.");
  135.         }
  136.         String modifiedKey = null;
  137.         if (key.length() == prefix.length()) {
  138.             modifiedKey = "";
  139.         } else {
  140.             final int i = prefix.length() + (delimiter != null ? delimiter.length() : 0);
  141.             modifiedKey = key.substring(i);
  142.         }

  143.         return modifiedKey;
  144.     }

  145.     @Override
  146.     protected Iterator<String> getKeysInternal() {
  147.         return new SubsetIterator(parent.getKeys(prefix, delimiter));
  148.     }

  149.     @Override
  150.     protected Iterator<String> getKeysInternal(final String prefix) {
  151.         return new SubsetIterator(parent.getKeys(getParentKey(prefix)));
  152.     }

  153.     /**
  154.      * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is
  155.      * obtained from there.
  156.      */
  157.     @Override
  158.     public ListDelimiterHandler getListDelimiterHandler() {
  159.         return parent instanceof AbstractConfiguration ? ((AbstractConfiguration) parent).getListDelimiterHandler() : super.getListDelimiterHandler();
  160.     }

  161.     /**
  162.      * Gets the parent configuration for this subset.
  163.      *
  164.      * @return the parent configuration
  165.      */
  166.     public Configuration getParent() {
  167.         return parent;
  168.     }

  169.     /**
  170.      * Gets the key in the parent configuration associated to the specified key in this subset.
  171.      *
  172.      * @param key The key in the subset.
  173.      * @return the key as to be used by the parent
  174.      */
  175.     protected String getParentKey(final String key) {
  176.         if (StringUtils.isEmpty(key)) {
  177.             return prefix;
  178.         }
  179.         return delimiter == null ? prefix + key : prefix + delimiter + key;
  180.     }

  181.     /**
  182.      * Gets the prefix used to select the properties in the parent configuration.
  183.      *
  184.      * @return the prefix used by this subset
  185.      */
  186.     public String getPrefix() {
  187.         return prefix;
  188.     }

  189.     @Override
  190.     protected Object getPropertyInternal(final String key) {
  191.         return parent.getProperty(getParentKey(key));
  192.     }

  193.     /**
  194.      * Initializes the {@code ConfigurationInterpolator} for this sub configuration. This is a standard
  195.      * {@code ConfigurationInterpolator} which also references the {@code ConfigurationInterpolator} of the parent
  196.      * configuration.
  197.      */
  198.     private void initInterpolator() {
  199.         getInterpolator().setParentInterpolator(getParent().getInterpolator());
  200.     }

  201.     @Override
  202.     protected boolean isEmptyInternal() {
  203.         return !getKeysInternal().hasNext();
  204.     }

  205.     /**
  206.      * {@inheritDoc}
  207.      *
  208.      * The subset inherits this feature from its parent if it supports this feature.
  209.      */
  210.     @Override
  211.     public boolean isThrowExceptionOnMissing() {
  212.         if (parent instanceof AbstractConfiguration) {
  213.             return ((AbstractConfiguration) parent).isThrowExceptionOnMissing();
  214.         }
  215.         return super.isThrowExceptionOnMissing();
  216.     }

  217.     /**
  218.      * {@inheritDoc} If the parent configuration extends {@link AbstractConfiguration}, the list delimiter handler is passed
  219.      * to the parent.
  220.      */
  221.     @Override
  222.     public void setListDelimiterHandler(final ListDelimiterHandler listDelimiterHandler) {
  223.         if (parent instanceof AbstractConfiguration) {
  224.             ((AbstractConfiguration) parent).setListDelimiterHandler(listDelimiterHandler);
  225.         } else {
  226.             super.setListDelimiterHandler(listDelimiterHandler);
  227.         }
  228.     }

  229.     /**
  230.      * Sets the prefix used to select the properties in the parent configuration.
  231.      *
  232.      * @param prefix the prefix
  233.      */
  234.     public void setPrefix(final String prefix) {
  235.         this.prefix = prefix;
  236.     }

  237.     /**
  238.      * {@inheritDoc}
  239.      *
  240.      * Change the behavior of the parent configuration if it supports this feature.
  241.      */
  242.     @Override
  243.     public void setThrowExceptionOnMissing(final boolean throwExceptionOnMissing) {
  244.         if (parent instanceof AbstractConfiguration) {
  245.             ((AbstractConfiguration) parent).setThrowExceptionOnMissing(throwExceptionOnMissing);
  246.         } else {
  247.             super.setThrowExceptionOnMissing(throwExceptionOnMissing);
  248.         }
  249.     }

  250.     @Override
  251.     public Configuration subset(final String prefix) {
  252.         return parent.subset(getParentKey(prefix));
  253.     }
  254. }