##### Coverage Report - org.apache.commons.configuration.tree.DefaultConfigurationKey

Classes in this File | Line Coverage | Branch Coverage | Complexity
DefaultConfigurationKey |
99% | 71/72
100% | 19/19
| 1.9555555555555555;1,956
DefaultConfigurationKey$KeyIterator |
93% | 71/76
100% | 19/19
| 1.9555555555555555;1,956

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 439648 2006-09-02 20:42:10Z oheger $
51 |  |
      */
52 | 208517 |
     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 | 28 |
         {
71 | 28 |
             keyBuffer = new StringBuffer(INITIAL_SIZE);
72 | 28 |
             setExpressionEngine(engine);
73 | 28 |
         }
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 | 14037 |
         {
84 | 14037 |
             setExpressionEngine(engine);
85 | 14037 |
             keyBuffer = new StringBuffer(trim(key));
86 | 14037 |
         }
87 |  |
88 |  |
         /**
89 |  |
          * Returns the associated default expression engine.
90 |  |
          *
91 |  |
          * @return the associated expression engine
92 |  |
          */
93 |  |
         public DefaultExpressionEngine getExpressionEngine()
94 |  |
         {
95 | 315289 |
             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 | 14066 |
             if (expressionEngine == null)
106 |  |
             {
107 | 1 |
                 throw new IllegalArgumentException(
108 |  |
                         "Expression engine must not be null!");
109 |  |
             }
110 | 14065 |
             this.expressionEngine = expressionEngine;
111 | 14065 |
         }
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 | 2913 |
             if (escape && property != null)
127 |  |
             {
128 | 2877 |
                 key = escapeDelimiters(property);
129 | 2877 |
             }
130 |  |
             else
131 |  |
             {
132 | 36 |
                 key = property;
133 |  |
             }
134 | 2913 |
             key = trim(key);
135 |  |
136 | 2913 |
             if (keyBuffer.length() > 0 && !isAttributeKey(property)
137 |  |
                     && key.length() > 0)
138 |  |
             {
139 | 2124 |
                 keyBuffer.append(getExpressionEngine().getPropertyDelimiter());
140 |  |
             }
141 |  |
142 | 2913 |
             keyBuffer.append(key);
143 | 2913 |
             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 | 35 |
             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 | 4 |
             keyBuffer.append(getExpressionEngine().getIndexStart());
168 | 4 |
             keyBuffer.append(index);
169 | 4 |
             keyBuffer.append(getExpressionEngine().getIndexEnd());
170 | 4 |
             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 | 773 |
             keyBuffer.append(constructAttributeKey(attr));
182 | 773 |
             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 | 71934 |
             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 | 1 |
             keyBuffer.setLength(len);
206 | 1 |
         }
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 | 6 |
             if (c == null)
218 |  |
             {
219 | 1 |
                 return false;
220 |  |
             }
221 |  |
222 | 5 |
             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 | 2 |
             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 | 3662 |
             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 | 24394 |
             if (key == null)
256 |  |
             {
257 | 4 |
                 return false;
258 |  |
             }
259 |  |
260 | 24390 |
             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 | 780 |
             if (key == null)
276 |  |
             {
277 | 2 |
                 return StringUtils.EMPTY;
278 |  |
             }
279 | 778 |
             if (isAttributeKey(key))
280 |  |
             {
281 | 3 |
                 return key;
282 |  |
             }
283 |  |
             else
