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