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:
018: package org.apache.commons.configuration;
019:
020: import java.io.Serializable;
021: import java.util.Iterator;
022: import java.util.NoSuchElementException;
023:
024: /**
025: * <p>A simple class that supports creation of and iteration on complex
026: * configuration keys.</p>
027: *
028: * <p>For key creation the class works similar to a StringBuffer: There are
029: * several <code>appendXXXX()</code> methods with which single parts
030: * of a key can be constructed. All these methods return a reference to the
031: * actual object so they can be written in a chain. When using this methods
032: * the exact syntax for keys need not be known.</p>
033: *
034: * <p>This class also defines a specialized iterator for configuration keys.
035: * With such an iterator a key can be tokenized into its single parts. For
036: * each part it can be checked whether it has an associated index.</p>
037: *
038: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
039: * @version $Id: ConfigurationKey.java 439648 2006-09-02 20:42:10Z oheger $
040: */
041: public class ConfigurationKey implements Serializable {
042: /** Constant for a property delimiter.*/
043: public static final char PROPERTY_DELIMITER = '.';
044:
045: /** Constant for an escaped delimiter. */
046: public static final String ESCAPED_DELIMITER = String
047: .valueOf(PROPERTY_DELIMITER)
048: + String.valueOf(PROPERTY_DELIMITER);
049:
050: /** Constant for an attribute start marker.*/
051: private static final String ATTRIBUTE_START = "[@";
052:
053: /** Constant for an attribute end marker.*/
054: private static final String ATTRIBUTE_END = "]";
055:
056: /** Constant for an index start marker.*/
057: private static final char INDEX_START = '(';
058:
059: /** Constant for an index end marker.*/
060: private static final char INDEX_END = ')';
061:
062: /** Constant for the initial StringBuffer size.*/
063: private static final int INITIAL_SIZE = 32;
064:
065: /**
066: * The serial version ID.
067: */
068: private static final long serialVersionUID = -4299732083605277656L;
069:
070: /** Holds a buffer with the so far created key.*/
071: private StringBuffer keyBuffer;
072:
073: /**
074: * Creates a new, empty instance of <code>ConfigurationKey</code>.
075: */
076: public ConfigurationKey() {
077: keyBuffer = new StringBuffer(INITIAL_SIZE);
078: }
079:
080: /**
081: * Creates a new instance of <code>ConfigurationKey</code> and
082: * initializes it with the given key.
083: *
084: * @param key the key as a string
085: */
086: public ConfigurationKey(String key) {
087: keyBuffer = new StringBuffer(key);
088: removeTrailingDelimiter();
089: }
090:
091: /**
092: * Appends the name of a property to this key. If necessary, a
093: * property delimiter will be added.
094: *
095: * @param property the name of the property to be added
096: * @return a reference to this object
097: */
098: public ConfigurationKey append(String property) {
099: if (keyBuffer.length() > 0 && !hasDelimiter()
100: && !isAttributeKey(property)) {
101: keyBuffer.append(PROPERTY_DELIMITER);
102: }
103:
104: keyBuffer.append(property);
105: removeTrailingDelimiter();
106: return this ;
107: }
108:
109: /**
110: * Appends an index to this configuration key.
111: *
112: * @param index the index to be appended
113: * @return a reference to this object
114: */
115: public ConfigurationKey appendIndex(int index) {
116: keyBuffer.append(INDEX_START).append(index);
117: keyBuffer.append(INDEX_END);
118: return this ;
119: }
120:
121: /**
122: * Appends an attribute to this configuration key.
123: *
124: * @param attr the name of the attribute to be appended
125: * @return a reference to this object
126: */
127: public ConfigurationKey appendAttribute(String attr) {
128: keyBuffer.append(constructAttributeKey(attr));
129: return this ;
130: }
131:
132: /**
133: * Checks if this key is an attribute key.
134: *
135: * @return a flag if this key is an attribute key
136: */
137: public boolean isAttributeKey() {
138: return isAttributeKey(keyBuffer.toString());
139: }
140:
141: /**
142: * Checks if the passed in key is an attribute key. Such attribute keys
143: * start and end with certain marker strings. In some cases they must be
144: * treated slightly different.
145: *
146: * @param key the key (part) to be checked
147: * @return a flag if this key is an attribute key
148: */
149: public static boolean isAttributeKey(String key) {
150: return key != null && key.startsWith(ATTRIBUTE_START)
151: && key.endsWith(ATTRIBUTE_END);
152: }
153:
154: /**
155: * Decorates the given key so that it represents an attribute. Adds
156: * special start and end markers.
157: *
158: * @param key the key to be decorated
159: * @return the decorated attribute key
160: */
161: public static String constructAttributeKey(String key) {
162: StringBuffer buf = new StringBuffer();
163: buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
164: return buf.toString();
165: }
166:
167: /**
168: * Extracts the name of the attribute from the given attribute key.
169: * This method removes the attribute markers - if any - from the
170: * specified key.
171: *
172: * @param key the attribute key
173: * @return the name of the corresponding attribute
174: */
175: public static String attributeName(String key) {
176: return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
177: }
178:
179: /**
180: * Helper method for removing attribute markers from a key.
181: *
182: * @param key the key
183: * @return the key with removed attribute markers
184: */
185: static String removeAttributeMarkers(String key) {
186: return key.substring(ATTRIBUTE_START.length(), key.length()
187: - ATTRIBUTE_END.length());
188: }
189:
190: /**
191: * Helper method that checks if the actual buffer ends with a property
192: * delimiter.
193: *
194: * @return a flag if there is a trailing delimiter
195: */
196: private boolean hasDelimiter() {
197: int count = 0;
198: for (int idx = keyBuffer.length() - 1; idx >= 0
199: && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--) {
200: count++;
201: }
202: return count % 2 == 1;
203: }
204:
205: /**
206: * Removes a trailing delimiter if there is any.
207: */
208: private void removeTrailingDelimiter() {
209: while (hasDelimiter()) {
210: keyBuffer.deleteCharAt(keyBuffer.length() - 1);
211: }
212: }
213:
214: /**
215: * Returns a string representation of this object. This is the
216: * configuration key as a plain string.
217: *
218: * @return a string for this object
219: */
220: public String toString() {
221: return keyBuffer.toString();
222: }
223:
224: /**
225: * Returns an iterator for iterating over the single components of
226: * this configuration key.
227: *
228: * @return an iterator for this key
229: */
230: public KeyIterator iterator() {
231: return new KeyIterator();
232: }
233:
234: /**
235: * Returns the actual length of this configuration key.
236: *
237: * @return the length of this key
238: */
239: public int length() {
240: return keyBuffer.length();
241: }
242:
243: /**
244: * Sets the new length of this configuration key. With this method it is
245: * possible to truncate the key, e.g. to return to a state prior calling
246: * some <code>append()</code> methods. The semantic is the same as
247: * the <code>setLength()</code> method of <code>StringBuffer</code>.
248: *
249: * @param len the new length of the key
250: */
251: public void setLength(int len) {
252: keyBuffer.setLength(len);
253: }
254:
255: /**
256: * Checks if two <code>ConfigurationKey</code> objects are equal. The
257: * method can be called with strings or other objects, too.
258: *
259: * @param c the object to compare
260: * @return a flag if both objects are equal
261: */
262: public boolean equals(Object c) {
263: if (c == null) {
264: return false;
265: }
266:
267: return keyBuffer.toString().equals(c.toString());
268: }
269:
270: /**
271: * Returns the hash code for this object.
272: *
273: * @return the hash code
274: */
275: public int hashCode() {
276: return String.valueOf(keyBuffer).hashCode();
277: }
278:
279: /**
280: * Returns a configuration key object that is initialized with the part
281: * of the key that is common to this key and the passed in key.
282: *
283: * @param other the other key
284: * @return a key object with the common key part
285: */
286: public ConfigurationKey commonKey(ConfigurationKey other) {
287: if (other == null) {
288: throw new IllegalArgumentException(
289: "Other key must no be null!");
290: }
291:
292: ConfigurationKey result = new ConfigurationKey();
293: KeyIterator it1 = iterator();
294: KeyIterator it2 = other.iterator();
295:
296: while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2)) {
297: if (it1.isAttribute()) {
298: result.appendAttribute(it1.currentKey());
299: } else {
300: result.append(it1.currentKey());
301: if (it1.hasIndex) {
302: result.appendIndex(it1.getIndex());
303: }
304: }
305: }
306:
307: return result;
308: }
309:
310: /**
311: * Returns the "difference key" to a given key. This value
312: * is the part of the passed in key that differs from this key. There is
313: * the following relation:
314: * <code>other = key.commonKey(other) + key.differenceKey(other)</code>
315: * for an arbitrary configuration key <code>key</code>.
316: *
317: * @param other the key for which the difference is to be calculated
318: * @return the difference key
319: */
320: public ConfigurationKey differenceKey(ConfigurationKey other) {
321: ConfigurationKey common = commonKey(other);
322: ConfigurationKey result = new ConfigurationKey();
323:
324: if (common.length() < other.length()) {
325: String k = other.toString().substring(common.length());
326: // skip trailing delimiters
327: int i = 0;
328: while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER) {
329: i++;
330: }
331:
332: if (i < k.length()) {
333: result.append(k.substring(i));
334: }
335: }
336:
337: return result;
338: }
339:
340: /**
341: * Helper method for comparing two key parts.
342: *
343: * @param it1 the iterator with the first part
344: * @param it2 the iterator with the second part
345: * @return a flag if both parts are equal
346: */
347: private static boolean partsEqual(KeyIterator it1, KeyIterator it2) {
348: return it1.nextKey().equals(it2.nextKey())
349: && it1.getIndex() == it2.getIndex()
350: && it1.isAttribute() == it2.isAttribute();
351: }
352:
353: /**
354: * A specialized iterator class for tokenizing a configuration key.
355: * This class implements the normal iterator interface. In addition it
356: * provides some specific methods for configuration keys.
357: */
358: public class KeyIterator implements Iterator, Cloneable {
359: /** Stores the current key name.*/
360: private String current;
361:
362: /** Stores the start index of the actual token.*/
363: private int startIndex;
364:
365: /** Stores the end index of the actual token.*/
366: private int endIndex;
367:
368: /** Stores the index of the actual property if there is one.*/
369: private int indexValue;
370:
371: /** Stores a flag if the actual property has an index.*/
372: private boolean hasIndex;
373:
374: /** Stores a flag if the actual property is an attribute.*/
375: private boolean attribute;
376:
377: /**
378: * Helper method for determining the next indices.
379: *
380: * @return the next key part
381: */
382: private String findNextIndices() {
383: startIndex = endIndex;
384: // skip empty names
385: while (startIndex < keyBuffer.length()
386: && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER) {
387: startIndex++;
388: }
389:
390: // Key ends with a delimiter?
391: if (startIndex >= keyBuffer.length()) {
392: endIndex = keyBuffer.length();
393: startIndex = endIndex - 1;
394: return keyBuffer.substring(startIndex, endIndex);
395: } else {
396: return nextKeyPart();
397: }
398: }
399:
400: /**
401: * Helper method for extracting the next key part. Takes escaping of
402: * delimiter characters into account.
403: *
404: * @return the next key part
405: */
406: private String nextKeyPart() {
407: StringBuffer key = new StringBuffer(INITIAL_SIZE);
408: int idx = startIndex;
409: int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
410: startIndex);
411: if (endIdx < 0 || endIdx == startIndex) {
412: endIdx = keyBuffer.length();
413: }
414: boolean found = false;
415:
416: while (!found && idx < endIdx) {
417: char c = keyBuffer.charAt(idx);
418: if (c == PROPERTY_DELIMITER) {
419: // a duplicated delimiter means escaping
420: if (idx == endIdx - 1
421: || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER) {
422: found = true;
423: } else {
424: idx++;
425: }
426: }
427: if (!found) {
428: key.append(c);
429: idx++;
430: }
431: }
432:
433: endIndex = idx;
434: return key.toString();
435: }
436:
437: /**
438: * Returns the next key part of this configuration key. This is a short
439: * form of <code>nextKey(false)</code>.
440: *
441: * @return the next key part
442: */
443: public String nextKey() {
444: return nextKey(false);
445: }
446:
447: /**
448: * Returns the next key part of this configuration key. The boolean
449: * parameter indicates wheter a decorated key should be returned. This
450: * affects only attribute keys: if the parameter is <b>false</b>, the
451: * attribute markers are stripped from the key; if it is <b>true</b>,
452: * they remain.
453: *
454: * @param decorated a flag if the decorated key is to be returned
455: * @return the next key part
456: */
457: public String nextKey(boolean decorated) {
458: if (!hasNext()) {
459: throw new NoSuchElementException("No more key parts!");
460: }
461:
462: hasIndex = false;
463: indexValue = -1;
464: String key = findNextIndices();
465:
466: current = key;
467: hasIndex = checkIndex(key);
468: attribute = checkAttribute(current);
469:
470: return currentKey(decorated);
471: }
472:
473: /**
474: * Helper method for checking if the passed key is an attribute.
475: * If this is the case, the internal fields will be set.
476: *
477: * @param key the key to be checked
478: * @return a flag if the key is an attribute
479: */
480: private boolean checkAttribute(String key) {
481: if (isAttributeKey(key)) {
482: current = removeAttributeMarkers(key);
483: return true;
484: } else {
485: return false;
486: }
487: }
488:
489: /**
490: * Helper method for checking if the passed key contains an index.
491: * If this is the case, internal fields will be set.
492: *
493: * @param key the key to be checked
494: * @return a flag if an index is defined
495: */
496: private boolean checkIndex(String key) {
497: boolean result = false;
498:
499: int idx = key.lastIndexOf(INDEX_START);
500: if (idx > 0) {
501: int endidx = key.indexOf(INDEX_END, idx);
502:
503: if (endidx > idx + 1) {
504: indexValue = Integer.parseInt(key.substring(
505: idx + 1, endidx));
506: current = key.substring(0, idx);
507: result = true;
508: }
509: }
510:
511: return result;
512: }
513:
514: /**
515: * Checks if there is a next element.
516: *
517: * @return a flag if there is a next element
518: */
519: public boolean hasNext() {
520: return endIndex < keyBuffer.length();
521: }
522:
523: /**
524: * Returns the next object in the iteration.
525: *
526: * @return the next object
527: */
528: public Object next() {
529: return nextKey();
530: }
531:
532: /**
533: * Removes the current object in the iteration. This method is not
534: * supported by this iterator type, so an exception is thrown.
535: */
536: public void remove() {
537: throw new UnsupportedOperationException(
538: "Remove not supported!");
539: }
540:
541: /**
542: * Returns the current key of the iteration (without skipping to the
543: * next element). This is the same key the previous <code>next()</code>
544: * call had returned. (Short form of <code>currentKey(false)</code>.
545: *
546: * @return the current key
547: */
548: public String currentKey() {
549: return currentKey(false);
550: }
551:
552: /**
553: * Returns the current key of the iteration (without skipping to the
554: * next element). The boolean parameter indicates wheter a decorated
555: * key should be returned. This affects only attribute keys: if the
556: * parameter is <b>false</b>, the attribute markers are stripped from
557: * the key; if it is <b>true</b>, they remain.
558: *
559: * @param decorated a flag if the decorated key is to be returned
560: * @return the current key
561: */
562: public String currentKey(boolean decorated) {
563: return (decorated && isAttribute()) ? constructAttributeKey(current)
564: : current;
565: }
566:
567: /**
568: * Returns a flag if the current key is an attribute. This method can
569: * be called after <code>next()</code>.
570: *
571: * @return a flag if the current key is an attribute
572: */
573: public boolean isAttribute() {
574: return attribute;
575: }
576:
577: /**
578: * Returns the index value of the current key. If the current key does
579: * not have an index, return value is -1. This method can be called
580: * after <code>next()</code>.
581: *
582: * @return the index value of the current key
583: */
584: public int getIndex() {
585: return indexValue;
586: }
587:
588: /**
589: * Returns a flag if the current key has an associated index.
590: * This method can be called after <code>next()</code>.
591: *
592: * @return a flag if the current key has an index
593: */
594: public boolean hasIndex() {
595: return hasIndex;
596: }
597:
598: /**
599: * Creates a clone of this object.
600: *
601: * @return a clone of this object
602: */
603: public Object clone() {
604: try {
605: return super .clone();
606: } catch (CloneNotSupportedException cex) {
607: // should not happen
608: return null;
609: }
610: }
611: }
612: }
|