~ubuntu-branches/ubuntu/saucy/commons-configuration/saucy

« back to all changes in this revision

Viewing changes to src/main/java/org/apache/commons/configuration/tree/DefaultConfigurationKey.java

  • Committer: Package Import Robot
  • Author(s): Emmanuel Bourg
  • Date: 2013-07-01 16:29:44 UTC
  • mfrom: (1.1.4)
  • Revision ID: package-import@ubuntu.com-20130701162944-98waq5gogha5gpn1
Tags: 1.9-1
* New upstream release
* debian/control:
  - Updated Standards-Version to 3.9.4 (no changes)
  - Use canonical URLs for the Vcs-* fields
  - Added new build dependencies (libjavacc-maven-plugin-java, junit4)
  - Upgraded the dependency on the Servlet API (2.5 -> 3.0)
  - Removed the dependency on the Activation Framework (glassfish-activation)
  - Replaced the dependency on glassfish-mail with libgnumail-java
  - Removed the unused dependencies:
    liblog4j1.2-java-doc, libmaven-assembly-plugin-java
  - Replaced the dependency on libcommons-jexl-java by libcommons-jexl2-java
* debian/watch: Changed to point the official Apache distribution server
* Removed the obsolete file debian/ant.properties
* Installed the upstream changelog in the binary packages
* Added the report plugins to maven.ignoreRules
* Added the classpath attribute to the jar manifest

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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
package org.apache.commons.configuration.tree;
 
18
 
 
19
import java.util.Iterator;
 
20
import java.util.NoSuchElementException;
 
21
 
 
22
import org.apache.commons.lang.StringUtils;
 
23
 
 
24
/**
 
25
 * <p>
 
26
 * A simple class that supports creation of and iteration on configuration keys
 
27
 * supported by a {@link DefaultExpressionEngine} object.
 
28
 * </p>
 
29
 * <p>
 
30
 * For key creation the class works similar to a StringBuffer: There are several
 
31
 * {@code appendXXXX()} methods with which single parts of a key can be
 
32
 * constructed. All these methods return a reference to the actual object so
 
33
 * they can be written in a chain. When using this methods the exact syntax for
 
34
 * keys need not be known.
 
35
 * </p>
 
36
 * <p>
 
37
 * This class also defines a specialized iterator for configuration keys. With
 
38
 * such an iterator a key can be tokenized into its single parts. For each part
 
39
 * it can be checked whether it has an associated index.
 
40
 * </p>
 
41
 * <p>
 
42
 * Instances of this class are always associated with an instance of
 
43
 * {@link DefaultExpressionEngine}, from which the current
 
44
 * delimiters are obtained. So key creation and parsing is specific to this
 
45
 * associated expression engine.
 
46
 * </p>
 
47
 *
 
48
 * @since 1.3
 
49
 * @author <a
 
50
 * href="http://commons.apache.org/configuration/team-list.html">Commons
 
51
 * Configuration team</a>
 
52
 * @version $Id: DefaultConfigurationKey.java 1231724 2012-01-15 18:40:31Z oheger $
 
53
 */
 
54
public class DefaultConfigurationKey
 
