Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
DefaultConfigurationKey |
|
| 1.9555555555555555;1,956 | ||||
DefaultConfigurationKey$KeyIterator |
|
| 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 | 115002 | 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 | 5534 | { |
84 | 5534 | setExpressionEngine(engine); |
85 | 5534 | keyBuffer = new StringBuffer(trim(key)); |
86 | 5534 | } |
87 | ||
88 | /** | |
89 | * Returns the associated default expression engine. | |
90 | * | |
91 | * @return the associated expression engine | |
92 | */ | |
93 | public DefaultExpressionEngine getExpressionEngine() | |
94 | { | |
95 | 174131 | 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 | 5563 | if (expressionEngine == null) |
106 | { | |
107 | 1 | throw new IllegalArgumentException( |
108 | "Expression engine must not be null!"); | |
109 | } | |
110 | 5562 | this.expressionEngine = expressionEngine; |
111 | 5562 | } |
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 | 785 | if (escape && property != null) |
127 | { | |
128 | 749 | key = escapeDelimiters(property); |
129 | } | |
130 | else | |
131 | { | |
132 | 36 | key = property; |
133 | } | |
134 | 785 | key = trim(key); |
135 | ||
136 | 785 | if (keyBuffer.length() > 0 && !isAttributeKey(property) |
137 | && key.length() > 0) | |
138 | { | |
139 | 578 | keyBuffer.append(getExpressionEngine().getPropertyDelimiter()); |
140 | } | |
141 | ||
142 | 785 | keyBuffer.append(key); |
143 | 785 | 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 | 151 | keyBuffer.append(constructAttributeKey(attr)); |
182 | 151 | 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 | 41381 | 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 | 912 | 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 | 12586 | if (key == null) |
256 | { | |
257 | 4 | return false; |
258 | } | |
259 | ||
260 | 12582 | 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 | 158 | if (key == null) |
276 | { | |
277 | 2 | return StringUtils.EMPTY; |
278 | } | |
279 | 156 | if (isAttributeKey(key)) |
280 | { | |
281 | 3 | return key; |
282 | } | |
283 | else | |
284 | { | |
285 | 153 | StringBuffer buf = new StringBuffer(); |
286 | 153 | buf.append(getExpressionEngine().getAttributeStart()).append(key); |
287 | 153 | if (getExpressionEngine().getAttributeEnd() != null) |
288 | { | |
289 | 150 | buf.append(getExpressionEngine().getAttributeEnd()); |
290 | } | |
291 | 153 | 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 | 6324 | if (key == null) |
316 | { | |
317 | 19 | return StringUtils.EMPTY; |
318 | } | |
319 | else | |
320 | { | |
321 | 6305 | String result = key; |
322 | 12626 | while (hasLeadingDelimiter(result)) |
323 | { | |
324 | 16 | result = result.substring(getExpressionEngine() |
325 | .getPropertyDelimiter().length()); | |
326 | } | |
327 | 6305 | 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 | 6324 | if (key == null) |
340 | { | |
341 | 0 | return StringUtils.EMPTY; |
342 | } | |
343 | else | |
344 | { | |
345 | 6324 | String result = key; |
346 | 12662 | while (hasTrailingDelimiter(result)) |
347 | { | |
348 | 14 | result = result |
349 | .substring(0, result.length() | |
350 | - getExpressionEngine().getPropertyDelimiter() | |
351 | .length()); | |
352 | } | |
353 | 6324 | 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 | 6322 | 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 | 4646 | 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 | 6338 | 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 | 24769 | 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 | 889 | 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 | 11839 | 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 | 749 | 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 | 4646 | 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 | 824 | 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 | 11840 | if (!hasNext()) |
502 | { | |
503 | 1 | throw new NoSuchElementException("No more key parts!"); |
504 | } | |
505 | ||
506 | 11839 | hasIndex = false; |
507 | 11839 | indexValue = -1; |
508 | 11839 | String key = findNextIndices(); |
509 | ||
510 | 11839 | current = key; |
511 | 11839 | hasIndex = checkIndex(key); |
512 | 11839 | attribute = checkAttribute(current); |
513 | ||
514 | 11839 | 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 | 29862 | 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 | 801 | 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 | 2481 | 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 | 14325 | 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 | 7193 | 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 | 12445 | 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 | 971 | 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 | 9963 | 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 | 7041 | return super.clone(); |
635 | } | |
636 | 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 | 11839 | startIndex = endIndex; |
651 | // skip empty names | |
652 | 11839 | while (startIndex < length() |
653 | 18448 | && hasLeadingDelimiter(keyBuffer.substring(startIndex))) |
654 | { | |
655 | 6609 | startIndex += getExpressionEngine().getPropertyDelimiter() |
656 | .length(); | |
657 | } | |
658 | ||
659 | // Key ends with a delimiter? | |
660 | 11839 | 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 | 11839 | 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 | 11839 | int attrIdx = keyBuffer.toString().indexOf( |
681 | getExpressionEngine().getAttributeStart(), startIndex); | |
682 | 11839 | if (attrIdx < 0 || attrIdx == startIndex) |
683 | { | |
684 | 11091 | attrIdx = length(); |
685 | } | |
686 | ||
687 | 11839 | int delIdx = nextDelimiterPos(keyBuffer.toString(), startIndex, |
688 | attrIdx); | |
689 | 11839 | if (delIdx < 0) |
690 | { | |
691 | 5544 | delIdx = attrIdx; |
692 | } | |
693 | ||
694 | 11839 | endIndex = Math.min(attrIdx, delIdx); |
695 | 11839 | 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 | 11839 | int delimiterPos = pos; |
709 | 11839 | boolean found = false; |
710 | ||
711 | do | |
712 | { | |
713 | 11927 | delimiterPos = key.indexOf(getExpressionEngine() |
714 | .getPropertyDelimiter(), delimiterPos); | |
715 | 11927 | if (delimiterPos < 0 || delimiterPos >= endPos) |
716 | { | |
717 | 5544 | return -1; |
718 | } | |
719 | 6383 | int escapePos = escapedPosition(key, delimiterPos); |
720 | 6383 | if (escapePos < 0) |
721 | { | |
722 | 6295 | found = true; |
723 | } | |
724 | else | |
725 | { | |
726 | 88 | delimiterPos = escapePos; |
727 | } | |
728 | } | |
729 | 6383 | while (!found); |
730 | ||
731 | 6295 | 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 | 6383 | if (getExpressionEngine().getEscapedDelimiter() == null) |
746 | { | |
747 | // nothing to escape | |
748 | 13 | return -1; |
749 | } | |
750 | 6370 | int escapeOffset = escapeOffset(); |
751 | 6370 | if (escapeOffset < 0 || escapeOffset > pos) |
752 | { | |
753 | // No escaping possible at this position | |
754 | 55 | return -1; |
755 | } | |
756 | ||
757 | 6315 | int escapePos = key.indexOf(getExpressionEngine() |
758 | .getEscapedDelimiter(), pos - escapeOffset); | |
759 | 6315 | if (escapePos <= pos && escapePos >= 0) |
760 | { | |
761 | // The found delimiter is escaped. Next valid search position | |
762 | // is behind the escaped delimiter. | |
763 | 88 | return escapePos |
764 | + getExpressionEngine().getEscapedDelimiter().length(); | |
765 | } | |
766 | else | |
767 | { | |
768 | 6227 | 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 (".") is | |
777 | * used as delimiter, and a doubled dot ("..") as escaped | |
778 | * delimiter, the escaped delimiter starts at the same position as the | |
779 | * delimiter. If the token "\." was used, it would start one | |
780 | * character before the delimiter because the delimiter character | |
781 | * "." 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 | 6370 | 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 | 11839 | if (isAttributeKey(key)) |
805 | { | |
806 | 888 | current = removeAttributeMarkers(key); |
807 | 888 | return true; |
808 | } | |
809 | else | |
810 | { | |
811 | 10951 | 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 | 11839 | boolean result = false; |
825 | ||
826 | 11839 | int idx = key.lastIndexOf(getExpressionEngine().getIndexStart()); |
827 | 11839 | if (idx > 0) |
828 | { | |
829 | 481 | int endidx = key.indexOf(getExpressionEngine().getIndexEnd(), |
830 | idx); | |
831 | ||
832 | 481 | if (endidx > idx + 1) |
833 | { | |
834 | 479 | indexValue = Integer.parseInt(key |
835 | .substring(idx + 1, endidx)); | |
836 | 479 | current = key.substring(0, idx); |
837 | 479 | result = true; |
838 | } | |
839 | } | |
840 | ||
841 | 11839 | 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 "attribute emulating mode". | |
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 | 6374 | return getExpressionEngine().getAttributeEnd() == null |
859 | && StringUtils.equals(getExpressionEngine() | |
860 | .getPropertyDelimiter(), getExpressionEngine() | |
861 | .getAttributeStart()); | |
862 | } | |
863 | } | |
864 | } |