View Javadoc

1   /*
2    * Copyright 2004 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 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 &quot;difference key&quot; 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             // skip trailing delimiters
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             // skip empty names
402             while (startIndex < keyBuffer.length()
403             && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
404             {
405                 startIndex++;
406             }
407 
408             // Key ends with a delimiter?
409             if (startIndex >= keyBuffer.length())
410             {
411                 endIndex = keyBuffer.length();
412                 startIndex = endIndex - 1;
413             }
414             else
415             {
416                 String s = keyBuffer.toString();    // for compatibility
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                 // should not happen
626                 return null;
627             }
628         }
629     }
630 }