001 /*
002 // $Id: //open/util/resgen/src/org/eigenbase/xom/DOMElementParser.java#6 $
003 // Package org.eigenbase.xom is an XML Object Mapper.
004 // Copyright (C) 2005-2005 The Eigenbase Project
005 // Copyright (C) 2005-2005 Disruptive Tech
006 // Copyright (C) 2005-2005 LucidEra, Inc.
007 // Portions Copyright (C) 2000-2005 Kana Software, Inc. and others.
008 //
009 // This library is free software; you can redistribute it and/or modify it
010 // under the terms of the GNU Lesser General Public License as published by the
011 // Free Software Foundation; either version 2 of the License, or (at your
012 // option) any later version approved by The Eigenbase Project.
013 //
014 // This library is distributed in the hope that it will be useful,
015 // but WITHOUT ANY WARRANTY; without even the implied warranty of
016 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
017 // GNU Lesser General Public License for more details.
018 //
019 // You should have received a copy of the GNU Lesser General Public License
020 // along with this library; if not, write to the Free Software
021 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022 //
023 // dsommerfield, 6 November, 2000
024 */
025
026 package org.eigenbase.xom;
027 import java.lang.reflect.Constructor;
028 import java.lang.reflect.InvocationTargetException;
029 import java.lang.reflect.Method;
030 import java.lang.reflect.Modifier;
031 import java.util.Vector;
032
033 /**
034 * DOMElementParser is a utility wrapper around DOMWrapper.
035 * Implements a parseable stream of child DOMWrappers and also provides
036 * validation on an XML document beyond the DTD.
037 */
038 public class DOMElementParser {
039
040 private DOMWrapper wrapper;
041 private DOMWrapper[] children;
042 private int currentIndex;
043 private DOMWrapper currentChild;
044
045 private int optionIndex;
046 private String prefix;
047 private Class enclosure;
048
049 /**
050 * Constructs a new ElementParser based on an Element of the XML parse
051 * tree wrapped in a DOMWrapper, and a prefix (to be applied to all element
052 * tags except the root), and the name of the enclosing class.
053 * @param wrapper a DOMWrapper representing the section of the XML parse tree
054 * to traverse.
055 */
056 public DOMElementParser(DOMWrapper wrapper, String prefix, Class enclosure)
057 throws XOMException
058 {
059 this.wrapper = wrapper;
060 children = wrapper.getElementChildren();
061 currentIndex = 0;
062 currentChild = null;
063 getNextElement();
064
065 this.prefix = prefix;
066 if (prefix == null) {
067 this.prefix = "";
068 }
069 this.enclosure = enclosure;
070 }
071
072 /**
073 * Private helper function to retrieve the next child element in sequence.
074 * @return the next element, or null if the enumerator has no more
075 * elements to return.
076 */
077 private void getNextElement()
078 {
079 if (currentIndex >= children.length) {
080 currentChild = null;
081 } else {
082 currentChild = children[currentIndex++];
083 }
084 }
085
086 /**
087 * Private helper function to verify that the next element matches a
088 * specific name.
089 * @param name name of the element to match. Names are not case-sensitive.
090 * @throws XOMException if there is no current element or the names do
091 * not match.
092 */
093 private void requiredName(String name)
094 throws XOMException
095 {
096 String augName = prefix + name;
097 if (currentChild == null) {
098 throw new XOMException(
099 "Expected <" + augName + "> but found " + "nothing.");
100 } else if (!augName.equalsIgnoreCase(currentChild.getTagName())) {
101 throw new XOMException(
102 "Expected <" + augName + "> but found <"
103 + currentChild.getTagName() + ">");
104 }
105 }
106
107 /**
108 * Private helper function to determine if the next element has the
109 * specified name.
110 * @return true if the next element's name matches <i>name</i>. Matching
111 * is not case-sensitive. Returns false if there is no next element or
112 * if the names don't match.
113 */
114 private boolean optionalName(String name)
115 {
116 String augName = prefix + name;
117 if (currentChild == null) {
118 return false;
119 } else if (augName.equalsIgnoreCase(currentChild.getTagName())) {
120 return true;
121 } else {
122 return false;
123 }
124 }
125
126 /**
127 * Returns the enclosure class associated with clazz, or falls back on
128 * the fixed enclosure if none can be found.
129 */
130 private Class getEnclosureClass(Class clazz)
131 {
132 // Instead of using a fixed enclosure, derive it from the given Class.
133 // If we can't figure it out, just use the given enclosure instead.
134 Class thisEnclosure = enclosure;
135 String className = clazz.getName();
136 int dollarPos = className.indexOf('$');
137 if (dollarPos >= 0) {
138 String encName = className.substring(0, dollarPos);
139 try {
140 thisEnclosure = Class.forName(encName);
141 } catch (ClassNotFoundException ex) {
142 throw new AssertFailure("Enclosure class " + encName
143 + " not found.");
144 }
145 }
146 return thisEnclosure;
147 }
148
149 /**
150 * Private helper function to determine if the next element's corresponding
151 * definition class is a subclass of the given class. This may be used
152 * to detect if a name matches a class.
153 * @param clazz the class to match the next element against.
154 * @return true if the next element's name matches the given class, false
155 * otherwise.
156 * @throws XOMException if the next name is invalid (either doesn't
157 * start with DM or has no associated definition class).
158 */
159 private boolean nameMatchesClass(Class clazz)
160 throws XOMException
161 {
162 // Get the next name. It must start with the set prefix, and it must
163 // match a definition in the enclosure class.
164 Class thisEnclosure = getEnclosureClass(clazz);
165 Class nextClass = ElementDef.getElementClass(currentChild,
166 thisEnclosure,
167 prefix);
168
169 // Determine if nextClass is a subclass of clazz. Return true if so.
170 return nextClass != null &&
171 clazz.isAssignableFrom(nextClass);
172 }
173
174 /**
175 * This function retrieves a required String element from this parser,
176 * advancing the parser after the read.
177 * @param elementName the name of the element to retrieve.
178 * @return the String value stored inside the element to retrieve.
179 * @throws XOMException if there is no element with the given name.
180 */
181 public String requiredString(String elementName)
182 throws XOMException
183 {
184 requiredName(elementName);
185 String retval = currentChild.getText().trim();
186 getNextElement();
187 return retval;
188 }
189
190 /**
191 * This function retrieves an optional String element from this parser,
192 * advancing the parser if the element is found.
193 * If no element of the correct name is found, this function returns null.
194 * @param elementName the name of the element to retrieve.
195 * @return the String value stored inside the element to retrieve.
196 */
197 public String optionalString(String elementName)
198 throws XOMException
199 {
200 if (optionalName(elementName)) {
201 String retval = currentChild.getText().trim();
202 getNextElement();
203 return retval;
204 } else {
205 return null;
206 }
207 }
208
209 /**
210 * This function retrieves a required Element from this parser,
211 * advancing the parser after the read.
212 * @param elementName the name of the element to retrieve.
213 * @return the DOMWrapper to retrieve.
214 * @throws XOMException if there is no element with the given name.
215 */
216 public DOMWrapper requiredElement(String elementName)
217 throws XOMException
218 {
219 requiredName(elementName);
220 DOMWrapper prevWrapper = currentChild;
221 getNextElement();
222 return prevWrapper;
223 }
224
225 /**
226 * This function is used to return a CDATA section as text. It does
227 * no parsing.
228 * @return the contents of the CDATA element as text.
229 */
230 public String getText()
231 {
232 return wrapper.getText().trim();
233 }
234
235 /**
236 * This function retrieves an optional Element from this parser,
237 * advancing the parser if the element is found.
238 * If no element of the correct name is found, this function returns null.
239 * @param elementName the name of the element to retrieve.
240 * @return the DOMWrapper to retreive, or null if none found.
241 */
242 public DOMWrapper optionalElement(String elementName)
243 throws XOMException
244 {
245 if (optionalName(elementName)) {
246 DOMWrapper prevChild = currentChild;
247 getNextElement();
248 return prevChild;
249 } else {
250 return null;
251 }
252 }
253
254 /**
255 * This private helper function formats a list of element names into
256 * a readable string for error messages.
257 */
258 private String formatOption(String[] elementNames)
259 {
260 StringBuffer sbuf = new StringBuffer();
261 for (int i = 0; i < elementNames.length; i++) {
262 sbuf.append("<DM" + prefix);
263 sbuf.append(elementNames[i]);
264 sbuf.append(">");
265 if (i < elementNames.length - 1) {
266 sbuf.append(" or ");
267 }
268 }
269 return sbuf.toString();
270 }
271
272 /**
273 * This function retrieves a required element which may have one of a
274 * number of names. The parser is advanced after the read.
275 * @param elementNames an array of allowed names. Names are compared in
276 * a case-insensitive fashion.
277 * @return the first element with one of the given names.
278 * @throws XOMException if there are no more elements to read or if
279 * the next element's name is not in the elementNames list.
280 */
281 public DOMWrapper requiredOption(String[] elementNames)
282 throws XOMException
283 {
284 if (currentChild == null) {
285 throw new XOMException("Expecting "
286 + formatOption(elementNames)
287 + " but found nothing.");
288 } else {
289 for (int i = 0; i < elementNames.length; i++) {
290 String augName = "DM" + elementNames[i];
291 if (augName.equalsIgnoreCase(
292 currentChild.getTagName().toString())) {
293 DOMWrapper prevWrapper = currentChild;
294 getNextElement();
295 optionIndex = i;
296 return prevWrapper;
297 }
298 }
299
300 // If we got here, no names match.
301 throw new XOMException("Expecting "
302 + formatOption(elementNames)
303 + " but found <"
304 + currentChild.getTagName()
305 + ">.");
306 }
307 }
308
309 /**
310 * This function retrieves a required Element of a specific class
311 * from this parser, advancing the parser after the read.
312 * The class must be derived from ElementDef.
313 */
314 public NodeDef requiredClass(Class classTemplate)
315 throws XOMException
316 {
317 // The name must match the class.
318 if (!nameMatchesClass(classTemplate)) {
319 throw new XOMException("element <" + currentChild.getTagName()
320 + "> does not match expected class "
321 + classTemplate.getName());
322 }
323
324 // Get the class corresponding to the current tag
325 Class currentClass = ElementDef.getElementClass(currentChild,
326 enclosure, prefix);
327
328 // Get the element
329 DOMWrapper prevWrapper = currentChild;
330 getNextElement();
331
332 // Construct an ElementDef of the correct class from the element
333 return ElementDef.constructElement(prevWrapper, currentClass);
334 }
335
336 /**
337 * Returns the option index of the element returned through the last
338 * requiredOption call.
339 */
340 public int lastOptionIndex()
341 {
342 return optionIndex;
343 }
344
345 /**
346 * This function retrieves a required Attribute by name from the
347 * current Element.
348 * @param attrName the name of the attribute.
349 * @return the String value of the attribute.
350 * @throws XOMException if no attribute of this name is set.
351 */
352 public String requiredAttribute(String attrName)
353 throws XOMException
354 {
355 Object attr = wrapper.getAttribute(attrName);
356 if (attr == null) {
357 throw new XOMException("Required attribute '"
358 + attrName + "' is not set.");
359 }
360 return attr.toString();
361 }
362
363 /**
364 * This static version of requiredAttribute uses any element definition
365 * as a basis for the attribute. It is used by Plugin definitions to
366 * return attributes before the parser is created.
367 * @param wrapper the Element in which to find the attribute.
368 * @param attrName the name of the attribute to retrieve.
369 * @param defaultVal the default value of the attribute to retrieve.
370 * @throws XOMException if no attribute of this name is set.
371 */
372 public static String requiredDefAttribute(DOMWrapper wrapper,
373 String attrName,
374 String defaultVal)
375 throws XOMException
376 {
377 Object attr = wrapper.getAttribute(attrName);
378 if (attr == null) {
379 if (defaultVal == null) {
380 throw new XOMException("Required attribute "
381 + attrName + " is not set.");
382 } else {
383 return defaultVal;
384 }
385 }
386 return attr.toString();
387 }
388
389 /**
390 * This function retrieves an optional Attribute by name from the
391 * current Element.
392 * @param attrName the name of the attribute.
393 * @return the String value of the attribute, or null if the
394 * attribute is not set.
395 */
396 public String optionalAttribute(String attrName)
397 throws XOMException
398 {
399 Object attr = wrapper.getAttribute(attrName);
400 if (attr == null) {
401 return null;
402 }
403 return attr.toString();
404 }
405
406 /**
407 * This function retrieves an optional Attribute by name from the
408 * current Element, converting it to an Integer.
409 * @param attrName the name of the attribute.
410 * @return the Integer value of the attribute, or null if the
411 * attribute is not set.
412 * @throws XOMException if the value is set to an illegal
413 * integer value.
414 */
415 public Integer optionalIntegerAttribute(String attrName)
416 throws XOMException
417 {
418 Object attr = wrapper.getAttribute(attrName);
419 if (attr == null) {
420 return null;
421 }
422 try {
423 return new Integer(attr.toString());
424 } catch (NumberFormatException ex) {
425 throw new XOMException("Illegal integer value \""
426 + attr.toString() + "\" for attribute "
427 + attrName + ": " + ex.getMessage());
428 }
429 }
430
431 /**
432 * This function retrieves an optional Attribute by name from the
433 * current Element, converting it to a Double.
434 * @param attrName the name of the attribute.
435 * @return the Double value of the attribute, or null if the
436 * attribute is not set.
437 * @throws XOMException if the value is set to an illegal
438 * double value.
439 */
440 public Double optionalDoubleAttribute(String attrName)
441 throws XOMException
442 {
443 Object attr = wrapper.getAttribute(attrName);
444 if (attr == null) {
445 return null;
446 }
447 try {
448 return new Double(attr.toString());
449 } catch (NumberFormatException ex) {
450 throw new XOMException("Illegal double value \""
451 + attr.toString() + "\" for attribute "
452 + attrName + ": " + ex.getMessage());
453 }
454 }
455
456 /**
457 * This function retrieves an required Attribute by name from the
458 * current Element, converting it to an Integer.
459 * @param attrName the name of the attribute.
460 * @return the Integer value of the attribute.
461 * @throws XOMException if the value is not set, or is set to
462 * an illegal integer value.
463 */
464 public Integer requiredIntegerAttribute(String attrName)
465 throws XOMException
466 {
467 Object attr = wrapper.getAttribute(attrName);
468 if (attr == null) {
469 throw new XOMException("Required integer attribute "
470 + attrName + " is not set.");
471 }
472 try {
473 return new Integer(attr.toString());
474 } catch (NumberFormatException ex) {
475 throw new XOMException("Illegal integer value \""
476 + attr.toString() + "\" for attribute "
477 + attrName + ": " + ex.getMessage());
478 }
479 }
480
481 /**
482 * This function retrieves an optional Attribute by name from the
483 * current Element, converting it to an Boolean. The string value
484 * "true" (in any case) is considered TRUE. Any other value is
485 * considered false.
486 * @param attrName the name of the attribute.
487 * @return the Boolean value of the attribute, or null if the
488 * attribute is not set.
489 * @throws XOMException if the value is set to an illegal
490 * integer value.
491 */
492 public Boolean optionalBooleanAttribute(String attrName)
493 throws XOMException
494 {
495 Object attr = wrapper.getAttribute(attrName);
496 if (attr == null) {
497 return null;
498 }
499 return new Boolean(attr.toString());
500 }
501
502 /**
503 * This function retrieves an required Attribute by name from the
504 * current Element, converting it to a Boolean. The string value
505 * "true" (in any case) is considered TRUE. Any other value is
506 * considered false.
507 * @param attrName the name of the attribute.
508 * @return the Boolean value of the attribute.
509 */
510 public Boolean requiredBooleanAttribute(String attrName)
511 throws XOMException
512 {
513 Object attr = wrapper.getAttribute(attrName);
514 if (attr == null) {
515 throw new XOMException("Required boolean attribute "
516 + attrName + " is not set.");
517 }
518 return new Boolean(attr.toString());
519 }
520
521 /**
522 * This function retrieves a collection of elements with the given name,
523 * returning them as an array.
524 * @param elemName the element name.
525 * @param min the minimum number of elements required in the array. Set
526 * this parameter to 0 to indicate no minimum.
527 * @param max the maximum number of elements allowed in the array. Set
528 * this parameter to 0 to indicate no maximum.
529 * @return an Element array containing the discovered elements.
530 * @throws XOMException if there are fewer than min or more than max
531 * elements with the name <i>elemName</i>.
532 */
533 public DOMWrapper[] optionalArray(String elemName, int min, int max)
534 throws XOMException
535 {
536 // First, read the appropriate elements into a vector.
537 Vector vec = new Vector();
538 String augName = "DM" + elemName;
539 while (currentChild != null &&
540 augName.equalsIgnoreCase(currentChild.getTagName())) {
541 vec.addElement(currentChild);
542 getNextElement();
543 }
544
545 // Now, check for size violations
546 if (min > 0 && vec.size() < min) {
547 throw new XOMException("Expecting at least " + min + " <"
548 + elemName + "> but found " + vec.size());
549 }
550 if (max > 0 && vec.size() > max) {
551 throw new XOMException("Expecting at most " + max + " <"
552 + elemName + "> but found " +
553 vec.size());
554 }
555
556 // Finally, convert to an array and return.
557 DOMWrapper[] retval = new DOMWrapper[vec.size()];
558 for (int i = 0; i < retval.length; i++) {
559 retval[i] = (DOMWrapper)(vec.elementAt(i));
560 }
561 return retval;
562 }
563
564 /**
565 * This function retrieves a collection of elements which are subclasses of
566 * the given class, returning them as an array. The array will contain
567 * ElementDef objects automatically constructed to be of the correct class.
568 * @param elemClass the element class.
569 * @param min the minimum number of elements required in the array. Set
570 * this parameter to 0 to indicate no minimum.
571 * @param max the maximum number of elements allowed in the array. Set
572 * this parameter to 0 to indicate no maximum.
573 * @return an ElementDef array containing the discovered elements.
574 * @throws XOMException if there are fewer than min or more than max
575 * elements with the name <i>elemName</i>.
576 */
577 public NodeDef[] classArray(Class elemClass, int min, int max)
578 throws XOMException
579 {
580 // Instead of using a fixed enclosure, derive it from the given Class.
581 // If we can't figure it out, just use the given enclosure instead.
582 Class thisEnclosure = getEnclosureClass(elemClass);
583
584 // First, read the appropriate elements into a vector.
585 Vector vec = new Vector();
586 while (currentChild != null &&
587 nameMatchesClass(elemClass)) {
588 vec.addElement(currentChild);
589 getNextElement();
590 }
591
592 // Now, check for size violations
593 if (min > 0 && vec.size() < min) {
594 throw new XOMException("Expecting at least " + min + " <"
595 + elemClass.getName()
596 + "> but found " + vec.size());
597 }
598 if (max > 0 && vec.size() > max) {
599 throw new XOMException("Expecting at most " + max + " <"
600 + elemClass.getName()
601 + "> but found " +
602 vec.size());
603 }
604
605 // Finally, convert to an array and return.
606 NodeDef[] retval = new NodeDef[vec.size()];
607 for (int i = 0; i < retval.length; i++) {
608 retval[i] =
609 ElementDef.constructElement((DOMWrapper)(vec.elementAt(i)),
610 thisEnclosure, prefix);
611 }
612 return retval;
613 }
614
615 /**
616 * This function retrieves an Element from this parser, advancing the
617 * parser if the element is found. The Element's corresponding
618 * ElementDef class is looked up and its constructor is called
619 * automatically. If the requested Element is not found the function
620 * returns null <i>unless</i> required is set to true. In this case,
621 * a XOMException is thrown.
622 * @param elementClass the Class of the element to retrieve.
623 * @param required true to throw an exception if the element is not
624 * found, false to simply return null.
625 * @return the element, as an ElementDef, or null if it is not found
626 * and required is false.
627 * @throws XOMException if required is true and the element could not
628 * be found.
629 */
630 public NodeDef getElement(Class elementClass,
631 boolean required)
632 throws XOMException
633 {
634 // If current element is null, return null immediately
635 if (currentChild == null) {
636 return null;
637 }
638
639 // Check if the name matches the class
640 if (!nameMatchesClass(elementClass)) {
641 if (required) {
642 throw new XOMException("element <" + currentChild.getTagName()
643 + "> is not of expected type "
644 + elementClass.getName());
645 } else {
646 return null;
647 }
648 }
649
650
651
652 // Get the class corresponding to the current tag. This will be
653 // equal to elementClass if the current content was declared using
654 // an Element, but not if the current content was declared using
655 // a Class.
656 Class thisEnclosure = getEnclosureClass(elementClass);
657 Class currentClass = ElementDef.getElementClass(currentChild,
658 thisEnclosure, prefix);
659
660 // Get the element
661 DOMWrapper prevChild = currentChild;
662 getNextElement();
663
664 // Construct an ElementDef of the correct class from the element
665 return ElementDef.constructElement(prevChild, currentClass);
666 }
667
668 /**
669 * This function retrieves a collection of elements which are subclasses of
670 * the given class, returning them as an array. The array will contain
671 * ElementDef objects automatically constructed to be of the correct class.
672 * @param elemClass the element class.
673 * @param min the minimum number of elements required in the array. Set
674 * this parameter to 0 to indicate no minimum.
675 * @param max the maximum number of elements allowed in the array. Set
676 * this parameter to 0 to indicate no maximum.
677 * @return an ElementDef array containing the discovered elements.
678 * @throws XOMException if there are fewer than min or more than max
679 * elements with the name <i>elemName</i>.
680 */
681 public NodeDef[] getArray(Class elemClass, int min, int max)
682 throws XOMException
683 {
684 return classArray(elemClass, min, max);
685 }
686
687 /**
688 * This function retrieves a String element from this parser,
689 * advancing the parser if the element is found.
690 * If no element of the correct name is found, this function returns null,
691 * unless required is true, in which case a XOMException is thrown.
692 * @param elementName the name of the element to retrieve.
693 * @param required true to throw an exception if the element is not
694 * found, false to simply return null.
695 * @return the String value stored inside the element to retrieve, or
696 * null if no element with the given elementName could be found.
697 */
698 public String getString(String elementName, boolean required)
699 throws XOMException
700 {
701 boolean found;
702 if (required) {
703 requiredName(elementName);
704 found = true;
705 } else {
706 found = optionalName(elementName);
707 }
708 if (found) {
709 String retval = currentChild.getText().trim();
710 getNextElement();
711 return retval;
712 } else {
713 return null;
714 }
715 }
716
717 /**
718 * This function returns a collection of String elements of the given
719 * name, returning them as an array.
720 * @param elemName the element name.
721 * @param min the minimum number of elements required in the array. Set
722 * this parameter to 0 to indicate no minimum.
723 * @param max the maximum number of elements allowed in the array. Set
724 * this parameter to 0 to indicate no maximum.
725 * @return a String array containing the discovered elements.
726 * @throws XOMException if there are fewer than min or more than max
727 * elements with the name <i>elemName</i>.
728 */
729 public String[] getStringArray(String elemName, int min, int max)
730 throws XOMException
731 {
732 // First, read the appropriate elements into a vector.
733 Vector vec = new Vector();
734 String augName = prefix + elemName;
735 while (currentChild != null &&
736 augName.equalsIgnoreCase(currentChild.getTagName().toString())) {
737 vec.addElement(currentChild);
738 getNextElement();
739 }
740
741 // Now, check for size violations
742 if (min > 0 && vec.size() < min) {
743 throw new XOMException("Expecting at least " + min + " <"
744 + elemName + "> but found " + vec.size());
745 }
746 if (max > 0 && vec.size() > max) {
747 throw new XOMException("Expecting at most " + max + " <"
748 + elemName + "> but found " +
749 vec.size());
750 }
751
752 // Finally, convert to an array, retrieve the text from each
753 // element, and return.
754 String[] retval = new String[vec.size()];
755 for (int i = 0; i < retval.length; i++) {
756 retval[i] = ((DOMWrapper)(vec.elementAt(i))).getText().trim();
757 }
758 return retval;
759 }
760
761 // Determine if a String is present anywhere in a given array.
762 private boolean stringInArray(String str, String[] array)
763 {
764 for (int i = 0; i < array.length; i++) {
765 if (str.equals(array[i])) {
766 return true;
767 }
768 }
769 return false;
770 }
771
772 // Convert an array of Strings into a single String for display.
773 private String arrayToString(String[] array)
774 {
775 StringBuffer sbuf = new StringBuffer();
776 sbuf.append("{");
777 for (int i = 0; i < array.length; i++) {
778 sbuf.append(array[i]);
779 if (i < array.length - 1) {
780 sbuf.append(", ");
781 }
782 }
783 sbuf.append("}");
784 return sbuf.toString();
785 }
786
787 /**
788 * Get a Class object representing a plugin class, identified either
789 * directly by a Java package and Java class name, or indirectly
790 * by a Java package and Java class which defines a method called
791 * getXMLDefClass() to return the appropriate class.
792 * @param packageName the name of the Java package containing the
793 * plugin class.
794 * @param className the name of the plugin definition class.
795 * @throws XOMException if the plugin class cannot be located
796 * or if the designated class is not suitable as a plugin class.
797 */
798 public static Class getPluginClass(String packageName,
799 String className)
800 throws XOMException
801 {
802 Class managerClass = null;
803 try {
804 managerClass = Class.forName(packageName + "." + className);
805 } catch (ClassNotFoundException ex) {
806 throw new XOMException("Unable to locate plugin class "
807 + packageName + "."
808 + className + ": "
809 + ex.getMessage());
810 }
811
812 return getPluginClass(managerClass);
813 }
814
815 /**
816 * Get a Class object representing a plugin class, given a manager
817 * class that implements the static method getXMLDefClass().
818 * @param managerClass any Class that implements getXMLDefClass.
819 * @return the plugin Class.
820 */
821 public static Class getPluginClass(Class managerClass)
822 throws XOMException
823 {
824 // Look for a static method called getXMLDefClass which returns
825 // type Class. If we find this method, call it to produce the
826 // actual plugin class. Otherwise, throw an exception; the
827 // class we selected is inappropriate.
828 Method[] methods = managerClass.getMethods();
829 for (int i = 0; i < methods.length; i++) {
830 // Must be static, take no args, and return Class.
831 if (methods[i].getParameterTypes().length != 0) {
832 continue;
833 }
834 if (!(methods[i].getReturnType() == Class.class)) {
835 continue;
836 }
837 if (!(Modifier.isStatic(methods[i].getModifiers()))) {
838 continue;
839 }
840
841 // Invoke the method here.
842 try {
843 Object[] args = new Object[0];
844 return (Class)(methods[i].invoke(null, args));
845 } catch (InvocationTargetException ex) {
846 throw new XOMException("Exception while retrieving "
847 + "plugin class: " +
848 ex.getTargetException().toString());
849 } catch (IllegalAccessException ex) {
850 throw new XOMException("Illegal access while retrieving "
851 + "plugin class: " +
852 ex.getMessage());
853 }
854 }
855
856 // Class is inappropriate.
857 throw new XOMException("Plugin class " + managerClass.getName()
858 + " is not an appropriate plugin class; "
859 + "getXMLDefClass() is not defined.");
860 }
861
862 /**
863 * Retrieve an Attribute from the parser. The Attribute may be of any
864 * Java class, provided that the class supports a constructor from the
865 * String class. The Attribute's value will be returned as an Object,
866 * which must then be cast to the appropraite type. If the attribute
867 * is not defined and has no default, either null is returned (if
868 * required is false), or a XOMException is thrown (if required is
869 * true).
870 * @param attrName the name of the attribute to retreive.
871 * @param attrType a String naming a Java Class to serve as the type.
872 * If attrType contains a "." character, the class is looked up directly
873 * from the type name. Otherwise, the class is looked up in the
874 * java.lang package. Finally, the class must have a constructor which
875 * takes a String as an argument.
876 * @param defaultValue the default value for this attribute. If values
877 * is set, the defaultValue must also be one of the set of values.
878 * defaultValue may be null.
879 * @param values an array of possible values for the attribute. If
880 * this parameter is not null, then the attribute's value must be one
881 * of the listed set of values or an exception will be thrown.
882 * @param required if set, then this function will throw an exception
883 * if the attribute has no value and defaultValue is null.
884 * @return the Attribute's value as an Object. The actual class of
885 * this object is determined by attrType.
886 */
887 public Object getAttribute(String attrName, String attrType,
888 String defaultValue, String[] values,
889 boolean required)
890 throws XOMException
891 {
892 // Retrieve the attribute type class
893 if (attrType.indexOf('.') == -1) {
894 attrType = "java.lang." + attrType;
895 }
896 Class typeClass = null;
897 try {
898 typeClass = Class.forName(attrType);
899 } catch (ClassNotFoundException ex) {
900 throw new XOMException("Class could not be found for attribute "
901 + "type: " + attrType + ": "
902 + ex.getMessage());
903 }
904
905 // Get a constructor from the type class which takes a String as
906 // input. If one does not exist, throw an exception.
907 Class[] classArray = new Class[1];
908 classArray[0] = java.lang.String.class;
909 Constructor stringConstructor = null;
910 try {
911 stringConstructor = typeClass.getConstructor(classArray);
912 } catch (NoSuchMethodException ex) {
913 throw new XOMException("Attribute type class " +
914 attrType + " does not have a "
915 + "constructor which takes a String: "
916 + ex.getMessage());
917 }
918
919 // Get the Attribute of the given name
920 Object attrVal = wrapper.getAttribute(attrName);
921 if (attrVal == null) {
922 attrVal = defaultValue;
923 }
924 // Check for null
925 if (attrVal == null) {
926 if (required) {
927 throw new XOMException(
928 "Attribute '" + attrName +
929 "' is unset and has no default value.");
930 } else {
931 return null;
932 }
933 }
934
935 // Make sure it is on the list of acceptable values
936 if (values != null) {
937 if (!stringInArray(attrVal.toString(), values)) {
938 throw new XOMException(
939 "Value '" + attrVal.toString()
940 + "' of attribute '"
941 + attrName + "' has illegal value '"
942 + attrVal + "'. Legal values: "
943 + arrayToString(values));
944 }
945 }
946
947 // Invoke the constructor to get the final object
948 Object[] args = new Object[1];
949 args[0] = attrVal.toString();
950 try {
951 return stringConstructor.newInstance(args);
952 } catch (InstantiationException ex) {
953 throw new XOMException(
954 "Unable to construct a " + attrType
955 + " from value \"" + attrVal + "\": "
956 + ex.getMessage());
957 } catch (InvocationTargetException ex) {
958 throw new XOMException(
959 "Unable to construct a " + attrType
960 + " from value \"" + attrVal + "\": "
961 + ex.getMessage());
962 } catch (IllegalAccessException ex) {
963 throw new XOMException(
964 "Unable to construct a " + attrType
965 + " from value \"" + attrVal + "\": "
966 + ex.getMessage());
967 }
968 }
969 }
970
971 // End DOMElementParser.java