DefaultConfigurationKey.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.tree;

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

  20. import org.apache.commons.lang3.StringUtils;

  21. /**
  22.  * <p>
  23.  * A simple class that supports creation of and iteration on configuration keys supported by a
  24.  * {@link DefaultExpressionEngine} object.
  25.  * </p>
  26.  * <p>
  27.  * For key creation the class works similar to a StringBuffer: There are several {@code appendXXXX()} methods with which
  28.  * single parts of a key can be constructed. All these methods return a reference to the actual object so they can be
  29.  * written in a chain. When using this methods the exact syntax for keys need not be known.
  30.  * </p>
  31.  * <p>
  32.  * This class also defines a specialized iterator for configuration keys. With such an iterator a key can be tokenized
  33.  * into its single parts. For each part it can be checked whether it has an associated index.
  34.  * </p>
  35.  * <p>
  36.  * Instances of this class are always associated with an instance of {@link DefaultExpressionEngine}, from which the
  37.  * current delimiters are obtained. So key creation and parsing is specific to this associated expression engine.
  38.  * </p>
  39.  *
  40.  * @since 1.3
  41.  */
  42. public class DefaultConfigurationKey {
  43.     /**
  44.      * A specialized iterator class for tokenizing a configuration key. This class implements the normal iterator interface.
  45.      * In addition it provides some specific methods for configuration keys.
  46.      */
  47.     public class KeyIterator implements Iterator<Object>, Cloneable {
  48.         /** Stores the current key name. */
  49.         private String current;

  50.         /** Stores the start index of the actual token. */
  51.         private int startIndex;

  52.         /** Stores the end index of the actual token. */
  53.         private int endIndex;

  54.         /** Stores the index of the actual property if there is one. */
  55.         private int indexValue;

  56.         /** Stores a flag if the actual property has an index. */
  57.         private boolean hasIndex;

  58.         /** Stores a flag if the actual property is an attribute. */
  59.         private boolean attribute;

  60.         /**
  61.          * Helper method for checking if the passed key is an attribute. If this is the case, the internal fields will be set.
  62.          *
  63.          * @param key the key to be checked
  64.          * @return a flag if the key is an attribute
  65.          */
  66.         private boolean checkAttribute(final String key) {
  67.             if (isAttributeKey(key)) {
  68.                 current = removeAttributeMarkers(key);
  69.                 return true;
  70.             }
  71.             return false;
  72.         }

  73.         /**
  74.          * Helper method for checking if the passed key contains an index. If this is the case, internal fields will be set.
  75.          *
  76.          * @param key the key to be checked
  77.          * @return a flag if an index is defined
  78.          */
  79.         private boolean checkIndex(final String key) {
  80.             boolean result = false;

  81.             try {
  82.                 final int idx = key.lastIndexOf(getSymbols().getIndexStart());
  83.                 if (idx > 0) {
  84.                     final int endidx = key.indexOf(getSymbols().getIndexEnd(), idx);

  85.                     if (endidx > idx + 1) {
  86.                         indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
  87.                         current = key.substring(0, idx);
  88.                         result = true;
  89.                     }
  90.                 }
  91.             } catch (final NumberFormatException nfe) {
  92.                 result = false;
  93.             }

  94.             return result;
  95.         }

  96.         /**
  97.          * Creates a clone of this object.
  98.          *
  99.          * @return a clone of this object
  100.          */
  101.         @Override
  102.         public Object clone() {
  103.             try {
  104.                 return super.clone();
  105.             } catch (final CloneNotSupportedException cex) {
  106.                 // should not happen
  107.                 return null;
  108.             }
  109.         }

  110.         /**
  111.          * Returns the current key of the iteration (without skipping to the next element). This is the same key the previous
  112.          * {@code next()} call had returned. (Short form of {@code currentKey(false)}.
  113.          *
  114.          * @return the current key
  115.          */
  116.         public String currentKey() {
  117.             return currentKey(false);
  118.         }

  119.         /**
  120.          * Returns the current key of the iteration (without skipping to the next element). The boolean parameter indicates
  121.          * wheter a decorated key should be returned. This affects only attribute keys: if the parameter is <strong>false</strong>, the
  122.          * attribute markers are stripped from the key; if it is <strong>true</strong>, they remain.
  123.          *
  124.          * @param decorated a flag if the decorated key is to be returned
  125.          * @return the current key
  126.          */
  127.         public String currentKey(final boolean decorated) {
  128.             return decorated && !isPropertyKey() ? constructAttributeKey(current) : current;
  129.         }

  130.         /**
  131.          * Checks if a delimiter at the specified position is escaped. If this is the case, the next valid search position will
  132.          * be returned. Otherwise the return value is -1.
  133.          *
  134.          * @param key the key to check
  135.          * @param pos the position where a delimiter was found
  136.          * @return information about escaped delimiters
  137.          */
  138.         private int escapedPosition(final String key, final int pos) {
  139.             if (getSymbols().getEscapedDelimiter() == null) {
  140.                 // nothing to escape
  141.                 return -1;
  142.             }
  143.             final int escapeOffset = escapeOffset();
  144.             if (escapeOffset < 0 || escapeOffset > pos) {
  145.                 // No escaping possible at this position
  146.                 return -1;
  147.             }

  148.             final int escapePos = key.indexOf(getSymbols().getEscapedDelimiter(), pos - escapeOffset);
  149.             if (escapePos <= pos && escapePos >= 0) {
  150.                 // The found delimiter is escaped. Next valid search position
  151.                 // is behind the escaped delimiter.
  152.                 return escapePos + getSymbols().getEscapedDelimiter().length();
  153.             }
  154.             return -1;
  155.         }

  156.         /**
  157.          * Determines the relative offset of an escaped delimiter in relation to a delimiter. Depending on the used delimiter
  158.          * and escaped delimiter tokens the position where to search for an escaped delimiter is different. If, for instance,
  159.          * the dot character (&quot;.&quot;) is used as delimiter, and a doubled dot (&quot;..&quot;) as escaped delimiter, the
  160.          * escaped delimiter starts at the same position as the delimiter. If the token &quot;\.&quot; was used, it would start
  161.          * one character before the delimiter because the delimiter character &quot;.&quot; is the second character in the
  162.          * escaped delimiter string. This relation will be determined by this method. For this to work the delimiter string must
  163.          * be contained in the escaped delimiter string.
  164.          *
  165.          * @return the relative offset of the escaped delimiter in relation to a delimiter
  166.          */
  167.         private int escapeOffset() {
  168.             return getSymbols().getEscapedDelimiter().indexOf(getSymbols().getPropertyDelimiter());
  169.         }

  170.         /**
  171.          * Helper method for determining the next indices.
  172.          *
  173.          * @return the next key part
  174.          */
  175.         private String findNextIndices() {
  176.             startIndex = endIndex;
  177.             // skip empty names
  178.             while (startIndex < length() && hasLeadingDelimiter(keyBuffer.substring(startIndex))) {
  179.                 startIndex += getSymbols().getPropertyDelimiter().length();
  180.             }

  181.             // Key ends with a delimiter?
  182.             if (startIndex >= length()) {
  183.                 endIndex = length();
  184.                 startIndex = endIndex - 1;
  185.                 return keyBuffer.substring(startIndex, endIndex);
  186.             }
  187.             return nextKeyPart();
  188.         }

  189.         /**
  190.          * Gets the index value of the current key. If the current key does not have an index, return value is -1. This
  191.          * method can be called after {@code next()}.
  192.          *
  193.          * @return the index value of the current key
  194.          */
  195.         public int getIndex() {
  196.             return indexValue;
  197.         }

  198.         /**
  199.          * Returns a flag if the current key has an associated index. This method can be called after {@code next()}.
  200.          *
  201.          * @return a flag if the current key has an index
  202.          */
  203.         public boolean hasIndex() {
  204.             return hasIndex;
  205.         }

  206.         /**
  207.          * Checks if there is a next element.
  208.          *
  209.          * @return a flag if there is a next element
  210.          */
  211.         @Override
  212.         public boolean hasNext() {
  213.             return endIndex < keyBuffer.length();
  214.         }

  215.         /**
  216.          * Returns a flag if the current key is an attribute. This method can be called after {@code next()}.
  217.          *
  218.          * @return a flag if the current key is an attribute
  219.          */
  220.         public boolean isAttribute() {
  221.             // if attribute emulation mode is active, the last part of a key is
  222.             // always an attribute key, too
  223.             return attribute || isAttributeEmulatingMode() && !hasNext();
  224.         }

  225.         /**
  226.          * Returns a flag whether attributes are marked the same way as normal property keys. We call this the &quot;attribute
  227.          * emulating mode&quot;. When navigating through node hierarchies it might be convenient to treat attributes the same
  228.          * way than other child nodes, so an expression engine supports to set the attribute markers to the same value than the
  229.          * property delimiter. If this is the case, some special checks have to be performed.
  230.          *
  231.          * @return a flag if attributes and normal property keys are treated the same way
  232.          */
  233.         private boolean isAttributeEmulatingMode() {
  234.             return getSymbols().getAttributeEnd() == null && StringUtils.equals(getSymbols().getPropertyDelimiter(), getSymbols().getAttributeStart());
  235.         }

  236.         /**
  237.          * Returns a flag whether the current key refers to a property (i.e. is no special attribute key). Usually this method
  238.          * will return the opposite of {@code isAttribute()}, but if the delimiters for normal properties and attributes are set
  239.          * to the same string, it is possible that both methods return <strong>true</strong>.
  240.          *
  241.          * @return a flag if the current key is a property key
  242.          * @see #isAttribute()
  243.          */
  244.         public boolean isPropertyKey() {
  245.             return !attribute;
  246.         }

  247.         /**
  248.          * Returns the next object in the iteration.
  249.          *
  250.          * @return the next object
  251.          */
  252.         @Override
  253.         public Object next() {
  254.             return nextKey();
  255.         }

  256.         /**
  257.          * Searches the next unescaped delimiter from the given position.
  258.          *
  259.          * @param key the key
  260.          * @param pos the start position
  261.          * @param endPos the end position
  262.          * @return the position of the next delimiter or -1 if there is none
  263.          */
  264.         private int nextDelimiterPos(final String key, final int pos, final int endPos) {
  265.             int delimiterPos = pos;
  266.             boolean found = false;

  267.             do {
  268.                 delimiterPos = key.indexOf(getSymbols().getPropertyDelimiter(), delimiterPos);
  269.                 if (delimiterPos < 0 || delimiterPos >= endPos) {
  270.                     return -1;
  271.                 }
  272.                 final int escapePos = escapedPosition(key, delimiterPos);
  273.                 if (escapePos < 0) {
  274.                     found = true;
  275.                 } else {
  276.                     delimiterPos = escapePos;
  277.                 }
  278.             } while (!found);

  279.             return delimiterPos;
  280.         }

  281.         /**
  282.          * Returns the next key part of this configuration key. This is a short form of {@code nextKey(false)}.
  283.          *
  284.          * @return the next key part
  285.          */
  286.         public String nextKey() {
  287.             return nextKey(false);
  288.         }

  289.         /**
  290.          * Returns the next key part of this configuration key. The boolean parameter indicates wheter a decorated key should be
  291.          * returned. This affects only attribute keys: if the parameter is <strong>false</strong>, the attribute markers are stripped from
  292.          * the key; if it is <strong>true</strong>, they remain.
  293.          *
  294.          * @param decorated a flag if the decorated key is to be returned
  295.          * @return the next key part
  296.          */
  297.         public String nextKey(final boolean decorated) {
  298.             if (!hasNext()) {
  299.                 throw new NoSuchElementException("No more key parts!");
  300.             }

  301.             hasIndex = false;
  302.             indexValue = -1;
  303.             final String key = findNextIndices();

  304.             current = key;
  305.             hasIndex = checkIndex(key);
  306.             attribute = checkAttribute(current);

  307.             return currentKey(decorated);
  308.         }

  309.         /**
  310.          * Helper method for extracting the next key part. Takes escaping of delimiter characters into account.
  311.          *
  312.          * @return the next key part
  313.          */
  314.         private String nextKeyPart() {
  315.             int attrIdx = keyBuffer.toString().indexOf(getSymbols().getAttributeStart(), startIndex);
  316.             if (attrIdx < 0 || attrIdx == startIndex) {
  317.                 attrIdx = length();
  318.             }

  319.             int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex, attrIdx);
  320.             if (delIdx < 0) {
  321.                 delIdx = attrIdx;
  322.             }

  323.             endIndex = Math.min(attrIdx, delIdx);
  324.             return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
  325.         }

  326.         /**
  327.          * Removes the current object in the iteration. This method is not supported by this iterator type, so an exception is
  328.          * thrown.
  329.          */
  330.         @Override
  331.         public void remove() {
  332.             throw new UnsupportedOperationException("Remove not supported!");
  333.         }
  334.     }

  335.     /** Constant for the initial StringBuffer size. */
  336.     private static final int INITIAL_SIZE = 32;

  337.     /**
  338.      * Helper method for comparing two key parts.
  339.      *
  340.      * @param it1 the iterator with the first part
  341.      * @param it2 the iterator with the second part
  342.      * @return a flag if both parts are equal
  343.      */
  344.     private static boolean partsEqual(final KeyIterator it1, final KeyIterator it2) {
  345.         return it1.nextKey().equals(it2.nextKey()) && it1.getIndex() == it2.getIndex() && it1.isAttribute() == it2.isAttribute();
  346.     }

  347.     /** Stores a reference to the associated expression engine. */
  348.     private final DefaultExpressionEngine expressionEngine;

  349.     /** Holds a buffer with the so far created key. */
  350.     private final StringBuilder keyBuffer;

  351.     /**
  352.      * Creates a new instance of {@code DefaultConfigurationKey} and sets the associated expression engine.
  353.      *
  354.      * @param engine the expression engine (must not be <strong>null</strong>)
  355.      * @throws IllegalArgumentException if the expression engine is <strong>null</strong>
  356.      */
  357.     public DefaultConfigurationKey(final DefaultExpressionEngine engine) {
  358.         this(engine, null);
  359.     }

  360.     /**
  361.      * Creates a new instance of {@code DefaultConfigurationKey} and sets the associated expression engine and an initial
  362.      * key.
  363.      *
  364.      * @param engine the expression engine (must not be <strong>null</strong>)
  365.      * @param key the key to be wrapped
  366.      * @throws IllegalArgumentException if the expression engine is <strong>null</strong>
  367.      */
  368.     public DefaultConfigurationKey(final DefaultExpressionEngine engine, final String key) {
  369.         if (engine == null) {
  370.             throw new IllegalArgumentException("Expression engine must not be null!");
  371.         }
  372.         expressionEngine = engine;
  373.         if (key != null) {
  374.             keyBuffer = new StringBuilder(trim(key));
  375.         } else {
  376.             keyBuffer = new StringBuilder(INITIAL_SIZE);
  377.         }
  378.     }

  379.     /**
  380.      * Appends the name of a property to this key. If necessary, a property delimiter will be added. Property delimiters in
  381.      * the given string will not be escaped.
  382.      *
  383.      * @param property the name of the property to be added
  384.      * @return a reference to this object
  385.      */
  386.     public DefaultConfigurationKey append(final String property) {
  387.         return append(property, false);
  388.     }

  389.     /**
  390.      * Appends the name of a property to this key. If necessary, a property delimiter will be added. If the boolean argument
  391.      * is set to <strong>true</strong>, property delimiters contained in the property name will be escaped.
  392.      *
  393.      * @param property the name of the property to be added
  394.      * @param escape a flag if property delimiters in the passed in property name should be escaped
  395.      * @return a reference to this object
  396.      */
  397.     public DefaultConfigurationKey append(final String property, final boolean escape) {
  398.         String key;
  399.         if (escape && property != null) {
  400.             key = escapeDelimiters(property);
  401.         } else {
  402.             key = property;
  403.         }
  404.         key = trim(key);

  405.         if (keyBuffer.length() > 0 && !isAttributeKey(property) && !key.isEmpty()) {
  406.             keyBuffer.append(getSymbols().getPropertyDelimiter());
  407.         }

  408.         keyBuffer.append(key);
  409.         return this;
  410.     }

  411.     /**
  412.      * Appends an attribute to this configuration key.
  413.      *
  414.      * @param attr the name of the attribute to be appended
  415.      * @return a reference to this object
  416.      */
  417.     public DefaultConfigurationKey appendAttribute(final String attr) {
  418.         keyBuffer.append(constructAttributeKey(attr));
  419.         return this;
  420.     }

  421.     /**
  422.      * Appends an index to this configuration key.
  423.      *
  424.      * @param index the index to be appended
  425.      * @return a reference to this object
  426.      */
  427.     public DefaultConfigurationKey appendIndex(final int index) {
  428.         keyBuffer.append(getSymbols().getIndexStart());
  429.         keyBuffer.append(index);
  430.         keyBuffer.append(getSymbols().getIndexEnd());
  431.         return this;
  432.     }

  433.     /**
  434.      * Extracts the name of the attribute from the given attribute key. This method removes the attribute markers - if any -
  435.      * from the specified key.
  436.      *
  437.      * @param key the attribute key
  438.      * @return the name of the corresponding attribute
  439.      */
  440.     public String attributeName(final String key) {
  441.         return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
  442.     }

  443.     /**
  444.      * Returns a configuration key object that is initialized with the part of the key that is common to this key and the
  445.      * passed in key.
  446.      *
  447.      * @param other the other key
  448.      * @return a key object with the common key part
  449.      */
  450.     public DefaultConfigurationKey commonKey(final DefaultConfigurationKey other) {
  451.         if (other == null) {
  452.             throw new IllegalArgumentException("Other key must no be null!");
  453.         }

  454.         final DefaultConfigurationKey result = new DefaultConfigurationKey(getExpressionEngine());
  455.         final KeyIterator it1 = iterator();
  456.         final KeyIterator it2 = other.iterator();

  457.         while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2)) {
  458.             if (it1.isAttribute()) {
  459.                 result.appendAttribute(it1.currentKey());
  460.             } else {
  461.                 result.append(it1.currentKey());
  462.                 if (it1.hasIndex) {
  463.                     result.appendIndex(it1.getIndex());
  464.                 }
  465.             }
  466.         }

  467.         return result;
  468.     }

  469.     /**
  470.      * Decorates the given key so that it represents an attribute. Adds special start and end markers. The passed in string
  471.      * will be modified only if does not already represent an attribute.
  472.      *
  473.      * @param key the key to be decorated
  474.      * @return the decorated attribute key
  475.      */
  476.     public String constructAttributeKey(final String key) {
  477.         if (key == null) {
  478.             return StringUtils.EMPTY;
  479.         }
  480.         if (isAttributeKey(key)) {
  481.             return key;
  482.         }
  483.         final StringBuilder buf = new StringBuilder();
  484.         buf.append(getSymbols().getAttributeStart()).append(key);
  485.         if (getSymbols().getAttributeEnd() != null) {
  486.             buf.append(getSymbols().getAttributeEnd());
  487.         }
  488.         return buf.toString();
  489.     }

  490.     /**
  491.      * Returns the &quot;difference key&quot; to a given key. This value is the part of the passed in key that differs from
  492.      * this key. There is the following relation: {@code other = key.commonKey(other) + key.differenceKey(other)} for an
  493.      * arbitrary configuration key {@code key}.
  494.      *
  495.      * @param other the key for which the difference is to be calculated
  496.      * @return the difference key
  497.      */
  498.     public DefaultConfigurationKey differenceKey(final DefaultConfigurationKey other) {
  499.         final DefaultConfigurationKey common = commonKey(other);
  500.         final DefaultConfigurationKey result = new DefaultConfigurationKey(getExpressionEngine());

  501.         if (common.length() < other.length()) {
  502.             final String k = other.toString().substring(common.length());
  503.             // skip trailing delimiters
  504.             int i = 0;
  505.             while (i < k.length() && String.valueOf(k.charAt(i)).equals(getSymbols().getPropertyDelimiter())) {
  506.                 i++;
  507.             }

  508.             if (i < k.length()) {
  509.                 result.append(k.substring(i));
  510.             }
  511.         }

  512.         return result;
  513.     }

  514.     /**
  515.      * Checks if two {@code ConfigurationKey} objects are equal. Two instances of this class are considered equal if they
  516.      * have the same content (i.e. their internal string representation is equal). The expression engine property is not
  517.      * taken into account.
  518.      *
  519.      * @param obj the object to compare
  520.      * @return a flag if both objects are equal
  521.      */
  522.     @Override
  523.     public boolean equals(final Object obj) {
  524.         if (this == obj) {
  525.             return true;
  526.         }
  527.         if (!(obj instanceof DefaultConfigurationKey)) {
  528.             return false;
  529.         }

  530.         final DefaultConfigurationKey c = (DefaultConfigurationKey) obj;
  531.         return keyBuffer.toString().equals(c.toString());
  532.     }

  533.     /**
  534.      * Escapes the delimiters in the specified string.
  535.      *
  536.      * @param key the key to be escaped
  537.      * @return the escaped key
  538.      */
  539.     private String escapeDelimiters(final String key) {
  540.         return getSymbols().getEscapedDelimiter() == null || !key.contains(getSymbols().getPropertyDelimiter()) ? key
  541.             : StringUtils.replace(key, getSymbols().getPropertyDelimiter(), getSymbols().getEscapedDelimiter());
  542.     }

  543.     /**
  544.      * Gets the associated default expression engine.
  545.      *
  546.      * @return the associated expression engine
  547.      */
  548.     public DefaultExpressionEngine getExpressionEngine() {
  549.         return expressionEngine;
  550.     }

  551.     /**
  552.      * Gets the symbols object from the associated expression engine.
  553.      *
  554.      * @return the {@code DefaultExpressionEngineSymbols}
  555.      */
  556.     private DefaultExpressionEngineSymbols getSymbols() {
  557.         return getExpressionEngine().getSymbols();
  558.     }

  559.     /**
  560.      * Returns the hash code for this object.
  561.      *
  562.      * @return the hash code
  563.      */
  564.     @Override
  565.     public int hashCode() {
  566.         return String.valueOf(keyBuffer).hashCode();
  567.     }

  568.     /**
  569.      * Helper method that checks if the specified key starts with a property delimiter.
  570.      *
  571.      * @param key the key to check
  572.      * @return a flag if there is a leading delimiter
  573.      */
  574.     private boolean hasLeadingDelimiter(final String key) {
  575.         return key.startsWith(getSymbols().getPropertyDelimiter())
  576.             && (getSymbols().getEscapedDelimiter() == null || !key.startsWith(getSymbols().getEscapedDelimiter()));
  577.     }

  578.     /**
  579.      * Helper method that checks if the specified key ends with a property delimiter.
  580.      *
  581.      * @param key the key to check
  582.      * @return a flag if there is a trailing delimiter
  583.      */
  584.     private boolean hasTrailingDelimiter(final String key) {
  585.         return key.endsWith(getSymbols().getPropertyDelimiter())
  586.             && (getSymbols().getEscapedDelimiter() == null || !key.endsWith(getSymbols().getEscapedDelimiter()));
  587.     }

  588.     /**
  589.      * Tests if the specified key represents an attribute according to the current expression engine.
  590.      *
  591.      * @param key the key to be checked
  592.      * @return <strong>true</strong> if this is an attribute key, <strong>false</strong> otherwise
  593.      */
  594.     public boolean isAttributeKey(final String key) {
  595.         if (key == null) {
  596.             return false;
  597.         }

  598.         return key.startsWith(getSymbols().getAttributeStart()) && (getSymbols().getAttributeEnd() == null || key.endsWith(getSymbols().getAttributeEnd()));
  599.     }

  600.     /**
  601.      * Returns an iterator for iterating over the single components of this configuration key.
  602.      *
  603.      * @return an iterator for this key
  604.      */
  605.     public KeyIterator iterator() {
  606.         return new KeyIterator();
  607.     }

  608.     /**
  609.      * Returns the actual length of this configuration key.
  610.      *
  611.      * @return the length of this key
  612.      */
  613.     public int length() {
  614.         return keyBuffer.length();
  615.     }

  616.     /**
  617.      * Helper method for removing attribute markers from a key.
  618.      *
  619.      * @param key the key
  620.      * @return the key with removed attribute markers
  621.      */
  622.     private String removeAttributeMarkers(final String key) {
  623.         return key.substring(getSymbols().getAttributeStart().length(),
  624.             key.length() - (getSymbols().getAttributeEnd() != null ? getSymbols().getAttributeEnd().length() : 0));
  625.     }

  626.     /**
  627.      * Sets the new length of this configuration key. With this method it is possible to truncate the key, for example to return to
  628.      * a state prior calling some {@code append()} methods. The semantic is the same as the {@code setLength()} method of
  629.      * {@code StringBuilder}.
  630.      *
  631.      * @param len the new length of the key
  632.      */
  633.     public void setLength(final int len) {
  634.         keyBuffer.setLength(len);
  635.     }

  636.     /**
  637.      * Returns a string representation of this object. This is the configuration key as a plain string.
  638.      *
  639.      * @return a string for this object
  640.      */
  641.     @Override
  642.     public String toString() {
  643.         return keyBuffer.toString();
  644.     }

  645.     /**
  646.      * Removes delimiters at the beginning and the end of the specified key.
  647.      *
  648.      * @param key the key
  649.      * @return the key with removed property delimiters
  650.      */
  651.     public String trim(final String key) {
  652.         return trimRight(trimLeft(key));
  653.     }

  654.     /**
  655.      * Removes leading property delimiters from the specified key.
  656.      *
  657.      * @param key the key
  658.      * @return the key with removed leading property delimiters
  659.      */
  660.     public String trimLeft(final String key) {
  661.         if (key == null) {
  662.             return StringUtils.EMPTY;
  663.         }
  664.         String result = key;
  665.         while (hasLeadingDelimiter(result)) {
  666.             result = result.substring(getSymbols().getPropertyDelimiter().length());
  667.         }
  668.         return result;
  669.     }

  670.     /**
  671.      * Removes trailing property delimiters from the specified key.
  672.      *
  673.      * @param key the key
  674.      * @return the key with removed trailing property delimiters
  675.      */
  676.     public String trimRight(final String key) {
  677.         if (key == null) {
  678.             return StringUtils.EMPTY;
  679.         }
  680.         String result = key;
  681.         while (hasTrailingDelimiter(result)) {
  682.             result = result.substring(0, result.length() - getSymbols().getPropertyDelimiter().length());
  683.         }
  684.         return result;
  685.     }

  686.     /**
  687.      * Unescapes the delimiters in the specified string.
  688.      *
  689.      * @param key the key to be unescaped
  690.      * @return the unescaped key
  691.      */
  692.     private String unescapeDelimiters(final String key) {
  693.         return getSymbols().getEscapedDelimiter() == null ? key
  694.             : StringUtils.replace(key, getSymbols().getEscapedDelimiter(), getSymbols().getPropertyDelimiter());
  695.     }
  696. }