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

« back to all changes in this revision

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