001: /*
002: * Enhydra Java Application Server Project
003: *
004: * The contents of this file are subject to the Enhydra Public License
005: * Version 1.1 (the "License"); you may not use this file except in
006: * compliance with the License. You may obtain a copy of the License on
007: * the Enhydra web site ( http://www.enhydra.org/ ).
008: *
009: * Software distributed under the License is distributed on an "AS IS"
010: * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
011: * the License for the specific terms governing rights and limitations
012: * under the License.
013: *
014: * The Initial Developer of the Enhydra Application Server is Lutris
015: * Technologies, Inc. The Enhydra Application Server and portions created
016: * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
017: * All Rights Reserved.
018: *
019: * Contributor(s):
020: *
021: * $Id: KeywordValueTable.java,v 1.3 2007-10-19 10:35:46 sinisa Exp $
022: */
023:
024: package com.lutris.util;
025:
026: import java.lang.reflect.Array;
027: import java.lang.reflect.Method;
028: import java.util.Enumeration;
029: import java.util.Hashtable;
030: import java.util.Vector;
031:
032: /**
033: * Class that implements a recursive keyword/value table. The key is a string
034: * that is restricted to be a valid Java identifier. That is, starting with
035: * an letter and containing letters or digits. The characters '_' and '$'
036: * are also allowed and are treated as letters. The value maybe any object.
037: * A keyword and its value are collectively referred to as a <I>field</I>
038: *
039: * The table is recursive. Values of class KeywordValueTable are referred to as
040: * <I>sections</I>. A field of a section maybe addressed from the parent
041: * object using a dot ('.') separated name path.
042: *
043: * @version $Revision: 1.3 $
044: * @author Mark Diekhans
045: * @since Harmony 1.0
046: */
047: public class KeywordValueTable implements java.io.Serializable {
048: private Hashtable hashTable; // Table keyed by keyword component.
049:
050: /**
051: * Constructor.
052: */
053: public KeywordValueTable() {
054: hashTable = new Hashtable();
055: }
056:
057: /**
058: * Recursively locate the section named by a keyword path. This finds
059: * the section containing the last component of the path.
060: *
061: * @param keywordPath The parsed keyword to locate.
062: * @param create If <CODE>true</CODE>, create missing section, if
063: * <CODE>false</CODE> return null on a missing section.
064: * @param pathIdx Current index in keywordPath. Zero for top level call.
065: * @return A reference to the section. For a single component keyword,
066: * <CODE>this</CODE> is returned. If the second is not found.
067: * @exception KeywordValueException If a non-leaf
068: * element of the keyword is not a section.
069: */
070: private synchronized KeywordValueTable findSection(
071: String[] keywordPath, boolean create, int pathIdx)
072: throws KeywordValueException {
073: /*
074: * We don't return the leaf key, stop one above.
075: */
076: if (pathIdx == keywordPath.length - 1) {
077: return this ;
078: }
079:
080: /*
081: * Recurse down to the leaf's section.
082: */
083: Object value = hashTable.get(keywordPath[pathIdx]);
084: if (value != null) {
085: /*
086: * If its a different type, then replace if create otherwise its
087: * an error.
088: */
089: if (!(value instanceof KeywordValueTable)) {
090: if (!create) {
091: String msg = "keyword specifies a non-leaf component "
092: + "that is not a KeywordValueTable: "
093: + KeywordParser.join(keywordPath)
094: + " (component #" + pathIdx + ")";
095: throw new KeywordValueException(msg);
096: }
097: value = newSection();
098: hashTable.put(keywordPath[pathIdx], value);
099: }
100: } else {
101: /*
102: * Value does not exist.
103: */
104: if (!create) {
105: return null;
106: }
107: value = newSection();
108: hashTable.put(keywordPath[pathIdx], value);
109: }
110: return ((KeywordValueTable) value).findSection(keywordPath,
111: create, pathIdx + 1);
112: }
113:
114: /**
115: * Allocate a new section. The default implementation of this
116: * method returns a new KeywordValueTable object. A class derived
117: * from KeywordValueTable overrides this method to create a new
118: * object of the derived type. Sections are only allocated by
119: * this method.
120: *
121: * @return A reference to a new section.
122: */
123: protected KeywordValueTable newSection() {
124: return new KeywordValueTable();
125: }
126:
127: /**
128: * Get the value of a field as an object.
129: *
130: * @param keyword The keyword of the field. This can be a simple keyword
131: * or a recursive, dot-seperated keyword path.
132: * @return The object value or null if its not found.
133: * @exception KeywordValueException If the keyword is not syntactically
134: * legal or is a non-leaf element of the keyword is not a section.
135: */
136: public synchronized Object get(String keyword)
137: throws KeywordValueException {
138:
139: String[] keywordPath = KeywordParser.parse(keyword);
140: KeywordValueTable section = findSection(keywordPath, false, // create
141: 0);
142: if (section == null) {
143: return null;
144: }
145: return section.hashTable
146: .get(keywordPath[keywordPath.length - 1]);
147: }
148:
149: /**
150: * Get the value of a field as an object, return a default if it
151: * does not exist.
152: *
153: * @param keyword The keyword of the field. This can be a simple keyword
154: * or a recursive, dot-seperated keyword path.
155: * @param defaultValue The default value to return it the keyword does not
156: * exist.
157: * @return The object value or <CODE>defaultValue</CODE> if its not found.
158: * @exception KeywordValueException If the keyword is not syntactically
159: * legal or is a non-leaf element of the keyword is not a section.
160: */
161: public synchronized Object get(String keyword, Object defaultValue)
162: throws KeywordValueException {
163:
164: Object value;
165: String[] keywordPath = KeywordParser.parse(keyword);
166: KeywordValueTable section = findSection(keywordPath, false, // create
167: 0);
168: if (section == null) {
169: value = defaultValue;
170: } else {
171: value = section.hashTable
172: .get(keywordPath[keywordPath.length - 1]);
173: if (value == null) {
174: value = defaultValue;
175: }
176: }
177: return value;
178: }
179:
180: /**
181: * Get the value of a field as a string
182: *
183: * @param keyword The keyword of the field. This can be a simple keyword
184: * or a recursive, dot-seperated keyword path.
185: * @return The result of calling toString on the value object
186: * or null if its not found.
187: * @exception KeywordValueException If the keyword is not syntactically
188: * legal or is a non-leaf element of the keyword is not a section.
189: */
190: public synchronized String getString(String keyword)
191: throws KeywordValueException {
192:
193: Object value = get(keyword);
194: if (value == null) {
195: return null;
196: }
197: return value.toString();
198: }
199:
200: /**
201: * Get the value of a field as a string, return a default if it
202: * does not exist.
203: *
204: * @param keyword The keyword of the field. This can be a simple keyword
205: * or a recursive, dot-seperated keyword path.
206: * @return The result of calling toString on the value object
207: * or <CODE>defaultValue</CODE> if its not found.
208: * @exception KeywordValueException If the keyword is not syntactically
209: * legal or the value object is not a String.
210: */
211: public synchronized String getString(String keyword,
212: String defaultValue) throws KeywordValueException {
213:
214: Object value = get(keyword);
215: if (value == null) {
216: return defaultValue;
217: }
218: return value.toString();
219: }
220:
221: /**
222: * Get the value of a section. The section is a value that is another
223: * KeywordValueTable object.
224: *
225: * @param keyword The keyword of the field. This can be a simple keyword
226: * or a recursive, dot-seperated keyword path.
227: * @return A reference to the section object or null if not found.
228: * @exception KeywordValueException If the keyword is not syntactically
229: * legal or a non-leaf element of the
230: * keyword is not a section or the value object is not a
231: * KeywordValueTable.
232: */
233: public synchronized KeywordValueTable getSection(String keyword)
234: throws KeywordValueException {
235:
236: Object value = get(keyword);
237: if (value == null) {
238: return null;
239: }
240: if (!(value instanceof KeywordValueTable)) {
241: String msg = "Value of field \"" + keyword
242: + " is not a KeywordValueTable; it is "
243: + value.getClass().getName();
244: throw new KeywordValueException(msg);
245: }
246: return (KeywordValueTable) value;
247: }
248:
249: /**
250: * Set the value of a field. If a keyword path is specified and the
251: * subsections do not exist, they are created. If a field other than
252: * a KeywordValueTable is one of the intermediate sections in the path, it
253: * will be deleted and replaced by a section.
254: *
255: * @param keyword The keyword of the field. This can be a simple keyword
256: * or a recursive, dot-seperated keyword path.
257: * @param value The value to associate with the keyword. The value may
258: * not be null.
259: * @exception KeywordValueException If the keyword is not syntactically
260: * legal.
261: */
262: public synchronized void set(String keyword, Object value)
263: throws KeywordValueException {
264:
265: String[] keywordPath = KeywordParser.parse(keyword);
266: KeywordValueTable section = findSection(keywordPath, true, // create
267: 0);
268: section.hashTable.put(keywordPath[keywordPath.length - 1],
269: value);
270: }
271:
272: /**
273: * Sets a default value for a keyword. This method only sets a value
274: * for the specified keyword if a value is <B>not</B> already set for
275: * that keyword. If a value is not set for the keyword, then
276: * if a keyword path is specified and the
277: * subsections do not exist, they are created. If a field other than
278: * a KeywordValueTable is one of the intermediate sections in the path, it
279: * will be deleted and replaced by a section.
280: *
281: * @param keyword The keyword of the field. This can be a simple keyword
282: * or a recursive, dot-seperated keyword path.
283: * @param defaultValue The default value to associate with the keyword.
284: * The default value may not be null. The default value is only
285: * set if the specified keyword does
286: * not already have a value associated with it.
287: * @exception KeywordValueException If the keyword is not syntactically
288: * legal.
289: */
290: public synchronized void setDefault(String keyword,
291: Object defaultValue) throws KeywordValueException {
292: if (!containsKey(keyword))
293: set(keyword, defaultValue);
294: }
295:
296: /**
297: * Determine if the a field with the specified keyword exists.
298: *
299: * @param keyword The keyword of the field. This can be a simple keyword
300: * or a recursive, dot-seperated keyword path.
301: * @return <CODE>true</CODE> if the code is in the table;
302: * <CODE>false</CODE> if its not.
303: * @exception KeywordValueException If the keyword is not syntactically
304: * legal.
305: */
306: public synchronized boolean containsKey(String keyword)
307: throws KeywordValueException {
308:
309: String[] keywordPath = KeywordParser.parse(keyword);
310: KeywordValueTable section = findSection(keywordPath, false, // create
311: 0);
312: if (section == null) {
313: return false;
314: }
315: return section.hashTable
316: .containsKey(keywordPath[keywordPath.length - 1]);
317: }
318:
319: /**
320: * Get the keywords in the table. This is only the keywords at the top
321: * level, its doesn't recurse.
322: *
323: * @return An string array of the keywords.
324: */
325: public synchronized String[] keys() {
326: Enumeration keyEnum = hashTable.keys();
327:
328: /*
329: * Build list as vector then convert it to a array when size is known.
330: */
331: Vector keyList = new Vector();
332: while (keyEnum.hasMoreElements()) {
333: keyList.addElement(keyEnum.nextElement());
334: }
335:
336: String[] keyStrings = new String[keyList.size()];
337: for (int idx = 0; idx < keyList.size(); idx++) {
338: keyStrings[idx] = (String) keyList.elementAt(idx);
339: }
340: return keyStrings;
341: }
342:
343: /**
344: * Recursively get the keywords for the entire table. This returns the
345: * full keyword of all leaf values.
346: *
347: * @return An string array of the keywords.
348: */
349: public synchronized String[] leafKeys() {
350: Enumeration keyEnum = hashTable.keys();
351:
352: /*
353: * Build list as vector then convert it to a array when size is known.
354: * Recurse down each value if its another table.
355: */
356: Vector keyList = new Vector();
357: while (keyEnum.hasMoreElements()) {
358: String key = (String) keyEnum.nextElement();
359: Object value = hashTable.get(key);
360:
361: if (value instanceof KeywordValueTable) {
362: String subKeys[] = ((KeywordValueTable) value)
363: .leafKeys();
364: for (int idx = 0; idx < subKeys.length; idx++) {
365: keyList.addElement(KeywordParser.concat(key,
366: subKeys[idx]));
367: }
368: } else {
369: keyList.addElement(key);
370: }
371: }
372:
373: String[] keyStrings = new String[keyList.size()];
374: for (int idx = 0; idx < keyList.size(); idx++) {
375: keyStrings[idx] = (String) keyList.elementAt(idx);
376: }
377: return keyStrings;
378: }
379:
380: /**
381: * Delete a field, if the field does not exist, the operation is ignored.
382: *
383: * @param keyword The keyword of the field. This can be a simple keyword
384: * or a recursive, dot-seperated keyword path.
385: * @exception KeywordValueException If the keyword is not syntactically
386: * legal.
387: */
388: public synchronized void remove(String keyword)
389: throws KeywordValueException {
390:
391: String[] keywordPath = KeywordParser.parse(keyword);
392: KeywordValueTable section = findSection(keywordPath, false, // create
393: 0);
394: if (section != null) {
395: section.hashTable
396: .remove(keywordPath[keywordPath.length - 1]);
397: }
398: }
399:
400: /**
401: * Convert to a string.
402: *
403: * @return Generate a string representation of this object.
404: */
405: public synchronized String toString() {
406: return hashTable.toString();
407: }
408:
409: /**
410: * Convert to an Html representation.
411: *
412: * @return the generated Html.
413: */
414: public synchronized String toHtml() {
415:
416: StringBuffer html = new StringBuffer();
417: Enumeration keyEnum = hashTable.keys();
418:
419: html.append("<UL>\n");
420: // Vector keyList = new Vector();
421: if (!keyEnum.hasMoreElements())
422: return ""; // This makes it possible to detect empty tables.
423: while (keyEnum.hasMoreElements()) {
424: String key = (String) keyEnum.nextElement();
425: Object value = hashTable.get(key);
426:
427: html.append("<LI> <TT>");
428: html.append(key);
429: html.append(": </TT>");
430: html.append(formatFieldAsHtml(value));
431: html.append("\n");
432: }
433: html.append("</UL>\n");
434:
435: return html.toString();
436: }
437:
438: /**
439: * Format an array section as a HTML ordered list, appending it
440: * to the list.
441: *
442: * @param arrayObj Field array object to format.
443: * @return generated Html
444: */
445: private String formatArrayAsHtml(Object arrayObj) {
446:
447: StringBuffer html = new StringBuffer();
448:
449: html.append("<OL START=0>\n");
450: for (int idx = 0; idx < Array.getLength(arrayObj); idx++) {
451: html.append("<LI>");
452: html.append(formatFieldAsHtml(Array.get(arrayObj, idx)));
453: html.append("\n");
454: }
455: html.append("</OL>\n");
456:
457: return html.toString();
458: }
459:
460: /**
461: * Format an object as HTML. Known printable objects are converted
462: * to a string, while others are simple listed as <Object>.
463: *
464: * @param obj Field array object to format.
465: * @return generated Html
466: */
467: private String formatObjectAsHtml(Object obj) {
468: String html;
469:
470: if (obj instanceof String) {
471: html = obj.toString();
472: } else if (obj instanceof Integer) {
473: html = "<I><FONT SIZE=-1>(Integer)</FONT></I>"
474: + obj.toString();
475: } else if (obj instanceof Boolean) {
476: html = "<I><FONT SIZE=-1>(Boolean)</FONT></I>"
477: + obj.toString();
478: } else if (obj instanceof Double) {
479: html = "<I><FONT SIZE=-1>(Double)</FONT></I>"
480: + obj.toString();
481: } else if (obj instanceof Long) {
482: html = "<I><FONT SIZE=-1>(Long)</FONT></I>"
483: + obj.toString();
484: } else if (obj instanceof Short) {
485: html = "<I><FONT SIZE=-1>(Short)</FONT></I>"
486: + obj.toString();
487: } else if (obj instanceof Float) {
488: html = "<I><FONT SIZE=-1>(Float)</FONT></I>"
489: + obj.toString();
490: } else if (obj instanceof Character) {
491: html = "<I><FONT SIZE=-1>(Character)</FONT></I>"
492: + obj.toString();
493: } else {
494: // If object has a toHtml() method then call it,
495: // else print nothing.
496: try {
497: Class objClass = obj.getClass();
498: Class[] params = null;
499: Method toHtmlMethod = objClass.getMethod("toHtml",
500: params);
501: Object[] args = null;
502: html = "<I><FONT SIZE=-1>(Object)"
503: + toHtmlMethod.invoke(obj, args)
504: + "</FONT></I>";
505: } catch (Exception e) {
506: html = "<I><FONT SIZE=-1>(Object)</FONT></I>";
507: }
508: }
509:
510: return html;
511: }
512:
513: /**
514: * Format an field. Normally just append toString the object.
515: * However, arrays and recursive data are formatted.
516: *
517: * @param fieldObj Field object to format.
518: * @return generated Html
519: */
520: private String formatFieldAsHtml(Object fieldObj) {
521:
522: String html;
523:
524: if (fieldObj instanceof KeywordValueTable) {
525: html = ((KeywordValueTable) fieldObj).toHtml();
526: } else if (fieldObj.getClass().isArray()) {
527: html = formatArrayAsHtml(fieldObj);
528: } else {
529: html = formatObjectAsHtml(fieldObj);
530: }
531:
532: return html;
533: }
534: }
|