001: package net.myvietnam.mvncore.configuration;
002:
003: /* ====================================================================
004: * The Apache Software License, Version 1.1
005: *
006: * Copyright (c) 1999-2002 The Apache Software Foundation. All rights
007: * reserved.
008: *
009: * Redistribution and use in source and binary forms, with or without
010: * modification, are permitted provided that the following conditions
011: * are met:
012: *
013: * 1. Redistributions of source code must retain the above copyright
014: * notice, this list of conditions and the following disclaimer.
015: *
016: * 2. Redistributions in binary form must reproduce the above copyright
017: * notice, this list of conditions and the following disclaimer in
018: * the documentation and/or other materials provided with the
019: * distribution.
020: *
021: * 3. The end-user documentation included with the redistribution, if
022: * any, must include the following acknowlegement:
023: * "This product includes software developed by the
024: * Apache Software Foundation (http://www.apache.org/)."
025: * Alternately, this acknowlegement may appear in the software itself,
026: * if and wherever such third-party acknowlegements normally appear.
027: *
028: * 4. The names "The Jakarta Project", "Commons", and "Apache Software
029: * Foundation" must not be used to endorse or promote products derived
030: * from this software without prior written permission. For written
031: * permission, please contact apache@apache.org.
032: *
033: * 5. Products derived from this software may not be called "Apache"
034: * nor may "Apache" appear in their names without prior written
035: * permission of the Apache Group.
036: *
037: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
038: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
039: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
040: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
041: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
042: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
043: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
044: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
045: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
046: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
047: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
048: * SUCH DAMAGE.
049: * ====================================================================
050: *
051: * This software consists of voluntary contributions made by many
052: * individuals on behalf of the Apache Software Foundation. For more
053: * information on the Apache Software Foundation, please see
054: * <http://www.apache.org/>.
055: */
056:
057: import java.io.Serializable;
058: import java.util.Iterator;
059: import java.util.NoSuchElementException;
060:
061: /**
062: * <p>A simple class that supports creation of and iteration on complex
063: * configuration keys.</p>
064: * <p>For key creation the class works similar to a StringBuffer: There are
065: * several <code>appendXXXX()</code> methods with which single parts
066: * of a key can be constructed. All these methods return a reference to the
067: * actual object so they can be written in a chain. When using this methods
068: * the exact syntax for keys need not be known.</p>
069: * <p>This class also defines a specialized iterator for configuration keys.
070: * With such an iterator a key can be tokenized into its single parts. For
071: * each part it can be checked whether it has an associated index.</p>
072: *
073: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
074: * @version $Id: ConfigurationKey.java,v 1.2 2004/10/10 16:51:13 minhnn Exp $
075: */
076: public class ConfigurationKey implements Serializable {
077: /** Constant for an attribute start marker.*/
078: private static final String ATTRIBUTE_START = "[@";
079:
080: /** Constant for an attribute end marker.*/
081: private static final String ATTRIBUTE_END = "]";
082:
083: /** Constant for a property delimiter.*/
084: private static final char PROPERTY_DELIMITER = '.';
085:
086: /** Constant for an index start marker.*/
087: private static final char INDEX_START = '(';
088:
089: /** Constant for an index end marker.*/
090: private static final char INDEX_END = ')';
091:
092: /** Constant for the initial StringBuffer size.*/
093: private static final int INITIAL_SIZE = 32;
094:
095: /** Holds a buffer with the so far created key.*/
096: private StringBuffer keyBuffer;
097:
098: /**
099: * Creates a new, empty instance of <code>ConfigurationKey</code>.
100: */
101: public ConfigurationKey() {
102: keyBuffer = new StringBuffer(INITIAL_SIZE);
103: }
104:
105: /**
106: * Creates a new instance of <code>ConfigurationKey</code> and
107: * initializes it with the given key.
108: * @param key the key as a string
109: */
110: public ConfigurationKey(String key) {
111: keyBuffer = new StringBuffer(key);
112: removeTrailingDelimiter();
113: }
114:
115: /**
116: * Appends the name of a property to this key. If necessary, a
117: * property delimiter will be added.
118: * @param property the name of the property to be added
119: * @return a reference to this object
120: */
121: public ConfigurationKey append(String property) {
122: if (keyBuffer.length() > 0 && !hasDelimiter()
123: && !isAttributeKey(property)) {
124: keyBuffer.append(PROPERTY_DELIMITER);
125: } /* if */
126:
127: keyBuffer.append(property);
128: removeTrailingDelimiter();
129: return this ;
130: }
131:
132: /**
133: * Appends an index to this configuration key.
134: * @param index the index to be appended
135: * @return a reference to this object
136: */
137: public ConfigurationKey appendIndex(int index) {
138: keyBuffer.append(INDEX_START).append(index);
139: keyBuffer.append(INDEX_END);
140: return this ;
141: }
142:
143: /**
144: * Appends an attribute to this configuration key.
145: * @param attr the name of the attribute to be appended
146: * @return a reference to this object
147: */
148: public ConfigurationKey appendAttribute(String attr) {
149: keyBuffer.append(constructAttributeKey(attr));
150: return this ;
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: * @param key the key (part) to be checked
158: * @return a flag if this key is an attribute key
159: */
160: public static boolean isAttributeKey(String key) {
161: return key != null && key.startsWith(ATTRIBUTE_START)
162: && key.endsWith(ATTRIBUTE_END);
163: }
164:
165: /**
166: * Decorates the given key so that it represents an attribute. Adds
167: * special start and end markers.
168: * @param key the key to be decorated
169: * @return the decorated attribute key
170: */
171: public static String constructAttributeKey(String key) {
172: StringBuffer buf = new StringBuffer();
173: buf.append(ATTRIBUTE_START).append(key).append(ATTRIBUTE_END);
174: return buf.toString();
175: }
176:
177: /**
178: * Extracts the name of the attribute from the given attribute key.
179: * This method removes the attribute markers - if any - from the
180: * specified key.
181: * @param key the attribute key
182: * @return the name of the corresponding attribute
183: */
184: public static String attributeName(String key) {
185: return (isAttributeKey(key)) ? removeAttributeMarkers(key)
186: : key;
187: }
188:
189: /**
190: * Helper method for removing attribute markers from a key.
191: * @param key the key
192: * @return the key with removed attribute markers
193: */
194: private static String removeAttributeMarkers(String key) {
195: return key.substring(ATTRIBUTE_START.length(), key.length()
196: - ATTRIBUTE_END.length());
197: }
198:
199: /**
200: * Helper method that checks if the actual buffer ends with a property
201: * delimiter.
202: * @return a flag if there is a trailing delimiter
203: */
204: private boolean hasDelimiter() {
205: return keyBuffer.length() > 0
206: && keyBuffer.charAt(keyBuffer.length() - 1) == PROPERTY_DELIMITER;
207: }
208:
209: /**
210: * Removes a trailing delimiter if there is any.
211: */
212: private void removeTrailingDelimiter() {
213: while (hasDelimiter()) {
214: keyBuffer.deleteCharAt(keyBuffer.length() - 1);
215: } /* while */
216: }
217:
218: /**
219: * Returns a string representation of this object. This is the
220: * configuration key as a plain string.
221: * @return a string for this object
222: */
223: public String toString() {
224: return keyBuffer.toString();
225: }
226:
227: /**
228: * Returns an iterator for iterating over the single components of
229: * this configuration key.
230: * @return an iterator for this key
231: */
232: public KeyIterator iterator() {
233: return new KeyIterator();
234: }
235:
236: /**
237: * Returns the actual length of this configuration key.
238: * @return the length of this key
239: */
240: public int length() {
241: return keyBuffer.length();
242: }
243:
244: /**
245: * Sets the new length of this configuration key. With this method it is
246: * possible to truncate the key, e.g. to return to a state prior calling
247: * some <code>append()</code> methods. The semantic is the same as
248: * the <code>setLength()</code> method of <code>StringBuffer</code>.
249: * @param len the new length of the key
250: */
251: public void setLength(int len) {
252: keyBuffer.setLength(len);
253: }
254:
255: /**
256: * Checks if two <code>ConfigurationKey</code> objects are equal. The
257: * method can be called with strings or other objects, too.
258: * @param c the object to compare
259: * @return a flag if both objects are equal
260: */
261: public boolean equals(Object c) {
262: if (c == null) {
263: return false;
264: } /* if */
265:
266: return keyBuffer.toString().equals(c.toString());
267: }
268:
269: /**
270: * Returns the hash code for this object.
271: * @return the hash code
272: */
273: public int hashCode() {
274: return keyBuffer.hashCode();
275: }
276:
277: /**
278: * Returns a configuration key object that is initialized with the part
279: * of the key that is common to this key and the passed in key.
280: * @param other the other key
281: * @return a key object with the common key part
282: */
283: public ConfigurationKey commonKey(ConfigurationKey other) {
284: if (other == null) {
285: throw new IllegalArgumentException(
286: "Other key must no be null!");
287: } /* if */
288:
289: ConfigurationKey result = new ConfigurationKey();
290: KeyIterator it1 = iterator();
291: KeyIterator it2 = other.iterator();
292:
293: while (it1.hasNext() && it2.hasNext() && partsEqual(it1, it2)) {
294: if (it1.isAttribute()) {
295: result.appendAttribute(it1.currentKey());
296: } /* if */
297: else {
298: result.append(it1.currentKey());
299: if (it1.hasIndex) {
300: result.appendIndex(it1.getIndex());
301: } /* if */
302: } /* else */
303: } /* while */
304:
305: return result;
306: }
307:
308: /**
309: * Returns the "difference key" to a given key. This value
310: * is the part of the passed in key that differs from this key. There is
311: * the following relation:
312: * <code>other = key.commonKey(other) + key.differenceKey(other)</code>
313: * for an arbitrary configuration key <code>key</code>.
314: * @param other the key for which the difference is to be calculated
315: * @return the difference key
316: */
317: public ConfigurationKey differenceKey(ConfigurationKey other) {
318: ConfigurationKey common = commonKey(other);
319: ConfigurationKey result = new ConfigurationKey();
320:
321: if (common.length() < other.length()) {
322: String k = other.toString().substring(common.length());
323: // skip trailing delimiters
324: int i = 0;
325: while (i < k.length() && k.charAt(i) == PROPERTY_DELIMITER) {
326: i++;
327: } /* while */
328:
329: if (i < k.length()) {
330: result.append(k.substring(i));
331: } /* if */
332: } /* if */
333:
334: return result;
335: }
336:
337: /**
338: * Helper method for comparing two key parts.
339: * @param it1 the iterator with the first part
340: * @param it2 the iterator with the second part
341: * @return a flag if both parts are equal
342: */
343: private static boolean partsEqual(KeyIterator it1, KeyIterator it2) {
344: return it1.nextKey().equals(it2.nextKey())
345: && it1.getIndex() == it2.getIndex()
346: && it1.isAttribute() == it2.isAttribute();
347: }
348:
349: /**
350: * A specialized iterator class for tokenizing a configuration key.
351: * This class implements the normal iterator interface. In addition it
352: * provides some specific methods for configuration keys.
353: *
354: * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
355: */
356: public class KeyIterator implements Iterator, Cloneable {
357: /** Stores the current key name.*/
358: private String current;
359:
360: /** Stores the start index of the actual token.*/
361: private int startIndex;
362:
363: /** Stores the end index of the actual token.*/
364: private int endIndex;
365:
366: /** Stores the index of the actual property if there is one.*/
367: private int indexValue;
368:
369: /** Stores a flag if the actual property has an index.*/
370: private boolean hasIndex;
371:
372: /** Stores a flag if the actual property is an attribute.*/
373: private boolean attribute;
374:
375: /**
376: * Helper method for determining the next indices.
377: */
378: private void findNextIndices() {
379: startIndex = endIndex;
380: // skip empty names
381: while (startIndex < keyBuffer.length()
382: && keyBuffer.charAt(startIndex) == PROPERTY_DELIMITER) {
383: startIndex++;
384: }
385:
386: // Key ends with a delimiter?
387: if (startIndex >= keyBuffer.length()) {
388: endIndex = keyBuffer.length();
389: startIndex = endIndex - 1;
390: } else {
391: String s = keyBuffer.toString(); // for compatibility
392: endIndex = s.indexOf(PROPERTY_DELIMITER, startIndex);
393: if (endIndex < 0) {
394: endIndex = s.indexOf(ATTRIBUTE_START, startIndex);
395: if (endIndex < 0 || endIndex == startIndex) {
396: endIndex = keyBuffer.length();
397: }
398: }
399: }
400: }
401:
402: /**
403: * Returns the next key part of this configuration key. This is a short
404: * form of <code>nextKey(false)</code>.
405: * @return the next key part
406: */
407: public String nextKey() {
408: return nextKey(false);
409: }
410:
411: /**
412: * Returns the next key part of this configuration key. The boolean
413: * parameter indicates wheter a decorated key should be returned. This
414: * affects only attribute keys: if the parameter is <b>false</b>, the
415: * attribute markers are stripped from the key; if it is <b>true</b>,
416: * they remain.
417: * @param decorated a flag if the decorated key is to be returned
418: * @return the next key part
419: */
420: public String nextKey(boolean decorated) {
421: if (!hasNext()) {
422: throw new NoSuchElementException("No more key parts!");
423: } /* if */
424:
425: hasIndex = false;
426: indexValue = -1;
427: findNextIndices();
428: String key = keyBuffer.substring(startIndex, endIndex)
429: .toString();
430:
431: attribute = checkAttribute(key);
432: if (!attribute) {
433: hasIndex = checkIndex(key);
434: if (!hasIndex) {
435: current = key;
436: } /* if */
437: } /* if */
438:
439: return currentKey(decorated);
440: }
441:
442: /**
443: * Helper method for checking if the passed key is an attribute.
444: * If this is the case, the internal fields will be set.
445: * @param key the key to be checked
446: * @return a flag if the key is an attribute
447: */
448: private boolean checkAttribute(String key) {
449: if (isAttributeKey(key)) {
450: current = removeAttributeMarkers(key);
451: return true;
452: } /* if */
453: else {
454: return false;
455: } /* else */
456: }
457:
458: /**
459: * Helper method for checking if the passed key contains an index.
460: * If this is the case, internal fields will be set.
461: * @param key the key to be checked
462: * @return a flag if an index is defined
463: */
464: private boolean checkIndex(String key) {
465: boolean result = false;
466:
467: int idx = key.indexOf(INDEX_START);
468: if (idx > 0) {
469: int endidx = key.indexOf(INDEX_END, idx);
470:
471: if (endidx > idx + 1) {
472: indexValue = Integer.parseInt(key.substring(
473: idx + 1, endidx));
474: current = key.substring(0, idx);
475: result = true;
476: } /* if */
477: } /* if */
478:
479: return result;
480: }
481:
482: /**
483: * Checks if there is a next element.
484: * @return a flag if there is a next element
485: */
486: public boolean hasNext() {
487: return endIndex < keyBuffer.length();
488: }
489:
490: /**
491: * Returns the next object in the iteration.
492: * @return the next object
493: */
494: public Object next() {
495: return nextKey();
496: }
497:
498: /**
499: * Removes the current object in the iteration. This method is not
500: * supported by this iterator type, so an exception is thrown.
501: */
502: public void remove() {
503: throw new UnsupportedOperationException(
504: "Remove not supported!");
505: }
506:
507: /**
508: * Returns the current key of the iteration (without skipping to the
509: * next element). This is the same key the previous <code>next()</code>
510: * call had returned. (Short form of <code>currentKey(false)</code>.
511: * @return the current key
512: */
513: public String currentKey() {
514: return currentKey(false);
515: }
516:
517: /**
518: * Returns the current key of the iteration (without skipping to the
519: * next element). The boolean parameter indicates wheter a decorated
520: * key should be returned. This affects only attribute keys: if the
521: * parameter is <b>false</b>, the attribute markers are stripped from
522: * the key; if it is <b>true</b>, they remain.
523: * @param decorated a flag if the decorated key is to be returned
524: * @return the current key
525: */
526: public String currentKey(boolean decorated) {
527: return (decorated && isAttribute()) ? constructAttributeKey(current)
528: : current;
529: }
530:
531: /**
532: * Returns a flag if the current key is an attribute. This method can
533: * be called after <code>next()</code>.
534: * @return a flag if the current key is an attribute
535: */
536: public boolean isAttribute() {
537: return attribute;
538: }
539:
540: /**
541: * Returns the index value of the current key. If the current key does
542: * not have an index, return value is -1. This method can be called
543: * after <code>next()</code>.
544: * @return the index value of the current key
545: */
546: public int getIndex() {
547: return indexValue;
548: }
549:
550: /**
551: * Returns a flag if the current key has an associated index.
552: * This method can be called after <code>next()</code>.
553: * @return a flag if the current key has an index
554: */
555: public boolean hasIndex() {
556: return hasIndex;
557: }
558:
559: /**
560: * Creates a clone of this object.
561: * @return a clone of this object
562: */
563: protected Object clone() {
564: try {
565: return super .clone();
566: } /* try */
567: catch (CloneNotSupportedException cex) {
568: // should not happen
569: return null;
570: } /* catch */
571: }
572:
573: }
574: }
|