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,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 &quot;difference key&quot; 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             // skip trailing delimiters
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             // skip empty names
392             while (startIndex < keyBuffer.length()
393             && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
394             {
395                 startIndex++;
396             }
397 
398             // Key ends with a delimiter?
399             if (startIndex >= keyBuffer.length())
400             {
401                 endIndex = keyBuffer.length();
402                 startIndex = endIndex - 1;
403             }
404             else
405             {
406                 String s = keyBuffer.toString();    // for compatibility
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                 // should not happen
616                 return null;
617             }
618         }
619     }
620 }