View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.Serializable;
21  import java.util.Iterator;
22  import java.util.NoSuchElementException;
23  
24  /***
25   * <p>A simple class that supports creation of and iteration on complex
26   * configuration keys.</p>
27   *
28   * <p>For key creation the class works similar to a StringBuffer: There are
29   * several <code>appendXXXX()</code> methods with which single parts
30   * of a key can be constructed. All these methods return a reference to the
31   * actual object so they can be written in a chain. When using this methods
32   * the exact syntax for keys need not be known.</p>
33   *
34   * <p>This class also defines a specialized iterator for configuration keys.
35   * With such an iterator a key can be tokenized into its single parts. For
36   * each part it can be checked whether it has an associated index.</p>
37   *
38   * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
39   * @version $Id: ConfigurationKey.java 439648 2006-09-02 20:42:10Z oheger $
40   */
41  public class ConfigurationKey implements Serializable
42  {
43      /*** Constant for a property delimiter.*/
44      public static final char PROPERTY_DELIMITER = '.';
45  
46      /*** Constant for an escaped delimiter. */
47      public static final String ESCAPED_DELIMITER =
48          String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
49  
50      /*** Constant for an attribute start marker.*/
51      private static final String ATTRIBUTE_START = "[@";
52  
53      /*** Constant for an attribute end marker.*/
54      private static final String ATTRIBUTE_END = "]";
55  
56      /*** Constant for an index start marker.*/
57      private static final char INDEX_START = '(';
58  
59      /*** Constant for an index end marker.*/
60      private static final char INDEX_END = ')';
61  
62      /*** Constant for the initial StringBuffer size.*/
63      private static final int INITIAL_SIZE = 32;
64  
65      /***
66       * The serial version ID.
67       */
68      private static final long serialVersionUID = -4299732083605277656L;
69  
70      /*** Holds a buffer with the so far created key.*/
71      private StringBuffer keyBuffer;
72  
73      /***
74       * Creates a new, empty instance of <code>ConfigurationKey</code>.
75       */
76      public ConfigurationKey()
77      {
78          keyBuffer = new StringBuffer(INITIAL_SIZE);
79      }
80  
81      /***
82       * Creates a new instance of <code>ConfigurationKey</code> and
83       * initializes it with the given key.
84       *
85       * @param key the key as a string
86       */
87      public ConfigurationKey(String key)
88      {
89          keyBuffer = new StringBuffer(key);
90          removeTrailingDelimiter();
91      }
92  
93      /***
94       * Appends the name of a property to this key. If necessary, a
95       * property delimiter will be added.
96       *
97       * @param property the name of the property to be added
98       * @return a reference to this object
99       */
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 == 1;
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 &quot;difference key&quot; 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 }