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
018package org.apache.commons.configuration;
019
020import java.io.Serializable;
021import java.util.Iterator;
022import 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.html 901729 2014-03-15 20:24:09Z 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
047public 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}