284 |  |
             {
285 | 775 |
                 StringBuffer buf = new StringBuffer();
286 | 775 |
                 buf.append(getExpressionEngine().getAttributeStart()).append(key);
287 | 775 |
                 if (getExpressionEngine().getAttributeEnd() != null)
288 |  |
                 {
289 | 772 |
                     buf.append(getExpressionEngine().getAttributeEnd());
290 |  |
                 }
291 | 775 |
                 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 | 3 |
             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 | 16955 |
             if (key == null)
316 |  |
             {
317 | 107 |
                 return StringUtils.EMPTY;
318 |  |
             }
319 |  |
             else
320 |  |
             {
321 | 16848 |
                 String result = key;
322 | 16864 |
                 while (hasLeadingDelimiter(result))
323 |  |
                 {
324 | 16 |
                     result = result.substring(getExpressionEngine()
325 |  |
                             .getPropertyDelimiter().length());
326 | 16 |
                 }
327 | 16848 |
                 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 | 16955 |
             if (key == null)
340 |  |
             {
341 | 0 |
                 return StringUtils.EMPTY;
342 |  |
             }
343 |  |
             else
344 |  |
             {
345 | 16955 |
                 String result = key;
346 | 16969 |
                 while (hasTrailingDelimiter(result))
347 |  |
                 {
348 | 14 |
                     result = result
349 |  |
                             .substring(0, result.length()
350 |  |
                                     - getExpressionEngine().getPropertyDelimiter()
351 |  |
                                             .length());
352 | 14 |
                 }
353 | 16955 |
                 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 | 16953 |
             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 | 10399 |
             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 | 16969 |
             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 | 47652 |
             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 | 2234 |
             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 | 21479 |
             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 | 2877 |
             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 | 10399 |
         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 | 1106 |
                 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 | 21480 |
                 if (!hasNext())
502 |  |
                 {
503 | 1 |
                     throw new NoSuchElementException("No more key parts!");
504 |  |
                 }
505 |  |
506 | 21479 |
                 hasIndex = false;
507 | 21479 |
                 indexValue = -1;
508 | 21479 |
                 String key = findNextIndices();
509 |  |
510 | 21479 |
                 current = key;
511 | 21479 |
                 hasIndex = checkIndex(key);
512 | 21479 |
                 attribute = checkAttribute(current);
513 |  |
514 | 21479 |
                 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 | 58792 |
                 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 | 1083 |
                 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 | 1 |
                 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 | 5794 |
                 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 | 27278 |
                 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 | 13483 |
                 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 | 22329 |
                 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 | 1483 |
                 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 | 16534 |
                 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 | 14197 |
                     return super.clone();
635 |  |
                 }
636 | 0 |
                 catch (CloneNotSupportedException cex)
637 |  |
                 {
638 |  |
                     // should not happen
639 | 0 |
                     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 | 21479 |
                 startIndex = endIndex;
651 |  |
                 // skip empty names
652 |  |
                 while (startIndex < length()
653 | 30788 |
                         && hasLeadingDelimiter(keyBuffer.substring(startIndex)))
654 |  |
                 {
655 | 9309 |
                     startIndex += getExpressionEngine().getPropertyDelimiter()
656 |  |
                             .length();
657 | 9309 |
                 }
658 |  |
659 |  |
                 // Key ends with a delimiter?
660 | 21479 |
                 if (startIndex >= length())
661 |  |
                 {
662 | 0 |
                     endIndex = length();
663 | 0 |
                     startIndex = endIndex - 1;
664 | 0 |
                     return keyBuffer.substring(startIndex, endIndex);
665 |  |
                 }
666 |  |
                 else
667 |  |
                 {
668 | 21479 |
                     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 | 21479 |
                 int attrIdx = keyBuffer.toString().indexOf(
681 |  |
                         getExpressionEngine().getAttributeStart(), startIndex);
682 | 21479 |
                 if (attrIdx < 0 || attrIdx == startIndex)
683 |  |
                 {
684 | 19664 |
                     attrIdx = length();
685 |  |
                 }
686 |  |
687 | 21479 |
                 int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex,
688 |  |
                         attrIdx);
689 | 21479 |
                 if (delIdx < 0)
690 |  |
                 {
691 | 12803 |
                     delIdx = attrIdx;
692 |  |
                 }
693 |  |
694 | 21479 |
                 endIndex = Math.min(attrIdx, delIdx);
695 | 21479 |
                 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 | 21479 |
                 int delimiterPos = pos;
709 | 21479 |
                 boolean found = false;
710 |  |
711 |  |
                 do
712 |  |
                 {
713 | 21672 |
                     delimiterPos = key.indexOf(getExpressionEngine()
714 |  |
                             .getPropertyDelimiter(), delimiterPos);
715 | 21672 |
                     if (delimiterPos < 0 || delimiterPos >= endPos)
716 |  |
                     {
717 | 12803 |
                         return -1;
718 |  |
                     }
719 | 8869 |
                     int escapePos = escapedPosition(key, delimiterPos);
720 | 8869 |
                     if (escapePos < 0)
721 |  |
                     {
722 | 8676 |
                         found = true;
723 | 8676 |
                     }
724 |  |
                     else
725 |  |
                     {
726 | 193 |
                         delimiterPos = escapePos;
727 |  |
                     }
728 |  |
                 }
729 | 8869 |
                 while (!found);
730 |  |
731 | 8676 |
                 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 | 8869 |
                 if (getExpressionEngine().getEscapedDelimiter() == null)
746 |  |
                 {
747 |  |
                     // nothing to escape
748 | 13 |
                     return -1;
749 |  |
                 }
750 | 8856 |
                 int escapeOffset = escapeOffset();
751 | 8856 |
                 if (escapeOffset < 0 || escapeOffset > pos)
752 |  |
                 {
753 |  |
                     // No escaping possible at this position
754 | 55 |
                     return -1;
755 |  |
                 }
756 |  |
757 | 8801 |
                 int escapePos = key.indexOf(getExpressionEngine()
758 |  |
                         .getEscapedDelimiter(), pos - escapeOffset);
759 | 8801 |
                 if (escapePos <= pos && escapePos >= 0)
760 |  |
                 {
761 |  |
                     // The found delimiter is escaped. Next valid search position
762 |  |
                     // is behind the escaped delimiter.
763 | 193 |
                     return escapePos
764 |  |
                             + getExpressionEngine().getEscapedDelimiter().length();
765 |  |
                 }
766 |  |
                 else
767 |  |
                 {
768 | 8608 |
                     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 | 8856 |
                 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 | 21479 |
                 if (isAttributeKey(key))
805 |  |
                 {
806 | 2233 |
                     current = removeAttributeMarkers(key);
807 | 2233 |
                     return true;
808 |  |
                 }
809 |  |
                 else
810 |  |
                 {
811 | 19246 |
                     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 | 21479 |
                 boolean result = false;
825 |  |
826 | 21479 |
                 int idx = key.lastIndexOf(getExpressionEngine().getIndexStart());
827 | 21479 |
                 if (idx > 0)
828 |  |
                 {
829 | 731 |
                     int endidx = key.indexOf(getExpressionEngine().getIndexEnd(),
830 |  |
                             idx);
831 |  |
832 | 731 |
                     if (endidx > idx + 1)
833 |  |
                     {
834 | 729 |
                         indexValue = Integer.parseInt(key
835 |  |
                                 .substring(idx + 1, endidx));
836 | 729 |
                         current = key.substring(0, idx);
837 | 729 |
                         result = true;
838 |  |
                     }
839 |  |
                 }
840 |  |
841 | 21479 |
                 return result;
842 |  |
             }
843 |  |
844 |  |
             /**
845 |  |
              * Returns a flag whether attributes are marked the same way as normal
846 |  |
              * property keys. We call this the &quot;attribute emulating mode&quot;.
847 |  |
              * When navigating through node hierarchies it might be convenient to
848 |  |
              * treat attributes the same way than other child nodes, so an
849 |  |
              * expression engine supports to set the attribute markers to the same
850 |  |
              * value than the property delimiter. If this is the case, some special
851 |  |
              * checks have to be performed.
852 |  |
              *
853 |  |
              * @return a flag if attributes and normal property keys are treated the
854 |  |
              * same way
855 |  |
              */
856 |  |
             private boolean isAttributeEmulatingMode()
857 |  |
             {
858 | 11365 |
                 return getExpressionEngine().getAttributeEnd() == null
859 |  |
                         && StringUtils.equals(getExpressionEngine()
860 |  |
                                 .getPropertyDelimiter(), getExpressionEngine()
861 |  |
                                 .getAttributeStart());
862 |  |
             }
863 |  |
         }
864 |  |
     }