Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.configuration; import java.io.Serializable; import java.util.Iterator; import java.util.NoSuchElementException; /** * <p>A simple class that supports creation of and iteration on complex * configuration keys.</p> * * <p>For key creation the class works similar to a StringBuffer: There are * several <code>appendXXXX()</code> methods with which single parts * of a key can be constructed. All these methods return a reference to the * actual object so they can be written in a chain. When using this methods * the exact syntax for keys need not be known.</p> * * <p>This class also defines a specialized iterator for configuration keys. * With such an iterator a key can be tokenized into its single parts. For * each part it can be checked whether it has an associated index.</p> * * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a> * @version $Id: ConfigurationKey.java 726809 2008-12-15 21:29:56Z oheger $ */ public class ConfigurationKey implements Serializable { /** Constant for a property delimiter.*/ public static final char PROPERTY_DELIMITER = '.'; /** Constant for an escaped delimiter. */ public static final String ESCAPED_DELIMITER = String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER); /** Constant for an attribute start marker.*/ private static final String ATTRIBUTE_START = "[@"; /** Constant for an attribute end marker.*/ private static final String ATTRIBUTE_END = "]"; /** Constant for an index start marker.*/ private static final char INDEX_START = '('; /** Constant for an index end marker.*/ private static final char INDEX_END = ')'; /** Constant for the initial StringBuffer size.*/ private static final int INITIAL_SIZE = 32; /** * The serial version ID. */ private static final long serialVersionUID = -4299732083605277656L; /** Holds a buffer with the so far created key.*/ private StringBuffer keyBuffer; /** * Creates a new, empty instance of <code>ConfigurationKey</code>. */ public ConfigurationKey() { keyBuffer = new StringBuffer(INITIAL_SIZE); } /** * Creates a new instance of <code>ConfigurationKey</code> and * initializes it with the given key. * * @param key the key as a string */ public ConfigurationKey(String key) { keyBuffer = new StringBuffer(key); removeTrailingDelimiter(); } /** * Appends the name of a property to this key. If necessary, a * property delimiter will be added. * * @param property the name of the property to be added * @return a reference to this object */ public ConfigurationKey append(String property) { if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property)) { keyBuffer.append(PROPERTY_DELIMITER); } keyBuffer.append(property); removeTrailingDelimiter(); return this; } /** * Appends an index to this configuration key. * * @param index the index to be appended * @return a reference to this object */ public ConfigurationKey appendIndex(int index) { keyBuffer.append(INDEX_START).append(index); keyBuffer.append(INDEX_END); return this; } /** * Appends an attribute to this configuration key. * * @param attr the name of the attribute to be appended * @return a reference to this object */ public ConfigurationKey appendAttribute(String attr) { keyBuffer.append(constructAttributeKey(attr)); return this; } /** * Checks if this key is an attribute key. * * @return a flag if this key is an attribute key */ public boolean isAttributeKey() { return isAttributeKey(keyBuffer.toString()); } /** * Checks if the passed in key is an attribute key. Such attribute keys * start and end with certain marker strings. In some cases they must be * treated slightly different. * * @param key the key (part) to be checked * @return a flag if this key is an attribute key */ public static boolean isAttributeKey(String key) { return key != null && key.startsWith(ATTRIBUTE_START) && key.endsWith(ATTRIBUTE_END); } /** * Decorates the given key so that it represents an attribute. Adds * special start and end markers. * * @param key the key to be decorated * @return the decorated attribute key */ public static String constructAttributeKey(String key) { StringBuffer buf = new StringBuffer(); buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END); return buf.toString(); } /** * Extracts the name of the attribute from the given attribute key. * This method removes the attribute markers - if any - from the * specified key. * * @param key the attribute key * @return the name of the corresponding attribute */ public static String attributeName(String key) { return isAttributeKey(key) ? removeAttributeMarkers(key) : key; } /** * Helper method for removing attribute markers from a key. * * @param key the key * @return the key with removed attribute markers */ static String removeAttributeMarkers(String key) { return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length()); } /** * Helper method that checks if the actual buffer ends with a property * delimiter. * * @return a flag if there is a trailing delimiter */ private boolean hasDelimiter() { int count = 0; for (int idx = keyBuffer.length() - 1; idx >= 0 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--) { count++; } return count % 2 != 0; } /** * Removes a trailing delimiter if there is any. */ private void removeTrailingDelimiter() { while (hasDelimiter()) { keyBuffer.deleteCharAt(keyBuffer.length() - 1); } } /** * Returns a string representation of this object. This is the * configuration key as a plain string. * * @return a string for this object */ public String toString() { return keyBuffer.toString(); } /** * Returns an iterator for iterating over the single components of * this configuration key. * * @return an iterator for this key */ public KeyIterator iterator() { return new KeyIterator(); } /** * Returns the actual length of this configuration key. * * @return the length of this key */ public int length() { return keyBuffer.length(); } /** * Sets the new length of this configuration key. With this method it is * possible to truncate the key, e.g. to return to a state prior calling * some <code>append()</code> methods. The semantic is the same as * the <code>setLength()</code> method of <code>StringBuffer</code>. * * @param len the new length of the key */ public void setLength(int len) { keyBuffer.setLength(len); } /** * Checks if two <code>ConfigurationKey</code> objects are equal. The * method can be called with strings or other objects, too. * * @param c the object to compare * @return a flag if both objects are equal */ public boolean equals(Object c) { if (c == null) { return false; } return keyBuffer.toString().equals(c.toString()); } /** * Returns the hash code for this object. * * @return the hash code */ public int hashCode() { return String.valueOf(keyBuffer).hashCode(); } /** * Returns a configuration key object that is initialized with the part * of the key that is common to this key and the passed in key. * * @param other the other key * @return a key object with the common key part */ public ConfigurationKey commonKey(ConfigurationKey other) { if (other == null) { throw new IllegalArgumentException("Other key must no be null!"); } ConfigurationKey result = new ConfigurationKey(); KeyIterator it1 = iterator(); KeyIterator it2 = other.iterator(); while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2)) { if (it1.isAttribute()) { result.appendAttribute(it1.currentKey()); } else { result.append(it1.currentKey()); if (it1.hasIndex) { result.appendIndex(it1.getIndex()); } } } return result; } /** * Returns the "difference key" to a given key. This value * is the part of the passed in key that differs from this key. There is * the following relation: * <code>other = key.commonKey(other) + key.differenceKey(other)</code> * for an arbitrary configuration key <code>key</code>. * * @param other the key for which the difference is to be calculated * @return the difference key */ public ConfigurationKey differenceKey(ConfigurationKey other) { ConfigurationKey common = commonKey(other); ConfigurationKey result = new ConfigurationKey(); if (common.length() < other.length()) { String k = other.toString().substring(common.length()); // skip trailing delimiters int i = 0; while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER) { i++; } if (i < k.length()) { result.append(k.substring(i)); } } return result; } /** * Helper method for comparing two key parts. * * @param it1 the iterator with the first part * @param it2 the iterator with the second part * @return a flag if both parts are equal */ private static boolean partsEqual(KeyIterator it1, KeyIterator it2) { return it1.nextKey().equals(it2.nextKey()) && it1.getIndex() == it2.getIndex() && it1.isAttribute() == it2.isAttribute(); } /** * A specialized iterator class for tokenizing a configuration key. * This class implements the normal iterator interface. In addition it * provides some specific methods for configuration keys. */ public class KeyIterator implements Iterator, Cloneable { /** Stores the current key name.*/ private String current; /** Stores the start index of the actual token.*/ private int startIndex; /** Stores the end index of the actual token.*/ private int endIndex; /** Stores the index of the actual property if there is one.*/ private int indexValue; /** Stores a flag if the actual property has an index.*/ private boolean hasIndex; /** Stores a flag if the actual property is an attribute.*/ private boolean attribute; /** * Helper method for determining the next indices. * * @return the next key part */ private String findNextIndices() { startIndex = endIndex; // skip empty names while (startIndex < keyBuffer.length() && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER) { startIndex++; } // Key ends with a delimiter? if (startIndex >= keyBuffer.length()) { endIndex = keyBuffer.length(); startIndex = endIndex - 1; return keyBuffer.substring(startIndex, endIndex); } else { return nextKeyPart(); } } /** * Helper method for extracting the next key part. Takes escaping of * delimiter characters into account. * * @return the next key part */ private String nextKeyPart() { StringBuffer key = new StringBuffer(INITIAL_SIZE); int idx = startIndex; int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START, startIndex); if (endIdx < 0 || endIdx == startIndex) { endIdx = keyBuffer.length(); } boolean found = false; while (!found && idx < endIdx) { char c = keyBuffer.charAt(idx); if (c == PROPERTY_DELIMITER) { // a duplicated delimiter means escaping if (idx == endIdx - 1 || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER) { found = true; } else { idx++; } } if (!found) { key.append(c); idx++; } } endIndex = idx; return key.toString(); } /** * Returns the next key part of this configuration key. This is a short * form of <code>nextKey(false)</code>. * * @return the next key part */ public String nextKey() { return nextKey(false); } /** * Returns the next key part of this configuration key. The boolean * parameter indicates wheter a decorated key should be returned. This * affects only attribute keys: if the parameter is <b>false</b>, the * attribute markers are stripped from the key; if it is <b>true</b>, * they remain. * * @param decorated a flag if the decorated key is to be returned * @return the next key part */ public String nextKey(boolean decorated) { if (!hasNext()) { throw new NoSuchElementException("No more key parts!"); } hasIndex = false; indexValue = -1; String key = findNextIndices(); current = key; hasIndex = checkIndex(key); attribute = checkAttribute(current); return currentKey(decorated); } /** * Helper method for checking if the passed key is an attribute. * If this is the case, the internal fields will be set. * * @param key the key to be checked * @return a flag if the key is an attribute */ private boolean checkAttribute(String key) { if (isAttributeKey(key)) { current = removeAttributeMarkers(key); return true; } else { return false; } } /** * Helper method for checking if the passed key contains an index. * If this is the case, internal fields will be set. * * @param key the key to be checked * @return a flag if an index is defined */ private boolean checkIndex(String key) { boolean result = false; int idx = key.lastIndexOf(INDEX_START); if (idx > 0) { int endidx = key.indexOf(INDEX_END, idx); if (endidx > idx + 1) { indexValue = Integer.parseInt(key.substring(idx + 1, endidx)); current = key.substring(0, idx); result = true; } } return result; } /** * Checks if there is a next element. * * @return a flag if there is a next element */ public boolean hasNext() { return endIndex < keyBuffer.length(); } /** * Returns the next object in the iteration. * * @return the next object */ public Object next() { return nextKey(); } /** * Removes the current object in the iteration. This method is not * supported by this iterator type, so an exception is thrown. */ public void remove() { throw new UnsupportedOperationException("Remove not supported!"); } /** * Returns the current key of the iteration (without skipping to the * next element). This is the same key the previous <code>next()</code> * call had returned. (Short form of <code>currentKey(false)</code>. * * @return the current key */ public String currentKey() { return currentKey(false); } /** * Returns the current key of the iteration (without skipping to the * next element). The boolean parameter indicates wheter a decorated * key should be returned. This affects only attribute keys: if the * parameter is <b>false</b>, the attribute markers are stripped from * the key; if it is <b>true</b>, they remain. * * @param decorated a flag if the decorated key is to be returned * @return the current key */ public String currentKey(boolean decorated) { return (decorated && isAttribute()) ? constructAttributeKey(current) : current; } /** * Returns a flag if the current key is an attribute. This method can * be called after <code>next()</code>. * * @return a flag if the current key is an attribute */ public boolean isAttribute() { return attribute; } /** * Returns the index value of the current key. If the current key does * not have an index, return value is -1. This method can be called * after <code>next()</code>. * * @return the index value of the current key */ public int getIndex() { return indexValue; } /** * Returns a flag if the current key has an associated index. * This method can be called after <code>next()</code>. * * @return a flag if the current key has an index */ public boolean hasIndex() { return hasIndex; } /** * Creates a clone of this object. * * @return a clone of this object */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException cex) { // should not happen return null; } } } }