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 StringBuilder: There are
29   * several {@code appendXXXX()} 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
39   * href="http://commons.apache.org/configuration/team-list.html">Commons
40   * Configuration team</a>
41   * @version $Id: ConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $
42   * @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey}
43   * instead. It is associated with a {@code DefaultExpressionEngine} and thus
44   * can produce correct keys even if key separators have been changed.
45   */
46  @Deprecated
47  public class ConfigurationKey implements Serializable
48  {
49      /** Constant for a property delimiter.*/
50      public static final char PROPERTY_DELIMITER = '.';
51  
52      /** Constant for an escaped delimiter. */
53      public static final String ESCAPED_DELIMITER =
54          String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
55  
56      /** Constant for an attribute start marker.*/
57      private static final String ATTRIBUTE_START = "[@";
58  
59      /** Constant for an attribute end marker.*/
60      private static final String ATTRIBUTE_END = "]";
61  
62      /** Constant for an index start marker.*/
63      private static final char INDEX_START = '(';
64  
65      /** Constant for an index end marker.*/
66      private static final char INDEX_END = ')';
67  
68      /** Constant for the initial StringBuilder size.*/
69      private static final int INITIAL_SIZE = 32;
70  
71      /**
72       * The serial version ID.
73       */
74      private static final long serialVersionUID = -4299732083605277656L;
75  
76      /** Holds a buffer with the so far created key.*/
77      private StringBuilder keyBuffer;
78  
79      /**
80       * Creates a new, empty instance of {@code ConfigurationKey}.
81       */
82      public ConfigurationKey()
83      {
84          keyBuffer = new StringBuilder(INITIAL_SIZE);
85      }
86  
87      /**
88       * Creates a new instance of {@code ConfigurationKey} and
89       * initializes it with the given key.
90       *
91       * @param key the key as a string
92       */
93      public ConfigurationKey(String key)
94      {
95          keyBuffer = new StringBuilder(key);
96          removeTrailingDelimiter();
97      }
98  
99      /**
100      * Appends the name of a property to this key. If necessary, a
101      * property delimiter will be added.
102      *
103      * @param property the name of the property to be added
104      * @return a reference to this object
105      */
106     public ConfigurationKey append(String property)
107     {
108         if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
109         {
110             keyBuffer.append(PROPERTY_DELIMITER);
111         }
112 
113         keyBuffer.append(property);
114         removeTrailingDelimiter();
115         return this;
116     }
117 
118     /**
119      * Appends an index to this configuration key.
120      *
121      * @param index the index to be appended
122      * @return a reference to this object
123      */
124     public ConfigurationKey appendIndex(int index)
125     {
126         keyBuffer.append(INDEX_START).append(index);
127         keyBuffer.append(INDEX_END);
128         return this;
129     }
130 
131     /**
132      * Appends an attribute to this configuration key.
133      *
134      * @param attr the name of the attribute to be appended
135      * @return a reference to this object
136      */
137     public ConfigurationKey appendAttribute(String attr)
138     {
139         keyBuffer.append(constructAttributeKey(attr));
140         return this;
141     }
142 
143     /**
144      * Checks if this key is an attribute key.
145      *
146      * @return a flag if this key is an attribute key
147      */
148     public boolean isAttributeKey()
149     {
150         return isAttributeKey(keyBuffer.toString());
151     }
152 
153     /**
154      * Checks if the passed in key is an attribute key. Such attribute keys
155      * start and end with certain marker strings. In some cases they must be
156      * treated slightly different.
157      *
158      * @param key the key (part) to be checked
159      * @return a flag if this key is an attribute key
160      */
161     public static boolean isAttributeKey(String key)
162     {
163         return key != null
164         && key.startsWith(ATTRIBUTE_START)
165         && key.endsWith(ATTRIBUTE_END);
166     }
167 
168     /**
169      * Decorates the given key so that it represents an attribute. Adds
170      * special start and end markers.
171      *
172      * @param key the key to be decorated
173      * @return the decorated attribute key
174      */
175     public static String constructAttributeKey(String key)
176     {
177         StringBuilder buf = new StringBuilder();
178         buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
179         return buf.toString();
180     }
181 
182     /**
183      * Extracts the name of the attribute from the given attribute key.
184      * This method removes the attribute markers - if any - from the
185      * specified key.
186      *
187      * @param key the attribute key
188      * @return the name of the corresponding attribute
189      */
190     public static String attributeName(String key)
191     {
192         return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
193     }
194 
195     /**
196      * Helper method for removing attribute markers from a key.
197      *
198      * @param key the key
199      * @return the key with removed attribute markers
200      */
201     static String removeAttributeMarkers(String key)
202     {
203         return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
204     }
205 
206     /**
207      * Helper method that checks if the actual buffer ends with a property
208      * delimiter.
209      *
210      * @return a flag if there is a trailing delimiter
211      */
212     private boolean hasDelimiter()
213     {
214         int count = 0;
215         for (int idx = keyBuffer.length() - 1; idx >= 0
216                 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
217         {
218             count++;
219         }
220         return count % 2 != 0;
221     }
222 
223     /**
224      * Removes a trailing delimiter if there is any.
225      */
226     private void removeTrailingDelimiter()
227     {
228         while (hasDelimiter())
229         {
230             keyBuffer.deleteCharAt(keyBuffer.length() - 1);
231         }
232     }
233 
234     /**
235      * Returns a string representation of this object. This is the
236      * configuration key as a plain string.
237      *
238      * @return a string for this object
239      */
240     @Override
241     public String toString()
242     {
243         return keyBuffer.toString();
244     }
245 
246     /**
247      * Returns an iterator for iterating over the single components of
248      * this configuration key.
249      *
250      * @return an iterator for this key
251      */
252     public KeyIterator iterator()
253     {
254         return new KeyIterator();
255     }
256 
257     /**
258      * Returns the actual length of this configuration key.
259      *
260      * @return the length of this key
261      */
262     public int length()
263     {
264         return keyBuffer.length();
265     }
266 
267     /**
268      * Sets the new length of this configuration key. With this method it is
269      * possible to truncate the key, e.g. to return to a state prior calling
270      * some {@code append()} methods. The semantic is the same as
271      * the {@code setLength()} method of {@code StringBuilder}.
272      *
273      * @param len the new length of the key
274      */
275     public void setLength(int len)
276     {
277         keyBuffer.setLength(len);
278     }
279 
280     /**
281      * Checks if two {@code ConfigurationKey} objects are equal. The
282      * method can be called with strings or other objects, too.
283      *
284      * @param c the object to compare
285      * @return a flag if both objects are equal
286      */
287     @Override
288     public boolean equals(Object c)
289     {
290         if (c == null)
291         {
292             return false;
293         }
294 
295         return keyBuffer.toString().equals(c.toString());
296     }
297 
298     /**
299      * Returns the hash code for this object.
300      *
301      * @return the hash code
302      */
303     @Override
304     public int hashCode()
305     {
306         return String.valueOf(keyBuffer).hashCode();
307     }
308 
309     /**
310      * Returns a configuration key object that is initialized with the part
311      * of the key that is common to this key and the passed in key.
312      *
313      * @param other the other key
314      * @return a key object with the common key part
315      */
316     public ConfigurationKey commonKey(ConfigurationKey other)
317     {
318         if (other == null)
319         {
320             throw new IllegalArgumentException("Other key must no be null!");
321         }
322 
323         ConfigurationKey result = new ConfigurationKey();
324         KeyIterator it1 = iterator();
325         KeyIterator it2 = other.iterator();
326 
327         while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
328         {
329             if (it1.isAttribute())
330             {
331                 result.appendAttribute(it1.currentKey());
332             }
333             else
334             {
335                 result.append(it1.currentKey());
336                 if (it1.hasIndex)
337                 {
338                     result.appendIndex(it1.getIndex());
339                 }
340             }
341         }
342 
343         return result;
344     }
345 
346     /**
347      * Returns the &quot;difference key&quot; to a given key. This value
348      * is the part of the passed in key that differs from this key. There is
349      * the following relation:
350      * {@code other = key.commonKey(other) + key.differenceKey(other)}
351      * for an arbitrary configuration key {@code key}.
352      *
353      * @param other the key for which the difference is to be calculated
354      * @return the difference key
355      */
356     public ConfigurationKey differenceKey(ConfigurationKey other)
357     {
358         ConfigurationKey common = commonKey(other);
359         ConfigurationKey result = new ConfigurationKey();
360 
361         if (common.length() < other.length())
362         {
363             String k = other.toString().substring(common.length());
364             // skip trailing delimiters
365             int i = 0;
366             while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
367             {
368                 i++;
369             }
370 
371             if (i < k.length())
372             {
373                 result.append(k.substring(i));
374             }
375         }
376 
377         return result;
378     }
379 
380     /**
381      * Helper method for comparing two key parts.
382      *
383      * @param it1 the iterator with the first part
384      * @param it2 the iterator with the second part
385      * @return a flag if both parts are equal
386      */
387     private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
388     {
389         return it1.nextKey().equals(it2.nextKey())
390         && it1.getIndex() == it2.getIndex()
391         && it1.isAttribute() == it2.isAttribute();
392     }
393 
394     /**
395      * A specialized iterator class for tokenizing a configuration key.
396      * This class implements the normal iterator interface. In addition it
397      * provides some specific methods for configuration keys.
398      */
399     public class KeyIterator implements Iterator<Object>, Cloneable
400     {
401         /** Stores the current key name.*/
402         private String current;
403 
404         /** Stores the start index of the actual token.*/
405         private int startIndex;
406 
407         /** Stores the end index of the actual token.*/
408         private int endIndex;
409 
410         /** Stores the index of the actual property if there is one.*/
411         private int indexValue;
412 
413         /** Stores a flag if the actual property has an index.*/
414         private boolean hasIndex;
415 
416         /** Stores a flag if the actual property is an attribute.*/
417         private boolean attribute;
418 
419         /**
420          * Helper method for determining the next indices.
421          *
422          * @return the next key part
423          */
424         private String findNextIndices()
425         {
426             startIndex = endIndex;
427             // skip empty names
428             while (startIndex < keyBuffer.length()
429                     && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
430             {
431                 startIndex++;
432             }
433 
434             // Key ends with a delimiter?
435             if (startIndex >= keyBuffer.length())
436             {
437                 endIndex = keyBuffer.length();
438                 startIndex = endIndex - 1;
439                 return keyBuffer.substring(startIndex, endIndex);
440             }
441             else
442             {
443                 return nextKeyPart();
444             }
445         }
446 
447         /**
448          * Helper method for extracting the next key part. Takes escaping of
449          * delimiter characters into account.
450          *
451          * @return the next key part
452          */
453         private String nextKeyPart()
454         {
455             StringBuilder key = new StringBuilder(INITIAL_SIZE);
456             int idx = startIndex;
457             int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
458                     startIndex);
459             if (endIdx < 0 || endIdx == startIndex)
460             {
461                 endIdx = keyBuffer.length();
462             }
463             boolean found = false;
464 
465             while (!found && idx < endIdx)
466             {
467                 char c = keyBuffer.charAt(idx);
468                 if (c == PROPERTY_DELIMITER)
469                 {
470                     // a duplicated delimiter means escaping
471                     if (idx == endIdx - 1
472                             || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
473                     {
474                         found = true;
475                     }
476                     else
477                     {
478                         idx++;
479                     }
480                 }
481                 if (!found)
482                 {
483                     key.append(c);
484                     idx++;
485                 }
486             }
487 
488             endIndex = idx;
489             return key.toString();
490         }
491 
492         /**
493          * Returns the next key part of this configuration key. This is a short
494          * form of {@code nextKey(false)}.
495          *
496          * @return the next key part
497          */
498         public String nextKey()
499         {
500             return nextKey(false);
501         }
502 
503         /**
504          * Returns the next key part of this configuration key. The boolean
505          * parameter indicates wheter a decorated key should be returned. This
506          * affects only attribute keys: if the parameter is <b>false</b>, the
507          * attribute markers are stripped from the key; if it is <b>true</b>,
508          * they remain.
509          *
510          * @param decorated a flag if the decorated key is to be returned
511          * @return the next key part
512          */
513         public String nextKey(boolean decorated)
514         {
515             if (!hasNext())
516             {
517                 throw new NoSuchElementException("No more key parts!");
518             }
519 
520             hasIndex = false;
521             indexValue = -1;
522             String key = findNextIndices();
523 
524             current = key;
525             hasIndex = checkIndex(key);
526             attribute = checkAttribute(current);
527 
528             return currentKey(decorated);
529         }
530 
531         /**
532          * Helper method for checking if the passed key is an attribute.
533          * If this is the case, the internal fields will be set.
534          *
535          * @param key the key to be checked
536          * @return a flag if the key is an attribute
537          */
538         private boolean checkAttribute(String key)
539         {
540             if (isAttributeKey(key))
541             {
542                 current = removeAttributeMarkers(key);
543                 return true;
544             }
545             else
546             {
547                 return false;
548             }
549         }
550 
551         /**
552          * Helper method for checking if the passed key contains an index.
553          * If this is the case, internal fields will be set.
554          *
555          * @param key the key to be checked
556          * @return a flag if an index is defined
557          */
558         private boolean checkIndex(String key)
559         {
560             boolean result = false;
561 
562             int idx = key.lastIndexOf(INDEX_START);
563             if (idx > 0)
564             {
565                 int endidx = key.indexOf(INDEX_END, idx);
566 
567                 if (endidx > idx + 1)
568                 {
569                     indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
570                     current = key.substring(0, idx);
571                     result = true;
572                 }
573             }
574 
575             return result;
576         }
577 
578         /**
579          * Checks if there is a next element.
580          *
581          * @return a flag if there is a next element
582          */
583         public boolean hasNext()
584         {
585             return endIndex < keyBuffer.length();
586         }
587 
588         /**
589          * Returns the next object in the iteration.
590          *
591          * @return the next object
592          */
593         public Object next()
594         {
595             return nextKey();
596         }
597 
598         /**
599          * Removes the current object in the iteration. This method is not
600          * supported by this iterator type, so an exception is thrown.
601          */
602         public void remove()
603         {
604             throw new UnsupportedOperationException("Remove not supported!");
605         }
606 
607         /**
608          * Returns the current key of the iteration (without skipping to the
609          * next element). This is the same key the previous {@code next()}
610          * call had returned. (Short form of {@code currentKey(false)}.
611          *
612          * @return the current key
613          */
614         public String currentKey()
615         {
616             return currentKey(false);
617         }
618 
619         /**
620          * Returns the current key of the iteration (without skipping to the
621          * next element). The boolean parameter indicates wheter a decorated
622          * key should be returned. This affects only attribute keys: if the
623          * parameter is <b>false</b>, the attribute markers are stripped from
624          * the key; if it is <b>true</b>, they remain.
625          *
626          * @param decorated a flag if the decorated key is to be returned
627          * @return the current key
628          */
629         public String currentKey(boolean decorated)
630         {
631             return (decorated && isAttribute()) ? constructAttributeKey(current) : current;
632         }
633 
634         /**
635          * Returns a flag if the current key is an attribute. This method can
636          * be called after {@code next()}.
637          *
638          * @return a flag if the current key is an attribute
639          */
640         public boolean isAttribute()
641         {
642             return attribute;
643         }
644 
645         /**
646          * Returns the index value of the current key. If the current key does
647          * not have an index, return value is -1. This method can be called
648          * after {@code next()}.
649          *
650          * @return the index value of the current key
651          */
652         public int getIndex()
653         {
654             return indexValue;
655         }
656 
657         /**
658          * Returns a flag if the current key has an associated index.
659          * This method can be called after {@code next()}.
660          *
661          * @return a flag if the current key has an index
662          */
663         public boolean hasIndex()
664         {
665             return hasIndex;
666         }
667 
668         /**
669          * Creates a clone of this object.
670          *
671          * @return a clone of this object
672          */
673         @Override
674         public Object clone()
675         {
676             try
677             {
678                 return super.clone();
679             }
680             catch (CloneNotSupportedException cex)
681             {
682                 // should not happen
683                 return null;
684             }
685         }
686     }
687 }