001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018 package org.apache.commons.configuration;
019
020 import java.io.Serializable;
021 import java.util.Iterator;
022 import java.util.NoSuchElementException;
023
024 /**
025 * <p>A simple class that supports creation of and iteration on complex
026 * configuration keys.</p>
027 *
028 * <p>For key creation the class works similar to a StringBuilder: There are
029 * several {@code appendXXXX()} methods with which single parts
030 * of a key can be constructed. All these methods return a reference to the
031 * actual object so they can be written in a chain. When using this methods
032 * the exact syntax for keys need not be known.</p>
033 *
034 * <p>This class also defines a specialized iterator for configuration keys.
035 * With such an iterator a key can be tokenized into its single parts. For
036 * each part it can be checked whether it has an associated index.</p>
037 *
038 * @author <a
039 * href="http://commons.apache.org/configuration/team-list.html">Commons
040 * Configuration team</a>
041 * @version $Id: ConfigurationKey.java 1231749 2012-01-15 20:48:56Z oheger $
042 * @deprecated Use {@link org.apache.commons.configuration.tree.DefaultConfigurationKey}
043 * instead. It is associated with a {@code DefaultExpressionEngine} and thus
044 * can produce correct keys even if key separators have been changed.
045 */
046 @Deprecated
047 public class ConfigurationKey implements Serializable
048 {
049 /** Constant for a property delimiter.*/
050 public static final char PROPERTY_DELIMITER = '.';
051
052 /** Constant for an escaped delimiter. */
053 public static final String ESCAPED_DELIMITER =
054 String.valueOf(PROPERTY_DELIMITER) + String.valueOf(PROPERTY_DELIMITER);
055
056 /** Constant for an attribute start marker.*/
057 private static final String ATTRIBUTE_START = "[@";
058
059 /** Constant for an attribute end marker.*/
060 private static final String ATTRIBUTE_END = "]";
061
062 /** Constant for an index start marker.*/
063 private static final char INDEX_START = '(';
064
065 /** Constant for an index end marker.*/
066 private static final char INDEX_END = ')';
067
068 /** Constant for the initial StringBuilder size.*/
069 private static final int INITIAL_SIZE = 32;
070
071 /**
072 * The serial version ID.
073 */
074 private static final long serialVersionUID = -4299732083605277656L;
075
076 /** Holds a buffer with the so far created key.*/
077 private StringBuilder keyBuffer;
078
079 /**
080 * Creates a new, empty instance of {@code ConfigurationKey}.
081 */
082 public ConfigurationKey()
083 {
084 keyBuffer = new StringBuilder(INITIAL_SIZE);
085 }
086
087 /**
088 * Creates a new instance of {@code ConfigurationKey} and
089 * initializes it with the given key.
090 *
091 * @param key the key as a string
092 */
093 public ConfigurationKey(String key)
094 {
095 keyBuffer = new StringBuilder(key);
096 removeTrailingDelimiter();
097 }
098
099 /**
100 * Appends the name of a property to this key. If necessary, a
101 * property delimiter will be added.
102 *
103 * @param property the name of the property to be added
104 * @return a reference to this object
105 */
106 public ConfigurationKey append(String property)
107 {
108 if (keyBuffer.length() > 0 && !hasDelimiter() && !isAttributeKey(property))
109 {
110 keyBuffer.append(PROPERTY_DELIMITER);
111 }
112
113 keyBuffer.append(property);
114 removeTrailingDelimiter();
115 return this;
116 }
117
118 /**
119 * Appends an index to this configuration key.
120 *
121 * @param index the index to be appended
122 * @return a reference to this object
123 */
124 public ConfigurationKey appendIndex(int index)
125 {
126 keyBuffer.append(INDEX_START).append(index);
127 keyBuffer.append(INDEX_END);
128 return this;
129 }
130
131 /**
132 * Appends an attribute to this configuration key.
133 *
134 * @param attr the name of the attribute to be appended
135 * @return a reference to this object
136 */
137 public ConfigurationKey appendAttribute(String attr)
138 {
139 keyBuffer.append(constructAttributeKey(attr));
140 return this;
141 }
142
143 /**
144 * Checks if this key is an attribute key.
145 *
146 * @return a flag if this key is an attribute key
147 */
148 public boolean isAttributeKey()
149 {
150 return isAttributeKey(keyBuffer.toString());
151 }
152
153 /**
154 * Checks if the passed in key is an attribute key. Such attribute keys
155 * start and end with certain marker strings. In some cases they must be
156 * treated slightly different.
157 *
158 * @param key the key (part) to be checked
159 * @return a flag if this key is an attribute key
160 */
161 public static boolean isAttributeKey(String key)
162 {
163 return key != null
164 && key.startsWith(ATTRIBUTE_START)
165 && key.endsWith(ATTRIBUTE_END);
166 }
167
168 /**
169 * Decorates the given key so that it represents an attribute. Adds
170 * special start and end markers.
171 *
172 * @param key the key to be decorated
173 * @return the decorated attribute key
174 */
175 public static String constructAttributeKey(String key)
176 {
177 StringBuilder buf = new StringBuilder();
178 buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
179 return buf.toString();
180 }
181
182 /**
183 * Extracts the name of the attribute from the given attribute key.
184 * This method removes the attribute markers - if any - from the
185 * specified key.
186 *
187 * @param key the attribute key
188 * @return the name of the corresponding attribute
189 */
190 public static String attributeName(String key)
191 {
192 return isAttributeKey(key) ? removeAttributeMarkers(key) : key;
193 }
194
195 /**
196 * Helper method for removing attribute markers from a key.
197 *
198 * @param key the key
199 * @return the key with removed attribute markers
200 */
201 static String removeAttributeMarkers(String key)
202 {
203 return key.substring(ATTRIBUTE_START.length(), key.length() - ATTRIBUTE_END.length());
204 }
205
206 /**
207 * Helper method that checks if the actual buffer ends with a property
208 * delimiter.
209 *
210 * @return a flag if there is a trailing delimiter
211 */
212 private boolean hasDelimiter()
213 {
214 int count = 0;
215 for (int idx = keyBuffer.length() - 1; idx >= 0
216 && keyBuffer.charAt(idx) == PROPERTY_DELIMITER; idx--)
217 {
218 count++;
219 }
220 return count % 2 != 0;
221 }
222
223 /**
224 * Removes a trailing delimiter if there is any.
225 */
226 private void removeTrailingDelimiter()
227 {
228 while (hasDelimiter())
229 {
230 keyBuffer.deleteCharAt(keyBuffer.length() - 1);
231 }
232 }
233
234 /**
235 * Returns a string representation of this object. This is the
236 * configuration key as a plain string.
237 *
238 * @return a string for this object
239 */
240 @Override
241 public String toString()
242 {
243 return keyBuffer.toString();
244 }
245
246 /**
247 * Returns an iterator for iterating over the single components of
248 * this configuration key.
249 *
250 * @return an iterator for this key
251 */
252 public KeyIterator iterator()
253 {
254 return new KeyIterator();
255 }
256
257 /**
258 * Returns the actual length of this configuration key.
259 *
260 * @return the length of this key
261 */
262 public int length()
263 {
264 return keyBuffer.length();
265 }
266
267 /**
268 * Sets the new length of this configuration key. With this method it is
269 * possible to truncate the key, e.g. to return to a state prior calling
270 * some {@code append()} methods. The semantic is the same as
271 * the {@code setLength()} method of {@code StringBuilder}.
272 *
273 * @param len the new length of the key
274 */
275 public void setLength(int len)
276 {
277 keyBuffer.setLength(len);
278 }
279
280 /**
281 * Checks if two {@code ConfigurationKey} objects are equal. The
282 * method can be called with strings or other objects, too.
283 *
284 * @param c the object to compare
285 * @return a flag if both objects are equal
286 */
287 @Override
288 public boolean equals(Object c)
289 {
290 if (c == null)
291 {
292 return false;
293 }
294
295 return keyBuffer.toString().equals(c.toString());
296 }
297
298 /**
299 * Returns the hash code for this object.
300 *
301 * @return the hash code
302 */
303 @Override
304 public int hashCode()
305 {
306 return String.valueOf(keyBuffer).hashCode();
307 }
308
309 /**
310 * Returns a configuration key object that is initialized with the part
311 * of the key that is common to this key and the passed in key.
312 *
313 * @param other the other key
314 * @return a key object with the common key part
315 */
316 public ConfigurationKey commonKey(ConfigurationKey other)
317 {
318 if (other == null)
319 {
320 throw new IllegalArgumentException("Other key must no be null!");
321 }
322
323 ConfigurationKey result = new ConfigurationKey();
324 KeyIterator it1 = iterator();
325 KeyIterator it2 = other.iterator();
326
327 while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2))
328 {
329 if (it1.isAttribute())
330 {
331 result.appendAttribute(it1.currentKey());
332 }
333 else
334 {
335 result.append(it1.currentKey());
336 if (it1.hasIndex)
337 {
338 result.appendIndex(it1.getIndex());
339 }
340 }
341 }
342
343 return result;
344 }
345
346 /**
347 * Returns the "difference key" to a given key. This value
348 * is the part of the passed in key that differs from this key. There is
349 * the following relation:
350 * {@code other = key.commonKey(other) + key.differenceKey(other)}
351 * for an arbitrary configuration key {@code key}.
352 *
353 * @param other the key for which the difference is to be calculated
354 * @return the difference key
355 */
356 public ConfigurationKey differenceKey(ConfigurationKey other)
357 {
358 ConfigurationKey common = commonKey(other);
359 ConfigurationKey result = new ConfigurationKey();
360
361 if (common.length() < other.length())
362 {
363 String k = other.toString().substring(common.length());
364 // skip trailing delimiters
365 int i = 0;
366 while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER)
367 {
368 i++;
369 }
370
371 if (i < k.length())
372 {
373 result.append(k.substring(i));
374 }
375 }
376
377 return result;
378 }
379
380 /**
381 * Helper method for comparing two key parts.
382 *
383 * @param it1 the iterator with the first part
384 * @param it2 the iterator with the second part
385 * @return a flag if both parts are equal
386 */
387 private static boolean partsEqual(KeyIterator it1, KeyIterator it2)
388 {
389 return it1.nextKey().equals(it2.nextKey())
390 && it1.getIndex() == it2.getIndex()
391 && it1.isAttribute() == it2.isAttribute();
392 }
393
394 /**
395 * A specialized iterator class for tokenizing a configuration key.
396 * This class implements the normal iterator interface. In addition it
397 * provides some specific methods for configuration keys.
398 */
399 public class KeyIterator implements Iterator<Object>, Cloneable
400 {
401 /** Stores the current key name.*/
402 private String current;
403
404 /** Stores the start index of the actual token.*/
405 private int startIndex;
406
407 /** Stores the end index of the actual token.*/
408 private int endIndex;
409
410 /** Stores the index of the actual property if there is one.*/
411 private int indexValue;
412
413 /** Stores a flag if the actual property has an index.*/
414 private boolean hasIndex;
415
416 /** Stores a flag if the actual property is an attribute.*/
417 private boolean attribute;
418
419 /**
420 * Helper method for determining the next indices.
421 *
422 * @return the next key part
423 */
424 private String findNextIndices()
425 {
426 startIndex = endIndex;
427 // skip empty names
428 while (startIndex < keyBuffer.length()
429 && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER)
430 {
431 startIndex++;
432 }
433
434 // Key ends with a delimiter?
435 if (startIndex >= keyBuffer.length())
436 {
437 endIndex = keyBuffer.length();
438 startIndex = endIndex - 1;
439 return keyBuffer.substring(startIndex, endIndex);
440 }
441 else
442 {
443 return nextKeyPart();
444 }
445 }
446
447 /**
448 * Helper method for extracting the next key part. Takes escaping of
449 * delimiter characters into account.
450 *
451 * @return the next key part
452 */
453 private String nextKeyPart()
454 {
455 StringBuilder key = new StringBuilder(INITIAL_SIZE);
456 int idx = startIndex;
457 int endIdx = keyBuffer.toString().indexOf(ATTRIBUTE_START,
458 startIndex);
459 if (endIdx < 0 || endIdx == startIndex)
460 {
461 endIdx = keyBuffer.length();
462 }
463 boolean found = false;
464
465 while (!found && idx < endIdx)
466 {
467 char c = keyBuffer.charAt(idx);
468 if (c == PROPERTY_DELIMITER)
469 {
470 // a duplicated delimiter means escaping
471 if (idx == endIdx - 1
472 || keyBuffer.charAt(idx + 1) != PROPERTY_DELIMITER)
473 {
474 found = true;
475 }
476 else
477 {
478 idx++;
479 }
480 }
481 if (!found)
482 {
483 key.append(c);
484 idx++;
485 }
486 }
487
488 endIndex = idx;
489 return key.toString();
490 }
491
492 /**
493 * Returns the next key part of this configuration key. This is a short
494 * form of {@code nextKey(false)}.
495 *
496 * @return the next key part
497 */
498 public String nextKey()
499 {
500 return nextKey(false);
501 }
502
503 /**
504 * Returns the next key part of this configuration key. The boolean
505 * parameter indicates wheter a decorated key should be returned. This
506 * affects only attribute keys: if the parameter is <b>false</b>, the
507 * attribute markers are stripped from the key; if it is <b>true</b>,
508 * they remain.
509 *
510 * @param decorated a flag if the decorated key is to be returned
511 * @return the next key part
512 */
513 public String nextKey(boolean decorated)
514 {
515 if (!hasNext())
516 {
517 throw new NoSuchElementException("No more key parts!");
518 }
519
520 hasIndex = false;
521 indexValue = -1;
522 String key = findNextIndices();
523
524 current = key;
525 hasIndex = checkIndex(key);
526 attribute = checkAttribute(current);
527
528 return currentKey(decorated);
529 }
530
531 /**
532 * Helper method for checking if the passed key is an attribute.
533 * If this is the case, the internal fields will be set.
534 *
535 * @param key the key to be checked
536 * @return a flag if the key is an attribute
537 */
538 private boolean checkAttribute(String key)
539 {
540 if (isAttributeKey(key))
541 {
542 current = removeAttributeMarkers(key);
543 return true;
544 }
545 else
546 {
547 return false;
548 }
549 }
550
551 /**
552 * Helper method for checking if the passed key contains an index.
553 * If this is the case, internal fields will be set.
554 *
555 * @param key the key to be checked
556 * @return a flag if an index is defined
557 */
558 private boolean checkIndex(String key)
559 {
560 boolean result = false;
561
562 int idx = key.lastIndexOf(INDEX_START);
563 if (idx > 0)
564 {
565 int endidx = key.indexOf(INDEX_END, idx);
566
567 if (endidx > idx + 1)
568 {
569 indexValue = Integer.parseInt(key.substring(idx + 1, endidx));
570 current = key.substring(0, idx);
571 result = true;
572 }
573 }
574
575 return result;
576 }
577
578 /**
579 * Checks if there is a next element.
580 *
581 * @return a flag if there is a next element
582 */
583 public boolean hasNext()
584 {
585 return endIndex < keyBuffer.length();
586 }
587
588 /**
589 * Returns the next object in the iteration.
590 *
591 * @return the next object
592 */
593 public Object next()
594 {
595 return nextKey();
596 }
597
598 /**
599 * Removes the current object in the iteration. This method is not
600 * supported by this iterator type, so an exception is thrown.
601 */
602 public void remove()
603 {
604 throw new UnsupportedOperationException("Remove not supported!");
605 }
606
607 /**
608 * Returns the current key of the iteration (without skipping to the
609 * next element). This is the same key the previous {@code next()}
610 * call had returned. (Short form of {@code currentKey(false)}.
611 *
612 * @return the current key
613 */
614 public String currentKey()
615 {
616 return currentKey(false);
617 }
618
619 /**
620 * Returns the current key of the iteration (without skipping to the
621 * next element). The boolean parameter indicates wheter a decorated
622 * key should be returned. This affects only attribute keys: if the
623 * parameter is <b>false</b>, the attribute markers are stripped from
624 * the key; if it is <b>true</b>, they remain.
625 *
626 * @param decorated a flag if the decorated key is to be returned
627 * @return the current key
628 */
629 public String currentKey(boolean decorated)
630 {
631 return (decorated && isAttribute()) ? constructAttributeKey(current) : current;
632 }
633
634 /**
635 * Returns a flag if the current key is an attribute. This method can
636 * be called after {@code next()}.
637 *
638 * @return a flag if the current key is an attribute
639 */
640 public boolean isAttribute()
641 {
642 return attribute;
643 }
644
645 /**
646 * Returns the index value of the current key. If the current key does
647 * not have an index, return value is -1. This method can be called
648 * after {@code next()}.
649 *
650 * @return the index value of the current key
651 */
652 public int getIndex()
653 {
654 return indexValue;
655 }
656
657 /**
658 * Returns a flag if the current key has an associated index.
659 * This method can be called after {@code next()}.
660 *
661 * @return a flag if the current key has an index
662 */
663 public boolean hasIndex()
664 {
665 return hasIndex;
666 }
667
668 /**
669 * Creates a clone of this object.
670 *
671 * @return a clone of this object
672 */
673 @Override
674 public Object clone()
675 {
676 try
677 {
678 return super.clone();
679 }
680 catch (CloneNotSupportedException cex)
681 {
682 // should not happen
683 return null;
684 }
685 }
686 }
687 }