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: package org.apache.commons.configuration.tree;
018:
019: import java.util.Iterator;
020: import java.util.NoSuchElementException;
021:
022: import org.apache.commons.lang.StringUtils;
023:
024: /**
025: * <p>
026: * A simple class that supports creation of and iteration on configuration keys
027: * supported by a <code>{@link DefaultExpressionEngine}</code> object.
028: * </p>
029: * <p>
030: * For key creation the class works similar to a StringBuffer: There are several
031: * <code>appendXXXX()</code> methods with which single parts of a key can be
032: * constructed. All these methods return a reference to the actual object so
033: * they can be written in a chain. When using this methods the exact syntax for
034: * keys need not be known.
035: * </p>
036: * <p>
037: * This class also defines a specialized iterator for configuration keys. With
038: * such an iterator a key can be tokenized into its single parts. For each part
039: * it can be checked whether it has an associated index.
040: * </p>
041: * <p>
042: * Instances of this class are always associated with an instance of
043: * <code>{@link DefaultExpressionEngine}</code>, from which the current
044: * delimiters are obtained. So key creation and parsing is specific to this
045: * associated expression engine.
046: * </p>
047: *
048: * @since 1.3
049: * @author Oliver Heger
050: * @version $Id: DefaultConfigurationKey.java 439648 2006-09-02 20:42:10Z oheger $
051: */
052: public class DefaultConfigurationKey {
053: /** Constant for the initial StringBuffer size. */
054: private static final int INITIAL_SIZE = 32;
055:
056: /** Stores a reference to the associated expression engine. */
057: private DefaultExpressionEngine expressionEngine;
058:
059: /** Holds a buffer with the so far created key. */
060: private StringBuffer keyBuffer;
061:
062: /**
063: * Creates a new instance of <code>DefaultConfigurationKey</code> and sets
064: * the associated expression engine.
065: *
066: * @param engine the expression engine
067: */
068: public DefaultConfigurationKey(DefaultExpressionEngine engine) {
069: keyBuffer = new StringBuffer(INITIAL_SIZE);
070: setExpressionEngine(engine);
071: }
072:
073: /**
074: * Creates a new instance of <code>DefaultConfigurationKey</code> and sets
075: * the associated expression engine and an initial key.
076: *
077: * @param engine the expression engine
078: * @param key the key to be wrapped
079: */
080: public DefaultConfigurationKey(DefaultExpressionEngine engine,
081: String key) {
082: setExpressionEngine(engine);
083: keyBuffer = new StringBuffer(trim(key));
084: }
085:
086: /**
087: * Returns the associated default expression engine.
088: *
089: * @return the associated expression engine
090: */
091: public DefaultExpressionEngine getExpressionEngine() {
092: return expressionEngine;
093: }
094:
095: /**
096: * Sets the associated expression engine.
097: *
098: * @param expressionEngine the expression engine (must not be <b>null</b>)
099: */
100: public void setExpressionEngine(
101: DefaultExpressionEngine expressionEngine) {
102: if (expressionEngine == null) {
103: throw new IllegalArgumentException(
104: "Expression engine must not be null!");
105: }
106: this .expressionEngine = expressionEngine;
107: }
108:
109: /**
110: * Appends the name of a property to this key. If necessary, a property
111: * delimiter will be added. If the boolean argument is set to <b>true</b>,
112: * property delimiters contained in the property name will be escaped.
113: *
114: * @param property the name of the property to be added
115: * @param escape a flag if property delimiters in the passed in property name
116: * should be escaped
117: * @return a reference to this object
118: */
119: public DefaultConfigurationKey append(String property,
120: boolean escape) {
121: String key;
122: if (escape && property != null) {
123: key = escapeDelimiters(property);
124: } else {
125: key = property;
126: }
127: key = trim(key);
128:
129: if (keyBuffer.length() > 0 && !isAttributeKey(property)
130: && key.length() > 0) {
131: keyBuffer.append(getExpressionEngine()
132: .getPropertyDelimiter());
133: }
134:
135: keyBuffer.append(key);
136: return this ;
137: }
138:
139: /**
140: * Appends the name of a property to this key. If necessary, a property
141: * delimiter will be added. Property delimiters in the given string will not
142: * be escaped.
143: *
144: * @param property the name of the property to be added
145: * @return a reference to this object
146: */
147: public DefaultConfigurationKey append(String property) {
148: return append(property, false);
149: }
150:
151: /**
152: * Appends an index to this configuration key.
153: *
154: * @param index the index to be appended
155: * @return a reference to this object
156: */
157: public DefaultConfigurationKey appendIndex(int index) {
158: keyBuffer.append(getExpressionEngine().getIndexStart());
159: keyBuffer.append(index);
160: keyBuffer.append(getExpressionEngine().getIndexEnd());
161: return this ;
162: }
163:
164: /**
165: * Appends an attribute to this configuration key.
166: *
167: * @param attr the name of the attribute to be appended
168: * @return a reference to this object
169: */
170: public DefaultConfigurationKey appendAttribute(String attr) {
171: keyBuffer.append(constructAttributeKey(attr));
172: return this ;
173: }
174:
175: /**
176: * Returns the actual length of this configuration key.
177: *
178: * @return the length of this key
179: */
180: public int length() {
181: return keyBuffer.length();
182: }
183:
184: /**
185: * Sets the new length of this configuration key. With this method it is
186: * possible to truncate the key, e.g. to return to a state prior calling
187: * some <code>append()</code> methods. The semantic is the same as the
188: * <code>setLength()</code> method of <code>StringBuffer</code>.
189: *
190: * @param len the new length of the key
191: */
192: public void setLength(int len) {
193: keyBuffer.setLength(len);
194: }
195:
196: /**
197: * Checks if two <code>ConfigurationKey</code> objects are equal. The
198: * method can be called with strings or other objects, too.
199: *
200: * @param c the object to compare
201: * @return a flag if both objects are equal
202: */
203: public boolean equals(Object c) {
204: if (c == null) {
205: return false;
206: }
207:
208: return keyBuffer.toString().equals(c.toString());
209: }
210:
211: /**
212: * Returns the hash code for this object.
213: *
214: * @return the hash code
215: */
216: public int hashCode() {
217: return String.valueOf(keyBuffer).hashCode();
218: }
219:
220: /**
221: * Returns a string representation of this object. This is the configuration
222: * key as a plain string.
223: *
224: * @return a string for this object
225: */
226: public String toString() {
227: return keyBuffer.toString();
228: }
229:
230: /**
231: * Tests if the specified key represents an attribute according to the
232: * current expression engine.
233: *
234: * @param key the key to be checked
235: * @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
236: */
237: public boolean isAttributeKey(String key) {
238: if (key == null) {
239: return false;
240: }
241:
242: return key
243: .startsWith(getExpressionEngine().getAttributeStart())
244: && (getExpressionEngine().getAttributeEnd() == null || key
245: .endsWith(getExpressionEngine()
246: .getAttributeEnd()));
247: }
248:
249: /**
250: * Decorates the given key so that it represents an attribute. Adds special
251: * start and end markers. The passed in string will be modified only if does
252: * not already represent an attribute.
253: *
254: * @param key the key to be decorated
255: * @return the decorated attribute key
256: */
257: public String constructAttributeKey(String key) {
258: if (key == null) {
259: return StringUtils.EMPTY;
260: }
261: if (isAttributeKey(key)) {
262: return key;
263: } else {
264: StringBuffer buf = new StringBuffer();
265: buf.append(getExpressionEngine().getAttributeStart())
266: .append(key);
267: if (getExpressionEngine().getAttributeEnd() != null) {
268: buf.append(getExpressionEngine().getAttributeEnd());
269: }
270: return buf.toString();
271: }
272: }
273:
274: /**
275: * Extracts the name of the attribute from the given attribute key. This
276: * method removes the attribute markers - if any - from the specified key.
277: *
278: * @param key the attribute key
279: * @return the name of the corresponding attribute
280: */
281: public String attributeName(String key) {
282: return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
283: }
284:
285: /**
286: * Removes leading property delimiters from the specified key.
287: *
288: * @param key the key
289: * @return the key with removed leading property delimiters
290: */
291: public String trimLeft(String key) {
292: if (key == null) {
293: return StringUtils.EMPTY;
294: } else {
295: String result = key;
296: while (hasLeadingDelimiter(result)) {
297: result = result.substring(getExpressionEngine()
298: .getPropertyDelimiter().length());
299: }
300: return result;
301: }
302: }
303:
304: /**
305: * Removes trailing property delimiters from the specified key.
306: *
307: * @param key the key
308: * @return the key with removed trailing property delimiters
309: */
310: public String trimRight(String key) {
311: if (key == null) {
312: return StringUtils.EMPTY;
313: } else {
314: String result = key;
315: while (hasTrailingDelimiter(result)) {
316: result = result.substring(0, result.length()
317: - getExpressionEngine().getPropertyDelimiter()
318: .length());
319: }
320: return result;
321: }
322: }
323:
324: /**
325: * Removes delimiters at the beginning and the end of the specified key.
326: *
327: * @param key the key
328: * @return the key with removed property delimiters
329: */
330: public String trim(String key) {
331: return trimRight(trimLeft(key));
332: }
333:
334: /**
335: * Returns an iterator for iterating over the single components of this
336: * configuration key.
337: *
338: * @return an iterator for this key
339: */
340: public KeyIterator iterator() {
341: return new KeyIterator();
342: }
343:
344: /**
345: * Helper method that checks if the specified key ends with a property
346: * delimiter.
347: *
348: * @param key the key to check
349: * @return a flag if there is a trailing delimiter
350: */
351: private boolean hasTrailingDelimiter(String key) {
352: return key.endsWith(getExpressionEngine()
353: .getPropertyDelimiter())
354: && (getExpressionEngine().getEscapedDelimiter() == null || !key
355: .endsWith(getExpressionEngine()
356: .getEscapedDelimiter()));
357: }
358:
359: /**
360: * Helper method that checks if the specified key starts with a property
361: * delimiter.
362: *
363: * @param key the key to check
364: * @return a flag if there is a leading delimiter
365: */
366: private boolean hasLeadingDelimiter(String key) {
367: return key.startsWith(getExpressionEngine()
368: .getPropertyDelimiter())
369: && (getExpressionEngine().getEscapedDelimiter() == null || !key
370: .startsWith(getExpressionEngine()
371: .getEscapedDelimiter()));
372: }
373:
374: /**
375: * Helper method for removing attribute markers from a key.
376: *
377: * @param key the key
378: * @return the key with removed attribute markers
379: */
380: private String removeAttributeMarkers(String key) {
381: return key
382: .substring(
383: getExpressionEngine().getAttributeStart()
384: .length(),
385: key.length()
386: - ((getExpressionEngine()
387: .getAttributeEnd() != null) ? getExpressionEngine()
388: .getAttributeEnd().length()
389: : 0));
390: }
391:
392: /**
393: * Unescapes the delimiters in the specified string.
394: *
395: * @param key the key to be unescaped
396: * @return the unescaped key
397: */
398: private String unescapeDelimiters(String key) {
399: return (getExpressionEngine().getEscapedDelimiter() == null) ? key
400: : StringUtils.replace(key, getExpressionEngine()
401: .getEscapedDelimiter(), getExpressionEngine()
402: .getPropertyDelimiter());
403: }
404:
405: /**
406: * Escapes the delimiters in the specified string.
407: *
408: * @param key the key to be escaped
409: * @return the escaped key
410: */
411: private String escapeDelimiters(String key) {
412: return (getExpressionEngine().getEscapedDelimiter() == null || key
413: .indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key
414: : StringUtils.replace(key, getExpressionEngine()
415: .getPropertyDelimiter(), getExpressionEngine()
416: .getEscapedDelimiter());
417: }
418:
419: /**
420: * A specialized iterator class for tokenizing a configuration key. This
421: * class implements the normal iterator interface. In addition it provides
422: * some specific methods for configuration keys.
423: */
424: public class KeyIterator implements Iterator, Cloneable {
425: /** Stores the current key name. */
426: private String current;
427:
428: /** Stores the start index of the actual token. */
429: private int startIndex;
430:
431: /** Stores the end index of the actual token. */
432: private int endIndex;
433:
434: /** Stores the index of the actual property if there is one. */
435: private int indexValue;
436:
437: /** Stores a flag if the actual property has an index. */
438: private boolean hasIndex;
439:
440: /** Stores a flag if the actual property is an attribute. */
441: private boolean attribute;
442:
443: /**
444: * Returns the next key part of this configuration key. This is a short
445: * form of <code>nextKey(false)</code>.
446: *
447: * @return the next key part
448: */
449: public String nextKey() {
450: return nextKey(false);
451: }
452:
453: /**
454: * Returns the next key part of this configuration key. The boolean
455: * parameter indicates wheter a decorated key should be returned. This
456: * affects only attribute keys: if the parameter is <b>false</b>, the
457: * attribute markers are stripped from the key; if it is <b>true</b>,
458: * they remain.
459: *
460: * @param decorated a flag if the decorated key is to be returned
461: * @return the next key part
462: */
463: public String nextKey(boolean decorated) {
464: if (!hasNext()) {
465: throw new NoSuchElementException("No more key parts!");
466: }
467:
468: hasIndex = false;
469: indexValue = -1;
470: String key = findNextIndices();
471:
472: current = key;
473: hasIndex = checkIndex(key);
474: attribute = checkAttribute(current);
475:
476: return currentKey(decorated);
477: }
478:
479: /**
480: * Checks if there is a next element.
481: *
482: * @return a flag if there is a next element
483: */
484: public boolean hasNext() {
485: return endIndex < keyBuffer.length();
486: }
487:
488: /**
489: * Returns the next object in the iteration.
490: *
491: * @return the next object
492: */
493: public Object next() {
494: return nextKey();
495: }
496:
497: /**
498: * Removes the current object in the iteration. This method is not
499: * supported by this iterator type, so an exception is thrown.
500: */
501: public void remove() {
502: throw new UnsupportedOperationException(
503: "Remove not supported!");
504: }
505:
506: /**
507: * Returns the current key of the iteration (without skipping to the
508: * next element). This is the same key the previous <code>next()</code>
509: * call had returned. (Short form of <code>currentKey(false)</code>.
510: *
511: * @return the current key
512: */
513: public String currentKey() {
514: return currentKey(false);
515: }
516:
517: /**
518: * Returns the current key of the iteration (without skipping to the
519: * next element). The boolean parameter indicates wheter a decorated key
520: * should be returned. This affects only attribute keys: if the
521: * parameter is <b>false</b>, the attribute markers are stripped from
522: * the key; if it is <b>true</b>, they remain.
523: *
524: * @param decorated a flag if the decorated key is to be returned
525: * @return the current key
526: */
527: public String currentKey(boolean decorated) {
528: return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
529: : current;
530: }
531:
532: /**
533: * Returns a flag if the current key is an attribute. This method can be
534: * called after <code>next()</code>.
535: *
536: * @return a flag if the current key is an attribute
537: */
538: public boolean isAttribute() {
539: // if attribute emulation mode is active, the last part of a key is
540: // always an attribute key, too
541: return attribute
542: || (isAttributeEmulatingMode() && !hasNext());
543: }
544:
545: /**
546: * Returns a flag whether the current key refers to a property (i.e. is
547: * no special attribute key). Usually this method will return the
548: * opposite of <code>isAttribute()</code>, but if the delimiters for
549: * normal properties and attributes are set to the same string, it is
550: * possible that both methods return <b>true</b>.
551: *
552: * @return a flag if the current key is a property key
553: * @see #isAttribute()
554: */
555: public boolean isPropertyKey() {
556: return !attribute;
557: }
558:
559: /**
560: * Returns the index value of the current key. If the current key does
561: * not have an index, return value is -1. This method can be called
562: * after <code>next()</code>.
563: *
564: * @return the index value of the current key
565: */
566: public int getIndex() {
567: return indexValue;
568: }
569:
570: /**
571: * Returns a flag if the current key has an associated index. This
572: * method can be called after <code>next()</code>.
573: *
574: * @return a flag if the current key has an index
575: */
576: public boolean hasIndex() {
577: return hasIndex;
578: }
579:
580: /**
581: * Creates a clone of this object.
582: *
583: * @return a clone of this object
584: */
585: public Object clone() {
586: try {
587: return super .clone();
588: } catch (CloneNotSupportedException cex) {
589: // should not happen
590: return null;
591: }
592: }
593:
594: /**
595: * Helper method for determining the next indices.
596: *
597: * @return the next key part
598: */
599: private String findNextIndices() {
600: startIndex = endIndex;
601: // skip empty names
602: while (startIndex < length()
603: && hasLeadingDelimiter(keyBuffer
604: .substring(startIndex))) {
605: startIndex += getExpressionEngine()
606: .getPropertyDelimiter().length();
607: }
608:
609: // Key ends with a delimiter?
610: if (startIndex >= length()) {
611: endIndex = length();
612: startIndex = endIndex - 1;
613: return keyBuffer.substring(startIndex, endIndex);
614: } else {
615: return nextKeyPart();
616: }
617: }
618:
619: /**
620: * Helper method for extracting the next key part. Takes escaping of
621: * delimiter characters into account.
622: *
623: * @return the next key part
624: */
625: private String nextKeyPart() {
626: int attrIdx = keyBuffer.toString().indexOf(
627: getExpressionEngine().getAttributeStart(),
628: startIndex);
629: if (attrIdx < 0 || attrIdx == startIndex) {
630: attrIdx = length();
631: }
632:
633: int delIdx = nextDelimiterPos(keyBuffer.toString(),
634: startIndex, attrIdx);
635: if (delIdx < 0) {
636: delIdx = attrIdx;
637: }
638:
639: endIndex = Math.min(attrIdx, delIdx);
640: return unescapeDelimiters(keyBuffer.substring(startIndex,
641: endIndex));
642: }
643:
644: /**
645: * Searches the next unescaped delimiter from the given position.
646: *
647: * @param key the key
648: * @param pos the start position
649: * @param endPos the end position
650: * @return the position of the next delimiter or -1 if there is none
651: */
652: private int nextDelimiterPos(String key, int pos, int endPos) {
653: int delimiterPos = pos;
654: boolean found = false;
655:
656: do {
657: delimiterPos = key.indexOf(getExpressionEngine()
658: .getPropertyDelimiter(), delimiterPos);
659: if (delimiterPos < 0 || delimiterPos >= endPos) {
660: return -1;
661: }
662: int escapePos = escapedPosition(key, delimiterPos);
663: if (escapePos < 0) {
664: found = true;
665: } else {
666: delimiterPos = escapePos;
667: }
668: } while (!found);
669:
670: return delimiterPos;
671: }
672:
673: /**
674: * Checks if a delimiter at the specified position is escaped. If this
675: * is the case, the next valid search position will be returned.
676: * Otherwise the return value is -1.
677: *
678: * @param key the key to check
679: * @param pos the position where a delimiter was found
680: * @return information about escaped delimiters
681: */
682: private int escapedPosition(String key, int pos) {
683: if (getExpressionEngine().getEscapedDelimiter() == null) {
684: // nothing to escape
685: return -1;
686: }
687: int escapeOffset = escapeOffset();
688: if (escapeOffset < 0 || escapeOffset > pos) {
689: // No escaping possible at this position
690: return -1;
691: }
692:
693: int escapePos = key.indexOf(getExpressionEngine()
694: .getEscapedDelimiter(), pos - escapeOffset);
695: if (escapePos <= pos && escapePos >= 0) {
696: // The found delimiter is escaped. Next valid search position
697: // is behind the escaped delimiter.
698: return escapePos
699: + getExpressionEngine().getEscapedDelimiter()
700: .length();
701: } else {
702: return -1;
703: }
704: }
705:
706: /**
707: * Determines the relative offset of an escaped delimiter in relation to
708: * a delimiter. Depending on the used delimiter and escaped delimiter
709: * tokens the position where to search for an escaped delimiter is
710: * different. If, for instance, the dot character (".") is
711: * used as delimiter, and a doubled dot ("..") as escaped
712: * delimiter, the escaped delimiter starts at the same position as the
713: * delimiter. If the token "\." was used, it would start one
714: * character before the delimiter because the delimiter character
715: * "." is the second character in the escaped delimiter
716: * string. This relation will be determined by this method. For this to
717: * work the delimiter string must be contained in the escaped delimiter
718: * string.
719: *
720: * @return the relative offset of the escaped delimiter in relation to a
721: * delimiter
722: */
723: private int escapeOffset() {
724: return getExpressionEngine().getEscapedDelimiter().indexOf(
725: getExpressionEngine().getPropertyDelimiter());
726: }
727:
728: /**
729: * Helper method for checking if the passed key is an attribute. If this
730: * is the case, the internal fields will be set.
731: *
732: * @param key the key to be checked
733: * @return a flag if the key is an attribute
734: */
735: private boolean checkAttribute(String key) {
736: if (isAttributeKey(key)) {
737: current = removeAttributeMarkers(key);
738: return true;
739: } else {
740: return false;
741: }
742: }
743:
744: /**
745: * Helper method for checking if the passed key contains an index. If
746: * this is the case, internal fields will be set.
747: *
748: * @param key the key to be checked
749: * @return a flag if an index is defined
750: */
751: private boolean checkIndex(String key) {
752: boolean result = false;
753:
754: int idx = key.lastIndexOf(getExpressionEngine()
755: .getIndexStart());
756: if (idx > 0) {
757: int endidx = key.indexOf(getExpressionEngine()
758: .getIndexEnd(), idx);
759:
760: if (endidx > idx + 1) {
761: indexValue = Integer.parseInt(key.substring(
762: idx + 1, endidx));
763: current = key.substring(0, idx);
764: result = true;
765: }
766: }
767:
768: return result;
769: }
770:
771: /**
772: * Returns a flag whether attributes are marked the same way as normal
773: * property keys. We call this the "attribute emulating mode".
774: * When navigating through node hierarchies it might be convenient to
775: * treat attributes the same way than other child nodes, so an
776: * expression engine supports to set the attribute markers to the same
777: * value than the property delimiter. If this is the case, some special
778: * checks have to be performed.
779: *
780: * @return a flag if attributes and normal property keys are treated the
781: * same way
782: */
783: private boolean isAttributeEmulatingMode() {
784: return getExpressionEngine().getAttributeEnd() == null
785: && StringUtils.equals(getExpressionEngine()
786: .getPropertyDelimiter(),
787: getExpressionEngine().getAttributeStart());
788: }
789: }
790: }
|