55
{
 
56
    /** Constant for the initial StringBuffer size. */
 
57
    private static final int INITIAL_SIZE = 32;
 
58
 
 
59
    /** Stores a reference to the associated expression engine. */
 
60
    private DefaultExpressionEngine expressionEngine;
 
61
 
 
62
    /** Holds a buffer with the so far created key. */
 
63
    private StringBuilder keyBuffer;
 
64
 
 
65
    /**
 
66
     * Creates a new instance of {@code DefaultConfigurationKey} and sets
 
67
     * the associated expression engine.
 
68
     *
 
69
     * @param engine the expression engine
 
70
     */
 
71
    public DefaultConfigurationKey(DefaultExpressionEngine engine)
 
72
    {
 
73
        keyBuffer = new StringBuilder(INITIAL_SIZE);
 
74
        setExpressionEngine(engine);
 
75
    }
 
76
 
 
77
    /**
 
78
     * Creates a new instance of {@code DefaultConfigurationKey} and sets
 
79
     * the associated expression engine and an initial key.
 
80
     *
 
81
     * @param engine the expression engine
 
82
     * @param key the key to be wrapped
 
83
     */
 
84
    public DefaultConfigurationKey(DefaultExpressionEngine engine, String key)
 
85
    {
 
86
        setExpressionEngine(engine);
 
87
        keyBuffer = new StringBuilder(trim(key));
 
88
    }
 
89
 
 
90
    /**
 
91
     * Returns the associated default expression engine.
 
92
     *
 
93
     * @return the associated expression engine
 
94
     */
 
95
    public DefaultExpressionEngine getExpressionEngine()
 
96
    {
 
97
        return expressionEngine;
 
98
    }
 
99
 
 
100
    /**
 
101
     * Sets the associated expression engine.
 
102
     *
 
103
     * @param expressionEngine the expression engine (must not be <b>null</b>)
 
104
     */
 
105
    public void setExpressionEngine(DefaultExpressionEngine expressionEngine)
 
106
    {
 
107
        if (expressionEngine == null)
 
108
        {
 
109
            throw new IllegalArgumentException(
 
110
                    "Expression engine must not be null!");
 
111
        }
 
112
        this.expressionEngine = expressionEngine;
 
113
    }
 
114
 
 
115
    /**
 
116
     * Appends the name of a property to this key. If necessary, a property
 
117
     * delimiter will be added. If the boolean argument is set to <b>true</b>,
 
118
     * property delimiters contained in the property name will be escaped.
 
119
     *
 
120
     * @param property the name of the property to be added
 
121
     * @param escape a flag if property delimiters in the passed in property name
 
122
     * should be escaped
 
123
     * @return a reference to this object
 
124
     */
 
125
    public DefaultConfigurationKey append(String property, boolean escape)
 
126
    {
 
127
        String key;
 
128
        if (escape && property != null)
 
129
        {
 
130
            key = escapeDelimiters(property);
 
131
        }
 
132
        else
 
133
        {
 
134
            key = property;
 
135
        }
 
136
        key = trim(key);
 
137
 
 
138
        if (keyBuffer.length() > 0 && !isAttributeKey(property)
 
139
                && key.length() > 0)
 
140
        {
 
141
            keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
 
142
        }
 
143
 
 
144
        keyBuffer.append(key);
 
145
        return this;
 
146
    }
 
147
 
 
148
    /**
 
149
     * Appends the name of a property to this key. If necessary, a property
 
150
     * delimiter will be added. Property delimiters in the given string will not
 
151
     * be escaped.
 
152
     *
 
153
     * @param property the name of the property to be added
 
154
     * @return a reference to this object
 
155
     */
 
156
    public DefaultConfigurationKey append(String property)
 
157
    {
 
158
        return append(property, false);
 
159
    }
 
160
 
 
161
    /**
 
162
     * Appends an index to this configuration key.
 
163
     *
 
164
     * @param index the index to be appended
 
165
     * @return a reference to this object
 
166
     */
 
167
    public DefaultConfigurationKey appendIndex(int index)
 
168
    {
 
169
        keyBuffer.append(getExpressionEngine().getIndexStart());
 
170
        keyBuffer.append(index);
 
171
        keyBuffer.append(getExpressionEngine().getIndexEnd());
 
172
        return this;
 
173
    }
 
174
 
 
175
    /**
 
176
     * Appends an attribute to this configuration key.
 
177
     *
 
178
     * @param attr the name of the attribute to be appended
 
179
     * @return a reference to this object
 
180
     */
 
181
    public DefaultConfigurationKey appendAttribute(String attr)
 
182
    {
 
183
        keyBuffer.append(constructAttributeKey(attr));
 
184
        return this;
 
185
    }
 
186
 
 
187
    /**
 
188
     * Returns the actual length of this configuration key.
 
189
     *
 
190
     * @return the length of this key
 
191
     */
 
192
    public int length()
 
193
    {
 
194
        return keyBuffer.length();
 
195
    }
 
196
 
 
197
    /**
 
198
     * Sets the new length of this configuration key. With this method it is
 
199
     * possible to truncate the key, e.g. to return to a state prior calling
 
200
     * some {@code append()} methods. The semantic is the same as the
 
201
     * {@code setLength()} method of {@code StringBuilder}.
 
202
     *
 
203
     * @param len the new length of the key
 
204
     */
 
205
    public void setLength(int len)
 
206
    {
 
207
        keyBuffer.setLength(len);
 
208
    }
 
209
 
 
210
    /**
 
211
     * Checks if two {@code ConfigurationKey} objects are equal. The
 
212
     * method can be called with strings or other objects, too.
 
213
     *
 
214
     * @param c the object to compare
 
215
     * @return a flag if both objects are equal
 
216
     */
 
217
    @Override
 
218
    public boolean equals(Object c)
 
219
    {
 
220
        if (c == null)
 
221
        {
 
222
            return false;
 
223
        }
 
224
 
 
225
        return keyBuffer.toString().equals(c.toString());
 
226
    }
 
227
 
 
228
    /**
 
229
     * Returns the hash code for this object.
 
230
     *
 
231
     * @return the hash code
 
232
     */
 
233
    @Override
 
234
    public int hashCode()
 
235
    {
 
236
        return String.valueOf(keyBuffer).hashCode();
 
237
    }
 
238
 
 
239
    /**
 
240
     * Returns a string representation of this object. This is the configuration
 
241
     * key as a plain string.
 
242
     *
 
243
     * @return a string for this object
 
244
     */
 
245
    @Override
 
246
    public String toString()
 
247
    {
 
248
        return keyBuffer.toString();
 
249
    }
 
250
 
 
251
    /**
 
252
     * Tests if the specified key represents an attribute according to the
 
253
     * current expression engine.
 
254
     *
 
255
     * @param key the key to be checked
 
256
     * @return <b>true</b> if this is an attribute key, <b>false</b> otherwise
 
257
     */
 
258
    public boolean isAttributeKey(String key)
 
259
    {
 
260
        if (key == null)
 
261
        {
 
262
            return false;
 
263
        }
 
264
 
 
265
        return key.startsWith(getExpressionEngine().getAttributeStart())
 
266
                && (getExpressionEngine().getAttributeEnd() == null || key
 
267
                        .endsWith(getExpressionEngine().getAttributeEnd()));
 
268
    }
 
269
 
 
270
    /**
 
271
     * Decorates the given key so that it represents an attribute. Adds special
 
272
     * start and end markers. The passed in string will be modified only if does
 
273
     * not already represent an attribute.
 
274
     *
 
275
     * @param key the key to be decorated
 
276
     * @return the decorated attribute key
 
277
     */
 
278
    public String constructAttributeKey(String key)
 
279
    {
 
280
        if (key == null)
 
281
        {
 
282
            return StringUtils.EMPTY;
 
283
        }
 
284
        if (isAttributeKey(key))
 
285
        {
 
286
            return key;
 
287
        }
 
288
        else
 
289
        {
 
290
            StringBuilder buf = new StringBuilder();
 
291
            buf.append(getExpressionEngine().getAttributeStart()).append(key);
 
292
            if (getExpressionEngine().getAttributeEnd() != null)
 
293
            {
 
294
                buf.append(getExpressionEngine().getAttributeEnd());
 
295
            }
 
296
            return buf.toString();
 
297
        }
 
298
    }
 
299
 
 
300
    /**
 
301
     * Extracts the name of the attribute from the given attribute key. This
 
302
     * method removes the attribute markers - if any - from the specified key.
 
303
     *
 
304
     * @param key the attribute key
 
305
     * @return the name of the corresponding attribute
 
306
     */
 
307
    public String attributeName(String key)
 
308
    {
 
309
        return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
 
310
    }
 
311
 
 
312
    /**
 
313
     * Removes leading property delimiters from the specified key.
 
314
     *
 
315
     * @param key the key
 
316
     * @return the key with removed leading property delimiters
 
317
     */
 
318
    public String trimLeft(String key)
 
319
    {
 
320
        if (key == null)
 
321
        {
 
322
            return StringUtils.EMPTY;
 
323
        }
 
324
        else
 
325
        {
 
326
            String result = key;
 
327
            while (hasLeadingDelimiter(result))
 
328
            {
 
329
                result = result.substring(getExpressionEngine()
 
330
                        .getPropertyDelimiter().length());
 
331
            }
 
332
            return result;
 
333
        }
 
334
    }
 
335
 
 
336
    /**
 
337
     * Removes trailing property delimiters from the specified key.
 
338
     *
 
339
     * @param key the key
 
340
     * @return the key with removed trailing property delimiters
 
341
     */
 
342
    public String trimRight(String key)
 
343
    {
 
344
        if (key == null)
 
345
        {
 
346
            return StringUtils.EMPTY;
 
347
        }
 
348
        else
 
349
        {
 
350
            String result = key;
 
351
            while (hasTrailingDelimiter(result))
 
352
            {
 
353
                result = result
 
354
                        .substring(0, result.length()
 
355
                                - getExpressionEngine().getPropertyDelimiter()
 
356
                                        .length());
 
357
            }
 
358
            return result;
 
359
        }
 
360
    }
 
361
 
 
362
    /**
 
363
     * Removes delimiters at the beginning and the end of the specified key.
 
364
     *
 
365
     * @param key the key
 
366
     * @return the key with removed property delimiters
 
367
     */
 
368
    public String trim(String key)
 
369
    {
 
370
        return trimRight(trimLeft(key));
 
371
    }
 
372
 
 
373
    /**
 
374
     * Returns an iterator for iterating over the single components of this
 
375
     * configuration key.
 
376
     *
 
377
     * @return an iterator for this key
 
378
     */
 
379
    public KeyIterator iterator()
 
380
    {
 
381
        return new KeyIterator();
 
382
    }
 
383
 
 
384
    /**
 
385
     * Helper method that checks if the specified key ends with a property
 
386
     * delimiter.
 
387
     *
 
388
     * @param key the key to check
 
389
     * @return a flag if there is a trailing delimiter
 
390
     */
 
391
    private boolean hasTrailingDelimiter(String key)
 
392
    {
 
393
        return key.endsWith(getExpressionEngine().getPropertyDelimiter())
 
394
                && (getExpressionEngine().getEscapedDelimiter() == null || !key
 
395
                        .endsWith(getExpressionEngine().getEscapedDelimiter()));
 
396
    }
 
397
 
 
398
    /**
 
399
     * Helper method that checks if the specified key starts with a property
 
400
     * delimiter.
 
401
     *
 
402
     * @param key the key to check
 
403
     * @return a flag if there is a leading delimiter
 
404
     */
 
405
    private boolean hasLeadingDelimiter(String key)
 
406
    {
 
407
        return key.startsWith(getExpressionEngine().getPropertyDelimiter())
 
408
                && (getExpressionEngine().getEscapedDelimiter() == null || !key
 
409
                        .startsWith(getExpressionEngine().getEscapedDelimiter()));
 
410
    }
 
411
 
 
412
    /**
 
413
     * Helper method for removing attribute markers from a key.
 
414
     *
 
415
     * @param key the key
 
416
     * @return the key with removed attribute markers
 
417
     */
 
418
    private String removeAttributeMarkers(String key)
 
419
    {
 
420
        return key
 
421
                .substring(
 
422
                        getExpressionEngine().getAttributeStart().length(),
 
423
                        key.length()
 
424
                                - ((getExpressionEngine().getAttributeEnd() != null) ? getExpressionEngine()
 
425
                                        .getAttributeEnd().length()
 
426
                                        : 0));
 
427
    }
 
428
 
 
429
    /**
 
430
     * Unescapes the delimiters in the specified string.
 
431
     *
 
432
     * @param key the key to be unescaped
 
433
     * @return the unescaped key
 
434
     */
 
435
    private String unescapeDelimiters(String key)
 
436
    {
 
437
        return (getExpressionEngine().getEscapedDelimiter() == null) ? key
 
438
                : StringUtils.replace(key, getExpressionEngine()
 
439
                        .getEscapedDelimiter(), getExpressionEngine()
 
440
                        .getPropertyDelimiter());
 
441
    }
 
442
 
 
443
    /**
 
444
     * Escapes the delimiters in the specified string.
 
445
     *
 
446
     * @param key the key to be escaped
 
447
     * @return the escaped key
 
448
     */
 
449
    private String escapeDelimiters(String key)
 
450
    {
 
451
        return (getExpressionEngine().getEscapedDelimiter() == null || key
 
452
                .indexOf(getExpressionEngine().getPropertyDelimiter()) < 0) ? key
 
453
                : StringUtils.replace(key, getExpressionEngine()
 
454
                        .getPropertyDelimiter(), getExpressionEngine()
 
455
                        .getEscapedDelimiter());
 
456
    }
 
457
 
 
458
    /**
 
459
     * A specialized iterator class for tokenizing a configuration key. This
 
460
     * class implements the normal iterator interface. In addition it provides
 
461
     * some specific methods for configuration keys.
 
462
     */
 
463
    public class KeyIterator implements Iterator<Object>, Cloneable
 
464
    {
 
465
        /** Stores the current key name. */
 
466
        private String current;
 
467
 
 
468
        /** Stores the start index of the actual token. */
 
469
        private int startIndex;
 
470
 
 
471
        /** Stores the end index of the actual token. */
 
472
        private int endIndex;
 
473
 
 
474
        /** Stores the index of the actual property if there is one. */
 
475
        private int indexValue;
 
476
 
 
477
        /** Stores a flag if the actual property has an index. */
 
478
        private boolean hasIndex;
 
479
 
 
480
        /** Stores a flag if the actual property is an attribute. */
 
481
        private boolean attribute;
 
482
 
 
483
        /**
 
484
         * Returns the next key part of this configuration key. This is a short
 
485
         * form of {@code nextKey(false)}.
 
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
         * Checks if there is a next element.
 
524
         *
 
525
         * @return a flag if there is a next element
 
526
         */
 
527
        public boolean hasNext()
 
528
        {
 
529
            return endIndex < keyBuffer.length();
 
530
        }
 
531
 
 
532
        /**
 
533
         * Returns the next object in the iteration.
 
534
         *
 
535
         * @return the next object
 
536
         */
 
537
        public Object next()
 
538
        {
 
539
            return nextKey();
 
540
        }
 
541
 
 
542
        /**
 
543
         * Removes the current object in the iteration. This method is not
 
544
         * supported by this iterator type, so an exception is thrown.
 
545
         */
 
546
        public void remove()
 
547
        {
 
548
            throw new UnsupportedOperationException("Remove not supported!");
 
549
        }
 
550
 
 
551
        /**
 
552
         * Returns the current key of the iteration (without skipping to the
 
553
         * next element). This is the same key the previous {@code next()}
 
554
         * call had returned. (Short form of {@code currentKey(false)}.
 
555
         *
 
556
         * @return the current key
 
557
         */
 
558
        public String currentKey()
 
559
        {
 
560
            return currentKey(false);
 
561
        }
 
562
 
 
563
        /**
 
564
         * Returns the current key of the iteration (without skipping to the
 
565
         * next element). The boolean parameter indicates wheter a decorated key
 
566
         * should be returned. This affects only attribute keys: if the
 
567
         * parameter is <b>false</b>, the attribute markers are stripped from
 
568
         * the key; if it is <b>true</b>, they remain.
 
569
         *
 
570
         * @param decorated a flag if the decorated key is to be returned
 
571
         * @return the current key
 
572
         */
 
573
        public String currentKey(boolean decorated)
 
574
        {
 
575
            return (decorated && !isPropertyKey()) ? constructAttributeKey(current)
 
576
                    : current;
 
577
        }
 
578
 
 
579
        /**
 
580
         * Returns a flag if the current key is an attribute. This method can be
 
581
         * called after {@code next()}.
 
582
         *
 
583
         * @return a flag if the current key is an attribute
 
584
         */
 
585
        public boolean isAttribute()
 
586
        {
 
587
            // if attribute emulation mode is active, the last part of a key is
 
588
            // always an attribute key, too
 
589
            return attribute || (isAttributeEmulatingMode() && !hasNext());
 
590
        }
 
591
 
 
592
        /**
 
593
         * Returns a flag whether the current key refers to a property (i.e. is
 
594
         * no special attribute key). Usually this method will return the
 
595
         * opposite of {@code isAttribute()}, but if the delimiters for
 
596
         * normal properties and attributes are set to the same string, it is
 
597
         * possible that both methods return <b>true</b>.
 
598
         *
 
599
         * @return a flag if the current key is a property key
 
600
         * @see #isAttribute()
 
601
         */
 
602
        public boolean isPropertyKey()
 
603
        {
 
604
            return !attribute;
 
605
        }
 
606
 
 
607
        /**
 
608
         * Returns the index value of the current key. If the current key does
 
609
         * not have an index, return value is -1. This method can be called
 
610
         * after {@code next()}.
 
611
         *
 
612
         * @return the index value of the current key
 
613
         */
 
614
        public int getIndex()
 
615
        {
 
616
            return indexValue;
 
617
        }
 
618
 
 
619
        /**
 
620
         * Returns a flag if the current key has an associated index. This
 
621
         * method can be called after {@code next()}.
 
622
         *
 
623
         * @return a flag if the current key has an index
 
624
         */
 
625
        public boolean hasIndex()
 
626
        {
 
627
            return hasIndex;
 
628
        }
 
629
 
 
630
        /**
 
631
         * Creates a clone of this object.
 
632
         *
 
633
         * @return a clone of this object
 
634
         */
 
635
        @Override
 
636
        public Object clone()
 
637
        {
 
638
            try
 
639
            {
 
640
                return super.clone();
 
641
            }
 
642
            catch (CloneNotSupportedException cex)
 
643
            {
 
644
                // should not happen
 
645
                return null;
 
646
            }
 
647
        }
 
648
 
 
649
        /**
 
650
         * Helper method for determining the next indices.
 
651
         *
 
652
         * @return the next key part
 
653
         */
 
654
        private String findNextIndices()
 
655
        {
 
656
            startIndex = endIndex;
 
657
            // skip empty names
 
658
            while (startIndex < length()
 
659
                    && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
 
660
            {
 
661
                startIndex += getExpressionEngine().getPropertyDelimiter()
 
662
                        .length();
 
663
            }
 
664
 
 
665
            // Key ends with a delimiter?
 
666
            if (startIndex >= length())
 
667
            {
 
668
                endIndex = length();
 
669
                startIndex = endIndex - 1;
 
670
                return keyBuffer.substring(startIndex, endIndex);
 
671
            }
 
672
            else
 
673
            {
 
674
                return nextKeyPart();
 
675
            }
 
676
        }
 
677
 
 
678
        /**
 
679
         * Helper method for extracting the next key part. Takes escaping of
 
680
         * delimiter characters into account.
 
681
         *
 
682
         * @return the next key part
 
683
         */
 
684
        private String nextKeyPart()
 
685
        {
 
686
            int attrIdx = keyBuffer.toString().indexOf(
 
687
                    getExpressionEngine().getAttributeStart(), startIndex);
 
688
            if (attrIdx < 0 || attrIdx == startIndex)
 
689
            {
 
690
                attrIdx = length();
 
691
            }
 
692
 
 
693
            int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
 
694
                    attrIdx);
 
695
            if (delIdx < 0)
 
696
            {
 
697
                delIdx = attrIdx;
 
698
            }
 
699
 
 
700
            endIndex = Math.min(attrIdx, delIdx);
 
701
            return unescapeDelimiters(keyBuffer.substring(startIndex, endIndex));
 
702
        }
 
703
 
 
704
        /**
 
705
         * Searches the next unescaped delimiter from the given position.
 
706
         *
 
707
         * @param key the key
 
708
         * @param pos the start position
 
709
         * @param endPos the end position
 
710
         * @return the position of the next delimiter or -1 if there is none
 
711
         */
 
712
        private int nextDelimiterPos(String key, int pos, int endPos)
 
713
        {
 
714
            int delimiterPos = pos;
 
715
            boolean found = false;
 
716
 
 
717
            do
 
718
            {
 
719
                delimiterPos = key.indexOf(getExpressionEngine()
 
720
                        .getPropertyDelimiter(), delimiterPos);
 
721
                if (delimiterPos < 0 || delimiterPos >= endPos)
 
722
                {
 
723
                    return -1;
 
724
                }
 
725
                int escapePos = escapedPosition(key, delimiterPos);
 
726
                if (escapePos < 0)
 
727
                {
 
728
                    found = true;
 
729
                }
 
730
                else
 
731
                {
 
732
                    delimiterPos = escapePos;
 
733
                }
 
734
            }
 
735
            while (!found);
 
736
 
 
737
            return delimiterPos;
 
738
        }
 
739
 
 
740
        /**
 
741
         * Checks if a delimiter at the specified position is escaped. If this
 
742
         * is the case, the next valid search position will be returned.
 
743
         * Otherwise the return value is -1.
 
744
         *
 
745
         * @param key the key to check
 
746
         * @param pos the position where a delimiter was found
 
747
         * @return information about escaped delimiters
 
748
         */
 
749
        private int escapedPosition(String key, int pos)
 
750
        {
 
751
            if (getExpressionEngine().getEscapedDelimiter() == null)
 
752
            {
 
753
                // nothing to escape
 
754
                return -1;
 
755
            }
 
756
            int escapeOffset = escapeOffset();
 
757
            if (escapeOffset < 0 || escapeOffset > pos)
 
758
            {
 
759
                // No escaping possible at this position
 
760
                return -1;
 
761
            }
 
762
 
 
763
            int escapePos = key.indexOf(getExpressionEngine()
 
764
                    .getEscapedDelimiter(), pos - escapeOffset);
 
765
            if (escapePos <= pos && escapePos >= 0)
 
766
            {
 
767
                // The found delimiter is escaped. Next valid search position
 
768
                // is behind the escaped delimiter.
 
769
                return escapePos
 
770
                        + getExpressionEngine().getEscapedDelimiter().length();
 
771
            }
 
772
            else
 
773
            {
 
774
                return -1;
 
775
            }
 
776
        }
 
777
 
 
778
        /**
 
779
         * Determines the relative offset of an escaped delimiter in relation to
 
780
         * a delimiter. Depending on the used delimiter and escaped delimiter
 
781
         * tokens the position where to search for an escaped delimiter is
 
782
         * different. If, for instance, the dot character (&quot;.&quot;) is
 
783
         * used as delimiter, and a doubled dot (&quot;..&quot;) as escaped
 
784
         * delimiter, the escaped delimiter starts at the same position as the
 
785
         * delimiter. If the token &quot;\.&quot; was used, it would start one
 
786
         * character before the delimiter because the delimiter character
 
787
         * &quot;.&quot; is the second character in the escaped delimiter
 
788
         * string. This relation will be determined by this method. For this to
 
789
         * work the delimiter string must be contained in the escaped delimiter
 
790
         * string.
 
791
         *
 
792
         * @return the relative offset of the escaped delimiter in relation to a
 
793
         * delimiter
 
794
         */
 
795
        private int escapeOffset()
 
796
        {
 
797
            return getExpressionEngine().getEscapedDelimiter().indexOf(
 
798
                    getExpressionEngine().getPropertyDelimiter());
 
799
        }
 
800
 
 
801
        /**
 
802
         * Helper method for checking if the passed key is an attribute. If this
 
803
         * is the case, the internal fields will be set.
 
804
         *
 
805
         * @param key the key to be checked
 
806
         * @return a flag if the key is an attribute
 
807
         */
 
808
        private boolean checkAttribute(String key)
 
809
        {
 
810
            if (isAttributeKey(key))
 
811
            {
 
812
                current = removeAttributeMarkers(key);
 
813
                return true;
 
814
            }
 
815
            else
 
816
            {
 
817
                return false;
 
818
            }
 
819
        }
 
820
 
 
821
        /**
 
822
         * Helper method for checking if the passed key contains an index. If
 
823
         * this is the case, internal fields will be set.
 
824
         *
 
825
         * @param key the key to be checked
 
826
         * @return a flag if an index is defined
 
827
         */
 
828
        private boolean checkIndex(String key)
 
829
        {
 
830
            boolean result = false;
 
831
 
 
832
            try
 
833
            {
 
834
                int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
 
835
                if (idx > 0)
 
836
                {
 
837
                    int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
 
838
                            idx);
 
839
 
 
840
                    if (endidx > idx + 1)
 
841
                    {
 
842
                        indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
 
843
                        current = key.substring(0, idx);
 
844
                        result = true;
 
845
                    }
 
846
                }
 
847
            }
 
848
            catch (NumberFormatException nfe)
 
849
            {
 
850
                result = false;
 
851
            }
 
852
 
 
853
            return result;
 
854
        }
 
855
 
 
856
        /**
 
857
         * Returns a flag whether attributes are marked the same way as normal
 
858
         * property keys. We call this the &quot;attribute emulating mode&quot;.
 
859
         * When navigating through node hierarchies it might be convenient to
 
860
         * treat attributes the same way than other child nodes, so an
 
861
         * expression engine supports to set the attribute markers to the same
 
862
         * value than the property delimiter. If this is the case, some special
 
863
         * checks have to be performed.
 
864
         *
 
865
         * @return a flag if attributes and normal property keys are treated the
 
866
         * same way
 
867
         */
 
868
        private boolean isAttributeEmulatingMode()
 
869
        {
 
870
            return getExpressionEngine().getAttributeEnd() == null
 
871
                    && StringUtils.equals(getExpressionEngine()
 
872
                            .getPropertyDelimiter(), getExpressionEngine()
 
873
                            .getAttributeStart());
 
874
        }
 
875
    }
 
876
}