001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.configuration;
019
020 import java.io.Serializable;
021 import java.util.Iterator;
022 import java.util.NoSuchElementException;
023
024 /**
025 * <p>A simple class that supports creation of and iteration on complex
026 * configuration keys.</p>
027 *
028 * <p>For key creation the class works similar to a StringBuffer: There are
029 * several <code>appendXXXX()</code> methods with which single parts
030 * of a key can be constructed. All these methods return a reference to the
031 * actual object so they can be written in a chain. When using this methods
032 * the exact syntax for keys need not be known.</p>
033 *
034 * <p>This class also defines a specialized iterator for configuration keys.
035 * With such an iterator a key can be tokenized into its single parts. For
036 * each part it can be checked whether it has an associated index.</p>
037 *
038 * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
039 * @version $Id: ConfigurationKey.java 726809 2008-12-15 21:29:56Z oheger $
040 */
041 public class ConfigurationKey implements Serializable
042 {
043 /** Constant for a property delimiter.*/
044 public static final char PROPERTY_DELIMITER = '.';
045
046 /** Constant for an escaped delimiter. */
047 public static final String ESCAPED_DELIMITER =
048 String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
049
050 /** Constant for an attribute start marker.*/
051 private static final String ATTRIBUTE_START = "[@";
052
053 /** Constant for an attribute end marker.*/
054 private static final String ATTRIBUTE_END = "]";
055
056 /** Constant for an index start marker.*/
057 private static final char INDEX_START = '(';
058
059 /** Constant for an index end marker.*/
060 private static final char INDEX_END = ')';
061
062 /** Constant for the initial StringBuffer size.*/
063 private static final int INITIAL_SIZE = 32;
064
065 /**
066 * The serial version ID.
067 */
068 private static final long serialVersionUID = -4299732083605277656L;
069
070 /** Holds a buffer with the so far created key.*/
071 private StringBuffer keyBuffer;
072
073 /**
074 * Creates a new, empty instance of <code>ConfigurationKey</code>.
075 */
076 public ConfigurationKey()
077 {
078 keyBuffer = new StringBuffer(INITIAL_SIZE);
079 }
080
081 /**
082 * Creates a new instance of <code>ConfigurationKey</code> and
083 * initializes it with the given key.
084 *
085 * @param key the key as a string
086 */
087 public ConfigurationKey(String key)
088 {
089 keyBuffer = new StringBuffer(key);
090 removeTrailingDelimiter();
091 }
092
093 /**
094 * Appends the name of a property to this key. If necessary, a
095 * property delimiter will be added.
096 *
097 * @param property the name of the property to be added
098 * @return a reference to this object
099 */
100 public ConfigurationKey append(String property)
101 {
102 if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
103 {
104 keyBuffer.append(PROPERTY_DELIMITER);
105 }
106
107 keyBuffer.append(property);
108 removeTrailingDelimiter();
109 return this;
110 }
111
112 /**
113 * Appends an index to this configuration key.
114 *
115 * @param index the index to be appended
116 * @return a reference to this object
117 */
118 public ConfigurationKey appendIndex(int index)
119 {
120 keyBuffer.append(INDEX_START).append(index);
121 keyBuffer.append(INDEX_END);
122 return this;
123 }
124
125 /**
126 * Appends an attribute to this configuration key.
127 *
128 * @param attr the name of the attribute to be appended
129 * @return a reference to this object
130 */
131 public ConfigurationKey appendAttribute(String attr)
132 {
133 keyBuffer.append(constructAttributeKey(attr));
134 return this;
135 }
136
137 /**
138 * Checks if this key is an attribute key.
139 *
140 * @return a flag if this key is an attribute key
141 */
142 public boolean isAttributeKey()
143 {
144 return isAttributeKey(keyBuffer.toString());
145 }
146
147 /**
148 * Checks if the passed in key is an attribute key. Such attribute keys
149 * start and end with certain marker strings. In some cases they must be
150 * treated slightly different.
151 *
152 * @param key the key (part) to be checked
153 * @return a flag if this key is an attribute key
154 */
155 public static boolean isAttributeKey(String key)
156 {
157 return key != null
158 && key.startsWith(ATTRIBUTE_START)
159 && key.endsWith(ATTRIBUTE_END);
160 }
161
162 /**
163 * Decorates the given key so that it represents an attribute. Adds
164 * special start and end markers.
165 *
166 * @param key the key to be decorated
167 * @return the decorated attribute key
168 */
169 public static String constructAttributeKey(String key)
170 {
171 StringBuffer buf = new StringBuffer();
172 buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
173 return buf.toString();
174 }
175
176 /**
177 * Extracts the name of the attribute from the given attribute key.
178 * This method removes the attribute markers - if any - from the
179 * specified key.
180 *
181 * @param key the attribute key
182 * @return the name of the corresponding attribute
183 */
184 public static String attributeName(String key)
185 {
186 return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
187 }
188
189 /**
190 * Helper method for removing attribute markers from a key.
191 *
192 * @param key the key
193 * @return the key with removed attribute markers
194 */
195 static String removeAttributeMarkers(String key)
196 {
197 return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
198 }
199
200 /**
201 * Helper method that checks if the actual buffer ends with a property
202 * delimiter.
203 *
204 * @return a flag if there is a trailing delimiter
205 */
206 private boolean hasDelimiter()
207 {
208 int count = 0;
209 for (int idx = keyBuffer.length() - 1; idx >= 0
210 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
211 {
212 count++;
213 }
214 return count % 2 != 0;
215 }
216
217 /**
218 * Removes a trailing delimiter if there is any.
219 */
220 private void removeTrailingDelimiter()
221 {
222 while (hasDelimiter())
223 {
224 keyBuffer.deleteCharAt(keyBuffer.length() - 1);
225 }
226 }
227
228 /**
229 * Returns a string representation of this object. This is the
230 * configuration key as a plain string.
231 *
232 * @return a string for this object
233 */
234 public String toString()
235 {
236 return keyBuffer.toString();
237 }
238
239 /**
240 * Returns an iterator for iterating over the single components of
241 * this configuration key.
242 *
243 * @return an iterator for this key
244 */
245 public KeyIterator iterator()
246 {
247 return new KeyIterator();
248 }
249
250 /**
251 * Returns the actual length of this configuration key.
252 *
253 * @return the length of this key
254 */
255 public int length()
256 {
257 return keyBuffer.length();
258 }
259
260 /**
261 * Sets the new length of this configuration key. With this method it is
262 * possible to truncate the key, e.g. to return to a state prior calling
263 * some <code>append()</code> methods. The semantic is the same as
264 * the <code>setLength()</code> method of <code>StringBuffer</code>.
265 *
266 * @param len the new length of the key
267 */
268 public void setLength(int len)
269 {
270 keyBuffer.setLength(len);
271 }
272
273 /**
274 * Checks if two <code>ConfigurationKey</code> objects are equal. The
275 * method can be called with strings or other objects, too.
276 *
277 * @param c the object to compare
278 * @return a flag if both objects are equal
279 */
280 public boolean equals(Object c)
281 {
282 if (c == null)
283 {
284 return false;
285 }
286
287 return keyBuffer.toString().equals(c.toString());
288 }
289
290 /**
291 * Returns the hash code for this object.
292 *
293 * @return the hash code
294 */
295 public int hashCode()
296 {
297 return String.valueOf(keyBuffer).hashCode();
298 }
299
300 /**
301 * Returns a configuration key object that is initialized with the part
302 * of the key that is common to this key and the passed in key.
303 *
304 * @param other the other key
305 * @return a key object with the common key part
306 */
307 public ConfigurationKey commonKey(ConfigurationKey other)
308 {
309 if (other == null)
310 {
311 throw new IllegalArgumentException("Other key must no be null!");
312 }
313
314 ConfigurationKey result = new ConfigurationKey();
315 KeyIterator it1 = iterator();
316 KeyIterator it2 = other.iterator();
317
318 while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
319 {
320 if (it1.isAttribute())
321 {
322 result.appendAttribute(it1.currentKey());
323 }
324 else
325 {
326 result.append(it1.currentKey());
327 if (it1.hasIndex)
328 {
329 result.appendIndex(it1.getIndex());
330 }
331 }
332 }
333
334 return result;
335 }
336
337 /**
338 * Returns the "difference key" to a given key. This value
339 * is the part of the passed in key that differs from this key. There is
340 * the following relation:
341 * <code>other = key.commonKey(other) + key.differenceKey(other)</code>
342 * for an arbitrary configuration key <code>key</code>.
343 *
344 * @param other the key for which the difference is to be calculated
345 * @return the difference key
346 */
347 public ConfigurationKey differenceKey(ConfigurationKey other)
348 {
349 ConfigurationKey common = commonKey(other);
350 ConfigurationKey result = new ConfigurationKey();
351
352 if (common.length() < other.length())
353 {
354 String k = other.toString().substring(common.length());
355 // skip trailing delimiters
356 int i = 0;
357 while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
358 {
359 i++;
360 }
361
362 if (i < k.length())
363 {
364 result.append(k.substring(i));
365 }
366 }
367
368 return result;
369 }
370
371 /**
372 * Helper method for comparing two key parts.
373 *
374 * @param it1 the iterator with the first part
375 * @param it2 the iterator with the second part
376 * @return a flag if both parts are equal
377 */
378 private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
379 {
380 return it1.nextKey().equals(it2.nextKey())
381 && it1.getIndex() == it2.getIndex()
382 && it1.isAttribute() == it2.isAttribute();
383 }
384
385 /**
386 * A specialized iterator class for tokenizing a configuration key.
387 * This class implements the normal iterator interface. In addition it
388 * provides some specific methods for configuration keys.
389 */
390 public class KeyIterator implements Iterator, Cloneable
391 {
392 /** Stores the current key name.*/
393 private String current;
394
395 /** Stores the start index of the actual token.*/
396 private int startIndex;
397
398 /** Stores the end index of the actual token.*/
399 private int endIndex;
400
401 /** Stores the index of the actual property if there is one.*/
402 private int indexValue;
403
404 /** Stores a flag if the actual property has an index.*/
405 private boolean hasIndex;
406
407 /** Stores a flag if the actual property is an attribute.*/
408 private boolean attribute;
409
410 /**
411 * Helper method for determining the next indices.
412 *
413 * @return the next key part
414 */
415 private String findNextIndices()
416 {
417 startIndex = endIndex;
418 // skip empty names
419 while (startIndex < keyBuffer.length()
420 && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
421 {
422 startIndex++;
423 }
424
425 // Key ends with a delimiter?
426 if (startIndex >= keyBuffer.length())
427 {
428 endIndex = keyBuffer.length();
429 startIndex = endIndex - 1;
430 return keyBuffer.substring(startIndex, endIndex);
431 }
432 else
433 {
434 return nextKeyPart();
435 }
436 }
437
438 /**
439 * Helper method for extracting the next key part. Takes escaping of
440 * delimiter characters into account.
441 *
442 * @return the next key part
443 */
444 private String nextKeyPart()
445 {
446 StringBuffer key = new StringBuffer(INITIAL_SIZE);
447 int idx = startIndex;
448 int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
449 startIndex);
450 if (endIdx < 0 || endIdx == startIndex)
451 {
452 endIdx = keyBuffer.length();
453 }
454 boolean found = false;
455
456 while (!found && idx < endIdx)
457 {
458 char c = keyBuffer.charAt(idx);
459 if (c == PROPERTY_DELIMITER)
460 {
461 // a duplicated delimiter means escaping
462 if (idx == endIdx - 1
463 || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
464 {
465 found = true;
466 }
467 else
468 {
469 idx++;
470 }
471 }
472 if (!found)
473 {
474 key.append(c);
475 idx++;
476 }
477 }
478
479 endIndex = idx;
480 return key.toString();
481 }
482
483 /**
484 * Returns the next key part of this configuration key. This is a short
485 * form of <code>nextKey(false)</code>.
486 *
487 * @return the next key part
488 */
489 public String nextKey()
490 {
491 return nextKey(false);
492 }
493
494 /**
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>,
499 * they remain.
500 *
501 * @param decorated a flag if the decorated key is to be returned
502 * @return the next key part
503 */
504 public String nextKey(boolean decorated)
505 {
506 if (!hasNext())
507 {
508 throw new NoSuchElementException("No more key parts!");
509 }
510
511 hasIndex = false;
512 indexValue = -1;
513 String key = findNextIndices();
514
515 current = key;
516 hasIndex = checkIndex(key);
517 attribute = checkAttribute(current);
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 // should not happen
673 return null;
674 }
675 }
676 }
677 }