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.
18
package org.apache.commons.configuration;
20
import java.io.Serializable;
21
import java.util.Iterator;
22
import java.util.NoSuchElementException;
25
* <p>A simple class that supports creation of and iteration on complex
26
* configuration keys.</p>
28
* <p>For key creation the class works similar to a StringBuilder: There are
29
* several {@code appendXXXX()} methods with which single parts
30
* of a key can be constructed. All these methods return a reference to the
31
* actual object so they can be written in a chain. When using this methods
32
* the exact syntax for keys need not be known.</p>
34
* <p>This class also defines a specialized iterator for configuration keys.
35
* With such an iterator a key can be tokenized into its single parts. For
36
* each part it can be checked whether it has an associated index.</p>
39
* href="http://commons.apache.org/configuration/team-list.html">Commons
40
* Configuration team</a>
41
* @version $Id: ConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $
42
* @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey}
43
* instead. It is associated with a {@code DefaultExpressionEngine} and thus
44
* can produce correct keys even if key separators have been changed.
47
public class ConfigurationKey implements Serializable
49
/** Constant for a property delimiter.*/
50
public static final char PROPERTY_DELIMITER = '.';
52
/** Constant for an escaped delimiter. */
53
public static final String ESCAPED_DELIMITER =
54
String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
56
/** Constant for an attribute start marker.*/
57
private static final String ATTRIBUTE_START = "[@";
59
/** Constant for an attribute end marker.*/
60
private static final String ATTRIBUTE_END = "]";
62
/** Constant for an index start marker.*/
63
private static final char INDEX_START = '(';
65
/** Constant for an index end marker.*/
66
private static final char INDEX_END = ')';
68
/** Constant for the initial StringBuilder size.*/
69
private static final int INITIAL_SIZE = 32;
72
* The serial version ID.
74
private static final long serialVersionUID = -4299732083605277656L;
76
/** Holds a buffer with the so far created key.*/
77
private StringBuilder keyBuffer;
80
* Creates a new, empty instance of {@code ConfigurationKey}.
82
public ConfigurationKey()
84
keyBuffer = new StringBuilder(INITIAL_SIZE);
88
* Creates a new instance of {@code ConfigurationKey} and
89
* initializes it with the given key.
91
* @param key the key as a string
93
public ConfigurationKey(String key)
95
keyBuffer = new StringBuilder(key);
96
removeTrailingDelimiter();
100
* Appends the name of a property to this key. If necessary, a
101
* property delimiter will be added.
103
* @param property the name of the property to be added
104
* @return a reference to this object
106
public ConfigurationKey append(String property)
108
if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
110
keyBuffer.append(PROPERTY_DELIMITER);
113
keyBuffer.append(property);
114
removeTrailingDelimiter();
119
* Appends an index to this configuration key.
121
* @param index the index to be appended
122
* @return a reference to this object
124
public ConfigurationKey appendIndex(int index)
126
keyBuffer.append(INDEX_START).append(index);
127
keyBuffer.append(INDEX_END);
132
* Appends an attribute to this configuration key.
134
* @param attr the name of the attribute to be appended
135
* @return a reference to this object
137
public ConfigurationKey appendAttribute(String attr)
139
keyBuffer.append(constructAttributeKey(attr));
144
* Checks if this key is an attribute key.
146
* @return a flag if this key is an attribute key
148
public boolean isAttributeKey()
150
return isAttributeKey(keyBuffer.toString());
154
* Checks if the passed in key is an attribute key. Such attribute keys
155
* start and end with certain marker strings. In some cases they must be
156
* treated slightly different.
158
* @param key the key (part) to be checked
159
* @return a flag if this key is an attribute key
161
public static boolean isAttributeKey(String key)
164
&& key.startsWith(ATTRIBUTE_START)
165
&& key.endsWith(ATTRIBUTE_END);
169
* Decorates the given key so that it represents an attribute. Adds
170
* special start and end markers.
172
* @param key the key to be decorated
173
* @return the decorated attribute key
175
public static String constructAttributeKey(String key)
177
StringBuilder buf = new StringBuilder();
178
buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
179
return buf.toString();
183
* Extracts the name of the attribute from the given attribute key.
184
* This method removes the attribute markers - if any - from the
187
* @param key the attribute key
188
* @return the name of the corresponding attribute
190
public static String attributeName(String key)
192
return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
196
* Helper method for removing attribute markers from a key.
199
* @return the key with removed attribute markers
201
static String removeAttributeMarkers(String key)
203
return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
207
* Helper method that checks if the actual buffer ends with a property
210
* @return a flag if there is a trailing delimiter
212
private boolean hasDelimiter()
215
for (int idx = keyBuffer.length() - 1; idx >= 0
216
&& keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
220
return count % 2 != 0;
224
* Removes a trailing delimiter if there is any.
226
private void removeTrailingDelimiter()
228
while (hasDelimiter())
230
keyBuffer.deleteCharAt(keyBuffer.length() - 1);
235
* Returns a string representation of this object. This is the
236
* configuration key as a plain string.
238
* @return a string for this object
241
public String toString()
243
return keyBuffer.toString();
247
* Returns an iterator for iterating over the single components of
248
* this configuration key.
250
* @return an iterator for this key
252
public KeyIterator iterator()
254
return new KeyIterator();
258
* Returns the actual length of this configuration key.
260
* @return the length of this key
264
return keyBuffer.length();
268
* Sets the new length of this configuration key. With this method it is
269
* possible to truncate the key, e.g. to return to a state prior calling
270
* some {@code append()} methods. The semantic is the same as
271
* the {@code setLength()} method of {@code StringBuilder}.
273
* @param len the new length of the key
275
public void setLength(int len)
277
keyBuffer.setLength(len);
281
* Checks if two {@code ConfigurationKey} objects are equal. The
282
* method can be called with strings or other objects, too.
284
* @param c the object to compare
285
* @return a flag if both objects are equal
288
public boolean equals(Object c)
295
return keyBuffer.toString().equals(c.toString());
299
* Returns the hash code for this object.
301
* @return the hash code
304
public int hashCode()
306
return String.valueOf(keyBuffer).hashCode();
310
* Returns a configuration key object that is initialized with the part
311
* of the key that is common to this key and the passed in key.
313
* @param other the other key
314
* @return a key object with the common key part
316
public ConfigurationKey commonKey(ConfigurationKey other)
320
throw new IllegalArgumentException("Other key must no be null!");
323
ConfigurationKey result = new ConfigurationKey();
324
KeyIterator it1 = iterator();
325
KeyIterator it2 = other.iterator();
327
while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
329
if (it1.isAttribute())
331
result.appendAttribute(it1.currentKey());
335
result.append(it1.currentKey());
338
result.appendIndex(it1.getIndex());
347
* Returns the "difference key" to a given key. This value
348
* is the part of the passed in key that differs from this key. There is
349
* the following relation:
350
* {@code other = key.commonKey(other) + key.differenceKey(other)}
351
* for an arbitrary configuration key {@code key}.
353
* @param other the key for which the difference is to be calculated
354
* @return the difference key
356
public ConfigurationKey differenceKey(ConfigurationKey other)
358
ConfigurationKey common = commonKey(other);
359
ConfigurationKey result = new ConfigurationKey();
361
if (common.length() < other.length())
363
String k = other.toString().substring(common.length());
364
// skip trailing delimiters
366
while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
373
result.append(k.substring(i));
381
* Helper method for comparing two key parts.
383
* @param it1 the iterator with the first part
384
* @param it2 the iterator with the second part
385
* @return a flag if both parts are equal
387
private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
389
return it1.nextKey().equals(it2.nextKey())
390
&& it1.getIndex() == it2.getIndex()
391
&& it1.isAttribute() == it2.isAttribute();
395
* A specialized iterator class for tokenizing a configuration key.
396
* This class implements the normal iterator interface. In addition it
397
* provides some specific methods for configuration keys.
399
public class KeyIterator implements Iterator<Object>, Cloneable
401
/** Stores the current key name.*/
402
private String current;
404
/** Stores the start index of the actual token.*/
405
private int startIndex;
407
/** Stores the end index of the actual token.*/
408
private int endIndex;
410
/** Stores the index of the actual property if there is one.*/
411
private int indexValue;
413
/** Stores a flag if the actual property has an index.*/
414
private boolean hasIndex;
416
/** Stores a flag if the actual property is an attribute.*/
417
private boolean attribute;
420
* Helper method for determining the next indices.
422
* @return the next key part
424
private String findNextIndices()
426
startIndex = endIndex;
428
while (startIndex < keyBuffer.length()
429
&& keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
434
// Key ends with a delimiter?
435
if (startIndex >= keyBuffer.length())
437
endIndex = keyBuffer.length();
438
startIndex = endIndex - 1;
439
return keyBuffer.substring(startIndex, endIndex);
443
return nextKeyPart();
448
* Helper method for extracting the next key part. Takes escaping of
449
* delimiter characters into account.
451
* @return the next key part
453
private String nextKeyPart()
455
StringBuilder key = new StringBuilder(INITIAL_SIZE);
456
int idx = startIndex;
457
int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
459
if (endIdx < 0 || endIdx == startIndex)
461
endIdx = keyBuffer.length();
463
boolean found = false;
465
while (!found && idx < endIdx)
467
char c = keyBuffer.charAt(idx);
468
if (c == PROPERTY_DELIMITER)
470
// a duplicated delimiter means escaping
471
if (idx == endIdx - 1
472
|| keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
489
return key.toString();
493
* Returns the next key part of this configuration key. This is a short
494
* form of {@code nextKey(false)}.
496
* @return the next key part
498
public String nextKey()
500
return nextKey(false);
504
* Returns the next key part of this configuration key. The boolean
505
* parameter indicates wheter a decorated key should be returned. This
506
* affects only attribute keys: if the parameter is <b>false</b>, the
507
* attribute markers are stripped from the key; if it is <b>true</b>,
510
* @param decorated a flag if the decorated key is to be returned
511
* @return the next key part
513
public String nextKey(boolean decorated)
517
throw new NoSuchElementException("No more key parts!");
522
String key = findNextIndices();
525
hasIndex = checkIndex(key);
526
attribute = checkAttribute(current);
528
return currentKey(decorated);
532
* Helper method for checking if the passed key is an attribute.
533
* If this is the case, the internal fields will be set.
535
* @param key the key to be checked
536
* @return a flag if the key is an attribute
538
private boolean checkAttribute(String key)
540
if (isAttributeKey(key))
542
current = removeAttributeMarkers(key);
552
* Helper method for checking if the passed key contains an index.
553
* If this is the case, internal fields will be set.
555
* @param key the key to be checked
556
* @return a flag if an index is defined
558
private boolean checkIndex(String key)
560
boolean result = false;
562
int idx = key.lastIndexOf(INDEX_START);
565
int endidx = key.indexOf(INDEX_END, idx);
567
if (endidx > idx + 1)
569
indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
570
current = key.substring(0, idx);
579
* Checks if there is a next element.
581
* @return a flag if there is a next element
583
public boolean hasNext()
585
return endIndex < keyBuffer.length();
589
* Returns the next object in the iteration.
591
* @return the next object
599
* Removes the current object in the iteration. This method is not
600
* supported by this iterator type, so an exception is thrown.
604
throw new UnsupportedOperationException("Remove not supported!");
608
* Returns the current key of the iteration (without skipping to the
609
* next element). This is the same key the previous {@code next()}
610
* call had returned. (Short form of {@code currentKey(false)}.
612
* @return the current key
614
public String currentKey()
616
return currentKey(false);
620
* Returns the current key of the iteration (without skipping to the
621
* next element). The boolean parameter indicates wheter a decorated
622
* key should be returned. This affects only attribute keys: if the
623
* parameter is <b>false</b>, the attribute markers are stripped from
624
* the key; if it is <b>true</b>, they remain.
626
* @param decorated a flag if the decorated key is to be returned
627
* @return the current key
629
public String currentKey(boolean decorated)
631
return (decorated && isAttribute()) ? constructAttributeKey(current) : current;
635
* Returns a flag if the current key is an attribute. This method can
636
* be called after {@code next()}.
638
* @return a flag if the current key is an attribute
640
public boolean isAttribute()
646
* Returns the index value of the current key. If the current key does
647
* not have an index, return value is -1. This method can be called
648
* after {@code next()}.
650
* @return the index value of the current key
652
public int getIndex()
658
* Returns a flag if the current key has an associated index.
659
* This method can be called after {@code next()}.
661
* @return a flag if the current key has an index
663
public boolean hasIndex()
669
* Creates a clone of this object.
671
* @return a clone of this object
674
public Object clone()
678
return super.clone();
680
catch (CloneNotSupportedException cex)