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 {@link DefaultExpressionEngine} object.
30
* For key creation the class works similar to a StringBuffer: There are several
31
* {@code appendXXXX()} 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
* {@link DefaultExpressionEngine}, from which the current
44
* delimiters are obtained. So key creation and parsing is specific to this
45
* associated expression engine.
50
* href="http://commons.apache.org/configuration/team-list.html">Commons
51
* Configuration team</a>
52
* @version $Id: DefaultConfigurationKey.java 1231724 2012-01-15 18:40:31Z oheger $
54
public class DefaultConfigurationKey
56
/** Constant for the initial StringBuffer size. */
57
private static final int INITIAL_SIZE = 32;
59
/** Stores a reference to the associated expression engine. */
60
private DefaultExpressionEngine expressionEngine;
62
/** Holds a buffer with the so far created key. */
63
private StringBuilder keyBuffer;
66
* Creates a new instance of {@code DefaultConfigurationKey} and sets
67
* the associated expression engine.
69
* @param engine the expression engine
71
public DefaultConfigurationKey(DefaultExpressionEngine engine)
73
keyBuffer = new StringBuilder(INITIAL_SIZE);
74
setExpressionEngine(engine);
78
* Creates a new instance of {@code DefaultConfigurationKey} and sets
79
* the associated expression engine and an initial key.
81
* @param engine the expression engine
82
* @param key the key to be wrapped
84
public DefaultConfigurationKey(DefaultExpressionEngine engine, String key)
86
setExpressionEngine(engine);
87
keyBuffer = new StringBuilder(trim(key));
91
* Returns the associated default expression engine.
93
* @return the associated expression engine
95
public DefaultExpressionEngine getExpressionEngine()
97
return expressionEngine;
101
* Sets the associated expression engine.
103
* @param expressionEngine the expression engine (must not be <b>null</b>)
105
public void setExpressionEngine(DefaultExpressionEngine expressionEngine)
107
if (expressionEngine == null)
109
throw new IllegalArgumentException(
110
"Expression engine must not be null!");
112
this.expressionEngine = expressionEngine;
116
* Appends the name of a property to this key. If necessary, a property
117
* delimiter will be added. If the boolean argument is set to <b>true</b>,
118
* property delimiters contained in the property name will be escaped.
120
* @param property the name of the property to be added
121
* @param escape a flag if property delimiters in the passed in property name
123
* @return a reference to this object
125
public DefaultConfigurationKey append(String property, boolean escape)
128
if (escape && property != null)
130
key = escapeDelimiters(property);
138
if (keyBuffer.length() > 0 && !isAttributeKey(property)
141
keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
144
keyBuffer.append(key);
149
* Appends the name of a property to this key. If necessary, a property
150
* delimiter will be added. Property delimiters in the given string will not
153
* @param property the name of the property to be added
154
* @return a reference to this object
156
public DefaultConfigurationKey append(String property)
158
return append(property, false);
162
* Appends an index to this configuration key.
164
* @param index the index to be appended
165
* @return a reference to this object
167
public DefaultConfigurationKey appendIndex(int index)
169
keyBuffer.append(getExpressionEngine().getIndexStart());
170
keyBuffer.append(index);
171
keyBuffer.append(getExpressionEngine().getIndexEnd());
176
* Appends an attribute to this configuration key.
178
* @param attr the name of the attribute to be appended
179
* @return a reference to this object
181
public DefaultConfigurationKey appendAttribute(String attr)
183
keyBuffer.append(constructAttributeKey(attr));
188
* Returns the actual length of this configuration key.
190
* @return the length of this key
194
return keyBuffer.length();
198
* Sets the new length of this configuration key. With this method it is
199
* possible to truncate the key, e.g. to return to a state prior calling
200
* some {@code append()} methods. The semantic is the same as the
201
* {@code setLength()} method of {@code StringBuilder}.
203
* @param len the new length of the key
205
public void setLength(int len)
207
keyBuffer.setLength(len);
211
* Checks if two {@code ConfigurationKey} objects are equal. The
212
* method can be called with strings or other objects, too.
214
* @param c the object to compare
215
* @return a flag if both objects are equal
218
public boolean equals(Object c)
225
return keyBuffer.toString().equals(c.toString());
229
* Returns the hash code for this object.
231
* @return the hash code
234
public int hashCode()
236
return String.valueOf(keyBuffer).hashCode();
240
* Returns a string representation of this object. This is the configuration
241
* key as a plain string.
243
* @return a string for this object
246
public String toString()
248
return keyBuffer.toString();
252
* Tests if the specified key represents an attribute according to the
253
* current expression engine.
255
* @param key the key to be checked
256
* @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
258
public boolean isAttributeKey(String key)
265
return key.startsWith(getExpressionEngine().getAttributeStart())
266
&& (getExpressionEngine().getAttributeEnd() == null || key
267
.endsWith(getExpressionEngine().getAttributeEnd()));
271
* Decorates the given key so that it represents an attribute. Adds special
272
* start and end markers. The passed in string will be modified only if does
273
* not already represent an attribute.
275
* @param key the key to be decorated
276
* @return the decorated attribute key
278
public String constructAttributeKey(String key)
282
return StringUtils.EMPTY;
284
if (isAttributeKey(key))
290
StringBuilder buf = new StringBuilder();
291
buf.append(getExpressionEngine().getAttributeStart()).append(key);
292
if (getExpressionEngine().getAttributeEnd() != null)
294
buf.append(getExpressionEngine().getAttributeEnd());
296
return buf.toString();
301
* Extracts the name of the attribute from the given attribute key. This
302
* method removes the attribute markers - if any - from the specified key.
304
* @param key the attribute key
305
* @return the name of the corresponding attribute
307
public String attributeName(String key)
309
return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
313
* Removes leading property delimiters from the specified key.
316
* @return the key with removed leading property delimiters
318
public String trimLeft(String key)
322
return StringUtils.EMPTY;
327
while (hasLeadingDelimiter(result))
329
result = result.substring(getExpressionEngine()
330
.getPropertyDelimiter().length());
337
* Removes trailing property delimiters from the specified key.
340
* @return the key with removed trailing property delimiters
342
public String trimRight(String key)
346
return StringUtils.EMPTY;
351
while (hasTrailingDelimiter(result))
354
.substring(0, result.length()
355
- getExpressionEngine().getPropertyDelimiter()
363
* Removes delimiters at the beginning and the end of the specified key.
366
* @return the key with removed property delimiters
368
public String trim(String key)
370
return trimRight(trimLeft(key));
374
* Returns an iterator for iterating over the single components of this
377
* @return an iterator for this key
379
public KeyIterator iterator()
381
return new KeyIterator();
385
* Helper method that checks if the specified key ends with a property
388
* @param key the key to check
389
* @return a flag if there is a trailing delimiter
391
private boolean hasTrailingDelimiter(String key)
393
return key.endsWith(getExpressionEngine().getPropertyDelimiter())
394
&& (getExpressionEngine().getEscapedDelimiter() == null || !key
395
.endsWith(getExpressionEngine().getEscapedDelimiter()));
399
* Helper method that checks if the specified key starts with a property
402
* @param key the key to check
403
* @return a flag if there is a leading delimiter
405
private boolean hasLeadingDelimiter(String key)
407
return key.startsWith(getExpressionEngine().getPropertyDelimiter())
408
&& (getExpressionEngine().getEscapedDelimiter() == null || !key
409
.startsWith(getExpressionEngine().getEscapedDelimiter()));
413
* Helper method for removing attribute markers from a key.
416
* @return the key with removed attribute markers
418
private String removeAttributeMarkers(String key)
422
getExpressionEngine().getAttributeStart().length(),
424
- ((getExpressionEngine().getAttributeEnd() != null) ? getExpressionEngine()
425
.getAttributeEnd().length()
430
* Unescapes the delimiters in the specified string.
432
* @param key the key to be unescaped
433
* @return the unescaped key
435
private String unescapeDelimiters(String key)
437
return (getExpressionEngine().getEscapedDelimiter() == null) ? key
438
: StringUtils.replace(key, getExpressionEngine()
439
.getEscapedDelimiter(), getExpressionEngine()
440
.getPropertyDelimiter());
444
* Escapes the delimiters in the specified string.
446
* @param key the key to be escaped
447
* @return the escaped key
449
private String escapeDelimiters(String key)
451
return (getExpressionEngine().getEscapedDelimiter() == null || key
452
.indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key
453
: StringUtils.replace(key, getExpressionEngine()
454
.getPropertyDelimiter(), getExpressionEngine()
455
.getEscapedDelimiter());
459
* A specialized iterator class for tokenizing a configuration key. This
460
* class implements the normal iterator interface. In addition it provides
461
* some specific methods for configuration keys.
463
public class KeyIterator implements Iterator<Object>, Cloneable
465
/** Stores the current key name. */
466
private String current;
468
/** Stores the start index of the actual token. */
469
private int startIndex;
471
/** Stores the end index of the actual token. */
472
private int endIndex;
474
/** Stores the index of the actual property if there is one. */
475
private int indexValue;
477
/** Stores a flag if the actual property has an index. */
478
private boolean hasIndex;
480
/** Stores a flag if the actual property is an attribute. */
481
private boolean attribute;
484
* Returns the next key part of this configuration key. This is a short
485
* form of {@code nextKey(false)}.
487
* @return the next key part
489
public String nextKey()
491
return nextKey(false);
495
* Returns the next key part of this configuration key. The boolean
496
* parameter indicates wheter a decorated key should be returned. This
497
* affects only attribute keys: if the parameter is <b>false</b>, the
498
* attribute markers are stripped from the key; if it is <b>true</b>,
501
* @param decorated a flag if the decorated key is to be returned
502
* @return the next key part
504
public String nextKey(boolean decorated)
508
throw new NoSuchElementException("No more key parts!");
513
String key = findNextIndices();
516
hasIndex = checkIndex(key);
517
attribute = checkAttribute(current);
519
return currentKey(decorated);
523
* Checks if there is a next element.
525
* @return a flag if there is a next element
527
public boolean hasNext()
529
return endIndex < keyBuffer.length();
533
* Returns the next object in the iteration.
535
* @return the next object
543
* Removes the current object in the iteration. This method is not
544
* supported by this iterator type, so an exception is thrown.
548
throw new UnsupportedOperationException("Remove not supported!");
552
* Returns the current key of the iteration (without skipping to the
553
* next element). This is the same key the previous {@code next()}
554
* call had returned. (Short form of {@code currentKey(false)}.
556
* @return the current key
558
public String currentKey()
560
return currentKey(false);
564
* Returns the current key of the iteration (without skipping to the
565
* next element). The boolean parameter indicates wheter a decorated key
566
* should be returned. This affects only attribute keys: if the
567
* parameter is <b>false</b>, the attribute markers are stripped from
568
* the key; if it is <b>true</b>, they remain.
570
* @param decorated a flag if the decorated key is to be returned
571
* @return the current key
573
public String currentKey(boolean decorated)
575
return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
580
* Returns a flag if the current key is an attribute. This method can be
581
* called after {@code next()}.
583
* @return a flag if the current key is an attribute
585
public boolean isAttribute()
587
// if attribute emulation mode is active, the last part of a key is
588
// always an attribute key, too
589
return attribute || (isAttributeEmulatingMode() && !hasNext());
593
* Returns a flag whether the current key refers to a property (i.e. is
594
* no special attribute key). Usually this method will return the
595
* opposite of {@code isAttribute()}, but if the delimiters for
596
* normal properties and attributes are set to the same string, it is
597
* possible that both methods return <b>true</b>.
599
* @return a flag if the current key is a property key
600
* @see #isAttribute()
602
public boolean isPropertyKey()
608
* Returns the index value of the current key. If the current key does
609
* not have an index, return value is -1. This method can be called
610
* after {@code next()}.
612
* @return the index value of the current key
614
public int getIndex()
620
* Returns a flag if the current key has an associated index. This
621
* method can be called after {@code next()}.
623
* @return a flag if the current key has an index
625
public boolean hasIndex()
631
* Creates a clone of this object.
633
* @return a clone of this object
636
public Object clone()
640
return super.clone();
642
catch (CloneNotSupportedException cex)
650
* Helper method for determining the next indices.
652
* @return the next key part
654
private String findNextIndices()
656
startIndex = endIndex;
658
while (startIndex < length()
659
&& hasLeadingDelimiter(keyBuffer.substring(startIndex)))
661
startIndex += getExpressionEngine().getPropertyDelimiter()
665
// Key ends with a delimiter?
666
if (startIndex >= length())
669
startIndex = endIndex - 1;
670
return keyBuffer.substring(startIndex, endIndex);
674
return nextKeyPart();
679
* Helper method for extracting the next key part. Takes escaping of
680
* delimiter characters into account.
682
* @return the next key part
684
private String nextKeyPart()
686
int attrIdx = keyBuffer.toString().indexOf(
687
getExpressionEngine().getAttributeStart(), startIndex);
688
if (attrIdx < 0 || attrIdx == startIndex)
693
int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
700
endIndex = Math.min(attrIdx, delIdx);
701
return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
705
* Searches the next unescaped delimiter from the given position.
708
* @param pos the start position
709
* @param endPos the end position
710
* @return the position of the next delimiter or -1 if there is none
712
private int nextDelimiterPos(String key, int pos, int endPos)
714
int delimiterPos = pos;
715
boolean found = false;
719
delimiterPos = key.indexOf(getExpressionEngine()
720
.getPropertyDelimiter(), delimiterPos);
721
if (delimiterPos < 0 || delimiterPos >= endPos)
725
int escapePos = escapedPosition(key, delimiterPos);
732
delimiterPos = escapePos;
741
* Checks if a delimiter at the specified position is escaped. If this
742
* is the case, the next valid search position will be returned.
743
* Otherwise the return value is -1.
745
* @param key the key to check
746
* @param pos the position where a delimiter was found
747
* @return information about escaped delimiters
749
private int escapedPosition(String key, int pos)
751
if (getExpressionEngine().getEscapedDelimiter() == null)
756
int escapeOffset = escapeOffset();
757
if (escapeOffset < 0 || escapeOffset > pos)
759
// No escaping possible at this position
763
int escapePos = key.indexOf(getExpressionEngine()
764
.getEscapedDelimiter(), pos - escapeOffset);
765
if (escapePos <= pos && escapePos >= 0)
767
// The found delimiter is escaped. Next valid search position
768
// is behind the escaped delimiter.
770
+ getExpressionEngine().getEscapedDelimiter().length();
779
* Determines the relative offset of an escaped delimiter in relation to
780
* a delimiter. Depending on the used delimiter and escaped delimiter
781
* tokens the position where to search for an escaped delimiter is
782
* different. If, for instance, the dot character (".") is
783
* used as delimiter, and a doubled dot ("..") as escaped
784
* delimiter, the escaped delimiter starts at the same position as the
785
* delimiter. If the token "\." was used, it would start one
786
* character before the delimiter because the delimiter character
787
* "." is the second character in the escaped delimiter
788
* string. This relation will be determined by this method. For this to
789
* work the delimiter string must be contained in the escaped delimiter
792
* @return the relative offset of the escaped delimiter in relation to a
795
private int escapeOffset()
797
return getExpressionEngine().getEscapedDelimiter().indexOf(
798
getExpressionEngine().getPropertyDelimiter());
802
* Helper method for checking if the passed key is an attribute. If this
803
* is the case, the internal fields will be set.
805
* @param key the key to be checked
806
* @return a flag if the key is an attribute
808
private boolean checkAttribute(String key)
810
if (isAttributeKey(key))
812
current = removeAttributeMarkers(key);
822
* Helper method for checking if the passed key contains an index. If
823
* this is the case, internal fields will be set.
825
* @param key the key to be checked
826
* @return a flag if an index is defined
828
private boolean checkIndex(String key)
830
boolean result = false;
834
int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
837
int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
840
if (endidx > idx + 1)
842
indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
843
current = key.substring(0, idx);
848
catch (NumberFormatException nfe)
857
* Returns a flag whether attributes are marked the same way as normal
858
* property keys. We call this the "attribute emulating mode".
859
* When navigating through node hierarchies it might be convenient to
860
* treat attributes the same way than other child nodes, so an
861
* expression engine supports to set the attribute markers to the same
862
* value than the property delimiter. If this is the case, some special
863
* checks have to be performed.
865
* @return a flag if attributes and normal property keys are treated the
868
private boolean isAttributeEmulatingMode()
870
return getExpressionEngine().getAttributeEnd() == null
871
&& StringUtils.equals(getExpressionEngine()
872
.getPropertyDelimiter(), getExpressionEngine()
873
.getAttributeStart());