View Javadoc

1   /*
2    * Copyright 2004-2005 The Apache Software Foundation.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License")
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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 &quot;difference key&quot; 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             // skip trailing delimiters
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             // skip empty names
413             while (startIndex < keyBuffer.length()
414                     && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
415             {
416                 startIndex++;
417             }
418 
419             // Key ends with a delimiter?
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                     // a duplicated delimiter means escaping
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                 // should not happen
673                 return null;
674             }
675         }
676     }
677 }