001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.configuration;
019    
020    import java.io.Serializable;
021    import java.util.Iterator;
022    import java.util.NoSuchElementException;
023    
024    /**
025     * <p>A simple class that supports creation of and iteration on complex
026     * configuration keys.</p>
027     *
028     * <p>For key creation the class works similar to a StringBuffer: There are
029     * several <code>appendXXXX()</code> methods with which single parts
030     * of a key can be constructed. All these methods return a reference to the
031     * actual object so they can be written in a chain. When using this methods
032     * the exact syntax for keys need not be known.</p>
033     *
034     * <p>This class also defines a specialized iterator for configuration keys.
035     * With such an iterator a key can be tokenized into its single parts. For
036     * each part it can be checked whether it has an associated index.</p>
037     *
038     * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
039     * @version $Id: ConfigurationKey.java 726809 2008-12-15 21:29:56Z oheger $
040     */
041    public class ConfigurationKey implements Serializable
042    {
043        /** Constant for a property delimiter.*/
044        public static final char PROPERTY_DELIMITER = '.';
045    
046        /** Constant for an escaped delimiter. */
047        public static final String ESCAPED_DELIMITER =
048            String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
049    
050        /** Constant for an attribute start marker.*/
051        private static final String ATTRIBUTE_START = "[@";
052    
053        /** Constant for an attribute end marker.*/
054        private static final String ATTRIBUTE_END = "]";
055    
056        /** Constant for an index start marker.*/
057        private static final char INDEX_START = '(';
058    
059        /** Constant for an index end marker.*/
060        private static final char INDEX_END = ')';
061    
062        /** Constant for the initial StringBuffer size.*/
063        private static final int INITIAL_SIZE = 32;
064    
065        /**
066         * The serial version ID.
067         */
068        private static final long serialVersionUID = -4299732083605277656L;
069    
070        /** Holds a buffer with the so far created key.*/
071        private StringBuffer keyBuffer;
072    
073        /**
074         * Creates a new, empty instance of <code>ConfigurationKey</code>.
075         */
076        public ConfigurationKey()
077        {
078            keyBuffer = new StringBuffer(INITIAL_SIZE);
079        }
080    
081        /**
082         * Creates a new instance of <code>ConfigurationKey</code> and
083         * initializes it with the given key.
084         *
085         * @param key the key as a string
086         */
087        public ConfigurationKey(String key)
088        {
089            keyBuffer = new StringBuffer(key);
090            removeTrailingDelimiter();
091        }
092    
093        /**
094         * Appends the name of a property to this key. If necessary, a
095         * property delimiter will be added.
096         *
097         * @param property the name of the property to be added
098         * @return a reference to this object
099         */
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 != 0;
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    }