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