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
9
* http://www.apache.org/licenses/LICENSE-2.0
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.
17
package org.apache.commons.configuration.tree;
19
import java.util.Iterator;
20
import java.util.NoSuchElementException;
22
import org.apache.commons.lang.StringUtils;
26
* A simple class that supports creation of and iteration on configuration keys
27
* supported by a <code>{@link DefaultExpressionEngine}</code> object.
30
* For key creation the class works similar to a StringBuffer: There are several
31
* <code>appendXXXX()</code> methods with which single parts of a key can be
32
* constructed. All these methods return a reference to the actual object so
33
* they can be written in a chain. When using this methods the exact syntax for
34
* keys need not be known.
37
* This class also defines a specialized iterator for configuration keys. With
38
* such an iterator a key can be tokenized into its single parts. For each part
39
* it can be checked whether it has an associated index.
42
* Instances of this class are always associated with an instance of
43
* <code>{@link DefaultExpressionEngine}</code>, from which the current
44
* delimiters are obtained. So key creation and parsing is specific to this
45
* associated expression engine.
49
* @author Oliver Heger
50
* @version $Id: DefaultConfigurationKey.java 1158891 2011-08-17 20:08:39Z oheger $
52
public class DefaultConfigurationKey
54
/** Constant for the initial StringBuffer size. */
55
private static final int INITIAL_SIZE = 32;
57
/** Stores a reference to the associated expression engine. */
58
private DefaultExpressionEngine expressionEngine;
60
/** Holds a buffer with the so far created key. */
61
private StringBuffer keyBuffer;
64
* Creates a new instance of <code>DefaultConfigurationKey</code> and sets
65
* the associated expression engine.
67
* @param engine the expression engine
69
public DefaultConfigurationKey(DefaultExpressionEngine engine)
71
keyBuffer = new StringBuffer(INITIAL_SIZE);
72
setExpressionEngine(engine);
76
* Creates a new instance of <code>DefaultConfigurationKey</code> and sets
77
* the associated expression engine and an initial key.
79
* @param engine the expression engine
80
* @param key the key to be wrapped
82
public DefaultConfigurationKey(DefaultExpressionEngine engine, String key)
84
setExpressionEngine(engine);
85
keyBuffer = new StringBuffer(trim(key));
89
* Returns the associated default expression engine.
91
* @return the associated expression engine
93
public DefaultExpressionEngine getExpressionEngine()
95
return expressionEngine;
99
* Sets the associated expression engine.
101
* @param expressionEngine the expression engine (must not be <b>null</b>)
103
public void setExpressionEngine(DefaultExpressionEngine expressionEngine)
105
if (expressionEngine == null)
107
throw new IllegalArgumentException(
108
"Expression engine must not be null!");
110
this.expressionEngine = expressionEngine;
114
* Appends the name of a property to this key. If necessary, a property
115
* delimiter will be added. If the boolean argument is set to <b>true</b>,
116
* property delimiters contained in the property name will be escaped.
118
* @param property the name of the property to be added
119
* @param escape a flag if property delimiters in the passed in property name
121
* @return a reference to this object
123
public DefaultConfigurationKey append(String property, boolean escape)
126
if (escape && property != null)
128
key = escapeDelimiters(property);
136
if (keyBuffer.length() > 0 && !isAttributeKey(property)
139
keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
142
keyBuffer.append(key);
147
* Appends the name of a property to this key. If necessary, a property
148
* delimiter will be added. Property delimiters in the given string will not
151
* @param property the name of the property to be added
152
* @return a reference to this object
154
public DefaultConfigurationKey append(String property)
156
return append(property, false);
160
* Appends an index to this configuration key.
162
* @param index the index to be appended
163
* @return a reference to this object
165
public DefaultConfigurationKey appendIndex(int index)
167
keyBuffer.append(getExpressionEngine().getIndexStart());
168
keyBuffer.append(index);
169
keyBuffer.append(getExpressionEngine().getIndexEnd());
174
* Appends an attribute to this configuration key.
176
* @param attr the name of the attribute to be appended
177
* @return a reference to this object
179
public DefaultConfigurationKey appendAttribute(String attr)
181
keyBuffer.append(constructAttributeKey(attr));
186
* Returns the actual length of this configuration key.
188
* @return the length of this key
192
return keyBuffer.length();
196
* Sets the new length of this configuration key. With this method it is
197
* possible to truncate the key, e.g. to return to a state prior calling
198
* some <code>append()</code> methods. The semantic is the same as the
199
* <code>setLength()</code> method of <code>StringBuffer</code>.
201
* @param len the new length of the key
203
public void setLength(int len)
205
keyBuffer.setLength(len);
209
* Checks if two <code>ConfigurationKey</code> objects are equal. The
210
* method can be called with strings or other objects, too.
212
* @param c the object to compare
213
* @return a flag if both objects are equal
215
public boolean equals(Object c)
222
return keyBuffer.toString().equals(c.toString());
226
* Returns the hash code for this object.
228
* @return the hash code
230
public int hashCode()
232
return String.valueOf(keyBuffer).hashCode();
236
* Returns a string representation of this object. This is the configuration
237
* key as a plain string.
239
* @return a string for this object
241
public String toString()
243
return keyBuffer.toString();
247
* Tests if the specified key represents an attribute according to the
248
* current expression engine.
250
* @param key the key to be checked
251
* @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
253
public boolean isAttributeKey(String key)
260
return key.startsWith(getExpressionEngine().getAttributeStart())
261
&& (getExpressionEngine().getAttributeEnd() == null || key
262
.endsWith(getExpressionEngine().getAttributeEnd()));
266
* Decorates the given key so that it represents an attribute. Adds special
267
* start and end markers. The passed in string will be modified only if does
268
* not already represent an attribute.
270
* @param key the key to be decorated
271
* @return the decorated attribute key
273
public String constructAttributeKey(String key)
277
return StringUtils.EMPTY;
279
if (isAttributeKey(key))
285
StringBuffer buf = new StringBuffer();
286
buf.append(getExpressionEngine().getAttributeStart()).append(key);
287
if (getExpressionEngine().getAttributeEnd() != null)
289
buf.append(getExpressionEngine().getAttributeEnd());
291
return buf.toString();
296
* Extracts the name of the attribute from the given attribute key. This
297
* method removes the attribute markers - if any - from the specified key.
299
* @param key the attribute key
300
* @return the name of the corresponding attribute
302
public String attributeName(String key)
304
return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
308
* Removes leading property delimiters from the specified key.
311
* @return the key with removed leading property delimiters
313
public String trimLeft(String key)
317
return StringUtils.EMPTY;
322
while (hasLeadingDelimiter(result))
324
result = result.substring(getExpressionEngine()
325
.getPropertyDelimiter().length());
332
* Removes trailing property delimiters from the specified key.
335
* @return the key with removed trailing property delimiters
337
public String trimRight(String key)
341
return StringUtils.EMPTY;
346
while (hasTrailingDelimiter(result))
349
.substring(0, result.length()
350
- getExpressionEngine().getPropertyDelimiter()
358
* Removes delimiters at the beginning and the end of the specified key.
361
* @return the key with removed property delimiters
363
public String trim(String key)
365
return trimRight(trimLeft(key));
369
* Returns an iterator for iterating over the single components of this
372
* @return an iterator for this key
374
public KeyIterator iterator()
376
return new KeyIterator();
380
* Helper method that checks if the specified key ends with a property
383
* @param key the key to check
384
* @return a flag if there is a trailing delimiter
386
private boolean hasTrailingDelimiter(String key)
388
return key.endsWith(getExpressionEngine().getPropertyDelimiter())
389
&& (getExpressionEngine().getEscapedDelimiter() == null || !key
390
.endsWith(getExpressionEngine().getEscapedDelimiter()));
394
* Helper method that checks if the specified key starts with a property
397
* @param key the key to check
398
* @return a flag if there is a leading delimiter
400
private boolean hasLeadingDelimiter(String key)
402
return key.startsWith(getExpressionEngine().getPropertyDelimiter())
403
&& (getExpressionEngine().getEscapedDelimiter() == null || !key
404
.startsWith(getExpressionEngine().getEscapedDelimiter()));
408
* Helper method for removing attribute markers from a key.
411
* @return the key with removed attribute markers
413
private String removeAttributeMarkers(String key)
417
getExpressionEngine().getAttributeStart().length(),
419
- ((getExpressionEngine().getAttributeEnd() != null) ? getExpressionEngine()
420
.getAttributeEnd().length()
425
* Unescapes the delimiters in the specified string.
427
* @param key the key to be unescaped
428
* @return the unescaped key
430
private String unescapeDelimiters(String key)
432
return (getExpressionEngine().getEscapedDelimiter() == null) ? key
433
: StringUtils.replace(key, getExpressionEngine()
434
.getEscapedDelimiter(), getExpressionEngine()
435
.getPropertyDelimiter());
439
* Escapes the delimiters in the specified string.
441
* @param key the key to be escaped
442
* @return the escaped key
444
private String escapeDelimiters(String key)
446
return (getExpressionEngine().getEscapedDelimiter() == null || key
447
.indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key
448
: StringUtils.replace(key, getExpressionEngine()
449
.getPropertyDelimiter(), getExpressionEngine()
450
.getEscapedDelimiter());
454
* A specialized iterator class for tokenizing a configuration key. This
455
* class implements the normal iterator interface. In addition it provides
456
* some specific methods for configuration keys.
458
public class KeyIterator implements Iterator, Cloneable
460
/** Stores the current key name. */
461
private String current;
463
/** Stores the start index of the actual token. */
464
private int startIndex;
466
/** Stores the end index of the actual token. */
467
private int endIndex;
469
/** Stores the index of the actual property if there is one. */
470
private int indexValue;
472
/** Stores a flag if the actual property has an index. */
473
private boolean hasIndex;
475
/** Stores a flag if the actual property is an attribute. */
476
private boolean attribute;
479
* Returns the next key part of this configuration key. This is a short
480
* form of <code>nextKey(false)</code>.
482
* @return the next key part
484
public String nextKey()
486
return nextKey(false);
490
* Returns the next key part of this configuration key. The boolean
491
* parameter indicates wheter a decorated key should be returned. This
492
* affects only attribute keys: if the parameter is <b>false</b>, the
493
* attribute markers are stripped from the key; if it is <b>true</b>,
496
* @param decorated a flag if the decorated key is to be returned
497
* @return the next key part
499
public String nextKey(boolean decorated)
503
throw new NoSuchElementException("No more key parts!");
508
String key = findNextIndices();
511
hasIndex = checkIndex(key);
512
attribute = checkAttribute(current);
514
return currentKey(decorated);
518
* Checks if there is a next element.
520
* @return a flag if there is a next element
522
public boolean hasNext()
524
return endIndex < keyBuffer.length();
528
* Returns the next object in the iteration.
530
* @return the next object
538
* Removes the current object in the iteration. This method is not
539
* supported by this iterator type, so an exception is thrown.
543
throw new UnsupportedOperationException("Remove not supported!");
547
* Returns the current key of the iteration (without skipping to the
548
* next element). This is the same key the previous <code>next()</code>
549
* call had returned. (Short form of <code>currentKey(false)</code>.
551
* @return the current key
553
public String currentKey()
555
return currentKey(false);
559
* Returns the current key of the iteration (without skipping to the
560
* next element). The boolean parameter indicates wheter a decorated key
561
* should be returned. This affects only attribute keys: if the
562
* parameter is <b>false</b>, the attribute markers are stripped from
563
* the key; if it is <b>true</b>, they remain.
565
* @param decorated a flag if the decorated key is to be returned
566
* @return the current key
568
public String currentKey(boolean decorated)
570
return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
575
* Returns a flag if the current key is an attribute. This method can be
576
* called after <code>next()</code>.
578
* @return a flag if the current key is an attribute
580
public boolean isAttribute()
582
// if attribute emulation mode is active, the last part of a key is
583
// always an attribute key, too
584
return attribute || (isAttributeEmulatingMode() && !hasNext());
588
* Returns a flag whether the current key refers to a property (i.e. is
589
* no special attribute key). Usually this method will return the
590
* opposite of <code>isAttribute()</code>, but if the delimiters for
591
* normal properties and attributes are set to the same string, it is
592
* possible that both methods return <b>true</b>.
594
* @return a flag if the current key is a property key
595
* @see #isAttribute()
597
public boolean isPropertyKey()
603
* Returns the index value of the current key. If the current key does
604
* not have an index, return value is -1. This method can be called
605
* after <code>next()</code>.
607
* @return the index value of the current key
609
public int getIndex()
615
* Returns a flag if the current key has an associated index. This
616
* method can be called after <code>next()</code>.
618
* @return a flag if the current key has an index
620
public boolean hasIndex()
626
* Creates a clone of this object.
628
* @return a clone of this object
630
public Object clone()
634
return super.clone();
636
catch (CloneNotSupportedException cex)
644
* Helper method for determining the next indices.
646
* @return the next key part
648
private String findNextIndices()
650
startIndex = endIndex;
652
while (startIndex < length()
653
&& hasLeadingDelimiter(keyBuffer.substring(startIndex)))
655
startIndex += getExpressionEngine().getPropertyDelimiter()
659
// Key ends with a delimiter?
660
if (startIndex >= length())
663
startIndex = endIndex - 1;
664
return keyBuffer.substring(startIndex, endIndex);
668
return nextKeyPart();
673
* Helper method for extracting the next key part. Takes escaping of
674
* delimiter characters into account.
676
* @return the next key part
678
private String nextKeyPart()
680
int attrIdx = keyBuffer.toString().indexOf(
681
getExpressionEngine().getAttributeStart(), startIndex);
682
if (attrIdx < 0 || attrIdx == startIndex)
687
int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
694
endIndex = Math.min(attrIdx, delIdx);
695
return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
699
* Searches the next unescaped delimiter from the given position.
702
* @param pos the start position
703
* @param endPos the end position
704
* @return the position of the next delimiter or -1 if there is none
706
private int nextDelimiterPos(String key, int pos, int endPos)
708
int delimiterPos = pos;
709
boolean found = false;
713
delimiterPos = key.indexOf(getExpressionEngine()
714
.getPropertyDelimiter(), delimiterPos);
715
if (delimiterPos < 0 || delimiterPos >= endPos)
719
int escapePos = escapedPosition(key, delimiterPos);
726
delimiterPos = escapePos;
735
* Checks if a delimiter at the specified position is escaped. If this
736
* is the case, the next valid search position will be returned.
737
* Otherwise the return value is -1.
739
* @param key the key to check
740
* @param pos the position where a delimiter was found
741
* @return information about escaped delimiters
743
private int escapedPosition(String key, int pos)
745
if (getExpressionEngine().getEscapedDelimiter() == null)
750
int escapeOffset = escapeOffset();
751
if (escapeOffset < 0 || escapeOffset > pos)
753
// No escaping possible at this position
757
int escapePos = key.indexOf(getExpressionEngine()
758
.getEscapedDelimiter(), pos - escapeOffset);
759
if (escapePos <= pos && escapePos >= 0)
761
// The found delimiter is escaped. Next valid search position
762
// is behind the escaped delimiter.
764
+ getExpressionEngine().getEscapedDelimiter().length();
773
* Determines the relative offset of an escaped delimiter in relation to
774
* a delimiter. Depending on the used delimiter and escaped delimiter
775
* tokens the position where to search for an escaped delimiter is
776
* different. If, for instance, the dot character (".") is
777
* used as delimiter, and a doubled dot ("..") as escaped
778
* delimiter, the escaped delimiter starts at the same position as the
779
* delimiter. If the token "\." was used, it would start one
780
* character before the delimiter because the delimiter character
781
* "." is the second character in the escaped delimiter
782
* string. This relation will be determined by this method. For this to
783
* work the delimiter string must be contained in the escaped delimiter
786
* @return the relative offset of the escaped delimiter in relation to a
789
private int escapeOffset()
791
return getExpressionEngine().getEscapedDelimiter().indexOf(
792
getExpressionEngine().getPropertyDelimiter());
796
* Helper method for checking if the passed key is an attribute. If this
797
* is the case, the internal fields will be set.
799
* @param key the key to be checked
800
* @return a flag if the key is an attribute
802
private boolean checkAttribute(String key)
804
if (isAttributeKey(key))
806
current = removeAttributeMarkers(key);
816
* Helper method for checking if the passed key contains an index. If
817
* this is the case, internal fields will be set.
819
* @param key the key to be checked
820
* @return a flag if an index is defined
822
private boolean checkIndex(String key)
824
boolean result = false;
828
int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
831
int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
834
if (endidx > idx + 1)
836
indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
837
current = key.substring(0, idx);
842
catch (NumberFormatException nfe)
851
* Returns a flag whether attributes are marked the same way as normal
852
* property keys. We call this the "attribute emulating mode".
853
* When navigating through node hierarchies it might be convenient to
854
* treat attributes the same way than other child nodes, so an
855
* expression engine supports to set the attribute markers to the same
856
* value than the property delimiter. If this is the case, some special
857
* checks have to be performed.
859
* @return a flag if attributes and normal property keys are treated the
862
private boolean isAttributeEmulatingMode()
864
return getExpressionEngine().getAttributeEnd() == null
865
&& StringUtils.equals(getExpressionEngine()
866
.getPropertyDelimiter(), getExpressionEngine()
867
.getAttributeStart());