/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2015 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ package com.sun.xml.bind.unmarshaller; import java.util.ArrayList; import java.util.Collections; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Stack; import java.util.StringTokenizer; import javax.xml.XMLConstants; import javax.xml.bind.JAXBException; import javax.xml.bind.UnmarshalException; import javax.xml.bind.UnmarshallerHandler; import javax.xml.bind.ValidationEvent; import org.xml.sax.Attributes; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import com.sun.xml.bind.TypeRegistry; import com.sun.xml.bind.util.AttributesImpl; /** * Implementation of {@link UnmarshallerHandler}. * * This object converts SAX events into unmarshaller events and * cooridnates the entire unmarshalling process. * * @author Kohsuke KAWAGUCHI * @since JAXB1.0 * @deprecated in JAXB1.0.1 */ public class SAXUnmarshallerHandlerImpl implements SAXUnmarshallerHandler, UnmarshallingContext { /** * This flag is set to true at the startDocument event * and false at the endDocument event. * * Until the first document is unmarshalled, we don't * want to return an object. So this variable is initialized * to true. */ private boolean isUnmarshalInProgress = true; public SAXUnmarshallerHandlerImpl( UnmarshallerImpl _parent, TypeRegistry _reg ) { this.parent = _parent; typeRegistry = _reg; startPrefixMapping("",""); // by default, the default ns is bound to "". } private final TypeRegistry typeRegistry; public TypeRegistry getTypeRegistry() { return typeRegistry; } public void startDocument() throws SAXException { // reset the object result = null; handlerLen=0; patchers=null; patchersLen=0; aborted = false; isUnmarshalInProgress = true; attStack.clear(); } public void endDocument() throws SAXException { runPatchers(); isUnmarshalInProgress = false; } public void startElement( String uri, String local, String qName, Attributes atts ) throws SAXException { // symbolize uri = uri.intern(); local = local.intern(); qName = qName.intern(); try { if(result==null) { // this is the root element. // create a root object and start unmarshalling result = typeRegistry.createRootElement(uri,local); if(result==null) { // the registry doesn't know about this element. throw new SAXParseException( Messages.format( Messages.UNEXPECTED_ROOT_ELEMENT, qName ), getLocator() ); } UnmarshallingEventHandler unmarshaller = result.getUnmarshaller(this); pushContentHandler(unmarshaller,0); } processText(true); getCurrentHandler().enterElement(uri,local,atts); } catch( UnreportedException e ) { // any SAXException encountered at this point has not been // reported to the client. Report it. reportAndThrow(e); } } public final void endElement( String uri, String local, String qname ) throws SAXException { // symbolize uri = uri.intern(); local = local.intern(); qname = qname.intern(); try { processText(false); getCurrentHandler().leaveElement(uri,local); } catch( UnreportedException e ) { // any SAXException encountered at this point has not been // reported to the client. Report it. reportAndThrow(e); } } /** Root object that is being unmarshalled. */ private UnmarshallableObject result; public Object getResult() throws UnmarshalException { if(isUnmarshalInProgress) throw new IllegalStateException(); if(!aborted) return result; // there was an error. throw new UnmarshalException((String)null); } // // // handler stack maintainance // // private UnmarshallingEventHandler[] handlers = new UnmarshallingEventHandler[16]; private int[] mementos = new int[16]; private int handlerLen=0; public void pushContentHandler( UnmarshallingEventHandler handler, int memento ) { if(handlerLen==handlers.length) { // expand buffer UnmarshallingEventHandler[] h = new UnmarshallingEventHandler[handlerLen*2]; int[] m = new int[handlerLen*2]; System.arraycopy(handlers,0,h,0,handlerLen); System.arraycopy(mementos,0,m,0,handlerLen); handlers = h; mementos = m; } handlers[handlerLen] = handler; mementos[handlerLen] = memento; handlerLen++; } public void popContentHandler() throws UnreportedException { handlerLen--; handlers[handlerLen]=null; // this handler is removed getCurrentHandler().leaveChild(mementos[handlerLen]); } /** * @deprecated */ public ContentHandlerEx getCurrentHandler() { return (ContentHandlerEx)getCurrentEventHandler(); } public UnmarshallingEventHandler getCurrentEventHandler() { return handlers[handlerLen-1]; } // // // text handling // // private StringBuffer buffer = new StringBuffer(); protected void consumeText( String str, boolean ignorable ) throws UnreportedException { if(getCurrentHandler().isListState()) { // in list state, we don't need to care about whitespaces. // if the text is all whitespace, this won't generate a text event, // so it would be just fine. StringTokenizer tokens = new StringTokenizer(str); while(tokens.hasMoreTokens()) // the handler can be switched during the text processing, // so the current handler has to be obtained inside the loop getCurrentHandler().text(tokens.nextToken()); } else { if(ignorable && str.trim().length()==0) // if we are allowed to ignore text and // the text is ignorable, ignore. return; // otherwise perform a transition by this token. getCurrentHandler().text(str); } } private void processText( boolean ignorable ) throws UnreportedException { consumeText(buffer.toString(),ignorable); // avoid excessive object allocation, but also avoid // keeping a huge array inside StringBuffer. if(buffer.length()<1024) buffer.setLength(0); else buffer = new StringBuffer(); } public final void characters( char[] buf, int start, int len ) { buffer.append(buf,start,len); } public final void ignorableWhitespace( char[] buf, int start, int len ) { characters(buf,start,len); } // // // namespace binding maintainance // // private String[] nsBind = new String[16]; private int nsLen=0; // in the current scope, nsBind[0] - nsBind[idxStack[idxStackTop]-1] // are active. private int[] idxStack = new int[16]; private int idxStackTop=0; public void startPrefixMapping( String prefix, String uri ) { if(nsBind.length==nsLen) { // expand the buffer String[] n = new String[nsLen*2]; System.arraycopy(nsBind,0,n,0,nsLen); nsBind=n; } nsBind[nsLen++] = prefix; nsBind[nsLen++] = uri; } public void endPrefixMapping( String prefix ) { nsLen-=2; } public String resolveNamespacePrefix( String prefix ) { if(prefix.equals("xml")) return "http://www.w3.org/XML/1998/namespace"; for( int i=idxStack[idxStackTop]-2; i>=0; i-=2 ) { if(prefix.equals(nsBind[i])) return nsBind[i+1]; } return null; } // // NamespaceContext2 implementation // public Iterator getPrefixes(String uri) { // TODO: could be implemented much faster // wrap it into unmodifiable list so that the remove method // will throw UnsupportedOperationException. return Collections.unmodifiableList( getAllPrefixesInList(uri)).iterator(); } private List getAllPrefixesInList(String uri) { List a = new ArrayList(); if( uri.equals(XMLConstants.XML_NS_URI) ) { a.add(XMLConstants.XML_NS_PREFIX); return a; } if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) ) { a.add(XMLConstants.XMLNS_ATTRIBUTE); return a; } if( uri==null ) throw new IllegalArgumentException(); for( int i=nsLen-2; i>=0; i-=2 ) if(uri.equals(nsBind[i+1])) if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) ) // make sure that this prefix is still effective. a.add(nsBind[i]); return a; } public String getPrefix(String uri) { if( uri.equals(XMLConstants.XML_NS_URI) ) return XMLConstants.XML_NS_PREFIX; if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) ) return XMLConstants.XMLNS_ATTRIBUTE; if( uri==null ) throw new IllegalArgumentException(); for( int i=idxStack[idxStackTop]-2; i>=0; i-=2 ) if(uri.equals(nsBind[i+1])) if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) ) // make sure that this prefix is still effective. return nsBind[i]; return null; } public String getNamespaceURI(String prefix) { if( prefix.equals(XMLConstants.XMLNS_ATTRIBUTE) ) return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; if( prefix==null ) throw new IllegalArgumentException(); return resolveNamespacePrefix(prefix); } // // // Attribute handling // // // TODO: implementation can be more efficient private final Stack attStack = new Stack(); public void pushAttributes( Attributes atts ) { // since Attributes object is mutable, it is criticall important // to make a copy. // also symbolize attribute names AttributesImpl a = new AttributesImpl(); for( int i=0; i<atts.getLength(); i++ ) a.addAttribute( atts.getURI(i).intern(), atts.getLocalName(i).intern(), atts.getQName(i).intern(), atts.getType(i), atts.getValue(i) ); // <foo xsi:nil="false">some value</foo> is a valid fragment, however // we need a look ahead to correctly handle this case. // (because when we process @xsi:nil, we don't know what the value is, // and by the time we read "false", we can't cancel this attribute anymore.) // // as a quick workaround, we remove @xsi:nil if the value is false. int idx = a.getIndex("http://www.w3.org/2001/XMLSchema-instance","nil"); if(idx!=-1) { String v = a.getValue(idx).trim(); if(v.equals("false") || v.equals("0")) a.removeAttribute(idx); } attStack.push(a); // start a new namespace scope if( ++idxStackTop==idxStack.length ) { // reallocation int[] newBuf = new int[idxStack.length*2]; System.arraycopy(idxStack,0,newBuf,0,idxStack.length); idxStack = newBuf; } idxStack[idxStackTop] = nsLen; } public void popAttributes() { attStack.pop(); idxStackTop--; } public Attributes getUnconsumedAttributes() { return (Attributes)attStack.peek(); } public int getAttribute( String uri, String local ) { // Consider a class that corresponds to the root element. // if this class has a transition from final state by an attribute, // then this attribute transitions are checked when the final // leaveElement event is consumed. // // to handle this case, return "not found" if there is no active // attribute scope if(attStack.isEmpty()) return -1; Attributes a = (Attributes)attStack.peek(); return a.getIndex(uri,local); } public void consumeAttribute( int idx ) throws UnreportedException { AttributesImpl a = (AttributesImpl)attStack.peek(); String uri = a.getURI(idx).intern(); String local = a.getLocalName(idx).intern(); String value = a.getValue(idx).intern(); // mark the attribute as consumed // we need to remove the attribute before we process it // because the event handler might access attributes. a.removeAttribute(idx); getCurrentHandler().enterAttribute(uri,local); consumeText(value,false); getCurrentHandler().leaveAttribute(uri,local); } // // // ID/IDREF related code // // /** * Submitted patchers in the order they've submitted. * Many XML vocabulary doesn't use ID/IDREF at all, so we * initialize it with null. */ private Runnable[] patchers = null; private int patchersLen = 0; public void addPatcher( Runnable job ) { // re-allocate buffer if necessary if( patchers==null ) patchers = new Runnable[32]; if( patchers.length == patchersLen ) { Runnable[] buf = new Runnable[patchersLen*2]; System.arraycopy(patchers,0,buf,0,patchersLen); patchers = buf; } patchers[patchersLen++] = job; } /** Executes all the patchers. */ private void runPatchers() { if( patchers!=null ) { for( int i=0; i<patchersLen; i++ ) patchers[i].run(); } } /** Records ID->Object map. */ private Hashtable idmap = null; public String addToIdTable( String id ) { if(idmap==null) idmap = new Hashtable(); idmap.put( id, getCurrentHandler().owner() ); return id; } public UnmarshallableObject getObjectFromId( String id ) { if(idmap==null) return null; return (UnmarshallableObject)idmap.get(id); } // // // Other SAX callbacks // // public void skippedEntity( String name ) { // TODO: throw an exception, perhaps? } public void processingInstruction( String target, String data ) { // just ignore } public void setDocumentLocator( Locator loc ) { locator = loc; } public Locator getLocator() { return locator; } private Locator locator; // // // error handling // // private final UnmarshallerImpl parent; private boolean aborted = false; public boolean handleEvent(ValidationEvent event) { try { // if the handler says "abort", we will not return // the object. boolean recover = parent.getEventHandler().handleEvent(event); if(!recover) aborted = true; return recover; } catch( JAXBException e ) { // we are not allowed to throw an exception from this method. return false; } catch( RuntimeException re ) { // if client event handler causes a runtime exception, then we // have to return false. return false; } } public void reportAndThrow( UnreportedException e ) throws SAXException { handleEvent(e.createValidationEvent()); // rewrap it into UnmarshalException. // we further re-wrap it into SAXException again so that // it can be tunneled through SAX Parser. throw new SAXException( e.createUnmarshalException() ); } // // // ValidationContext implementation // // public String getBaseUri() { return null; } public boolean isUnparsedEntity(String s) { return true; } public boolean isNotation(String s) { return true; } // // // debug trace methods // // private Tracer tracer; public void setTracer( Tracer t ) { this.tracer = t; } public Tracer getTracer() { if(tracer==null) tracer = new Tracer.Standard(); return tracer; } }