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 StringBuilder: There are
029     * several {@code appendXXXX()} 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
039     * href="http://commons.apache.org/configuration/team-list.html">Commons
040     * Configuration team</a>
041     * @version $Id: ConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $
042     * @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey}
043     * instead. It is associated with a {@code DefaultExpressionEngine} and thus
044     * can produce correct keys even if key separators have been changed.
045     */
046    @Deprecated
047    public class ConfigurationKey implements Serializable
048    {
049        /** Constant for a property delimiter.*/
050        public static final char PROPERTY_DELIMITER = '.';
051    
052        /** Constant for an escaped delimiter. */
053        public static final String ESCAPED_DELIMITER =
054            String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
055    
056        /** Constant for an attribute start marker.*/
057        private static final String ATTRIBUTE_START = "[@";
058    
059        /** Constant for an attribute end marker.*/
060        private static final String ATTRIBUTE_END = "]";
061    
062        /** Constant for an index start marker.*/
063        private static final char INDEX_START = '(';
064    
065        /** Constant for an index end marker.*/
066        private static final char INDEX_END = ')';
067    
068        /** Constant for the initial StringBuilder size.*/
069        private static final int INITIAL_SIZE = 32;
070    
071        /**
072         * The serial version ID.
073         */
074        private static final long serialVersionUID = -4299732083605277656L;
075    
076        /** Holds a buffer with the so far created key.*/
077        private StringBuilder keyBuffer;
078    
079        /**
080         * Creates a new, empty instance of {@code ConfigurationKey}.
081         */
082        public ConfigurationKey()
083        {
084            keyBuffer = new StringBuilder(INITIAL_SIZE);
085        }
086    
087        /**
088         * Creates a new instance of {@code ConfigurationKey} and
089         * initializes it with the given key.
090         *
091         * @param key the key as a string
092         */
093        public ConfigurationKey(String key)
094        {
095            keyBuffer = new StringBuilder(key);
096            removeTrailingDelimiter();
097        }
098    
099        /**
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    }