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