1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration;
18
19 import java.io.Serializable;
20 import java.util.Iterator;
21 import java.util.NoSuchElementException;
22
23 /***
24 * <p>A simple class that supports creation of and iteration on complex
25 * configuration keys.</p>
26 *
27 * <p>For key creation the class works similar to a StringBuffer: There are
28 * several <code>appendXXXX()</code> methods with which single parts
29 * of a key can be constructed. All these methods return a reference to the
30 * actual object so they can be written in a chain. When using this methods
31 * the exact syntax for keys need not be known.</p>
32 *
33 * <p>This class also defines a specialized iterator for configuration keys.
34 * With such an iterator a key can be tokenized into its single parts. For
35 * each part it can be checked whether it has an associated index.</p>
36 *
37 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
38 * @version $Id: ConfigurationKey.java 295090 2005-10-05 19:36:15Z oheger $
39 */
40 public class ConfigurationKey implements Serializable
41 {
42 /*** Constant for a property delimiter.*/
43 public static final char PROPERTY_DELIMITER = '.';
44
45 /*** Constant for an escaped delimiter. */
46 public static final String ESCAPED_DELIMITER =
47 String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
48
49 /*** Constant for an attribute start marker.*/
50 private static final String ATTRIBUTE_START = "[@";
51
52 /*** Constant for an attribute end marker.*/
53 private static final String ATTRIBUTE_END = "]";
54
55 /*** Constant for an index start marker.*/
56 private static final char INDEX_START = '(';
57
58 /*** Constant for an index end marker.*/
59 private static final char INDEX_END = ')';
60
61 /*** Constant for the initial StringBuffer size.*/
62 private static final int INITIAL_SIZE = 32;
63
64 /*** Holds a buffer with the so far created key.*/
65 private StringBuffer keyBuffer;
66
67 /***
68 * Creates a new, empty instance of <code>ConfigurationKey</code>.
69 */
70 public ConfigurationKey()
71 {
72 keyBuffer = new StringBuffer(INITIAL_SIZE);
73 }
74
75 /***
76 * Creates a new instance of <code>ConfigurationKey</code> and
77 * initializes it with the given key.
78 *
79 * @param key the key as a string
80 */
81 public ConfigurationKey(String key)
82 {
83 keyBuffer = new StringBuffer(key);
84 removeTrailingDelimiter();
85 }
86
87 /***
88 * Appends the name of a property to this key. If necessary, a
89 * property delimiter will be added.
90 *
91 * @param property the name of the property to be added
92 * @return a reference to this object
93 */
94 public ConfigurationKey append(String property)
95 {
96 if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
97 {
98 keyBuffer.append(PROPERTY_DELIMITER);
99 }
100
101 keyBuffer.append(property);
102 removeTrailingDelimiter();
103 return this;
104 }
105
106 /***
107 * Appends an index to this configuration key.
108 *
109 * @param index the index to be appended
110 * @return a reference to this object
111 */
112 public ConfigurationKey appendIndex(int index)
113 {
114 keyBuffer.append(INDEX_START).append(index);
115 keyBuffer.append(INDEX_END);
116 return this;
117 }
118
119 /***
120 * Appends an attribute to this configuration key.
121 *
122 * @param attr the name of the attribute to be appended
123 * @return a reference to this object
124 */
125 public ConfigurationKey appendAttribute(String attr)
126 {
127 keyBuffer.append(constructAttributeKey(attr));
128 return this;
129 }
130
131 /***
132 * Checks if this key is an attribute key.
133 *
134 * @return a flag if this key is an attribute key
135 */
136 public boolean isAttributeKey()
137 {
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 {
151 return key != null
152 && key.startsWith(ATTRIBUTE_START)
153 && key.endsWith(ATTRIBUTE_END);
154 }
155
156 /***
157 * Decorates the given key so that it represents an attribute. Adds
158 * special start and end markers.
159 *
160 * @param key the key to be decorated
161 * @return the decorated attribute key
162 */
163 public static String constructAttributeKey(String key)
164 {
165 StringBuffer buf = new StringBuffer();
166 buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
167 return buf.toString();
168 }
169
170 /***
171 * Extracts the name of the attribute from the given attribute key.
172 * This method removes the attribute markers - if any - from the
173 * specified key.
174 *
175 * @param key the attribute key
176 * @return the name of the corresponding attribute
177 */
178 public static String attributeName(String key)
179 {
180 return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
181 }
182
183 /***
184 * Helper method for removing attribute markers from a key.
185 *
186 * @param key the key
187 * @return the key with removed attribute markers
188 */
189 static String removeAttributeMarkers(String key)
190 {
191 return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
192 }
193
194 /***
195 * Helper method that checks if the actual buffer ends with a property
196 * delimiter.
197 *
198 * @return a flag if there is a trailing delimiter
199 */
200 private boolean hasDelimiter()
201 {
202 int count = 0;
203 for (int idx = keyBuffer.length() - 1; idx >= 0
204 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
205 {
206 count++;
207 }
208 return count % 2 == 1;
209 }
210
211 /***
212 * Removes a trailing delimiter if there is any.
213 */
214 private void removeTrailingDelimiter()
215 {
216 while (hasDelimiter())
217 {
218 keyBuffer.deleteCharAt(keyBuffer.length() - 1);
219 }
220 }
221
222 /***
223 * Returns a string representation of this object. This is the
224 * configuration key as a plain string.
225 *
226 * @return a string for this object
227 */
228 public String toString()
229 {
230 return keyBuffer.toString();
231 }
232
233 /***
234 * Returns an iterator for iterating over the single components of
235 * this configuration key.
236 *
237 * @return an iterator for this key
238 */
239 public KeyIterator iterator()
240 {
241 return new KeyIterator();
242 }
243
244 /***
245 * Returns the actual length of this configuration key.
246 *
247 * @return the length of this key
248 */
249 public int length()
250 {
251 return keyBuffer.length();
252 }
253
254 /***
255 * Sets the new length of this configuration key. With this method it is
256 * possible to truncate the key, e.g. to return to a state prior calling
257 * some <code>append()</code> methods. The semantic is the same as
258 * the <code>setLength()</code> method of <code>StringBuffer</code>.
259 *
260 * @param len the new length of the key
261 */
262 public void setLength(int len)
263 {
264 keyBuffer.setLength(len);
265 }
266
267 /***
268 * Checks if two <code>ConfigurationKey</code> objects are equal. The
269 * method can be called with strings or other objects, too.
270 *
271 * @param c the object to compare
272 * @return a flag if both objects are equal
273 */
274 public boolean equals(Object c)
275 {
276 if (c == null)
277 {
278 return false;
279 }
280
281 return keyBuffer.toString().equals(c.toString());
282 }
283
284 /***
285 * Returns the hash code for this object.
286 *
287 * @return the hash code
288 */
289 public int hashCode()
290 {
291 return String.valueOf(keyBuffer).hashCode();
292 }
293
294 /***
295 * Returns a configuration key object that is initialized with the part
296 * of the key that is common to this key and the passed in key.
297 *
298 * @param other the other key
299 * @return a key object with the common key part
300 */
301 public ConfigurationKey commonKey(ConfigurationKey other)
302 {
303 if (other == null)
304 {
305 throw new IllegalArgumentException("Other key must no be null!");
306 }
307
308 ConfigurationKey result = new ConfigurationKey();
309 KeyIterator it1 = iterator();
310 KeyIterator it2 = other.iterator();
311
312 while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
313 {
314 if (it1.isAttribute())
315 {
316 result.appendAttribute(it1.currentKey());
317 }
318 else
319 {
320 result.append(it1.currentKey());
321 if (it1.hasIndex)
322 {
323 result.appendIndex(it1.getIndex());
324 }
325 }
326 }
327
328 return result;
329 }
330
331 /***
332 * Returns the "difference key" to a given key. This value
333 * is the part of the passed in key that differs from this key. There is
334 * the following relation:
335 * <code>other = key.commonKey(other) + key.differenceKey(other)</code>
336 * for an arbitrary configuration key <code>key</code>.
337 *
338 * @param other the key for which the difference is to be calculated
339 * @return the difference key
340 */
341 public ConfigurationKey differenceKey(ConfigurationKey other)
342 {
343 ConfigurationKey common = commonKey(other);
344 ConfigurationKey result = new ConfigurationKey();
345
346 if (common.length() < other.length())
347 {
348 String k = other.toString().substring(common.length());
349
350 int i = 0;
351 while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
352 {
353 i++;
354 }
355
356 if (i < k.length())
357 {
358 result.append(k.substring(i));
359 }
360 }
361
362 return result;
363 }
364
365 /***
366 * Helper method for comparing two key parts.
367 *
368 * @param it1 the iterator with the first part
369 * @param it2 the iterator with the second part
370 * @return a flag if both parts are equal
371 */
372 private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
373 {
374 return it1.nextKey().equals(it2.nextKey())
375 && it1.getIndex() == it2.getIndex()
376 && it1.isAttribute() == it2.isAttribute();
377 }
378
379 /***
380 * A specialized iterator class for tokenizing a configuration key.
381 * This class implements the normal iterator interface. In addition it
382 * provides some specific methods for configuration keys.
383 */
384 public class KeyIterator implements Iterator, Cloneable
385 {
386 /*** Stores the current key name.*/
387 private String current;
388
389 /*** Stores the start index of the actual token.*/
390 private int startIndex;
391
392 /*** Stores the end index of the actual token.*/
393 private int endIndex;
394
395 /*** Stores the index of the actual property if there is one.*/
396 private int indexValue;
397
398 /*** Stores a flag if the actual property has an index.*/
399 private boolean hasIndex;
400
401 /*** Stores a flag if the actual property is an attribute.*/
402 private boolean attribute;
403
404 /***
405 * Helper method for determining the next indices.
406 *
407 * @return the next key part
408 */
409 private String findNextIndices()
410 {
411 startIndex = endIndex;
412
413 while (startIndex < keyBuffer.length()
414 && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
415 {
416 startIndex++;
417 }
418
419
420 if (startIndex >= keyBuffer.length())
421 {
422 endIndex = keyBuffer.length();
423 startIndex = endIndex - 1;
424 return keyBuffer.substring(startIndex, endIndex);
425 }
426 else
427 {
428 return nextKeyPart();
429 }
430 }
431
432 /***
433 * Helper method for extracting the next key part. Takes escaping of
434 * delimiter characters into account.
435 *
436 * @return the next key part
437 */
438 private String nextKeyPart()
439 {
440 StringBuffer key = new StringBuffer(INITIAL_SIZE);
441 int idx = startIndex;
442 int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
443 startIndex);
444 if (endIdx < 0 || endIdx == startIndex)
445 {
446 endIdx = keyBuffer.length();
447 }
448 boolean found = false;
449
450 while (!found && idx < endIdx)
451 {
452 char c = keyBuffer.charAt(idx);
453 if (c == PROPERTY_DELIMITER)
454 {
455
456 if (idx == endIdx - 1
457 || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
458 {
459 found = true;
460 }
461 else
462 {
463 idx++;
464 }
465 }
466 if (!found)
467 {
468 key.append(c);
469 idx++;
470 }
471 }
472
473 endIndex = idx;
474 return key.toString();
475 }
476
477 /***
478 * Returns the next key part of this configuration key. This is a short
479 * form of <code>nextKey(false)</code>.
480 *
481 * @return the next key part
482 */
483 public String nextKey()
484 {
485 return nextKey(false);
486 }
487
488 /***
489 * Returns the next key part of this configuration key. The boolean
490 * parameter indicates wheter a decorated key should be returned. This
491 * affects only attribute keys: if the parameter is <b>false</b>, the
492 * attribute markers are stripped from the key; if it is <b>true</b>,
493 * they remain.
494 *
495 * @param decorated a flag if the decorated key is to be returned
496 * @return the next key part
497 */
498 public String nextKey(boolean decorated)
499 {
500 if (!hasNext())
501 {
502 throw new NoSuchElementException("No more key parts!");
503 }
504
505 hasIndex = false;
506 indexValue = -1;
507 String key = findNextIndices();
508
509 attribute = checkAttribute(key);
510 if (!attribute)
511 {
512 hasIndex = checkIndex(key);
513 if (!hasIndex)
514 {
515 current = key;
516 }
517 }
518
519 return currentKey(decorated);
520 }
521
522 /***
523 * Helper method for checking if the passed key is an attribute.
524 * If this is the case, the internal fields will be set.
525 *
526 * @param key the key to be checked
527 * @return a flag if the key is an attribute
528 */
529 private boolean checkAttribute(String key)
530 {
531 if (isAttributeKey(key))
532 {
533 current = removeAttributeMarkers(key);
534 return true;
535 }
536 else
537 {
538 return false;
539 }
540 }
541
542 /***
543 * Helper method for checking if the passed key contains an index.
544 * If this is the case, internal fields will be set.
545 *
546 * @param key the key to be checked
547 * @return a flag if an index is defined
548 */
549 private boolean checkIndex(String key)
550 {
551 boolean result = false;
552
553 int idx = key.lastIndexOf(INDEX_START);
554 if (idx > 0)
555 {
556 int endidx = key.indexOf(INDEX_END, idx);
557
558 if (endidx > idx + 1)
559 {
560 indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
561 current = key.substring(0, idx);
562 result = true;
563 }
564 }
565
566 return result;
567 }
568
569 /***
570 * Checks if there is a next element.
571 *
572 * @return a flag if there is a next element
573 */
574 public boolean hasNext()
575 {
576 return endIndex < keyBuffer.length();
577 }
578
579 /***
580 * Returns the next object in the iteration.
581 *
582 * @return the next object
583 */
584 public Object next()
585 {
586 return nextKey();
587 }
588
589 /***
590 * Removes the current object in the iteration. This method is not
591 * supported by this iterator type, so an exception is thrown.
592 */
593 public void remove()
594 {
595 throw new UnsupportedOperationException("Remove not supported!");
596 }
597
598 /***
599 * Returns the current key of the iteration (without skipping to the
600 * next element). This is the same key the previous <code>next()</code>
601 * call had returned. (Short form of <code>currentKey(false)</code>.
602 *
603 * @return the current key
604 */
605 public String currentKey()
606 {
607 return currentKey(false);
608 }
609
610 /***
611 * Returns the current key of the iteration (without skipping to the
612 * next element). The boolean parameter indicates wheter a decorated
613 * key should be returned. This affects only attribute keys: if the
614 * parameter is <b>false</b>, the attribute markers are stripped from
615 * the key; if it is <b>true</b>, they remain.
616 *
617 * @param decorated a flag if the decorated key is to be returned
618 * @return the current key
619 */
620 public String currentKey(boolean decorated)
621 {
622 return (decorated && isAttribute()) ? constructAttributeKey(current) : current;
623 }
624
625 /***
626 * Returns a flag if the current key is an attribute. This method can
627 * be called after <code>next()</code>.
628 *
629 * @return a flag if the current key is an attribute
630 */
631 public boolean isAttribute()
632 {
633 return attribute;
634 }
635
636 /***
637 * Returns the index value of the current key. If the current key does
638 * not have an index, return value is -1. This method can be called
639 * after <code>next()</code>.
640 *
641 * @return the index value of the current key
642 */
643 public int getIndex()
644 {
645 return indexValue;
646 }
647
648 /***
649 * Returns a flag if the current key has an associated index.
650 * This method can be called after <code>next()</code>.
651 *
652 * @return a flag if the current key has an index
653 */
654 public boolean hasIndex()
655 {
656 return hasIndex;
657 }
658
659 /***
660 * Creates a clone of this object.
661 *
662 * @return a clone of this object
663 */
664 public Object clone()
665 {
666 try
667 {
668 return super.clone();
669 }
670 catch (CloneNotSupportedException cex)
671 {
672
673 return null;
674 }
675 }
676 }
677 }