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