1 /** 2 * Redistribution and use of this software and associated documentation 3 * ("Software"), with or without modification, are permitted provided 4 * that the following conditions are met: 5 * 6 * 1. Redistributions of source code must retain copyright 7 * statements and notices. Redistributions must also contain a 8 * copy of this document. 9 * 10 * 2. Redistributions in binary form must reproduce the 11 * above copyright notice, this list of conditions and the 12 * following disclaimer in the documentation and/or other 13 * materials provided with the distribution. 14 * 15 * 3. The name "Exolab" must not be used to endorse or promote 16 * products derived from this Software without prior written 17 * permission of Intalio, Inc. For written permission, 18 * please contact info@exolab.org. 19 * 20 * 4. Products derived from this Software may not be called "Exolab" 21 * nor may "Exolab" appear in their names without prior written 22 * permission of Intalio, Inc. Exolab is a registered 23 * trademark of Intalio, Inc. 24 * 25 * 5. Due credit should be given to the Exolab Project 26 * (http://www.exolab.org/). 27 * 28 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS 29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 32 * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 39 * OF THE POSSIBILITY OF SUCH DAMAGE. 40 * 41 * Copyright 1999-2004 (C) Intalio, Inc. All Rights Reserved. 42 * 43 * Portions of this file developed by Keith Visco after Jan 19 2005 are 44 * Copyright (C) Keith Visco. All Rights Reserverd. 45 * 46 * $Id: UnmarshalHandler.java 8057 2009-02-05 22:26:22Z jgrueneis $ 47 */ 48 package org.exolab.castor.xml; 49 50 import java.io.PrintWriter; 51 import java.io.Serializable; 52 import java.io.StringWriter; 53 import java.lang.reflect.Array; 54 import java.lang.reflect.InvocationTargetException; 55 import java.lang.reflect.Method; 56 import java.lang.reflect.Modifier; 57 import java.util.ArrayList; 58 import java.util.Enumeration; 59 import java.util.HashMap; 60 import java.util.Hashtable; 61 import java.util.Iterator; 62 import java.util.List; 63 import java.util.Stack; 64 import java.util.StringTokenizer; 65 66 import org.apache.commons.logging.Log; 67 import org.apache.commons.logging.LogFactory; 68 import org.castor.core.util.Base64Decoder; 69 import org.castor.core.util.HexDecoder; 70 import org.castor.xml.InternalContext; 71 import org.castor.xml.UnmarshalListenerAdapter; 72 import org.castor.xml.XMLProperties; 73 import org.exolab.castor.mapping.ClassDescriptor; 74 import org.exolab.castor.mapping.ExtendedFieldHandler; 75 import org.exolab.castor.mapping.FieldHandler; 76 import org.exolab.castor.mapping.MapItem; 77 import org.exolab.castor.mapping.loader.FieldHandlerImpl; 78 import org.exolab.castor.util.DefaultObjectFactory; 79 import org.exolab.castor.util.ObjectFactory; 80 import org.exolab.castor.xml.descriptors.PrimitivesClassDescriptor; 81 import org.exolab.castor.xml.descriptors.StringClassDescriptor; 82 import org.exolab.castor.xml.util.AttributeSetImpl; 83 import org.exolab.castor.xml.util.ContainerElement; 84 import org.exolab.castor.xml.util.SAX2ANY; 85 import org.exolab.castor.xml.util.XMLClassDescriptorImpl; 86 import org.exolab.castor.xml.util.XMLFieldDescriptorImpl; 87 import org.xml.sax.AttributeList; 88 import org.xml.sax.Attributes; 89 import org.xml.sax.ContentHandler; 90 import org.xml.sax.DocumentHandler; 91 import org.xml.sax.ErrorHandler; 92 import org.xml.sax.Locator; 93 import org.xml.sax.SAXException; 94 import org.xml.sax.SAXParseException; 95 96 /** 97 * An unmarshaller to allowing unmarshaling of XML documents to 98 * Java Objects. The Class must specify 99 * the proper access methods (setters/getters) in order for instances 100 * of the Class to be properly unmarshaled. 101 * 102 * @author <a href="mailto:keith AT kvisco DOT com">Keith Visco</a> 103 * @version $Revision: 8057 $ $Date: 2006-05-25 06:41:12 -0600 (Thu, 25 May 2006) $ 104 */ 105 public final class UnmarshalHandler extends MarshalFramework 106 implements ContentHandler, DocumentHandler, ErrorHandler { 107 /** 108 * Logger from commons-logging. 109 */ 110 private static final Log LOG = LogFactory.getLog(UnmarshalHandler.class); 111 112 //---------------------------/ 113 //- Private Class Variables -/ 114 //---------------------------/ 115 116 /** 117 * The error message when no class descriptor has been found 118 * TODO: move to resource bundle 119 */ 120 private static final String ERROR_DID_NOT_FIND_CLASSDESCRIPTOR = 121 "unable to find or create a ClassDescriptor for class: "; 122 123 /** 124 * The built-in XML prefix used for xml:space, xml:lang 125 * and, as the XML 1.0 Namespaces document specifies, are 126 * reserved for use by XML and XML related specs. 127 **/ 128 private static final String XML_PREFIX = "xml"; 129 130 /** 131 * Attribute name for default namespace declaration 132 **/ 133 private static final String XMLNS = "xmlns"; 134 135 /** 136 * Attribute prefix for prefixed namespace declaration 137 **/ 138 private final static String XMLNS_PREFIX = "xmlns:"; 139 private final static int XMLNS_PREFIX_LENGTH = XMLNS_PREFIX.length(); 140 141 /** 142 * The type attribute (xsi:type) used to denote the 143 * XML Schema type of the parent element 144 **/ 145 private static final String XSI_TYPE = "type"; 146 147 private static final String XML_SPACE = "space"; 148 private static final String XML_SPACE_WITH_PREFIX = "xml:space"; 149 private static final String PRESERVE = "preserve"; 150 151 //----------------------------/ 152 //- Private Member Variables -/ 153 //----------------------------/ 154 155 private Stack _stateInfo = null; 156 private UnmarshalState _topState = null; 157 private Class _topClass = null; 158 159 /** 160 * The top-level instance object, this may be set by the user 161 * by calling #setRootObject();. 162 **/ 163 private Object _topObject = null; 164 165 /** 166 * Indicates whether or not collections should be cleared 167 * upon first use (to remove default values, or old values). 168 * False by default for backward compatibility. 169 */ 170 private boolean _clearCollections = false; 171 172 /** 173 * The SAX Document Locator. 174 **/ 175 private Locator _locator = null; 176 177 /** 178 * The IDResolver for resolving IDReferences. 179 **/ 180 private IDResolverImpl _idResolver = null; 181 182 /** 183 * The unmarshaller listener. 184 */ 185 private org.castor.xml.UnmarshalListener _unmarshalListener = null; 186 187 /** 188 * A flag indicating whether or not to perform validation. 189 **/ 190 private boolean _validate = true; 191 192 private Hashtable _resolveTable = new Hashtable(); 193 194 private HashMap _javaPackages = null; 195 196 private ClassLoader _loader = null; 197 198 private static final StringClassDescriptor _stringDescriptor 199 = new StringClassDescriptor(); 200 201 /** 202 * A SAX2ANY unmarshaller in case we are dealing with {@literal <any>}. 203 */ 204 private SAX2ANY _anyUnmarshaller = null; 205 206 /** 207 * The any branch depth. 208 */ 209 private int _depth = 0; 210 211 /** 212 * The AnyNode to add (if any). 213 */ 214 private org.exolab.castor.types.AnyNode _node = null; 215 216 /** 217 * The namespace stack. 218 */ 219 private Namespaces _namespaces = null; 220 221 /** 222 * A map of namespace URIs to Package Names. 223 */ 224 private HashMap _namespaceToPackage = null; 225 226 /** 227 * A reference to the ObjectFactory used to create instances 228 * of the classes if the FieldHandler is not used. 229 */ 230 private ObjectFactory _objectFactory = new DefaultObjectFactory(); 231 232 /** 233 * A boolean to indicate that objects should 234 * be re-used where appropriate. 235 **/ 236 private boolean _reuseObjects = false; 237 238 /** 239 * A boolean that indicates attribute processing should 240 * be strict and an error should be flagged if any 241 * extra attributes exist. 242 **/ 243 private boolean _strictAttributes = false; 244 245 /** 246 * A boolean that indicates element processing should 247 * be strict and an error should be flagged if any 248 * extra elements exist. 249 **/ 250 private boolean _strictElements = true; 251 252 /** 253 * A depth counter that increases as we skip elements ( in startElement ) 254 * and decreases as we process and endElement. Only active if _strictElemnts 255 */ 256 private int _ignoreElementDepth = 0; 257 258 /** 259 * A flag to keep track of when a new namespace scope is needed. 260 */ 261 private boolean _createNamespaceScope = true; 262 263 /** 264 * Keeps track of the current element information 265 * as passed by the parser. 266 */ 267 private ElementInfo _elemInfo = null; 268 269 /** 270 * A "reusable" AttributeSet, for use when handling 271 * SAX 2 ContentHandler. 272 */ 273 private AttributeSetImpl _reusableAtts = null; 274 275 /** 276 * The top-level xml:space value. 277 */ 278 private boolean _wsPreserve = false; 279 280 //----------------/ 281 //- Constructors -/ 282 //----------------/ 283 284 /** 285 * Creates a new UnmarshalHandler 286 * The "root" class will be obtained by looking into the mapping 287 * for a descriptor that matches the root element. 288 **/ 289 protected UnmarshalHandler() { 290 this(null); 291 } 292 293 /** 294 * Creates a new UnmarshalHandler. 295 * 296 * @param topClass the Class to create the UnmarshalHandler for 297 */ 298 protected UnmarshalHandler(final Class<?> topClass) { 299 this(null, topClass); 300 } 301 302 /** 303 * Creates a new UnmarshalHandler. 304 * @param internalContext the {@link InternalContext} to use 305 * @param topClass the Class to work for 306 */ 307 protected UnmarshalHandler(final InternalContext internalContext, final Class<?> topClass) { 308 super(internalContext); 309 _stateInfo = new Stack(); 310 _idResolver = new IDResolverImpl(); 311 _javaPackages = new HashMap(); 312 _topClass = topClass; 313 _namespaces = new Namespaces(); 314 _namespaceToPackage = new HashMap(); 315 } 316 317 /** 318 * Adds a mapping from the given namespace URI to the given 319 * package name 320 * 321 * @param nsURI the namespace URI to map from 322 * @param packageName the package name to map to 323 */ 324 public void addNamespaceToPackageMapping(String nsURI, String packageName) 325 { 326 if (nsURI == null) nsURI = ""; 327 if (packageName == null) packageName = ""; 328 _namespaceToPackage.put(nsURI, packageName); 329 330 } //-- addNamespaceToPackageMapping 331 332 /** 333 * Returns the Object that the UnmarshalHandler is currently 334 * handling (within the object model), or null if the current 335 * element is a simpleType. 336 * 337 * @return the Object currently being unmarshalled, or null if the 338 * current element is a simpleType. 339 */ 340 public Object getCurrentObject() { 341 if (!_stateInfo.isEmpty()) { 342 UnmarshalState state = (UnmarshalState)_stateInfo.peek(); 343 if (state != null) { 344 return state.object; 345 } 346 } 347 return null; 348 } //-- getCurrentObject 349 350 /** 351 * Returns the "root" Object (ie. the entire object model) 352 * being unmarshalled. 353 * 354 * @return the root Object being unmarshalled. 355 **/ 356 public Object getObject() { 357 if (_topState != null) return _topState.object; 358 return null; 359 } //-- getObject 360 361 362 363 /** 364 * Sets the ClassLoader to use when loading classes 365 * 366 * @param loader the ClassLoader to use 367 **/ 368 public void setClassLoader(ClassLoader loader) { 369 _loader = loader; 370 } //-- setClassLoader 371 372 /** 373 * Sets whether or not to clear collections (including arrays) 374 * upon first use to remove default values. By default, and 375 * for backward compatibility with previous versions of Castor 376 * this value is false, indicating that collections are not 377 * cleared before initial use by Castor. 378 * 379 * @param clear the boolean value that when true indicates 380 * collections should be cleared upon first use. 381 */ 382 public void setClearCollections(boolean clear) { 383 _clearCollections = clear; 384 } //-- setClearCollections 385 386 /** 387 * Included for backward compatibility. Debug is replaced with 388 * commons-logging. 389 * @deprecated 390 **/ 391 public void setDebug(boolean debug) { 392 // no-op 393 } 394 395 /** 396 * Sets the IDResolver to use when resolving IDREFs for 397 * which no associated element may exist in XML document. 398 * 399 * @param idResolver the IDResolver to use when resolving 400 * IDREFs for which no associated element may exist in the 401 * XML document. 402 **/ 403 public void setIDResolver(IDResolver idResolver) { 404 _idResolver.setResolver(idResolver); 405 } //-- setIdResolver 406 407 408 /** 409 * Sets whether or not attributes that do not match 410 * a specific field should simply be ignored or 411 * reported as an error. By default, extra attributes 412 * are ignored. 413 * 414 * @param ignoreExtraAtts a boolean that when true will 415 * allow non-matched attributes to simply be ignored. 416 **/ 417 public void setIgnoreExtraAttributes(boolean ignoreExtraAtts) { 418 _strictAttributes = (!ignoreExtraAtts); 419 } //-- setIgnoreExtraAttributes 420 421 /** 422 * Sets whether or not elements that do not match 423 * a specific field should simply be ignored or 424 * reported as an error. By default, extra attributes 425 * are ignored. 426 * 427 * @param ignoreExtraElems a boolean that when true will 428 * allow non-matched attributes to simply be ignored. 429 **/ 430 public void setIgnoreExtraElements(boolean ignoreExtraElems) { 431 _strictElements = (!ignoreExtraElems); 432 } //-- setIgnoreExtraElements 433 434 /** 435 * Custom logging replaced with commons-logging. 436 * @deprecated 437 **/ 438 public void setLogWriter(PrintWriter printWriter) { 439 // no-op 440 } //-- setLogWriter 441 442 /** 443 * Sets a boolean that when true indicates that objects 444 * contained within the object model should be re-used 445 * where appropriate. This is only valid when unmarshalling 446 * to an existing object. 447 * 448 * @param reuse the boolean indicating whether or not 449 * to re-use existing objects in the object model. 450 **/ 451 public void setReuseObjects(boolean reuse) { 452 _reuseObjects = reuse; 453 } //-- setReuseObjects 454 455 // /** 456 // * Sets the ClassDescriptorResolver to use for loading and 457 // * resolving ClassDescriptors 458 // * 459 // * @param cdResolver the ClassDescriptorResolver to use 460 // **/ 461 // public void setResolver(XMLClassDescriptorResolver cdResolver) { 462 // this._cdResolver = cdResolver; 463 // } //-- setResolver 464 465 /** 466 * Sets the root (top-level) object to use for unmarshalling into. 467 * 468 * @param root the instance to unmarshal into. 469 **/ 470 public void setRootObject(Object root) { 471 _topObject = root; 472 } //-- setRootObject 473 474 /** 475 * Sets an {@link org.exolab.castor.xml.UnmarshalListener}. 476 * 477 * @param listener the {@link org.exolab.castor.xml.UnmarshalListener} to use with this instance 478 * of the UnmarshalHandler. 479 * @deprecated please move to the new {@link org.castor.xml.UnmarshalListener} interface 480 */ 481 public void setUnmarshalListener (org.exolab.castor.xml.UnmarshalListener listener) { 482 if (listener == null) { 483 listener = null; 484 } else { 485 UnmarshalListenerAdapter adapter = new UnmarshalListenerAdapter(); 486 adapter.setOldListener(listener); 487 _unmarshalListener = adapter; 488 } 489 } 490 491 /** 492 * Sets an {@link org.castor.xml.UnmarshalListener}. 493 * 494 * @param listener the {@link org.castor.xml.UnmarshalListener} to use with this instance 495 * of the UnmarshalHandler. 496 */ 497 public void setUnmarshalListener (org.castor.xml.UnmarshalListener listener) { 498 _unmarshalListener = listener; 499 } 500 501 /** 502 * Sets the flag for validation. 503 * 504 * @param validate A boolean to indicate whether or not validation should be done 505 * during umarshalling. 506 * <br/> 507 * By default, validation will be performed. 508 */ 509 public void setValidation(boolean validate) { 510 this._validate = validate; 511 } //-- setValidation 512 513 /** 514 * Sets the top-level whitespace (xml:space) to either 515 * preserving or non preserving. The XML document 516 * can override this value using xml:space on specific 517 * elements. This sets the "default" behavior 518 * when xml:space="default". 519 * 520 * @param preserve a boolean that when true enables 521 * whitespace preserving by default. 522 */ 523 public void setWhitespacePreserve(boolean preserve) { 524 _wsPreserve = preserve; 525 } //-- setWhitespacePreserve 526 527 //-----------------------------------/ 528 //- SAX Methods for DocumentHandler -/ 529 //-----------------------------------/ 530 531 public void characters(char[] ch, int start, int length) 532 throws SAXException 533 { 534 if (LOG.isTraceEnabled()) { 535 StringBuffer sb = new StringBuffer(21 + length); 536 sb.append("#characters: "); 537 sb.append(ch, start, length); 538 LOG.trace(sb.toString()); 539 } 540 541 //-- If we are skipping elements that have appeared in the XML but for 542 //-- which we have no mapping, skip the text and return 543 if ( _ignoreElementDepth > 0) { 544 return; 545 } 546 547 if (_stateInfo.empty()) { 548 return; 549 } 550 if (_anyUnmarshaller != null) 551 _anyUnmarshaller.characters(ch, start, length); 552 else { 553 UnmarshalState state = (UnmarshalState)_stateInfo.peek(); 554 //-- handle whitespace 555 boolean removedTrailingWhitespace = false; 556 boolean removedLeadingWhitespace = false; 557 if (!state.wsPreserve) { 558 //-- trim leading whitespace characters 559 while (length > 0) { 560 boolean whitespace = false; 561 switch(ch[start]) { 562 case ' ': 563 case '\r': 564 case '\n': 565 case '\t': 566 whitespace = true; 567 break; 568 default: 569 break; 570 } 571 if (!whitespace) break; 572 removedLeadingWhitespace = true; 573 ++start; 574 --length; 575 } 576 577 if (length == 0) { 578 //-- we also need to mark trailing whitespace removed 579 //-- when we received only whitespace characters 580 removedTrailingWhitespace = removedLeadingWhitespace; 581 } 582 else { 583 //-- trim trailing whitespace characters 584 while (length > 0) { 585 boolean whitespace = false; 586 switch(ch[start+length-1]) { 587 case ' ': 588 case '\r': 589 case '\n': 590 case '\t': 591 whitespace = true; 592 break; 593 default: 594 break; 595 } 596 if (!whitespace) break; 597 removedTrailingWhitespace = true; 598 --length; 599 } 600 } 601 } 602 603 if (state.buffer == null) state.buffer = new StringBuffer(); 604 else { 605 //-- non-whitespace content exists, add a space 606 if ((!state.wsPreserve) && (length > 0)) { 607 if (state.trailingWhitespaceRemoved || removedLeadingWhitespace) 608 { 609 state.buffer.append(' '); 610 } 611 } 612 } 613 state.trailingWhitespaceRemoved = removedTrailingWhitespace; 614 state.buffer.append(ch, start, length); 615 } 616 } //-- characters 617 618 619 public void endDocument() 620 throws org.xml.sax.SAXException 621 { 622 //-- I've found many application don't always call 623 //-- #endDocument, so I usually never put any 624 //-- important logic here 625 626 } //-- endDocument 627 628 629 public void endElement(String name) 630 throws org.xml.sax.SAXException 631 { 632 633 if (LOG.isTraceEnabled()) { 634 LOG.trace("#endElement: " + name); 635 } 636 637 //-- If we are skipping elements that have appeared in the XML but for 638 //-- which we have no mapping, decrease the ignore depth counter and return 639 if ( _ignoreElementDepth > 0) { 640 --_ignoreElementDepth; 641 return; 642 } 643 644 //-- Do delagation if necessary 645 if (_anyUnmarshaller != null) { 646 _anyUnmarshaller.endElement(name); 647 --_depth; 648 //we are back to the starting node 649 if (_depth == 0) { 650 _node = _anyUnmarshaller.getStartingNode(); 651 _anyUnmarshaller = null; 652 } 653 else return; 654 } 655 656 if (_stateInfo.empty()) { 657 throw new SAXException("missing start element: " + name); 658 } 659 660 //-- * Begin Namespace Handling 661 //-- XXX Note: This code will change when we update the XML event API 662 663 int idx = name.indexOf(':'); 664 if (idx >= 0) { 665 name = name.substring(idx+1); 666 } 667 //-- * End Namespace Handling 668 669 UnmarshalState state = (UnmarshalState) _stateInfo.pop(); 670 671 //-- make sure we have the correct closing tag 672 XMLFieldDescriptor descriptor = state.fieldDesc; 673 674 if (!state.elementName.equals(name)) { 675 676 //maybe there is still a container to end 677 if (descriptor.isContainer()) { 678 _stateInfo.push(state); 679 //-- check for possible characters added to 680 //-- the container's state that should 681 //-- really belong to the parent state 682 StringBuffer tmpBuffer = null; 683 if (state.buffer != null) { 684 if (!isWhitespace(state.buffer)) { 685 if (state.classDesc.getContentDescriptor() == null) { 686 tmpBuffer = state.buffer; 687 state.buffer = null; 688 } 689 } 690 } 691 //-- end container 692 endElement(state.elementName); 693 694 if (tmpBuffer != null) { 695 state = (UnmarshalState) _stateInfo.peek(); 696 if (state.buffer == null) 697 state.buffer = tmpBuffer; 698 else 699 state.buffer.append(tmpBuffer.toString()); 700 } 701 endElement(name); 702 return; 703 } 704 String err = "error in xml, expecting </" + state.elementName; 705 err += ">, but received </" + name + "> instead."; 706 throw new SAXException(err); 707 } 708 709 710 //-- clean up current Object 711 Class type = state.type; 712 713 if ( type == null ) { 714 if (!state.wrapper) { 715 //-- this message will only show up if debug 716 //-- is turned on...how should we handle this case? 717 //-- should it be a fatal error? 718 LOG.info("Ignoring " + state.elementName + " no descriptor was found"); 719 } 720 721 //-- handle possible location text content 722 //-- TODO: cleanup location path support. 723 //-- the following code needs to be improved as 724 //-- for searching descriptors in this manner can 725 //-- be slow 726 StringBuffer tmpBuffer = null; 727 if (state.buffer != null) { 728 if (!isWhitespace(state.buffer)) { 729 tmpBuffer = state.buffer; 730 state.buffer = null; 731 } 732 } 733 if (tmpBuffer != null) { 734 UnmarshalState targetState = state; 735 String locPath = targetState.elementName; 736 while ((targetState = targetState.parent) != null) { 737 if ((targetState.wrapper) || 738 (targetState.classDesc == null)) 739 { 740 locPath = targetState.elementName + "/" + locPath; 741 continue; 742 } 743 744 XMLFieldDescriptor tmpDesc = targetState.classDesc.getContentDescriptor(); 745 if (tmpDesc != null && locPath.equals(tmpDesc.getLocationPath())) { 746 if (targetState.buffer == null) 747 targetState.buffer = tmpBuffer; 748 else 749 targetState.buffer.append(tmpBuffer.toString()); 750 } 751 } 752 } 753 754 //-- remove current namespace scoping 755 _namespaces = _namespaces.getParent(); 756 return; 757 } 758 759 //-- check for special cases 760 boolean byteArray = false; 761 if (type.isArray()) { 762 byteArray = (type.getComponentType() == Byte.TYPE); 763 } 764 765 //-- If we don't have an instance object and the Class type 766 //-- is not a primitive or a byte[] we must simply return 767 if ((state.object == null) && (!state.primitiveOrImmutable)) { 768 //-- remove current namespace scoping 769 _namespaces = _namespaces.getParent(); 770 return; 771 } 772 773 /// DEBUG System.out.println("end: " + name); 774 775 if (state.primitiveOrImmutable) { 776 777 String str = null; 778 779 if (state.buffer != null) { 780 str = state.buffer.toString(); 781 state.buffer.setLength(0); 782 } 783 784 if (type == String.class && !((XMLFieldDescriptorImpl) descriptor).isDerivedFromXSList()) { 785 if (str != null) 786 state.object = str; 787 else if (state.nil) { 788 state.object = null; 789 } 790 else { 791 state.object = ""; 792 } 793 } 794 //-- special handling for byte[] 795 else if (byteArray && !descriptor.isDerivedFromXSList()) { 796 if (str == null) 797 state.object = new byte[0]; 798 else { 799 state.object = decodeBinaryData(descriptor, str); 800 } 801 } 802 else if (state.args != null) { 803 state.object = createInstance(state.type, state.args); 804 } 805 else if (descriptor.isMultivalued() 806 && descriptor.getSchemaType() != null 807 && descriptor.getSchemaType().equals("list") 808 && ((XMLFieldDescriptorImpl) descriptor).isDerivedFromXSList()) { 809 StringTokenizer attrValueTokenizer = new StringTokenizer(str); 810 List primitives = new ArrayList(); 811 while (attrValueTokenizer.hasMoreTokens()) { 812 String tokenValue = attrValueTokenizer.nextToken(); 813 if (isPrimitive(descriptor.getFieldType())) { 814 primitives.add(toPrimitiveObject(type, tokenValue, state.fieldDesc)); 815 } else { 816 Class valueType = descriptor.getFieldType(); 817 //-- handle base64/hexBinary 818 if (valueType.isArray() 819 && (valueType.getComponentType() == Byte.TYPE)) { 820 primitives.add(decodeBinaryData(descriptor, tokenValue)); 821 } 822 } 823 824 } 825 state.object = primitives; 826 } else { 827 if (state.nil) { 828 state.object = null; 829 } else { 830 state.object = toPrimitiveObject(type,str,state.fieldDesc); 831 } 832 } 833 } 834 else if (ArrayHandler.class.isAssignableFrom(state.type)) { 835 state.object = ((ArrayHandler)state.object).getObject(); 836 state.type = state.object.getClass(); 837 838 } 839 840 //-- check for character content 841 if ((state.buffer != null) && 842 (state.buffer.length() > 0) && 843 (state.classDesc != null)) { 844 XMLFieldDescriptor cdesc = state.classDesc.getContentDescriptor(); 845 if (cdesc != null) { 846 Object value = state.buffer.toString(); 847 if (isPrimitive(cdesc.getFieldType())) 848 value = toPrimitiveObject(cdesc.getFieldType(), (String)value, state.fieldDesc); 849 else { 850 Class valueType = cdesc.getFieldType(); 851 //-- handle base64/hexBinary 852 if (valueType.isArray() 853 && (valueType.getComponentType() == Byte.TYPE)) { 854 value = decodeBinaryData(descriptor, (String) value); 855 } 856 } 857 858 859 try { 860 FieldHandler handler = cdesc.getHandler(); 861 boolean addObject = true; 862 if (_reuseObjects) { 863 //-- check to see if we need to 864 //-- add the object or not 865 Object tmp = handler.getValue(state.object); 866 if (tmp != null) { 867 //-- Do not add object if values 868 //-- are equal 869 addObject = (!tmp.equals(value)); 870 } 871 } 872 if (addObject) handler.setValue(state.object, value); 873 } 874 catch(java.lang.IllegalStateException ise) { 875 String err = "unable to add text content to "; 876 err += descriptor.getXMLName(); 877 err += " due to the following error: " + ise; 878 throw new SAXException(err, ise); 879 } 880 } 881 //-- Handle references 882 else if (descriptor.isReference()) { 883 UnmarshalState pState = (UnmarshalState) _stateInfo.peek(); 884 processIDREF(state.buffer.toString(), descriptor, pState.object); 885 _namespaces = _namespaces.getParent(); 886 return; 887 } else { 888 //-- check for non-whitespace...and report error 889 if (!isWhitespace(state.buffer)) { 890 String err = "Illegal Text data found as child of: " 891 + name; 892 err += "\n value: \"" + state.buffer + "\""; 893 throw new SAXException(err); 894 } 895 } 896 } 897 898 //-- We're finished processing the object, so notify the 899 //-- Listener (if any). 900 if (_unmarshalListener != null && state.object != null) { 901 _unmarshalListener.unmarshalled(state.object, 902 (state.parent == null) ? null : state.parent.object); 903 } 904 905 //-- if we are at root....just validate and we are done 906 if (_stateInfo.empty()) { 907 if (isValidating()) { 908 ValidationException first = null; 909 ValidationException last = null; 910 911 //-- check unresolved references 912 if (_resolveTable != null && !getInternalContext().getLenientIdValidation()) { 913 Enumeration enumeration = _resolveTable.keys(); 914 while (enumeration.hasMoreElements()) { 915 Object ref = enumeration.nextElement(); 916 //if (ref.toString().startsWith(MapItem.class.getName())) continue; 917 String msg = "unable to resolve reference: " + ref; 918 if (first == null) { 919 first = new ValidationException(msg); 920 last = first; 921 } 922 else { 923 last.setNext(new ValidationException(msg)); 924 last = last.getNext(); 925 } 926 } 927 } 928 try { 929 Validator validator = new Validator(); 930 ValidationContext context = new ValidationContext(); 931 context.setInternalContext(getInternalContext()); 932 validator.validate(state.object, context); 933 if (!getInternalContext().getLenientIdValidation()) { 934 validator.checkUnresolvedIdrefs(context); 935 } 936 context.cleanup(); 937 } 938 catch(ValidationException vEx) { 939 if (first == null) 940 first = vEx; 941 else 942 last.setNext(vEx); 943 } 944 if (first != null) { 945 throw new SAXException(first); 946 } 947 } 948 return; 949 } 950 951 //-- Add object to parent if necessary 952 953 if (descriptor.isIncremental()) { 954 //-- remove current namespace scoping 955 _namespaces = _namespaces.getParent(); 956 return; //-- already added 957 } 958 959 Object val = state.object; 960 961 //--special code for AnyNode handling 962 if (_node != null) { 963 val = _node; 964 _node = null; 965 } 966 967 //-- save fieldState 968 UnmarshalState fieldState = state; 969 970 //-- have we seen this object before? 971 boolean firstOccurance = false; 972 973 //-- get target object 974 state = (UnmarshalState) _stateInfo.peek(); 975 if (state.wrapper) { 976 state = fieldState.targetState; 977 } 978 979 //-- check to see if we have already read in 980 //-- an element of this type. 981 //-- (Q: if we have a container, do we possibly need to 982 //-- also check the container's multivalued status?) 983 if ( ! descriptor.isMultivalued() ) { 984 985 if (state.isUsed(descriptor)) { 986 987 String err = "element \"" + name; 988 err += "\" occurs more than once. (parent class: " + state.type.getName() + ")"; 989 990 String location = name; 991 while (!_stateInfo.isEmpty()) { 992 UnmarshalState tmpState = (UnmarshalState)_stateInfo.pop(); 993 if (!tmpState.wrapper) { 994 if (tmpState.fieldDesc.isContainer()) continue; 995 } 996 location = state.elementName + "/" + location; 997 } 998 999 err += "\n location: /" + location; 1000 1001 ValidationException vx = 1002 new ValidationException(err); 1003 1004 throw new SAXException(vx); 1005 } 1006 state.markAsUsed(descriptor); 1007 //-- if this is the identity then save id 1008 if (state.classDesc.getIdentity() == descriptor) { 1009 state.key = val; 1010 } 1011 } 1012 else { 1013 //-- check occurance of descriptor 1014 if (!state.isUsed(descriptor)) { 1015 firstOccurance = true; 1016 } 1017 1018 //-- record usage of descriptor 1019 state.markAsUsed(descriptor); 1020 } 1021 1022 try { 1023 FieldHandler handler = descriptor.getHandler(); 1024 //check if the value is a QName that needs to 1025 //be resolved (ns:value -> {URI}value) 1026 String valueType = descriptor.getSchemaType(); 1027 if ((valueType != null) && (valueType.equals(QNAME_NAME))) { 1028 val = resolveNamespace(val); 1029 } 1030 1031 boolean addObject = true; 1032 if (_reuseObjects && fieldState.primitiveOrImmutable) { 1033 //-- check to see if we need to 1034 //-- add the object or not 1035 Object tmp = handler.getValue(state.object); 1036 if (tmp != null) { 1037 //-- Do not add object if values 1038 //-- are equal 1039 addObject = (!tmp.equals(val)); 1040 } 1041 } 1042 1043 //-- special handling for mapped objects 1044 if (descriptor.isMapped()) { 1045 if (!(val instanceof MapItem)) { 1046 MapItem mapItem = new MapItem(fieldState.key, val); 1047 val = mapItem; 1048 } 1049 else { 1050 //-- make sure value exists (could be a reference) 1051 MapItem mapItem = (MapItem)val; 1052 if (mapItem.getValue() == null) { 1053 //-- save for later... 1054 addObject = false; 1055 addReference(mapItem.toString(), state.object, descriptor); 1056 } 1057 } 1058 } 1059 1060 if (addObject) { 1061 //-- clear any collections if necessary 1062 if (firstOccurance && _clearCollections) { 1063 handler.resetValue(state.object); 1064 } 1065 1066 if (descriptor.isMultivalued() 1067 && descriptor.getSchemaType() != null 1068 && descriptor.getSchemaType().equals("list") 1069 && ((XMLFieldDescriptorImpl) descriptor).isDerivedFromXSList()) { 1070 List values = (List) val; 1071 for (Iterator iterator = values.iterator(); iterator.hasNext();) { 1072 //-- finally set the value!! 1073 Object value = iterator.next(); 1074 handler.setValue(state.object, value); 1075 1076 // If there is a parent for this object, pass along 1077 // a notification that we've finished adding a child 1078 if ( _unmarshalListener != null ) { 1079 _unmarshalListener.fieldAdded(descriptor.getFieldName(), state.object, fieldState.object); 1080 } 1081 } 1082 } else { 1083 1084 //-- finally set the value!! 1085 handler.setValue(state.object, val); 1086 1087 // If there is a parent for this object, pass along 1088 // a notification that we've finished adding a child 1089 if ( _unmarshalListener != null ) { 1090 _unmarshalListener.fieldAdded(descriptor.getFieldName(), state.object, fieldState.object); 1091 } 1092 } 1093 } 1094 1095 } 1096 /* 1097 catch(java.lang.reflect.InvocationTargetException itx) { 1098 1099 Throwable toss = itx.getTargetException(); 1100 if (toss == null) toss = itx; 1101 1102 String err = "unable to add '" + name + "' to <"; 1103 err += state.descriptor.getXMLName(); 1104 err += "> due to the following exception: " + toss; 1105 throw new SAXException(err); 1106 } 1107 */ 1108 catch(Exception ex) { 1109 StringWriter sw = new StringWriter(); 1110 PrintWriter pw = new PrintWriter(sw); 1111 ex.printStackTrace(pw); 1112 pw.flush(); 1113 String err = "unable to add '" + name + "' to <"; 1114 err += state.fieldDesc.getXMLName(); 1115 err += "> due to the following exception: \n"; 1116 err += ">>>--- Begin Exception ---<<< \n"; 1117 err += sw.toString(); 1118 err += ">>>---- End Exception ----<<< \n"; 1119 throw new SAXException(err, ex); 1120 } 1121 1122 //-- remove current namespace scoping 1123 _namespaces = _namespaces.getParent(); 1124 1125 // remove additional (artifical aka container) state introduced for single-valued (iow maxOccurs="1") choices. 1126 if (state.fieldDesc.isContainer() 1127 && state.classDesc.isChoice() 1128 && !state.fieldDesc.isMultivalued()) { 1129 this.endElement(state.elementName); 1130 } 1131 } //-- endElement 1132 1133 /** 1134 * Decode binary data and return decoded value. 1135 * @param descriptor {@link XMLFieldDescriptor} instance for the field whose value requires decoding. 1136 * @param binaryData The binary data value to be decoded 1137 * @return Decode data. 1138 */ 1139 private byte[] decodeBinaryData(final XMLFieldDescriptor descriptor, 1140 final String binaryData) { 1141 //-- Base64/HexBinary decoding 1142 byte[] decodedValue; 1143 if ((descriptor.isMultivalued() 1144 && HexDecoder.DATA_TYPE.equals(descriptor.getComponentType())) 1145 || HexDecoder.DATA_TYPE.equals(descriptor.getSchemaType())) { 1146 decodedValue = HexDecoder.decode(binaryData); 1147 } else { 1148 decodedValue = Base64Decoder.decode(binaryData); 1149 } 1150 return decodedValue; 1151 } 1152 1153 /** 1154 * <p>ContentHandler#endElement</p> 1155 * 1156 * Signals the end of an element 1157 * 1158 * @param localName The name of the element. 1159 */ 1160 public void endElement(String namespaceURI, String localName, String qName) 1161 throws org.xml.sax.SAXException { 1162 if ((qName == null) || (qName.length() == 0)) { 1163 if ((localName == null) || (localName.length() == 0)) { 1164 String error = "Missing either 'qName' or 'localName', both cannot be null or emtpy."; 1165 throw new SAXException(error); 1166 } 1167 qName = localName; 1168 if ((namespaceURI != null) && (namespaceURI.length() > 0)) { 1169 //-- rebuild qName, for now 1170 String prefix = _namespaces.getNamespacePrefix(namespaceURI); 1171 if ((prefix != null) && (prefix.length() > 0)) 1172 qName = prefix + ":" + localName; 1173 } 1174 } 1175 1176 endElement(qName); 1177 } //-- endElement 1178 1179 1180 /** 1181 * Signals to end the namespace prefix mapping 1182 * 1183 * @param prefix the namespace prefix 1184 */ 1185 public void endPrefixMapping(String prefix) 1186 throws SAXException 1187 { 1188 //-- nothing to do , already taken care of in 1189 //-- endElement except if we are unmarshalling an 1190 //-- AnyNode 1191 if (_anyUnmarshaller != null) { 1192 _anyUnmarshaller.endPrefixMapping(prefix); 1193 } 1194 1195 } //-- endPrefixMapping 1196 1197 1198 public void ignorableWhitespace(char[] ch, int start, int length) 1199 throws org.xml.sax.SAXException 1200 { 1201 1202 //-- If we are skipping elements that have appeared in the XML but for 1203 //-- which we have no mapping, skip the text and return 1204 if ( _ignoreElementDepth > 0) { 1205 return; 1206 } 1207 1208 if (_stateInfo.empty()) { 1209 return; 1210 } 1211 1212 if (_anyUnmarshaller != null) 1213 _anyUnmarshaller.ignorableWhitespace(ch, start, length); 1214 else { 1215 UnmarshalState state = (UnmarshalState)_stateInfo.peek(); 1216 if (state.wsPreserve) { 1217 if (state.buffer == null) state.buffer = new StringBuffer(); 1218 state.buffer.append(ch, start, length); 1219 } 1220 } 1221 } //-- ignorableWhitespace 1222 1223 public void processingInstruction(String target, String data) 1224 throws org.xml.sax.SAXException 1225 { 1226 //-- do nothing for now 1227 } //-- processingInstruction 1228 1229 1230 public void setDocumentLocator(Locator locator) { 1231 this._locator = locator; 1232 } //-- setDocumentLocator 1233 1234 public Locator getDocumentLocator() { 1235 return _locator; 1236 } //-- getDocumentLocator 1237 1238 /** 1239 * Signals that an entity was skipped by the parser 1240 * 1241 * @param name the skipped entity's name 1242 */ 1243 public void skippedEntity(String name) 1244 throws SAXException 1245 { 1246 //-- do nothing 1247 1248 } //-- skippedEntity 1249 1250 /** 1251 * Signals the start of a new document 1252 */ 1253 public void startDocument() 1254 throws org.xml.sax.SAXException 1255 { 1256 1257 //-- I've found many application don't always call 1258 //-- #startDocument, so I usually never put any 1259 //-- important logic here 1260 1261 } //-- startDocument 1262 1263 1264 /** 1265 * <p>ContentHandler#startElement</p> 1266 * 1267 * Signals the start of element. 1268 * 1269 * @param localName The name of the element. 1270 * @param atts The AttributeList containing the associated attributes for the element. 1271 */ 1272 public void startElement(String namespaceURI, String localName, String qName, Attributes atts) 1273 throws org.xml.sax.SAXException { 1274 if (LOG.isTraceEnabled()) { 1275 if ((qName != null) && (qName.length() > 0)) 1276 LOG.trace("#startElement: " + qName); 1277 else 1278 LOG.trace("#startElement: " + localName); 1279 } 1280 1281 //-- If we are skipping elements that have appeared in the XML but for 1282 //-- which we have no mapping, increase the ignore depth counter and return 1283 if ((!_strictElements) && (_ignoreElementDepth > 0)) { 1284 ++_ignoreElementDepth; 1285 return; 1286 } 1287 1288 //-- if we are in an <any> section 1289 //-- we delegate the event handling 1290 if (_anyUnmarshaller != null) { 1291 _depth++; 1292 _anyUnmarshaller.startElement(namespaceURI, localName, qName, atts); 1293 return; 1294 } 1295 1296 //-- Create a new namespace scope if necessary and 1297 //-- make sure the flag is reset to true 1298 if (_createNamespaceScope) 1299 _namespaces = _namespaces.createNamespaces(); 1300 else 1301 _createNamespaceScope = true; 1302 1303 1304 if (_reusableAtts == null) { 1305 if (atts != null) 1306 _reusableAtts = new AttributeSetImpl(atts.getLength()); 1307 else { 1308 //-- we can't pass a null AttributeSet to the 1309 //-- startElement 1310 _reusableAtts = new AttributeSetImpl(); 1311 } 1312 } 1313 else { 1314 _reusableAtts.clear(); 1315 } 1316 1317 //-- process attributes 1318 boolean hasQNameAtts = false; 1319 if ((atts != null) && (atts.getLength() > 0)) { 1320 //-- look for any potential namespace declarations 1321 //-- in case namespace processing was disable 1322 //-- on the parser 1323 for (int i = 0; i < atts.getLength(); i++) { 1324 String attName = atts.getQName(i); 1325 if ((attName != null) && (attName.length() > 0)) { 1326 if (attName.equals(XMLNS)) { 1327 _namespaces.addNamespace("", atts.getValue(i)); 1328 } 1329 else if (attName.startsWith(XMLNS_PREFIX)) { 1330 String prefix = attName.substring(XMLNS_PREFIX.length()); 1331 _namespaces.addNamespace(prefix, atts.getValue(i)); 1332 } 1333 else { 1334 //-- check for prefix 1335 if (attName.indexOf(':') < 0) { 1336 _reusableAtts.setAttribute(attName, 1337 atts.getValue(i), atts.getURI(i)); 1338 } 1339 else hasQNameAtts = true; 1340 } 1341 } 1342 else { 1343 //-- if attName is null or empty, just process as a normal 1344 //-- attribute 1345 attName = atts.getLocalName(i); 1346 if (XMLNS.equals(attName)) { 1347 _namespaces.addNamespace("", atts.getValue(i)); 1348 } 1349 else { 1350 _reusableAtts.setAttribute(attName, atts.getValue(i), atts.getURI(i)); 1351 } 1352 } 1353 } 1354 } 1355 //-- if we found any qName-only atts, process those 1356 if (hasQNameAtts) { 1357 for (int i = 0; i < atts.getLength(); i++) { 1358 String attName = atts.getQName(i); 1359 if ((attName != null) && (attName.length() > 0)) { 1360 //-- process any non-namespace qName atts 1361 if ((!attName.equals(XMLNS)) && (!attName.startsWith(XMLNS_PREFIX))) 1362 { 1363 int idx = attName.indexOf(':'); 1364 if (idx >= 0) { 1365 String prefix = attName.substring(0, idx); 1366 attName = attName.substring(idx+1); 1367 String nsURI = atts.getURI(i); 1368 if ((nsURI == null) || (nsURI.length() == 0)) { 1369 nsURI = _namespaces.getNamespaceURI(prefix); 1370 } 1371 _reusableAtts.setAttribute(attName, atts.getValue(i), nsURI); 1372 } 1373 } 1374 } 1375 //-- else skip already processed in previous loop 1376 } 1377 } 1378 1379 //-- preserve parser passed arguments for any potential 1380 //-- delegation 1381 if (_elemInfo == null) { 1382 _elemInfo = new ElementInfo(null, atts); 1383 } 1384 else { 1385 _elemInfo.clear(); 1386 _elemInfo.attributes = atts; 1387 } 1388 1389 if ((localName == null) || (localName.length() == 0)) { 1390 if ((qName == null) || (qName.length() == 0)) { 1391 String error = "Missing either 'localName' or 'qName', both cannot be emtpy or null."; 1392 throw new SAXException(error); 1393 } 1394 localName = qName; 1395 _elemInfo.qName = qName; 1396 } 1397 else { 1398 if ((qName == null) || (qName.length() == 0)) { 1399 if ((namespaceURI == null) || (namespaceURI.length() == 0)) { 1400 _elemInfo.qName = localName; 1401 } 1402 else { 1403 String prefix = _namespaces.getNamespacePrefix(namespaceURI); 1404 if ((prefix != null) && (prefix.length() > 0)) { 1405 _elemInfo.qName = prefix + ":" + localName; 1406 } 1407 } 1408 1409 } 1410 else { 1411 _elemInfo.qName = qName; 1412 } 1413 } 1414 1415 int idx = localName.indexOf(':'); 1416 if (idx >= 0) { 1417 String prefix = localName.substring(0, idx); 1418 localName = localName.substring(idx+1); 1419 if ((namespaceURI == null) || (namespaceURI.length() == 0)) { 1420 namespaceURI = _namespaces.getNamespaceURI(prefix); 1421 } 1422 } else { 1423 // check for default namespace declaration 1424 String defaultNamespace = _namespaces.getNamespaceURI(""); 1425 // TODO[WG]: remove unnecessary check as it simply is wrong 1426 if (defaultNamespace != null && !defaultNamespace.equals("http://castor.exolab.org")) { 1427 namespaceURI = defaultNamespace; 1428 } 1429 //-- adjust empty namespace 1430 if ((namespaceURI != null) && (namespaceURI.length() == 0)) 1431 namespaceURI = null; 1432 } 1433 1434 //-- call private startElement 1435 startElement(localName, namespaceURI, _reusableAtts); 1436 1437 } //-- startElement 1438 1439 /** 1440 * <p>DocumentHandler#startElement</p> 1441 * 1442 * Signals the start of element. 1443 * 1444 * @param name The name of the element. 1445 * @param attList The AttributeList containing the associated attributes for the 1446 * element. 1447 */ 1448 public void startElement(String name, AttributeList attList) 1449 throws org.xml.sax.SAXException { 1450 if (LOG.isTraceEnabled()) { 1451 LOG.trace("#startElement: " + name); 1452 } 1453 1454 //-- If we are skipping elements that have appeared in the XML but for 1455 //-- which we have no mapping, increase the ignore depth counter and return 1456 if ((!_strictElements) && (_ignoreElementDepth > 0)) { 1457 ++_ignoreElementDepth; 1458 return; 1459 } 1460 1461 //-- if we are in an <any> section 1462 //-- we delegate the event handling 1463 if (_anyUnmarshaller != null) { 1464 _depth++; 1465 _anyUnmarshaller.startElement(name,attList); 1466 return; 1467 } 1468 1469 if (_elemInfo == null) { 1470 _elemInfo = new ElementInfo(name, attList); 1471 } 1472 else { 1473 _elemInfo.clear(); 1474 _elemInfo.qName = name; 1475 _elemInfo.attributeList = attList; 1476 } 1477 1478 //-- The namespace of the given element 1479 String namespace = null; 1480 1481 //-- Begin Namespace Handling : 1482 //-- XXX Note: This code will change when we update the XML event API 1483 1484 _namespaces = _namespaces.createNamespaces(); 1485 1486 //-- convert AttributeList to AttributeSet and process 1487 //-- namespace declarations 1488 AttributeSet atts = processAttributeList(attList); 1489 1490 String prefix = ""; 1491 //String qName = name; 1492 int idx = name.indexOf(':'); 1493 if (idx >= 0) { 1494 prefix = name.substring(0,idx); 1495 name = name.substring(idx+1); 1496 } 1497 1498 namespace = _namespaces.getNamespaceURI(prefix); 1499 1500 //-- End Namespace Handling 1501 1502 //-- call private startElement method 1503 startElement(name, namespace, atts); 1504 1505 } //-- startElement 1506 1507 /** 1508 * Signals the start of an element with the given name. 1509 * 1510 * @param name the NCName of the element. It is an error 1511 * if the name is a QName (ie. contains a prefix). 1512 * @param namespace the namespace of the element. This may be null. 1513 * Note: A null namespace is not the same as the default namespace unless 1514 * the default namespace is also null. 1515 * @param atts the AttributeSet containing the attributes associated 1516 * with the element. 1517 */ 1518 private void startElement 1519 (String name, String namespace, AttributeSet atts) 1520 throws SAXException 1521 { 1522 1523 UnmarshalState state = null; 1524 String xmlSpace = null; 1525 1526 1527 //-- handle special atts 1528 if (atts != null) { 1529 //-- xml:space 1530 xmlSpace = atts.getValue(XML_SPACE, Namespaces.XML_NAMESPACE); 1531 if (xmlSpace == null) { 1532 xmlSpace = atts.getValue(XML_SPACE_WITH_PREFIX, ""); 1533 } 1534 } 1535 1536 if (_stateInfo.empty()) { 1537 //-- Initialize since this is the first element 1538 1539 if (_topClass == null) { 1540 if (_topObject != null) { 1541 _topClass = _topObject.getClass(); 1542 } 1543 } 1544 // if (_cdResolver == null) { 1545 // if (_topClass == null) { 1546 // String err = "The class for the root element '" + 1547 // name + "' could not be found."; 1548 // throw new SAXException(err); 1549 // } 1550 // _cdResolver = (XMLClassDescriptorResolver) ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML); 1551 // _cdResolver.setClassLoader(_loader); 1552 // } 1553 if (getInternalContext().getXMLClassDescriptorResolver() == null) { 1554 // Joachim 2007-09-04 check is new 1555 String message = "XMLClassDescriptorResolver is not set!"; 1556 LOG.warn(message); 1557 throw new IllegalStateException(message); 1558 } 1559 1560 _topState = new UnmarshalState(); 1561 _topState.elementName = name; 1562 _topState.wsPreserve = (xmlSpace != null) ? PRESERVE.equals(xmlSpace) : _wsPreserve; 1563 1564 XMLClassDescriptor classDesc = null; 1565 //-- If _topClass is null, then we need to search 1566 //-- the resolver for one 1567 String instanceClassname = null; 1568 if (_topClass == null) { 1569 1570 //-- check for xsi:type 1571 instanceClassname = getInstanceType(atts, null); 1572 if (instanceClassname != null) { 1573 //-- first try loading class directly 1574 try { 1575 _topClass = loadClass(instanceClassname, null); 1576 } 1577 catch(ClassNotFoundException cnfe) {} 1578 1579 if (_topClass == null) { 1580 classDesc = getClassDescriptor(instanceClassname); 1581 if (classDesc != null) { 1582 _topClass = classDesc.getJavaClass(); 1583 } 1584 if (_topClass == null) { 1585 throw new SAXException("Class not found: " + 1586 instanceClassname); 1587 } 1588 } 1589 } 1590 else { 1591 classDesc = resolveByXMLName(name, namespace, null); 1592 if (classDesc == null) { 1593 classDesc = getClassDescriptor(name, _loader); 1594 if (classDesc == null) { 1595 classDesc = getClassDescriptor(getJavaNaming().toJavaClassName(name)); 1596 } 1597 } 1598 if (classDesc != null) { 1599 _topClass = classDesc.getJavaClass(); 1600 } 1601 } 1602 1603 if (_topClass == null) { 1604 String err = "The class for the root element '" + 1605 name + "' could not be found."; 1606 throw new SAXException(err); 1607 } 1608 } 1609 1610 //-- create a "fake" FieldDescriptor for the root element 1611 XMLFieldDescriptorImpl fieldDesc 1612 = new XMLFieldDescriptorImpl(_topClass, 1613 name, 1614 name, 1615 NodeType.Element); 1616 1617 _topState.fieldDesc = fieldDesc; 1618 //-- look for XMLClassDescriptor if null 1619 //-- always check resolver first 1620 if (classDesc == null) 1621 classDesc = getClassDescriptor(_topClass); 1622 1623 //-- check for top-level primitives 1624 if (classDesc == null) { 1625 if (isPrimitive(_topClass)) { 1626 classDesc = new PrimitivesClassDescriptor(_topClass); 1627 fieldDesc.setIncremental(false); 1628 _topState.primitiveOrImmutable = true; 1629 } 1630 } 1631 1632 fieldDesc.setClassDescriptor(classDesc); 1633 if (classDesc == null) { 1634 //-- report error 1635 if ((!isPrimitive(_topClass)) && 1636 (!Serializable.class.isAssignableFrom( _topClass ))) 1637 throw new SAXException(MarshalException.NON_SERIALIZABLE_ERR); 1638 String err = "unable to create XMLClassDescriptor " + 1639 "for class: " + _topClass.getName(); 1640 throw new SAXException(err); 1641 } 1642 _topState.classDesc = classDesc; 1643 _topState.type = _topClass; 1644 1645 if ((_topObject == null) && (!_topState.primitiveOrImmutable)) { 1646 // Retrieving the xsi:type attribute, if present 1647 String topPackage = getJavaPackage(_topClass); 1648 1649 if (instanceClassname == null) 1650 instanceClassname = getInstanceType(atts, topPackage); 1651 else { 1652 //-- instance type already processed above, reset 1653 //-- to null to prevent entering next block 1654 instanceClassname = null; 1655 } 1656 1657 if (instanceClassname != null) { 1658 Class instanceClass = null; 1659 try { 1660 1661 XMLClassDescriptor xcd = 1662 getClassDescriptor(instanceClassname); 1663 1664 boolean loadClass = true; 1665 if (xcd != null) { 1666 instanceClass = xcd.getJavaClass(); 1667 if (instanceClass != null) { 1668 loadClass = (!instanceClassname.equals(instanceClass.getName())); 1669 } 1670 } 1671 1672 if (loadClass) { 1673 try { 1674 instanceClass = loadClass(instanceClassname, null); 1675 } 1676 catch(ClassNotFoundException cnfe) { 1677 //-- revert back to ClassDescriptor's associated 1678 //-- class 1679 if (xcd != null) 1680 instanceClass = xcd.getJavaClass(); 1681 } 1682 } 1683 1684 if (instanceClass == null) { 1685 throw new SAXException("Class not found: " + 1686 instanceClassname); 1687 } 1688 1689 if (!_topClass.isAssignableFrom(instanceClass)) { 1690 String err = instanceClass + " is not a subclass of " 1691 + _topClass; 1692 throw new SAXException(err); 1693 } 1694 1695 } 1696 catch(Exception ex) { 1697 String msg = "unable to instantiate " + 1698 instanceClassname + "; "; 1699 throw new SAXException(msg + ex, ex); 1700 } 1701 1702 //-- try to create instance of the given Class 1703 Arguments args = processConstructorArgs(atts, classDesc); 1704 _topState.object = createInstance(instanceClass, args); 1705 } 1706 //-- no xsi type information present 1707 else { 1708 //-- try to create instance of the given Class 1709 Arguments args = processConstructorArgs(atts, classDesc); 1710 _topState.object = createInstance(_topClass, args); 1711 } 1712 } 1713 //-- otherwise use _topObject 1714 else { 1715 _topState.object = _topObject; 1716 } 1717 1718 _stateInfo.push(_topState); 1719 1720 if (!_topState.primitiveOrImmutable) { 1721 //--The top object has just been initialized 1722 //--notify the listener 1723 if ( _unmarshalListener != null ) 1724 _unmarshalListener.initialized(_topState.object, (_topState.parent==null)?null:_topState.parent.object); 1725 1726 processAttributes(atts, classDesc); 1727 if ( _unmarshalListener != null ) 1728 _unmarshalListener.attributesProcessed(_topState.object, (_topState.parent==null)?null:_topState.parent.object); 1729 processNamespaces(classDesc); 1730 } 1731 1732 String pkg = getJavaPackage(_topClass); 1733 if (getMappedPackage(namespace) == null) 1734 { 1735 addNamespaceToPackageMapping(namespace, pkg); 1736 } 1737 return; 1738 } //--rootElement 1739 1740 1741 //-- get MarshalDescriptor for the given element 1742 UnmarshalState parentState = (UnmarshalState)_stateInfo.peek(); 1743 1744 //Test if we can accept the field in the parentState 1745 //in case the parentState fieldDesc is a container 1746 //-- This following logic tests to see if we are in a 1747 //-- container and we need to close out the container 1748 //-- before proceeding: 1749 boolean canAccept = false; 1750 while ((parentState.fieldDesc != null) && 1751 (parentState.fieldDesc.isContainer() && !canAccept) ) 1752 { 1753 XMLClassDescriptor tempClassDesc = parentState.classDesc; 1754 1755 //-- Find ClassDescriptor for Parent 1756 if (tempClassDesc == null) { 1757 tempClassDesc = (XMLClassDescriptor)parentState.fieldDesc.getClassDescriptor(); 1758 if (tempClassDesc == null) 1759 tempClassDesc = getClassDescriptor(parentState.object.getClass()); 1760 } 1761 1762 canAccept = tempClassDesc.canAccept(name, namespace, parentState.object); 1763 1764 if (!canAccept) { 1765 //-- Does container class even handle this field? 1766 if (tempClassDesc.getFieldDescriptor(name, namespace, NodeType.Element) != null) { 1767 if (!parentState.fieldDesc.isMultivalued()) { 1768 String error = "The container object (" + tempClassDesc.getJavaClass().getName(); 1769 error += ") cannot accept the child object associated with the element '" + name + "'"; 1770 error += " because the container is already full!"; 1771 ValidationException vx = new ValidationException(error); 1772 throw new SAXException(vx); 1773 } 1774 } 1775 endElement(parentState.elementName); 1776 parentState = (UnmarshalState)_stateInfo.peek(); 1777 } 1778 tempClassDesc = null; 1779 } 1780 1781 1782 1783 1784 1785 //-- create new state object 1786 state = new UnmarshalState(); 1787 state.elementName = name; 1788 state.parent = parentState; 1789 1790 if (xmlSpace != null) 1791 state.wsPreserve = PRESERVE.equals(xmlSpace); 1792 else 1793 state.wsPreserve = parentState.wsPreserve; 1794 1795 _stateInfo.push(state); 1796 1797 //-- make sure we should proceed 1798 if (parentState.object == null) { 1799 if (!parentState.wrapper) return; 1800 } 1801 1802 Class _class = null; 1803 1804 //-- Find ClassDescriptor for Parent 1805 XMLClassDescriptor classDesc = parentState.classDesc; 1806 if (classDesc == null) { 1807 classDesc = (XMLClassDescriptor)parentState.fieldDesc.getClassDescriptor(); 1808 if (classDesc == null) 1809 classDesc = getClassDescriptor(parentState.object.getClass()); 1810 } else { 1811 // classDesc.resetElementCount(); 1812 } 1813 1814 //----------------------------------------------------/ 1815 //- Find FieldDescriptor associated with the element -/ 1816 //----------------------------------------------------/ 1817 1818 //-- A reference to the FieldDescriptor associated 1819 //-- the the "current" element 1820 XMLFieldDescriptor descriptor = null; 1821 1822 //-- inherited class descriptor 1823 //-- (only needed if descriptor cannot be found directly) 1824 XMLClassDescriptor cdInherited = null; 1825 1826 1827 //-- loop through stack and find correct descriptor 1828 //int pIdx = _stateInfo.size() - 2; //-- index of parentState 1829 UnmarshalState targetState = parentState; 1830 String path = ""; 1831 StringBuffer pathBuf = null; 1832 int count = 0; 1833 boolean isWrapper = false; 1834 XMLClassDescriptor oldClassDesc = classDesc; 1835 while (descriptor == null) { 1836 1837 //-- NOTE (kv 20050228): 1838 //-- we need to clean this code up, I made this 1839 //-- fix to make sure the correct descriptor which 1840 //-- matches the location path is used 1841 if (path.length() > 0) { 1842 String tmpName = path + "/" + name; 1843 descriptor = classDesc.getFieldDescriptor(tmpName, namespace, NodeType.Element); 1844 } 1845 //-- End Patch 1846 1847 if (descriptor == null) { 1848 descriptor = classDesc.getFieldDescriptor(name, namespace, NodeType.Element); 1849 } 1850 1851 //-- Namespace patch, should be moved to XMLClassDescriptor, but 1852 //-- this is the least intrusive patch at the moment. kv - 20030423 1853 if ((descriptor != null) && (!descriptor.isContainer())) { 1854 if ((namespace != null) && (namespace.length() > 0)) { 1855 if (!namespaceEquals(namespace, descriptor.getNameSpaceURI())) { 1856 //-- if descriptor namespace is not null, then we must 1857 //-- have a namespace match, so set descriptor to null, 1858 //-- or if descriptor is not a wildcard we can also 1859 //-- set to null. 1860 if ((descriptor.getNameSpaceURI() != null) || (!descriptor.matches("*"))) { 1861 descriptor = null; 1862 } 1863 1864 } 1865 } 1866 } 1867 //-- end namespace patch 1868 1869 1870 /* 1871 If descriptor is null, we need to handle possible inheritence, 1872 which might not be described in the current ClassDescriptor. 1873 This can be a slow process...for speed use the match attribute 1874 of the xml element in the mapping file. This logic might 1875 not be completely necessary, and perhaps we should remove it. 1876 */ 1877 // handle multiple level locations (where count > 0) (CASTOR-1039) 1878 // if ((descriptor == null) && (count == 0) && (!targetState.wrapper)) { 1879 if ((descriptor == null) && (!targetState.wrapper)) { 1880 MarshalFramework.InheritanceMatch[] matches = null; 1881 try { 1882 matches = searchInheritance(name, namespace, classDesc); // TODO: Joachim, _cdResolver); 1883 } 1884 catch(MarshalException rx) { 1885 //-- TODO: 1886 } 1887 if (matches.length != 0) { 1888 InheritanceMatch match = null; 1889 // It may be the case that this class descriptor can 1890 // appear under multiple parent field descriptors. Look 1891 // for the first match whose parent file descriptor XML 1892 // name matches the name of the element we are under 1893 for(int i = 0; i < matches.length; i++) { 1894 if(parentState.elementName.equals(matches[i].parentFieldDesc.getLocationPath())) { 1895 match = matches[i]; 1896 break; 1897 } 1898 } 1899 if(match == null) match = matches[0]; 1900 descriptor = match.parentFieldDesc; 1901 cdInherited = match.inheritedClassDesc; 1902 break; //-- found 1903 } 1904 /* */ 1905 1906 // handle multiple level locations (where count > 0) (CASTOR-1039) 1907 // isWrapper = (isWrapper || hasFieldsAtLocation(name, classDesc)); 1908 String tmpLocation = name; 1909 if (count > 0) { tmpLocation = path + "/" + name; } 1910 isWrapper = (isWrapper || hasFieldsAtLocation(tmpLocation, classDesc)); 1911 } 1912 else if (descriptor != null) { 1913 String tmpPath = descriptor.getLocationPath(); 1914 if (tmpPath == null) tmpPath = ""; 1915 if (path.equals(tmpPath))break; //-- found 1916 descriptor = null; //-- not found, try again 1917 } 1918 else { 1919 if (pathBuf == null) 1920 pathBuf = new StringBuffer(); 1921 else 1922 pathBuf.setLength(0); 1923 pathBuf.append(path); 1924 pathBuf.append('/'); 1925 pathBuf.append(name); 1926 isWrapper = (isWrapper || hasFieldsAtLocation(pathBuf.toString(), classDesc)); 1927 } 1928 1929 //-- Make sure there are more parent classes on stack 1930 //-- otherwise break, since there is nothing to do 1931 //if (pIdx == 0) break; 1932 if (targetState == _topState) break; 1933 1934 //-- adjust name and try parent 1935 if (count == 0) 1936 path = targetState.elementName; 1937 else { 1938 if (pathBuf == null) 1939 pathBuf = new StringBuffer(); 1940 else 1941 pathBuf.setLength(0); 1942 pathBuf.append(targetState.elementName); 1943 pathBuf.append('/'); 1944 pathBuf.append(path); 1945 path = pathBuf.toString(); 1946 } 1947 1948 //-- get 1949 //--pIdx; 1950 //targetState = (UnmarshalState)_stateInfo.elementAt(pIdx); 1951 targetState = targetState.parent; 1952 classDesc = targetState.classDesc; 1953 count++; 1954 } 1955 1956 if (descriptor != null && isValidating() && !getInternalContext().getLenientSequenceOrder()) { 1957 try { 1958 classDesc.checkDescriptorForCorrectOrderWithinSequence(descriptor, parentState, name); 1959 } catch (ValidationException e) { 1960 throw new SAXException(e); 1961 } 1962 } 1963 1964 1965 //-- The field descriptor is still null, we face a problem 1966 if (descriptor == null) { 1967 1968 //-- reset classDesc 1969 classDesc = oldClassDesc; 1970 1971 //-- isWrapper? 1972 if (isWrapper) { 1973 state.classDesc = new XMLClassDescriptorImpl(ContainerElement.class, name); 1974 state.wrapper = true; 1975 if (LOG.isDebugEnabled()) { 1976 LOG.debug("wrapper-element: " + name); 1977 } 1978 //-- process attributes 1979 processWrapperAttributes(atts); 1980 return; 1981 } 1982 1983 String mesg = "unable to find FieldDescriptor for '" + name; 1984 mesg += "' in ClassDescriptor of " + classDesc.getXMLName(); 1985 1986 //-- unwrap classDesc, if necessary, for the check 1987 //-- Introspector.introspected done below 1988 if (classDesc instanceof InternalXMLClassDescriptor) { 1989 classDesc = ((InternalXMLClassDescriptor)classDesc).getClassDescriptor(); 1990 } 1991 1992 //-- If we are skipping elements that have appeared in the XML but for 1993 //-- which we have no mapping, increase the ignore depth counter and return 1994 boolean lenientElementStrictnessForIntrospection = 1995 getInternalContext() 1996 .getBooleanProperty(XMLProperties.LENIENT_INTROSPECTED_ELEMENT_STRICTNESS) 1997 .booleanValue(); 1998 if (! _strictElements) { 1999 ++_ignoreElementDepth; 2000 //-- remove the StateInfo we just added 2001 _stateInfo.pop(); 2002 // drop Namespace instance as well 2003 _namespaces = _namespaces.getParent(); 2004 if (LOG.isDebugEnabled()) { 2005 LOG.debug(mesg + " - ignoring extra element."); 2006 } 2007 return; 2008 } 2009 //if we have no field descriptor and 2010 //the class descriptor was introspected 2011 //just log it 2012 else if (lenientElementStrictnessForIntrospection && Introspector.introspected(classDesc)) { 2013 LOG.warn(mesg); 2014 return; 2015 } 2016 //-- otherwise report error since we cannot find a suitable 2017 //-- descriptor 2018 else { 2019 throw new SAXException(mesg); 2020 } 2021 } //-- end null descriptor 2022 2023 /// DEBUG: System.out.println("path: " + path); 2024 2025 //-- Save targetState (used in endElement) 2026 if (targetState != parentState) { 2027 state.targetState = targetState; 2028 parentState = targetState; //-- reassign 2029 } 2030 2031 Object object = parentState.object; 2032 //--container support 2033 if (descriptor.isContainer()) { 2034 //create a new state to set the container as the object 2035 //don't save the current state, it will be recreated later 2036 2037 if (LOG.isDebugEnabled()) { 2038 LOG.debug("#container: " + descriptor.getFieldName()); 2039 } 2040 2041 //-- clear current state and re-use for the container 2042 state.clear(); 2043 //-- inherit whitespace preserving from the parentState 2044 state.wsPreserve = parentState.wsPreserve; 2045 state.parent = parentState; 2046 2047 //here we can hard-code a name or take the field name 2048 state.elementName = descriptor.getFieldName(); 2049 state.fieldDesc = descriptor; 2050 state.classDesc = (XMLClassDescriptor)descriptor.getClassDescriptor(); 2051 Object containerObject = null; 2052 2053 //1-- the container is not multivalued (not a collection) 2054 if (!descriptor.isMultivalued()) { 2055 // Check if the container object has already been instantiated 2056 FieldHandler handler = descriptor.getHandler(); 2057 containerObject = handler.getValue(object); 2058 if (containerObject != null){ 2059 if (state.classDesc != null) { 2060 if (state.classDesc.canAccept(name, namespace, containerObject)) { 2061 //remove the descriptor from the used list 2062 parentState.markAsNotUsed(descriptor); 2063 } 2064 } 2065 else { 2066 //remove the descriptor from the used list 2067 parentState.markAsNotUsed(descriptor); 2068 } 2069 } 2070 else { 2071 containerObject = handler.newInstance(object); 2072 } 2073 2074 } 2075 //2-- the container is multivalued 2076 else { 2077 Class containerClass = descriptor.getFieldType(); 2078 try { 2079 containerObject = containerClass.newInstance(); 2080 } 2081 catch(Exception ex) { 2082 throw new SAXException(ex); 2083 } 2084 } 2085 state.object = containerObject; 2086 state.type = containerObject.getClass(); 2087 2088 //we need to recall startElement() 2089 //so that we can find a more appropriate descriptor in for the given name 2090 _namespaces = _namespaces.createNamespaces(); 2091 startElement(name, namespace, atts); 2092 return; 2093 } 2094 //--End of the container support 2095 2096 2097 2098 //-- Find object type and create new Object of that type 2099 state.fieldDesc = descriptor; 2100 2101 /* <update> 2102 * we need to add this code back in, to make sure 2103 * we have proper access rights. 2104 * 2105 if (!descriptor.getAccessRights().isWritable()) { 2106 if (debug) { 2107 buf.setLength(0); 2108 buf.append("The field for element '"); 2109 buf.append(name); 2110 buf.append("' is read-only."); 2111 message(buf.toString()); 2112 } 2113 return; 2114 } 2115 */ 2116 2117 //-- Find class to instantiate 2118 //-- check xml names to see if we should look for a more specific 2119 //-- ClassDescriptor, otherwise just use the one found in the 2120 //-- descriptor 2121 classDesc = null; 2122 if (cdInherited != null) classDesc = cdInherited; 2123 else if (!name.equals(descriptor.getXMLName())) 2124 classDesc = resolveByXMLName(name, namespace, null); 2125 2126 if (classDesc == null) 2127 classDesc = (XMLClassDescriptor)descriptor.getClassDescriptor(); 2128 FieldHandler handler = descriptor.getHandler(); 2129 boolean useHandler = true; 2130 2131 try { 2132 2133 //-- Get Class type...first use ClassDescriptor, 2134 //-- since it could be more specific than 2135 //-- the FieldDescriptor 2136 if (classDesc != null) { 2137 _class = classDesc.getJavaClass(); 2138 2139 //-- XXXX This is a hack I know...but we 2140 //-- XXXX can't use the handler if the field 2141 //-- XXXX types are different 2142 if (descriptor.getFieldType() != _class) { 2143 state.derived = true; 2144 } 2145 } 2146 else { 2147 _class = descriptor.getFieldType(); 2148 } 2149 2150 //-- This *shouldn't* happen, but a custom implementation 2151 //-- could return null in the XMLClassDesctiptor#getJavaClass 2152 //-- or XMLFieldDescriptor#getFieldType. If so, just replace 2153 //-- with java.lang.Object.class (basically "anyType"). 2154 if (_class == null) { 2155 _class = java.lang.Object.class; 2156 } 2157 2158 // Retrieving the xsi:type attribute, if present 2159 String currentPackage = getJavaPackage(parentState.type); 2160 String instanceType = getInstanceType(atts, currentPackage); 2161 if (instanceType != null) { 2162 2163 Class instanceClass = null; 2164 try { 2165 2166 XMLClassDescriptor instanceDesc 2167 = getClassDescriptor(instanceType, _loader); 2168 2169 boolean loadClass = true; 2170 2171 if (instanceDesc != null) { 2172 instanceClass = instanceDesc.getJavaClass(); 2173 classDesc = instanceDesc; 2174 if (instanceClass != null) { 2175 loadClass = (!instanceClass.getName().equals(instanceType)); 2176 } 2177 } 2178 2179 if (loadClass) { 2180 instanceClass = loadClass(instanceType, null); 2181 //the FieldHandler can be either an XMLFieldHandler 2182 //or a FieldHandlerImpl 2183 FieldHandler tempHandler = descriptor.getHandler(); 2184 2185 boolean collection = false; 2186 if (tempHandler instanceof FieldHandlerImpl) { 2187 collection = ((FieldHandlerImpl) tempHandler).isCollection(); 2188 } 2189 else { 2190 collection = Introspector.isCollection(instanceClass); 2191 } 2192 2193 if ((!collection) && !_class.isAssignableFrom(instanceClass)) 2194 { 2195 if (!isPrimitive(_class)) { 2196 String err = instanceClass.getName() 2197 + " is not a subclass of " + _class.getName(); 2198 throw new SAXException(err); 2199 } 2200 } 2201 } 2202 _class = instanceClass; 2203 useHandler = false; 2204 } 2205 catch(Exception ex) { 2206 String msg = "unable to instantiate " + instanceType; 2207 throw new SAXException(msg + "; " + ex, ex); 2208 } 2209 2210 } 2211 2212 //-- Handle ArrayHandler 2213 if (_class == Object.class) { 2214 if (parentState.object instanceof ArrayHandler) 2215 _class = ((ArrayHandler)parentState.object).componentType(); 2216 } 2217 2218 //-- Handle support for "Any" type 2219 2220 if (_class == Object.class) { 2221 Class pClass = parentState.type; 2222 ClassLoader loader = pClass.getClassLoader(); 2223 //-- first look for a descriptor based 2224 //-- on the XML name 2225 classDesc = resolveByXMLName(name, namespace, loader); 2226 //-- if null, create classname, and try resolving 2227 String cname = null; 2228 if (classDesc == null) { 2229 //-- create class name 2230 cname = getJavaNaming().toJavaClassName(name); 2231 classDesc = getClassDescriptor(cname, loader); 2232 } 2233 //-- if still null, try using parents package 2234 if (classDesc == null) { 2235 //-- use parent to get package information 2236 String pkg = pClass.getName(); 2237 int idx = pkg.lastIndexOf('.'); 2238 if (idx > 0) { 2239 pkg = pkg.substring(0,idx+1); 2240 cname = pkg + cname; 2241 classDesc = getClassDescriptor(cname, loader); 2242 } 2243 } 2244 2245 if (classDesc != null) { 2246 _class = classDesc.getJavaClass(); 2247 useHandler = false; 2248 } 2249 else { 2250 //we are dealing with an AnyNode 2251 //1- creates a new SAX2ANY handler 2252 _anyUnmarshaller = new SAX2ANY(_namespaces, state.wsPreserve); 2253 //2- delegates the element handling 2254 if (_elemInfo.attributeList != null) { 2255 //-- SAX 1 2256 _anyUnmarshaller.startElement(_elemInfo.qName, 2257 _elemInfo.attributeList); 2258 } 2259 else { 2260 //-- SAX 2 2261 _anyUnmarshaller.startElement(namespace, name, _elemInfo.qName, 2262 _elemInfo.attributes); 2263 } 2264 //first element so depth can only be one at this point 2265 _depth = 1; 2266 state.object = _anyUnmarshaller.getStartingNode(); 2267 state.type = _class; 2268 //don't need to continue 2269 return; 2270 } 2271 } 2272 2273 boolean byteArray = false; 2274 if (_class.isArray()) 2275 byteArray = (_class.getComponentType() == Byte.TYPE); 2276 2277 //-- check for immutable 2278 if (isPrimitive(_class) || 2279 descriptor.isImmutable() || 2280 byteArray) 2281 { 2282 state.object = null; 2283 state.primitiveOrImmutable = true; 2284 //-- handle immutable types, such as java.util.Locale 2285 if (descriptor.isImmutable()) { 2286 if (classDesc == null) 2287 classDesc = getClassDescriptor(_class); 2288 state.classDesc = classDesc; 2289 Arguments args = processConstructorArgs(atts, classDesc); 2290 if ((args != null) && (args.size() > 0)) { 2291 state.args = args; 2292 } 2293 } 2294 } 2295 else { 2296 if (classDesc == null) 2297 classDesc = getClassDescriptor(_class); 2298 2299 //-- XXXX should remove this test once we can 2300 //-- XXXX come up with a better solution 2301 if ((!state.derived) && useHandler) { 2302 2303 boolean create = true; 2304 if (_reuseObjects) { 2305 state.object = handler.getValue(parentState.object); 2306 create = (state.object == null); 2307 } 2308 if (create) { 2309 Arguments args = processConstructorArgs(atts, classDesc); 2310 if ((args._values != null) && (args._values.length > 0)) { 2311 if (handler instanceof ExtendedFieldHandler) { 2312 ExtendedFieldHandler efh = 2313 (ExtendedFieldHandler)handler; 2314 state.object = efh.newInstance(parentState.object, args._values); 2315 } 2316 else { 2317 String err = "constructor arguments can only be " + 2318 "used with an ExtendedFieldHandler."; 2319 throw new SAXException(err); 2320 } 2321 } 2322 else { 2323 state.object = handler.newInstance(parentState.object); 2324 } 2325 } 2326 } 2327 //-- reassign class in case there is a conflict 2328 //-- between descriptor#getFieldType and 2329 //-- handler#newInstance...I should hope not, but 2330 //-- who knows 2331 if (state.object != null) { 2332 _class = state.object.getClass(); 2333 if (classDesc != null) { 2334 if (classDesc.getJavaClass() != _class) { 2335 classDesc = null; 2336 } 2337 } 2338 } 2339 else { 2340 try { 2341 if (_class.isArray()) { 2342 state.object = new ArrayHandler(_class.getComponentType()); 2343 _class = ArrayHandler.class; 2344 } 2345 else { 2346 Arguments args = processConstructorArgs(atts, classDesc); 2347 state.object = createInstance(_class, args); 2348 //state.object = _class.newInstance(); 2349 } 2350 } 2351 catch(java.lang.Exception ex) { 2352 String err = "unable to instantiate a new type of: "; 2353 err += className(_class); 2354 err += "; " + ex.getMessage(); 2355 //storing causal exception using SAX non-standard method... 2356 SAXException sx = new SAXException(err, ex); 2357 //...and also using Java 1.4 method 2358 //sx.initCause(ex); 2359 throw sx; 2360 } 2361 } 2362 } 2363 state.type = _class; 2364 } 2365 catch (java.lang.IllegalStateException ise) { 2366 LOG.error(ise.toString()); 2367 throw new SAXException(ise); 2368 } 2369 2370 //-- At this point we should have a new object, unless 2371 //-- we are dealing with a primitive type, or a special 2372 //-- case such as byte[] 2373 if (classDesc == null) { 2374 classDesc = getClassDescriptor(_class); 2375 } 2376 state.classDesc = classDesc; 2377 2378 if ((state.object == null) && (!state.primitiveOrImmutable)) 2379 { 2380 String err = "unable to unmarshal: " + name + "\n"; 2381 err += " - unable to instantiate: " + className(_class); 2382 throw new SAXException(err); 2383 } 2384 2385 //-- assign object, if incremental 2386 2387 if (descriptor.isIncremental()) { 2388 if (LOG.isDebugEnabled()) { 2389 LOG.debug("debug: Processing incrementally for element: " + name); 2390 } 2391 try { 2392 handler.setValue(parentState.object, state.object); 2393 } 2394 catch(java.lang.IllegalStateException ise) { 2395 String err = "unable to add \"" + name + "\" to "; 2396 err += parentState.fieldDesc.getXMLName(); 2397 err += " due to the following error: " + ise; 2398 throw new SAXException(err, ise); 2399 } 2400 } 2401 2402 if (state.object != null) { 2403 //--The object has just been initialized 2404 //--notify the listener 2405 if ( _unmarshalListener != null ) 2406 _unmarshalListener.initialized(state.object, (state.parent==null)?null:state.parent.object); 2407 processAttributes(atts, classDesc); 2408 if ( _unmarshalListener != null ) 2409 _unmarshalListener.attributesProcessed(state.object, (state.parent==null)?null:state.parent.object); 2410 processNamespaces(classDesc); 2411 } 2412 else if ((state.type != null) && (!state.primitiveOrImmutable)) { 2413 if (atts != null) { 2414 processWrapperAttributes(atts); 2415 StringBuffer buffer = new StringBuffer(); 2416 buffer.append("The current object for element '"); 2417 buffer.append(name); 2418 buffer.append("\' is null. Processing attributes as location"); 2419 buffer.append("/wrapper only and ignoring all other attribtes."); 2420 LOG.warn(buffer.toString()); 2421 } 2422 } 2423 else { 2424 //-- check for special attributes, such as xsi:nil 2425 if (atts != null) { 2426 String nil = atts.getValue(NIL_ATTR, XSI_NAMESPACE); 2427 state.nil = "true".equals(nil); 2428 processWrapperAttributes(atts); 2429 } 2430 } 2431 2432 } //-- void startElement(String, AttributeList) 2433 2434 2435 2436 /** 2437 * Indicates whether validation is enabled or not. 2438 * @return True if validation is enabled. 2439 */ 2440 private boolean isValidating() { 2441 return _validate; 2442 } 2443 2444 /** 2445 * Signals to start the namespace - prefix mapping 2446 * 2447 * @param prefix the namespace prefix to map 2448 * @param uri the namespace URI 2449 */ 2450 public void startPrefixMapping(String prefix, String uri) 2451 throws SAXException 2452 { 2453 2454 //-- Patch for Xerces 2.x bug 2455 //-- prevent attempting to declare "XML" namespace 2456 if (Namespaces.XML_NAMESPACE_PREFIX.equals(prefix) && 2457 Namespaces.XML_NAMESPACE.equals(uri)) 2458 { 2459 return; 2460 } 2461 else if (XMLNS.equals(prefix)) { 2462 return; 2463 } 2464 //-- end Xerces 2.x bug 2465 2466 //-- Forward the call to SAX2ANY 2467 //-- or create a namespace node 2468 if (_anyUnmarshaller != null) { 2469 _anyUnmarshaller.startPrefixMapping(prefix, uri); 2470 } 2471 else if (_createNamespaceScope) { 2472 _namespaces = _namespaces.createNamespaces(); 2473 _createNamespaceScope = false; 2474 } 2475 2476 _namespaces.addNamespace(prefix, uri); 2477 2478 /* 2479 //-- add namespace declarations to set of current attributes 2480 String attName = null; 2481 if ((prefix == null) || (prefix.length() == 0)) 2482 attName = XMLNS_DECL; 2483 else 2484 attName = XMLNS_PREFIX + prefix; 2485 2486 _currentAtts.addAttribute(attName, uri); 2487 */ 2488 2489 } //-- startPrefixMapping 2490 2491 2492 //------------------------------------/ 2493 //- org.xml.sax.ErrorHandler methods -/ 2494 //------------------------------------/ 2495 2496 public void error(SAXParseException exception) 2497 throws org.xml.sax.SAXException 2498 { 2499 String err = "Parsing Error : "+exception.getMessage()+'\n'+ 2500 "Line : "+ exception.getLineNumber() + '\n'+ 2501 "Column : "+exception.getColumnNumber() + '\n'; 2502 throw new SAXException (err, exception); 2503 } //-- error 2504 2505 public void fatalError(SAXParseException exception) 2506 throws org.xml.sax.SAXException 2507 { 2508 String err = "Parsing Error : "+exception.getMessage()+'\n'+ 2509 "Line : "+ exception.getLineNumber() + '\n'+ 2510 "Column : "+exception.getColumnNumber() + '\n'; 2511 throw new SAXException (err, exception); 2512 2513 } //-- fatalError 2514 2515 2516 public void warning(SAXParseException exception) 2517 throws org.xml.sax.SAXException 2518 { 2519 String err = "Parsing Error : "+exception.getMessage()+'\n'+ 2520 "Line : "+ exception.getLineNumber() + '\n'+ 2521 "Column : "+exception.getColumnNumber() + '\n'; 2522 throw new SAXException (err, exception); 2523 2524 } //-- warning 2525 2526 //---------------------/ 2527 //- Protected Methods -/ 2528 //---------------------/ 2529 2530 // TODO: Joachim 2007-09-04 remove me 2531 // /** 2532 // * Sets the current Castor configuration. Currently this 2533 // * Configuration is only used during Validation (which is 2534 // * why this method is currently protected, since it has 2535 // * no effect at this point on the actual configuration of 2536 // * the unmarshaller) 2537 // * 2538 // * Currently, this method should only be called by the 2539 // * Unmarshaller. 2540 // */ 2541 // protected void setConfiguration(Configuration config) { 2542 // _config = config; 2543 // } //-- setConfiguration 2544 2545 //-------------------/ 2546 //- Private Methods -/ 2547 //-------------------/ 2548 2549 /** 2550 * Adds the given reference to the "queue" until the referenced object 2551 * has been unmarshalled. 2552 * 2553 * @param idRef the ID being referenced 2554 * @param parent the target/parent object for the field 2555 * @param descriptor the XMLFieldDescriptor for the field 2556 */ 2557 private void addReference(final String idRef, final Object parent, 2558 final XMLFieldDescriptor descriptor) { 2559 2560 ReferenceInfo refInfo = new ReferenceInfo(idRef, parent, descriptor); 2561 refInfo.setNext((ReferenceInfo) _resolveTable.get(idRef)); 2562 _resolveTable.put(idRef, refInfo); 2563 } 2564 2565 /** 2566 * Creates an instance of the given class /type, using 2567 * the arguments provided (if there are any). 2568 * @param type The class type to be used during instantiation 2569 * @param args (Optional) arguments to be used during instantiation 2570 */ 2571 private Object createInstance(final Class type, final Arguments args) 2572 throws SAXException { 2573 Object instance = null; 2574 try { 2575 if (args == null) { 2576 instance = _objectFactory.createInstance(type); 2577 } else { 2578 instance = _objectFactory.createInstance(type, args._types, 2579 args._values); 2580 } 2581 } catch (Exception ex) { 2582 String msg = "Unable to instantiate " + type.getName() + "; "; 2583 throw new SAXException(msg, ex); 2584 } 2585 return instance; 2586 } // -- createInstance 2587 2588 /** 2589 * Returns the resolved instance type attribute (xsi:type). 2590 * If present the instance type attribute is resolved into 2591 * a java class name and then returned. 2592 * 2593 * @param atts the AttributeList to search for the instance type 2594 * attribute. 2595 * @return the java class name corresponding to the value of 2596 * the instance type attribute, or null if no instance type 2597 * attribute exists in the given AttributeList. 2598 */ 2599 private String getInstanceType(AttributeSet atts, String currentPackage) 2600 throws SAXException 2601 { 2602 2603 if (atts == null) return null; 2604 2605 //-- find xsi:type attribute 2606 String type = atts.getValue(XSI_TYPE, XSI_NAMESPACE); 2607 2608 if (type != null) { 2609 2610 if (type.startsWith(JAVA_PREFIX)) { 2611 return type.substring(JAVA_PREFIX.length()); 2612 } 2613 2614 // check for namespace prefix in type 2615 int idx = type.indexOf(':'); 2616 String typeNamespaceURI = null; 2617 if (idx >= 0) { 2618 // there is a namespace prefix 2619 String prefix = type.substring(0, idx); 2620 type = type.substring(idx + 1); 2621 typeNamespaceURI = _namespaces.getNamespaceURI(prefix); 2622 } 2623 2624 //-- Retrieve the type corresponding to the schema name and 2625 //-- return it. 2626 XMLClassDescriptor classDesc = null; 2627 2628 try { 2629 classDesc = getInternalContext().getXMLClassDescriptorResolver().resolveByXMLName(type, typeNamespaceURI, _loader); 2630 2631 if (classDesc != null) 2632 return classDesc.getJavaClass().getName(); 2633 2634 2635 //-- if class descriptor is not found here, then no descriptors 2636 //-- existed in memory...try to load one based on name of 2637 //-- Schema type 2638 final String className = getJavaNaming().toJavaClassName(type); 2639 2640 String adjClassName = className; 2641 String mappedPackage = getMappedPackage(typeNamespaceURI); 2642 if ((mappedPackage != null) && (mappedPackage.length() > 0)) { 2643 adjClassName = mappedPackage + "." + className; 2644 } 2645 classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(adjClassName, _loader); 2646 if (classDesc != null) 2647 return classDesc.getJavaClass().getName(); 2648 2649 //-- try to use "current Package" 2650 if ((currentPackage != null) && currentPackage.length() > 0) { 2651 adjClassName = currentPackage + '.' + className; 2652 } 2653 2654 classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(adjClassName, _loader); 2655 if (classDesc != null) 2656 return classDesc.getJavaClass().getName(); 2657 2658 //-- Still can't find type, this may be due to an 2659 //-- attempt to unmarshal an older XML instance 2660 //-- that was marshalled with a previous Castor. A 2661 //-- bug fix in the XMLMappingLoader prevents old 2662 //-- xsi:type that are missing the "java:" 2663 classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(type, _loader); 2664 if (classDesc != null) 2665 return classDesc.getJavaClass().getName(); 2666 } 2667 catch(ResolverException rx) { 2668 throw new SAXException(rx); 2669 } 2670 } 2671 return null; 2672 } //-- getInstanceType 2673 2674 /** 2675 * Looks up the package name from the given namespace URI. 2676 * 2677 * @param namespace the namespace URI to lookup 2678 * @return the package name or null. 2679 */ 2680 private String getMappedPackage(final String namespace) { 2681 String lookUpKey = (namespace != null) ? namespace : ""; 2682 return (String) _namespaceToPackage.get(lookUpKey); 2683 } 2684 2685 /** 2686 * Processes the given attribute list, and attempts to add each 2687 * {@link Attributes} to the current {@link Object} on the stack. 2688 * 2689 * @param atts the AttributeSet to process 2690 * @param classDesc the classDesc to use during processing 2691 **/ 2692 private void processAttributes(final AttributeSet atts, XMLClassDescriptor classDesc) 2693 throws SAXException { 2694 2695 //-- handle empty attributes 2696 if ((atts == null) || (atts.getSize() == 0)) { 2697 if (classDesc != null) { 2698 XMLFieldDescriptor[] descriptors 2699 = classDesc.getAttributeDescriptors(); 2700 for (int i = 0; i < descriptors.length; i++) { 2701 XMLFieldDescriptor descriptor = descriptors[i]; 2702 if (descriptor == null) { 2703 continue; 2704 } 2705 //-- Since many attributes represent primitive 2706 //-- fields, we add an extra validation check here 2707 //-- in case the class doesn't have a "has-method". 2708 if (descriptor.isRequired() && (isValidating() || LOG.isDebugEnabled())) { 2709 String err = classDesc.getXMLName() + " is missing " 2710 + "required attribute: " + descriptor.getXMLName(); 2711 if (_locator != null) { 2712 err += "\n - line: " + _locator.getLineNumber() 2713 + " column: " + _locator.getColumnNumber(); 2714 } 2715 if (isValidating()) { 2716 throw new SAXException(err); 2717 } 2718 LOG.debug(err); 2719 } 2720 } 2721 } 2722 return; 2723 } 2724 2725 2726 UnmarshalState state = (UnmarshalState) _stateInfo.peek(); 2727 Object object = state.object; 2728 2729 if (classDesc == null) { 2730 classDesc = state.classDesc; 2731 if (classDesc == null) { 2732 //-- no class desc, cannot process atts 2733 //-- except for wrapper/location atts 2734 processWrapperAttributes(atts); 2735 return; 2736 } 2737 } 2738 2739 //-- First loop through Attribute Descriptors. 2740 //-- Then, if we have any attributes which 2741 //-- haven't been processed we can ask 2742 //-- the XMLClassDescriptor for the FieldDescriptor. 2743 2744 boolean[] processedAtts = new boolean[atts.getSize()]; 2745 XMLFieldDescriptor[] descriptors = classDesc.getAttributeDescriptors(); 2746 for (XMLFieldDescriptor descriptor : descriptors) { 2747 2748 String name = descriptor.getXMLName(); 2749 String namespace = descriptor.getNameSpaceURI(); 2750 String path = descriptor.getLocationPath(); 2751 String fullAttributePath = ""; 2752 2753 if (path != null && path.length() > 0) { 2754 fullAttributePath += path + "/"; 2755 } 2756 2757 fullAttributePath += name; 2758 2759 if (fullAttributePath != null && !name.equals(fullAttributePath)) { 2760 int index = atts.getIndex(name, namespace); 2761 if (index >= 0) { 2762 processedAtts[index] = true; 2763 } 2764 continue; 2765 } 2766 2767 int index = atts.getIndex(name, namespace); 2768 2769 String attValue = null; 2770 if (index >= 0) { 2771 attValue = atts.getValue(index); 2772 processedAtts[index] = true; 2773 } 2774 2775 try { 2776 processAttribute(name, namespace, attValue, descriptor, classDesc, object); 2777 } catch (IllegalStateException ise) { 2778 String err = "unable to add attribute \"" + name + "\" to '"; 2779 err += state.classDesc.getJavaClass().getName(); 2780 err += "' due to the following error: " + ise; 2781 throw new SAXException(err, ise); 2782 } 2783 } 2784 2785 //-- Handle any non processed attributes... 2786 //-- This is useful for descriptors that might use 2787 //-- wild-cards or other types of matching...as well 2788 //-- as backward compatibility...attribute descriptors 2789 //-- were erronously getting set with the default 2790 //-- namespace by the source generator...this is 2791 //-- also true of the generated classes for the 2792 //-- Mapping Framework...we need to clean this up 2793 //-- at some point in the future. 2794 for (int i = 0; i < processedAtts.length; i++) { 2795 if (processedAtts[i]) { 2796 continue; 2797 } 2798 2799 String namespace = atts.getNamespace(i); 2800 String name = atts.getName(i); 2801 2802 //-- skip XSI attributes 2803 if (XSI_NAMESPACE.equals(namespace)) { 2804 if (NIL_ATTR.equals(name)) { 2805 String value = atts.getValue(i); 2806 state.nil = ("true".equals(value)); 2807 } 2808 continue; 2809 } 2810 2811 2812 if (name.startsWith(XML_PREFIX + ':')) { 2813 2814 //-- XML specification specific attribute 2815 //-- It should be safe to ignore these...but 2816 //-- if you think otherwise...let use know! 2817 if (LOG.isDebugEnabled()) { 2818 String msg = "ignoring attribute '" + name + "' for class: " 2819 + state.classDesc.getJavaClass().getName(); 2820 LOG.debug(msg); 2821 } 2822 continue; 2823 } 2824 2825 //-- This really should handle namespace...but it currently 2826 //-- doesn't. Ignoring namespaces also helps with the 2827 //-- backward compatibility issue mentioned above. 2828 XMLFieldDescriptor descriptor = 2829 classDesc.getFieldDescriptor(name, namespace, NodeType.Attribute); 2830 2831 if (descriptor == null) { 2832 //-- check for nested attribute...loop through 2833 //-- stack and find correct descriptor 2834 int pIdx = _stateInfo.size() - 2; //-- index of parentState 2835 String path = state.elementName; 2836 StringBuffer pathBuf = null; 2837 while (pIdx >= 0) { 2838 UnmarshalState targetState = (UnmarshalState) _stateInfo.elementAt(pIdx); 2839 --pIdx; 2840 if (targetState.wrapper) { 2841 //path = targetState.elementName + "/" + path; 2842 if (pathBuf == null) { 2843 pathBuf = new StringBuffer(); 2844 } else { 2845 pathBuf.setLength(0); 2846 } 2847 pathBuf.append(targetState.elementName); 2848 pathBuf.append('/'); 2849 pathBuf.append(path); 2850 path = pathBuf.toString(); 2851 continue; 2852 } 2853 classDesc = targetState.classDesc; 2854 descriptor = classDesc.getFieldDescriptor(name, namespace, NodeType.Attribute); 2855 2856 if (descriptor != null) { 2857 String tmpPath = descriptor.getLocationPath(); 2858 if (tmpPath == null) { 2859 tmpPath = ""; 2860 } 2861 if (path.equals(tmpPath)) { 2862 break; //-- found 2863 } 2864 } 2865 2866 if (pathBuf == null) { 2867 pathBuf = new StringBuffer(); 2868 } else { 2869 pathBuf.setLength(0); 2870 } 2871 pathBuf.append(targetState.elementName); 2872 pathBuf.append('/'); 2873 pathBuf.append(path); 2874 path = pathBuf.toString(); 2875 //path = targetState.elementName + "/" + path; 2876 //-- reset descriptor to make sure we don't 2877 //-- exit the loop with a reference to a 2878 //-- potentially incorrect one. 2879 descriptor = null; 2880 } 2881 } 2882 if (descriptor == null) { 2883 if (_strictAttributes) { 2884 //-- handle error 2885 String error = "The attribute '" + name 2886 + "' appears illegally on element '" 2887 + state.elementName + "'."; 2888 throw new SAXException(error); 2889 } 2890 continue; 2891 } 2892 2893 try { 2894 processAttribute(name, namespace, atts.getValue(i), descriptor, classDesc, object); 2895 } catch (IllegalStateException ise) { 2896 String err = "unable to add attribute \"" + name + "\" to '"; 2897 err += state.classDesc.getJavaClass().getName(); 2898 err += "' due to the following error: " + ise; 2899 throw new SAXException(err, ise); 2900 } 2901 } 2902 2903 } 2904 2905 /** 2906 * Processes the given AttributeSet for wrapper elements. 2907 * 2908 * @param atts the AttributeSet to process 2909 * @throws SAXException If the AttributeSet cannot be processed 2910 */ 2911 private void processWrapperAttributes(final AttributeSet atts) 2912 throws SAXException { 2913 2914 UnmarshalState state = (UnmarshalState) _stateInfo.peek(); 2915 2916 //-- loop through attributes and look for the 2917 //-- ancestor objects that they may belong to 2918 for (int i = 0; i < atts.getSize(); i++) { 2919 String name = atts.getName(i); 2920 String namespace = atts.getNamespace(i); 2921 2922 //-- skip XSI attributes 2923 if (XSI_NAMESPACE.equals(namespace)) { 2924 continue; 2925 } 2926 2927 XMLFieldDescriptor descriptor = null; 2928 XMLClassDescriptor classDesc = null; 2929 //-- check for nested attribute...loop through 2930 //-- stack and find correct descriptor 2931 int pIdx = _stateInfo.size() - 2; //-- index of parentState 2932 String path = state.elementName; 2933 StringBuffer pathBuf = null; 2934 UnmarshalState targetState = null; 2935 while (pIdx >= 0) { 2936 targetState = (UnmarshalState) _stateInfo.elementAt(pIdx); 2937 --pIdx; 2938 if (targetState.wrapper) { 2939 //path = targetState.elementName + "/" + path; 2940 if (pathBuf == null) { 2941 pathBuf = new StringBuffer(); 2942 } else { 2943 pathBuf.setLength(0); 2944 } 2945 pathBuf.append(targetState.elementName); 2946 pathBuf.append('/'); 2947 pathBuf.append(path); 2948 path = pathBuf.toString(); 2949 continue; 2950 } 2951 classDesc = targetState.classDesc; 2952 2953 XMLFieldDescriptor[] descriptors = classDesc.getAttributeDescriptors(); 2954 boolean found = false; 2955 for (int a = 0; a < descriptors.length; a++) { 2956 descriptor = descriptors[a]; 2957 if (descriptor == null) { 2958 continue; 2959 } 2960 if (descriptor.matches(name)) { 2961 String tmpPath = descriptor.getLocationPath(); 2962 if (tmpPath == null) { 2963 tmpPath = ""; 2964 } 2965 if (path.equals(tmpPath)) { 2966 found = true; 2967 break; 2968 } 2969 } 2970 } 2971 if (found) { 2972 break; 2973 } 2974 2975 //path = targetState.elementName + "/" + path; 2976 if (pathBuf == null) { 2977 pathBuf = new StringBuffer(); 2978 } else { 2979 pathBuf.setLength(0); 2980 } 2981 pathBuf.append(targetState.elementName); 2982 pathBuf.append('/'); 2983 pathBuf.append(path); 2984 path = pathBuf.toString(); 2985 2986 //-- reset descriptor to make sure we don't 2987 //-- exit the loop with a reference to a 2988 //-- potentially incorrect one. 2989 descriptor = null; 2990 } 2991 if (descriptor != null) { 2992 try { 2993 processAttribute(name, namespace, atts.getValue(i), 2994 descriptor, classDesc, targetState.object); 2995 } catch (IllegalStateException ise) { 2996 String err = "unable to add attribute \"" + name + "\" to '"; 2997 err += state.classDesc.getJavaClass().getName(); 2998 err += "' due to the following error: " + ise; 2999 throw new SAXException(err, ise); 3000 } 3001 } 3002 } 3003 3004 } 3005 3006 /** 3007 * Processes the given Attribute. 3008 **/ 3009 private void processAttribute 3010 (final String attName, final String attNamespace, String attValue, 3011 XMLFieldDescriptor descriptor, 3012 final XMLClassDescriptor classDesc, 3013 Object parent) throws SAXException { 3014 3015 //Object value = attValue; 3016 while (descriptor.isContainer()) { 3017 FieldHandler handler = descriptor.getHandler(); 3018 Object containerObject = handler.getValue(parent); 3019 3020 if (containerObject == null) { 3021 containerObject = handler.newInstance(parent); 3022 handler.setValue(parent, containerObject); 3023 } 3024 3025 ClassDescriptor containerClassDesc = 3026 ((XMLFieldDescriptorImpl) descriptor).getClassDescriptor(); 3027 descriptor = ((XMLClassDescriptor) containerClassDesc).getFieldDescriptor( 3028 attName, attNamespace, NodeType.Attribute); 3029 parent = containerObject; 3030 } 3031 3032 if (attValue == null) { 3033 //-- Since many attributes represent primitive 3034 //-- fields, we add an extra validation check here 3035 //-- in case the class doesn't have a "has-method". 3036 if (descriptor.isRequired() && isValidating()) { 3037 String err = classDesc.getXMLName() + " is missing " 3038 + "required attribute: " + attName; 3039 if (_locator != null) { 3040 err += "\n - line: " + _locator.getLineNumber() 3041 + " column: " + _locator.getColumnNumber(); 3042 } 3043 throw new SAXException(err); 3044 } 3045 return; 3046 } 3047 3048 //-- if this is the identity then save id 3049 if (classDesc.getIdentity() == descriptor) { 3050 3051 try { 3052 _idResolver.bind(attValue, parent, 3053 isValidating() && !getInternalContext().getLenientIdValidation()); 3054 } catch (ValidationException e) { 3055 throw new SAXException("Duplicate ID " + attValue + " encountered.", e); 3056 } 3057 3058 //-- save key in current state 3059 UnmarshalState state = (UnmarshalState) _stateInfo.peek(); 3060 state.key = attValue; 3061 3062 //-- resolve waiting references 3063 resolveReferences(attValue, parent); 3064 } else if (descriptor.isReference()) { 3065 //-- if this is an IDREF(S) then resolve reference(s) 3066 if (descriptor.isMultivalued()) { 3067 StringTokenizer st = new StringTokenizer(attValue); 3068 while (st.hasMoreTokens()) { 3069 processIDREF(st.nextToken(), descriptor, parent); 3070 } 3071 } else { 3072 processIDREF(attValue, descriptor, parent); 3073 } 3074 //-- object values have been set by processIDREF 3075 //-- simply return 3076 return; 3077 } 3078 3079 //-- if it's a constructor argument, we can exit at this point 3080 //-- since constructor arguments have already been set 3081 if (descriptor.isConstructorArgument()) { 3082 return; 3083 } 3084 3085 //-- attribute handler 3086 FieldHandler handler = descriptor.getHandler(); 3087 if (handler == null) { 3088 return; 3089 } 3090 3091 //-- attribute field type 3092 Class type = descriptor.getFieldType(); 3093 String valueType = descriptor.getSchemaType(); 3094 boolean isPrimative = isPrimitive(type); 3095 boolean isQName = (valueType != null && valueType.equals(QNAME_NAME)); 3096 3097 boolean isByteArray = false; 3098 if (type.isArray()) { 3099 isByteArray = (type.getComponentType() == Byte.TYPE); 3100 } 3101 3102 //-- if this is an multi-value attribute 3103 if (descriptor.isMultivalued()) { 3104 StringTokenizer attrValueTokenizer = new StringTokenizer(attValue); 3105 while (attrValueTokenizer.hasMoreTokens()) { 3106 attValue = attrValueTokenizer.nextToken(); 3107 setAttributeValueOnObject(attValue, descriptor, parent, handler, 3108 type, isPrimative, isQName, isByteArray); 3109 } 3110 } else { 3111 setAttributeValueOnObject(attValue, descriptor, parent, handler, 3112 type, isPrimative, isQName, isByteArray); 3113 } 3114 3115 } 3116 3117 /** 3118 * Sets the value of an attribute on the target object, using the {@link FieldHandler} 3119 * provided. 3120 * @param attValue The attribute value. 3121 * @param descriptor Corresponding {@link XMLFieldDescriptor} instance for the attribute processed. 3122 * @param parent Parent object into which attribute value needs to be 'injected'. 3123 * @param handler {@link FieldHandler} used for 'value injection'. 3124 * @param type {@link Class} type. 3125 * @param isPrimitive Indicates whether the attribute value represents a primitive value. 3126 * @param isQName Indicates whether the attribute value represents a QName value. 3127 * @param isByteArray Indicates whether the attribute value represents a byte array. 3128 * @throws SAXException If there's a problem 'injecting' the attribute value into the target field. 3129 */ 3130 private void setAttributeValueOnObject(final String attValue, 3131 final XMLFieldDescriptor descriptor, 3132 final Object parent, 3133 final FieldHandler handler, 3134 final Class type, 3135 final boolean isPrimitive, 3136 final boolean isQName, 3137 final boolean isByteArray) throws SAXException { 3138 //-- value to set 3139 Object value = attValue; 3140 //-- special type conversion for primitives 3141 if (isPrimitive) { 3142 value = toPrimitiveObject(type, attValue, descriptor); 3143 } 3144 3145 //-- special byte[]s provessing, if required 3146 if (isByteArray) { 3147 if (attValue == null) { 3148 value = new byte[0]; 3149 } else { 3150 //-- Base64/hexbinary decoding 3151 if (HexDecoder.DATA_TYPE.equals(descriptor.getComponentType())) { 3152 value = HexDecoder.decode(attValue); 3153 } else { 3154 value = Base64Decoder.decode(attValue); 3155 } 3156 } 3157 } 3158 3159 //-- QName resolution (ns:value -> {URI}value), if required 3160 if (isQName) { 3161 value = resolveNamespace(value); 3162 } 3163 //-- set value 3164 handler.setValue(parent, value); 3165 } 3166 3167 /** 3168 * Processes the given attribute set, and creates the 3169 * constructor arguments. 3170 * 3171 * @param atts the AttributeSet to process 3172 * @param classDesc the XMLClassDescriptor of the objec 3173 * @return the array of constructor argument values. 3174 * @throws SAXException If there's a problem creating the constructor argument set. 3175 */ 3176 private Arguments processConstructorArgs 3177 (final AttributeSet atts, final XMLClassDescriptor classDesc) 3178 throws SAXException { 3179 3180 if (classDesc == null) { 3181 return new Arguments(); 3182 } 3183 3184 //-- Loop through Attribute Descriptors and build 3185 //-- the argument array 3186 3187 //-- NOTE: Due to IDREF being able to reference an 3188 //-- un-yet unmarshalled object, we cannot handle 3189 //-- references as constructor arguments. 3190 //-- kvisco - 20030421 3191 int count = 0; 3192 XMLFieldDescriptor[] descriptors = classDesc.getAttributeDescriptors(); 3193 for (XMLFieldDescriptor fieldDescriptor : descriptors) { 3194 if (fieldDescriptor == null) { 3195 continue; 3196 } 3197 if (fieldDescriptor.isConstructorArgument()) { 3198 ++count; 3199 } 3200 } 3201 3202 Arguments args = new Arguments(); 3203 3204 if (count == 0) { 3205 return args; 3206 } 3207 3208 args._values = new Object[count]; 3209 args._types = new Class[count]; 3210 3211 for (XMLFieldDescriptor descriptor : descriptors) { 3212 3213 if (descriptor == null) { 3214 continue; 3215 } 3216 if (!descriptor.isConstructorArgument()) { 3217 continue; 3218 } 3219 3220 int argIndex = descriptor.getConstructorArgumentIndex(); 3221 if (argIndex >= count) { 3222 String err = "argument index out of bounds: " + argIndex; 3223 throw new SAXException(err); 3224 } 3225 3226 args._types[argIndex] = descriptor.getFieldType(); 3227 String name = descriptor.getXMLName(); 3228 String namespace = descriptor.getNameSpaceURI(); 3229 3230 int index = atts.getIndex(name, namespace); 3231 3232 if (index >= 0) { 3233 Object value = atts.getValue(index); 3234 //-- check for proper type and do type 3235 //-- conversion 3236 if (isPrimitive(args._types[argIndex])) { 3237 value = toPrimitiveObject(args._types[argIndex], (String) value, descriptor); 3238 } else { 3239 // check whether we are looking at an enum-style object, and if so, 3240 // convert the (string) value 3241 value = convertToEnumObject(descriptor, value); 3242 } 3243 3244 //check if the value is a QName that needs to 3245 //be resolved (ns:value -> {URI}value) 3246 String valueType = descriptor.getSchemaType(); 3247 if ((valueType != null) && (valueType.equals(QNAME_NAME))) { 3248 value = resolveNamespace(value); 3249 } 3250 args._values[argIndex] = value; 3251 } else { 3252 if (isPrimitive(args._types[argIndex])) { 3253 args._values[argIndex] = 3254 toPrimitiveObject(args._types[argIndex], null, descriptor); 3255 } else { 3256 args._values[argIndex] = null; 3257 } 3258 } 3259 } 3260 return args; 3261 } 3262 3263 /** 3264 * Checks whether the actual value passed in should be converted to an enum-style class 3265 * instance. 3266 * 3267 * @param descriptor The {@link XMLFieldDescriptor} instance in question. 3268 * @param value The actual value (which might need conversion). 3269 * @return The value, potentially converted to an enum-style class. 3270 */ 3271 private Object convertToEnumObject(final XMLFieldDescriptor descriptor, Object value) { 3272 Class fieldType = descriptor.getFieldType(); 3273 Method valueOfMethod; 3274 try { 3275 valueOfMethod = fieldType.getMethod("valueOf", new Class[] {String.class}); 3276 if (valueOfMethod != null 3277 && Modifier.isStatic(valueOfMethod.getModifiers())) { 3278 Class returnType = valueOfMethod.getReturnType(); 3279 if (returnType.isAssignableFrom(fieldType)) { 3280 Object enumObject = valueOfMethod.invoke(null, new Object[] {value}); 3281 value = enumObject; 3282 } 3283 } 3284 } catch (SecurityException e) { 3285 // TODO: well, cannot do anything about it 3286 } catch (NoSuchMethodException e) { 3287 // TODO: nothing to do, as it simply isn't an enum-style class 3288 } catch (IllegalArgumentException e) { 3289 // TODO: cannot really happen 3290 } catch (IllegalAccessException e) { 3291 // TODO: indicates that the valueOf() method isn't public 3292 } catch (InvocationTargetException e) { 3293 // TODO: hmm .. what else 3294 } 3295 return value; 3296 } 3297 3298 /** 3299 * Processes the given IDREF. 3300 * 3301 * @param idRef the ID of the object in which to reference 3302 * @param descriptor the current FieldDescriptor 3303 * @param parent the current parent object 3304 * @return true if the ID was found and resolved properly 3305 */ 3306 private boolean processIDREF (final String idRef, final XMLFieldDescriptor descriptor, 3307 final Object parent) { 3308 Object value = _idResolver.resolve(idRef); 3309 if (value == null) { 3310 //-- save state to resolve later 3311 addReference(idRef, parent, descriptor); 3312 } else { 3313 FieldHandler handler = descriptor.getHandler(); 3314 if (handler != null) { 3315 handler.setValue(parent, value); 3316 } 3317 } 3318 return (value != null); 3319 } 3320 3321 /** 3322 * Processes the attributes and namespace declarations found 3323 * in the given SAX AttributeList. The global AttributeSet 3324 * is cleared and updated with the attributes. Namespace 3325 * declarations are added to the set of namespaces in scope. 3326 * 3327 * @param atts the AttributeList to process. 3328 **/ 3329 private AttributeSet processAttributeList(AttributeList atts) 3330 throws SAXException 3331 { 3332 3333 if (atts == null) return new AttributeSetImpl(0); 3334 3335 3336 //-- process all namespaces first 3337 int attCount = 0; 3338 boolean[] validAtts = new boolean[atts.getLength()]; 3339 for (int i = 0; i < validAtts.length; i++) { 3340 String attName = atts.getName(i); 3341 if (attName.equals(XMLNS)) { 3342 _namespaces.addNamespace("", atts.getValue(i)); 3343 } 3344 else if (attName.startsWith(XMLNS_PREFIX)) { 3345 String prefix = attName.substring(XMLNS_PREFIX_LENGTH); 3346 _namespaces.addNamespace(prefix, atts.getValue(i)); 3347 } 3348 else { 3349 validAtts[i] = true; 3350 ++attCount; 3351 } 3352 } 3353 //-- process validAtts...if any exist 3354 AttributeSetImpl attSet = null; 3355 if (attCount > 0) { 3356 attSet = new AttributeSetImpl(attCount); 3357 for (int i = 0; i < validAtts.length; i++) { 3358 if (!validAtts[i]) continue; 3359 String namespace = null; 3360 String attName = atts.getName(i); 3361 int idx = attName.indexOf(':'); 3362 if (idx > 0) { 3363 String prefix = attName.substring(0, idx); 3364 if (!prefix.equals(XML_PREFIX)) { 3365 attName = attName.substring(idx+1); 3366 namespace = _namespaces.getNamespaceURI(prefix); 3367 if (namespace == null) { 3368 String error = "The namespace associated with "+ 3369 "the prefix '" + prefix + 3370 "' could not be resolved."; 3371 throw new SAXException(error); 3372 3373 } 3374 } 3375 } 3376 attSet.setAttribute(attName, atts.getValue(i), namespace); 3377 } 3378 } 3379 else attSet = new AttributeSetImpl(0); 3380 3381 return attSet; 3382 3383 } //-- method: processAttributeList 3384 3385 /** 3386 * Saves local namespace declarations to the object 3387 * model if necessary. 3388 * 3389 * @param classDesc the current ClassDescriptor. 3390 **/ 3391 private void processNamespaces(XMLClassDescriptor classDesc) { 3392 3393 3394 if (classDesc == null) return; 3395 3396 //-- process namespace nodes 3397 XMLFieldDescriptor nsDescriptor = 3398 classDesc.getFieldDescriptor(null, null, NodeType.Namespace); 3399 3400 if (nsDescriptor != null) { 3401 UnmarshalState state = (UnmarshalState) _stateInfo.peek(); 3402 FieldHandler handler = nsDescriptor.getHandler(); 3403 if (handler != null) { 3404 Enumeration enumeration = _namespaces.getLocalNamespacePrefixes(); 3405 while (enumeration.hasMoreElements()) { 3406 String nsPrefix = (String)enumeration.nextElement(); 3407 if (nsPrefix == null) nsPrefix = ""; 3408 String nsURI = _namespaces.getNamespaceURI(nsPrefix); 3409 if (nsURI == null) nsURI = ""; 3410 MapItem mapItem = new MapItem(nsPrefix, nsURI); 3411 handler.setValue(state.object, mapItem); 3412 } 3413 } 3414 } 3415 } //-- processNamespaces 3416 3417 /** 3418 * Extracts the prefix and resolves it to it's associated namespace. 3419 * If the prefix is 'xml', then no resolution will occur, however 3420 * in all other cases the resolution will change the prefix:value 3421 * as {NamespaceURI}value 3422 * 3423 * @param value the QName to resolve. 3424 */ 3425 private Object resolveNamespace(Object value) 3426 throws SAXException 3427 { 3428 3429 if ( (value == null) || !(value instanceof String)) 3430 return value; 3431 3432 String result = (String)value; 3433 int idx = result.indexOf(':'); 3434 String prefix = null; 3435 if (idx > 0) { 3436 prefix = result.substring(0,idx); 3437 if (XML_PREFIX.equals(prefix)) { 3438 //-- Do NOT Resolve the 'xml' prefix. 3439 return value; 3440 } 3441 result = result.substring(idx+1); 3442 } 3443 String namespace = _namespaces.getNamespaceURI(prefix); 3444 if ((namespace != null) && (namespace.length() > 0)) { 3445 result = '{'+namespace+'}'+result; 3446 return result; 3447 } 3448 else if ((namespace == null) && (prefix!=null)) 3449 throw new SAXException("The namespace associated with the prefix: '" + 3450 prefix + "' is null."); 3451 else 3452 return result; 3453 3454 } 3455 3456 /** 3457 * Finds and returns an XMLClassDescriptor for the given class name. 3458 * If a ClassDescriptor could not be found one will attempt to 3459 * be generated. 3460 * @param className the name of the class to find the descriptor for 3461 **/ 3462 private XMLClassDescriptor getClassDescriptor (String className) 3463 throws SAXException 3464 { 3465 Class type = null; 3466 try { 3467 //-- use specified ClassLoader if necessary 3468 if (_loader != null) { 3469 type = _loader.loadClass(className); 3470 } 3471 //-- no loader available use Class.forName 3472 else type = Class.forName(className); 3473 } 3474 catch (ClassNotFoundException cnfe) { 3475 return null; 3476 } 3477 return getClassDescriptor(type); 3478 3479 } //-- getClassDescriptor 3480 3481 /** 3482 * Finds and returns an XMLClassDescriptor for the given class. If 3483 * a ClassDescriptor could not be found one will attempt to 3484 * be generated. 3485 * @param _class the Class to get the ClassDescriptor for 3486 **/ 3487 private XMLClassDescriptor getClassDescriptor(Class _class) 3488 throws SAXException 3489 { 3490 if (_class == null) return null; 3491 3492 3493 //-- special case for strings 3494 if (_class == String.class) 3495 return _stringDescriptor; 3496 3497 if (_class.isArray()) return null; 3498 if (isPrimitive(_class)) return null; 3499 3500 // TODO: Joachim 3501 // if (_cdResolver == null) 3502 // _cdResolver = (XMLClassDescriptorResolver) 3503 // ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML); 3504 3505 XMLClassDescriptor classDesc = null; 3506 3507 3508 try { 3509 classDesc = (XMLClassDescriptor) getInternalContext().getXMLClassDescriptorResolver().resolve(_class); 3510 } 3511 catch(ResolverException rx) { 3512 // TODO 3513 } 3514 3515 if (classDesc != null) { 3516 return new InternalXMLClassDescriptor(classDesc); 3517 } 3518 3519 if (LOG.isDebugEnabled()) { 3520 LOG.debug(ERROR_DID_NOT_FIND_CLASSDESCRIPTOR + _class.getName()); 3521 } 3522 3523 return classDesc; 3524 } //-- getClassDescriptor 3525 3526 3527 /** 3528 * Finds and returns a ClassDescriptor for the given class. If 3529 * a ClassDescriptor could not be found one will attempt to 3530 * be generated. 3531 * @param className the name of the class to get the Descriptor for 3532 **/ 3533 private XMLClassDescriptor getClassDescriptor 3534 (String className, ClassLoader loader) 3535 throws SAXException 3536 { 3537 // TODO: Joachim 3538 // if (_cdResolver == null) 3539 // _cdResolver = (XMLClassDescriptorResolver) 3540 // ClassDescriptorResolverFactory.createClassDescriptorResolver(BindingType.XML); 3541 3542 3543 XMLClassDescriptor classDesc = null; 3544 try { 3545 classDesc = getInternalContext().getXMLClassDescriptorResolver().resolve(className, loader); 3546 } 3547 catch(ResolverException rx) { 3548 throw new SAXException(rx); 3549 } 3550 3551 3552 if (classDesc != null) { 3553 return new InternalXMLClassDescriptor(classDesc); 3554 } 3555 3556 if (LOG.isDebugEnabled()) { 3557 LOG.debug(ERROR_DID_NOT_FIND_CLASSDESCRIPTOR + className); 3558 } 3559 3560 return classDesc; 3561 } //-- getClassDescriptor 3562 3563 /** 3564 * Returns the XMLClassLoader 3565 */ 3566 private XMLClassDescriptor resolveByXMLName 3567 (String name, String namespace, ClassLoader loader) 3568 throws SAXException 3569 { 3570 3571 try { 3572 return getInternalContext().getXMLClassDescriptorResolver().resolveByXMLName(name, namespace, loader); 3573 } 3574 catch(ResolverException rx) { 3575 throw new SAXException(rx); 3576 } 3577 3578 } 3579 3580 /** 3581 * Returns the package for the given Class 3582 * 3583 * @param type the Class to return the package of 3584 * @return the package for the given Class 3585 **/ 3586 private String getJavaPackage(Class type) 3587 { 3588 if (type == null) 3589 return null; 3590 String pkg = (String)_javaPackages.get(type); 3591 if(pkg == null) 3592 { 3593 pkg = type.getName(); 3594 int idx = pkg.lastIndexOf('.'); 3595 if (idx > 0) 3596 pkg = pkg.substring(0,idx); 3597 else 3598 pkg = ""; 3599 _javaPackages.put(type, pkg); 3600 } 3601 return pkg; 3602 } //-- getJavaPackage 3603 3604 /** 3605 * Returns the name of a class, handles array types 3606 * @return the name of a class, handles array types 3607 **/ 3608 private String className(Class type) { 3609 if (type.isArray()) { 3610 return className(type.getComponentType()) + "[]"; 3611 } 3612 return type.getName(); 3613 } //-- className 3614 3615 3616 /** 3617 * Checks the given StringBuffer to determine if it only 3618 * contains whitespace. 3619 * 3620 * @param sb the StringBuffer to check 3621 * @return true if the only whitespace characters were 3622 * found in the given StringBuffer 3623 **/ 3624 private static boolean isWhitespace(StringBuffer sb) { 3625 for (int i = 0; i < sb.length(); i++) { 3626 char ch = sb.charAt(i); 3627 switch (ch) { 3628 case ' ': 3629 case '\n': 3630 case '\t': 3631 case '\r': 3632 break; 3633 default: 3634 return false; 3635 } 3636 } 3637 return true; 3638 } //-- isWhitespace 3639 3640 /** 3641 * Loads and returns the class with the given class name using the 3642 * given loader. 3643 * @param className the name of the class to load 3644 * @param loader the ClassLoader to use, this may be null. 3645 **/ 3646 private Class loadClass(String className, ClassLoader loader) 3647 throws ClassNotFoundException 3648 { 3649 //-- use passed in loader 3650 if ( loader != null ) 3651 return loader.loadClass(className); 3652 //-- use internal loader 3653 else if (_loader != null) 3654 return _loader.loadClass(className); 3655 //-- no loader available use Class.forName 3656 return Class.forName(className); 3657 } //-- loadClass 3658 3659 /** 3660 * Resolves the current set of waiting references for the given Id. 3661 * @param id the id that references are waiting for. 3662 * @param value the value of the resolved id. 3663 * @throws SAXException Indicates a problem resolving an IDREF 3664 **/ 3665 private void resolveReferences(final String id, final Object value) 3666 throws org.xml.sax.SAXException { 3667 if ((id == null) || (value == null)) { 3668 return; 3669 } 3670 if (_resolveTable == null) { 3671 return; 3672 } 3673 3674 ReferenceInfo refInfo = (ReferenceInfo) _resolveTable.remove(id); 3675 while (refInfo != null) { 3676 try { 3677 FieldHandler handler = refInfo.getDescriptor().getHandler(); 3678 if (handler != null) { 3679 handler.setValue(refInfo.getTarget(), value); 3680 } 3681 3682 //-- special handling for MapItems 3683 if (refInfo.getTarget() instanceof MapItem) { 3684 resolveReferences(refInfo.getTarget().toString(), refInfo.getTarget()); 3685 } 3686 } catch (java.lang.IllegalStateException ise) { 3687 String err = "Attempting to resolve an IDREF: " + 3688 id + "resulted in the following error: " + 3689 ise.toString(); 3690 throw new SAXException(err, ise); 3691 } 3692 refInfo = refInfo.getNext(); 3693 } 3694 } 3695 3696 /** 3697 * Converts a String to the given primitive object type. 3698 * 3699 * @param type the class type of the primitive in which 3700 * to convert the String to 3701 * @param value the String to convert to a primitive 3702 * @param fieldDesc Descriptor for the given field (value) 3703 * @return the new primitive Object 3704 * @exception SAXException If the String cannot be converted to a primitive object type 3705 */ 3706 private Object toPrimitiveObject 3707 (final Class type, final String value, final XMLFieldDescriptor fieldDesc) 3708 throws SAXException { 3709 try { 3710 return toPrimitiveObject(type, value); 3711 } catch (Exception ex) { 3712 String err = "The following error occured while trying to "; 3713 err += "unmarshal field " + fieldDesc.getFieldName(); 3714 UnmarshalState state = (UnmarshalState) _stateInfo.peek(); 3715 if (state != null) { 3716 if (state.object != null) { 3717 err += " of class " + state.object.getClass().getName(); 3718 } 3719 } 3720 err += "\n"; 3721 err += ex.getMessage(); 3722 3723 throw new SAXException(err, ex); 3724 } 3725 } 3726 3727 3728 /** 3729 * Converts a {@link String} to the given primitive object type. 3730 * 3731 * @param type the class type of the primitive in which 3732 * to convert the String to 3733 * @param value the {@link String} to convert to a primitive 3734 * @return the new primitive {@link Object} 3735 */ 3736 public static Object toPrimitiveObject(final Class type, String value) { 3737 3738 Object primitive = value; 3739 3740 if (value != null) { 3741 //-- trim any numeric values 3742 if ((type != Character.TYPE) && (type != Character.class)) { 3743 value = value.trim(); 3744 } 3745 } 3746 3747 boolean isNull = ((value == null) || (value.length() == 0)); 3748 3749 //-- I tried to order these in the order in which 3750 //-- (I think) types are used more frequently 3751 3752 // int 3753 if ((type == Integer.TYPE) || (type == Integer.class)) { 3754 if (isNull) { 3755 primitive = new Integer(0); 3756 } else { 3757 primitive = new Integer(value); 3758 } 3759 } else if ((type == Boolean.TYPE) || (type == Boolean.class)) { 3760 // boolean 3761 if (isNull) { 3762 primitive = Boolean.FALSE; 3763 } else { 3764 primitive = (value.equals("1") 3765 || value.toLowerCase().equals("true")) ? Boolean.TRUE : Boolean.FALSE; 3766 } 3767 } else if ((type == Double.TYPE) || (type == Double.class)) { 3768 // double 3769 if (isNull) { 3770 primitive = new Double(0.0); 3771 } else { 3772 primitive = new Double(value); 3773 } 3774 } else if ((type == Long.TYPE) || (type == Long.class)) { 3775 // long 3776 if (isNull) { 3777 primitive = new Long(0); 3778 } else { 3779 primitive = new Long(value); 3780 } 3781 } else if ((type == Character.TYPE) || (type == Character.class)) { 3782 // char 3783 if (!isNull) { 3784 primitive = new Character(value.charAt(0)); 3785 } else { 3786 primitive = new Character('\0'); 3787 } 3788 } else if ((type == Short.TYPE) || (type == Short.class)) { 3789 // short 3790 if (isNull) { 3791 primitive = new Short((short) 0); 3792 } else { 3793 primitive = new Short(value); 3794 } 3795 } else if ((type == Float.TYPE) || (type == Float.class)) { 3796 // float 3797 if (isNull) { 3798 primitive = new Float(0); 3799 } else { 3800 primitive = new Float(value); 3801 } 3802 } else if ((type == Byte.TYPE) || (type == Byte.class)) { 3803 // byte 3804 if (isNull) { 3805 primitive = new Byte((byte)0); 3806 } else { 3807 primitive = new Byte(value); 3808 } 3809 } else if (type == java.math.BigDecimal.class) { 3810 //BigDecimal 3811 if (isNull) { 3812 primitive = new java.math.BigDecimal(0); 3813 } else { 3814 primitive = new java.math.BigDecimal(value); 3815 } 3816 } else if (type == java.math.BigInteger.class) { 3817 //BigInteger 3818 if (isNull) { 3819 primitive = java.math.BigInteger.valueOf(0); 3820 } else { 3821 primitive = new java.math.BigInteger(value); 3822 } 3823 } else if (type.getSuperclass().getName().equals("java.lang.Enum")) { 3824 // Java 5.0 enums 3825 if (isNull) { 3826 primitive = null; 3827 } else { 3828 // try discover the fromValue Method 3829 try { 3830 Method valueOfMethod = type.getMethod("fromValue", 3831 new Class[] {String.class}); 3832 primitive = valueOfMethod.invoke(null, new Object[] {value}); 3833 return primitive; 3834 } catch (NoSuchMethodException e) { 3835 // do nothing, check valueOf method 3836 } catch (IllegalArgumentException e) { 3837 throw new IllegalStateException(e.toString()); 3838 } catch (IllegalAccessException e) { 3839 throw new IllegalStateException(e.toString()); 3840 } catch (InvocationTargetException e) { 3841 if (e.getTargetException() instanceof RuntimeException) { 3842 throw (RuntimeException) e.getTargetException(); 3843 } 3844 } 3845 3846 // backwards compability, check valueOf method to support 3847 // "simple" enums without value object 3848 try { 3849 Method valueOfMethod = type.getMethod("valueOf", 3850 new Class[] {String.class}); 3851 primitive = valueOfMethod.invoke(null, new Object[] {value}); 3852 } catch (IllegalAccessException e) { 3853 throw new IllegalStateException(e.toString()); 3854 } catch (InvocationTargetException e) { 3855 if (e.getTargetException() instanceof RuntimeException) { 3856 throw (RuntimeException) e.getTargetException(); 3857 } 3858 } catch (NoSuchMethodException e) { 3859 String err = type.getName() 3860 + " does not contain the required method: public static " 3861 + type.getName() + " valueOf(String);"; 3862 throw new IllegalArgumentException(err); 3863 } 3864 } 3865 } 3866 return primitive; 3867 } 3868 3869 /** 3870 * A utility class for keeping track of the 3871 * qName and how the SAX parser passed attributes 3872 */ 3873 class ElementInfo { 3874 3875 String qName = null; 3876 Attributes attributes = null; 3877 AttributeList attributeList = null; 3878 3879 ElementInfo() { 3880 super(); 3881 } 3882 3883 ElementInfo(String qName, Attributes atts) { 3884 super(); 3885 this.qName = qName; 3886 this.attributes = atts; 3887 } 3888 3889 ElementInfo(String qName, AttributeList atts) { 3890 super(); 3891 this.qName = qName; 3892 this.attributeList = atts; 3893 } 3894 3895 void clear() { 3896 qName = null; 3897 attributes = null; 3898 attributeList = null; 3899 } 3900 } //-- ElementInfo 3901 3902 /** 3903 * Internal class used for passing constructor argument 3904 * information. 3905 */ 3906 class Arguments { 3907 /** 3908 * Constructor argument values. 3909 */ 3910 private Object[] _values = null; 3911 /** 3912 * Constructor argument types. 3913 */ 3914 private Class[] _types = null; 3915 3916 /** 3917 * Returns the number of constructor arguments. 3918 * @return The number of constructor arguments. 3919 */ 3920 public int size() { 3921 if (_values == null) { 3922 return 0; 3923 } 3924 return _values.length; 3925 } 3926 } 3927 3928 /** 3929 * A class for handling Arrays during unmarshalling. 3930 * 3931 * @author <a href="mailto:kvisco@intalio.com">kvisco@intalio.com</a> 3932 */ 3933 public static class ArrayHandler { 3934 3935 Class _componentType = null; 3936 3937 ArrayList<Object> _items = null; 3938 3939 /** 3940 * Creates a new ArrayHandler 3941 * 3942 * @param componentType the ComponentType for the array. 3943 */ 3944 ArrayHandler(final Class componentType) { 3945 if (componentType == null) { 3946 String err = "The argument 'componentType' may not be null."; 3947 throw new IllegalArgumentException(err); 3948 } 3949 _componentType = componentType; 3950 _items = new ArrayList<Object>(); 3951 } //-- ArrayHandler 3952 3953 /** 3954 * Adds the given object to the underlying array. 3955 * @param obj The object to be added to the underlying array. 3956 */ 3957 public void addObject(final Object obj) { 3958 if (obj == null) { 3959 return; 3960 } 3961 /* disable check for now until we write a 3962 small function to handle primitive and their 3963 associated wrapper classes 3964 if (!_componentType.isAssignableFrom(obj.getClass())) { 3965 String err = obj.getClass().getName() + " is not an instanceof " + 3966 _componentType.getName(); 3967 throw new IllegalArgumentException(err); 3968 } 3969 */ 3970 _items.add(obj); 3971 } 3972 3973 /** 3974 * Returns the data handled by this class as an array. 3975 * @return The data handled internally in the form of an array. 3976 */ 3977 public Object getObject() { 3978 int size = _items.size(); 3979 Object array = Array.newInstance(_componentType, size); 3980 for (int i = 0; i < size; i++) { 3981 Array.set(array, i, _items.get(i)); 3982 } 3983 return array; 3984 } 3985 3986 /** 3987 * Returns the component type handled by this class. 3988 * @return The component type handled by this class. 3989 */ 3990 public Class componentType() { 3991 return _componentType; 3992 } 3993 3994 } //-- ArrayHandler 3995 3996 /** 3997 * Returns the ObjectFactory instance in use. 3998 * @return the ObjectFactory instance in use. 3999 */ 4000 public ObjectFactory getObjectFactory() { 4001 return _objectFactory; 4002 } 4003 4004 /** 4005 * Sets a (custom) ObjectFactory instance. 4006 * @param objectFactory A (custom) ObjectFactory instance 4007 */ 4008 public void setObjectFactory(ObjectFactory objectFactory) { 4009 _objectFactory = objectFactory; 4010 } 4011 4012 } //-- Unmarshaller 4013