/* * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.xml.internal.bind.v2.runtime.unmarshaller; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.XMLConstants; import javax.xml.bind.JAXBElement; import javax.xml.bind.UnmarshalException; import javax.xml.bind.Unmarshaller; import javax.xml.bind.ValidationEvent; import javax.xml.bind.ValidationEventHandler; import javax.xml.bind.ValidationEventLocator; import javax.xml.bind.helpers.ValidationEventImpl; import javax.xml.namespace.NamespaceContext; import javax.xml.namespace.QName; import com.sun.istack.internal.NotNull; import com.sun.istack.internal.Nullable; import com.sun.istack.internal.SAXParseException2; import com.sun.xml.internal.bind.IDResolver; import com.sun.xml.internal.bind.Util; import com.sun.xml.internal.bind.api.AccessorException; import com.sun.xml.internal.bind.api.ClassResolver; import com.sun.xml.internal.bind.unmarshaller.InfosetScanner; import com.sun.xml.internal.bind.v2.ClassFactory; import com.sun.xml.internal.bind.v2.runtime.AssociationMap; import com.sun.xml.internal.bind.v2.runtime.Coordinator; import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl; import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo; import java.util.logging.Level; import java.util.logging.Logger; import org.xml.sax.ErrorHandler; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.helpers.LocatorImpl; /** * Center of the unmarshalling. * *

* This object is responsible for coordinating {@link Loader}s to * perform the whole unmarshalling. * * @author Kohsuke Kawaguchi */ public final class UnmarshallingContext extends Coordinator implements NamespaceContext, ValidationEventHandler, ErrorHandler, XmlVisitor, XmlVisitor.TextPredictor { private static final Logger logger = Logger.getLogger(UnmarshallingContext.class.getName()); /** * Root state. */ private final State root; /** * The currently active state. */ private State current; private static final LocatorEx DUMMY_INSTANCE; static { LocatorImpl loc = new LocatorImpl(); loc.setPublicId(null); loc.setSystemId(null); loc.setLineNumber(-1); loc.setColumnNumber(-1); DUMMY_INSTANCE = new LocatorExWrapper(loc); } private @NotNull LocatorEx locator = DUMMY_INSTANCE; /** Root object that is being unmarshalled. */ private Object result; /** * If non-null, this unmarshaller will unmarshal {@code JAXBElement} * regardless of the tag name, as opposed to deciding the root object by using * the tag name. * * The property has a package-level access, because we cannot copy this value * to {@link UnmarshallingContext} when it is created. The property * on {@link Unmarshaller} could be changed after the handler is created. */ private JaxBeanInfo expectedType; /** * Handles ID/IDREF. */ private IDResolver idResolver; /** * 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; private boolean aborted = false; public final UnmarshallerImpl parent; /** * If the unmarshaller is doing associative unmarshalling, * this field is initialized to non-null. */ private final AssociationMap assoc; /** * Indicates whether we are doing in-place unmarshalling * or not. * *

* This flag is unused when {@link #assoc}==null. * If it's non-null, then {@code true} indicates * that we are doing in-place associative unmarshalling. * If {@code false}, then we are doing associative unmarshalling * without object reuse. */ private boolean isInplaceMode; /** * This object is consulted to get the element object for * the current element event. * * This is used when we are building an association map. */ private InfosetScanner scanner; private Object currentElement; /** * @see XmlVisitor#startDocument(LocatorEx, NamespaceContext) */ private NamespaceContext environmentNamespaceContext; /** * Used to discover additional classes when we hit unknown elements/types. */ public @Nullable ClassResolver classResolver; /** * User-supplied {@link ClassLoader} for converting name to {@link Class}. * For backward compatibility, when null, use thread context classloader. */ public @Nullable ClassLoader classLoader; /** * The variable introduced to avoid reporting n^10 similar errors. * After error is reported counter is decremented. When it became 0 - errors should not be reported any more. * * volatile is required to ensure that concurrent threads will see changed value */ private static volatile int errorsCounter = 10; /** * State information for each element. */ public final class State { /** * Loader that owns this element. */ private Loader loader; /** * Once {@link #loader} is completed, this receiver * receives the result. */ private Receiver receiver; private Intercepter intercepter; /** * Object being unmarshalled by this {@link #loader}. */ private Object target; /** * Hack for making JAXBElement unmarshalling work. * *

* While the unmarshalling is in progress, the {@link #target} field stores the object being unmarshalled. * This makes it convenient to keep track of the unmarshalling activity in context of XML infoset, but * since there's only one {@link State} per element, this mechanism only works when there's one object * per element, which breaks down when we have {@link JAXBElement}, since the presence of JAXBElement * requires that we have two objects unmarshalled (a JAXBElement X and a value object Y bound to an XML type.) * *

* So to make room for storing both, this {@link #backup} field is used. When we create X instance * in the above example, we set that to {@code state.prev.target} and displace its old value to * {@code state.prev.backup} (where Y goes to {@code state.target}.) Upon the completion of the unmarshalling * of Y, we revert this. * *

* While this attributes X incorrectly to its parent element, this preserves the parent/child * relationship between unmarshalled objects and {@link State} parent/child relationship, and * it thereby makes {@link Receiver} mechanism simpler. * *

* Yes, I know this is a hack, and no, I'm not proud of it. * * @see ElementBeanInfoImpl.IntercepterLoader#startElement(State, TagName) * @see ElementBeanInfoImpl.IntercepterLoader#intercept(State, Object) */ private Object backup; /** * Number of {@link UnmarshallingContext#nsBind}s declared thus far. * (The value of {@link UnmarshallingContext#nsLen} when this state is pushed. */ private int numNsDecl; /** * If this element has an element default value. * * This should be set by either a parent {@link Loader} when * {@link Loader#childElement(State, TagName)} is called * or by a child {@link Loader} when * {@link Loader#startElement(State, TagName)} is called. */ private String elementDefaultValue; /** * {@link State} for the parent element * * {@link State} objects form a doubly linked list. */ private State prev; private State next; private boolean nil = false; /** * specifies that we are working with mixed content */ private boolean mixed = false; /** * Gets the context. */ public UnmarshallingContext getContext() { return UnmarshallingContext.this; } @SuppressWarnings("LeakingThisInConstructor") private State(State prev) { this.prev = prev; if (prev!=null) { prev.next = this; if (prev.mixed) // parent is in mixed mode this.mixed = true; } } private void push() { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "State.push"); } if (next==null) { assert current == this; next = new State(this); } nil = false; State n = next; n.numNsDecl = nsLen; current = n; } private void pop() { if (logger.isLoggable(Level.FINEST)) { logger.log(Level.FINEST, "State.pop"); } assert prev!=null; loader = null; nil = false; mixed = false; receiver = null; intercepter = null; elementDefaultValue = null; target = null; current = prev; next = null; } public boolean isMixed() { return mixed; } public Object getTarget() { return target; } public void setLoader(Loader loader) { if (loader instanceof StructureLoader) // set mixed mode mixed = !((StructureLoader)loader).getBeanInfo().hasElementOnlyContentModel(); this.loader = loader; } public void setReceiver(Receiver receiver) { this.receiver = receiver; } public State getPrev() { return prev; } public void setIntercepter(Intercepter intercepter) { this.intercepter = intercepter; } public void setBackup(Object backup) { this.backup = backup; } public void setTarget(Object target) { this.target = target; } public Object getBackup() { return backup; } public boolean isNil() { return nil; } public void setNil(boolean nil) { this.nil = nil; } public Loader getLoader() { return loader; } public String getElementDefaultValue() { return elementDefaultValue; } public void setElementDefaultValue(String elementDefaultValue) { this.elementDefaultValue = elementDefaultValue; } } /** * Stub to the user-specified factory method. */ private static class Factory { private final Object factorInstance; private final Method method; public Factory(Object factorInstance, Method method) { this.factorInstance = factorInstance; this.method = method; } public Object createInstance() throws SAXException { try { return method.invoke(factorInstance); } catch (IllegalAccessException e) { getInstance().handleError(e,false); } catch (InvocationTargetException e) { getInstance().handleError(e,false); } return null; // can never be executed } } /** * Creates a new unmarshaller. * * @param assoc * Must be both non-null when the unmarshaller does the * in-place unmarshalling. Otherwise must be both null. */ public UnmarshallingContext( UnmarshallerImpl _parent, AssociationMap assoc) { this.parent = _parent; this.assoc = assoc; this.root = this.current = new State(null); } public void reset(InfosetScanner scanner,boolean isInplaceMode, JaxBeanInfo expectedType, IDResolver idResolver) { this.scanner = scanner; this.isInplaceMode = isInplaceMode; this.expectedType = expectedType; this.idResolver = idResolver; } public JAXBContextImpl getJAXBContext() { return parent.context; } public State getCurrentState() { return current; } /** * On top of {@link JAXBContextImpl#selectRootLoader(State, TagName)}, * this method also consults {@link ClassResolver}. * * @throws SAXException * if {@link ValidationEventHandler} reported a failure. */ public Loader selectRootLoader(State state, TagName tag) throws SAXException { try { Loader l = getJAXBContext().selectRootLoader(state, tag); if(l!=null) return l; if(classResolver!=null) { Class clazz = classResolver.resolveElementName(tag.uri, tag.local); if(clazz!=null) { JAXBContextImpl enhanced = getJAXBContext().createAugmented(clazz); JaxBeanInfo bi = enhanced.getBeanInfo(clazz); return bi.getLoader(enhanced,true); } } } catch (RuntimeException e) { throw e; } catch (Exception e) { handleError(e); } return null; } public void clearStates() { State last = current; while (last.next != null) last = last.next; while (last.prev != null) { last.loader = null; last.nil = false; last.receiver = null; last.intercepter = null; last.elementDefaultValue = null; last.target = null; last = last.prev; last.next.prev = null; last.next = null; } current = last; } /** * User-specified factory methods. */ private final Map factories = new HashMap(); public void setFactories(Object factoryInstances) { factories.clear(); if(factoryInstances==null) { return; } if(factoryInstances instanceof Object[]) { for( Object factory : (Object[])factoryInstances ) { // look for all the public methods inlcuding derived ones addFactory(factory); } } else { addFactory(factoryInstances); } } private void addFactory(Object factory) { for( Method m : factory.getClass().getMethods() ) { // look for methods whose signature is T createXXX() if(!m.getName().startsWith("create")) continue; if(m.getParameterTypes().length>0) continue; Class type = m.getReturnType(); factories.put(type,new Factory(factory,m)); } } @Override public void startDocument(LocatorEx locator, NamespaceContext nsContext) throws SAXException { if(locator!=null) this.locator = locator; this.environmentNamespaceContext = nsContext; // reset the object result = null; current = root; patchersLen=0; aborted = false; isUnmarshalInProgress = true; nsLen=0; if(expectedType!=null) root.loader = EXPECTED_TYPE_ROOT_LOADER; else root.loader = DEFAULT_ROOT_LOADER; idResolver.startDocument(this); } @Override public void startElement(TagName tagName) throws SAXException { pushCoordinator(); try { _startElement(tagName); } finally { popCoordinator(); } } private void _startElement(TagName tagName) throws SAXException { // remember the current element if we are interested in it. // because the inner peer might not be found while we consume // the enter element token, we need to keep this information // longer than this callback. That's why we assign it to a field. if( assoc!=null ) currentElement = scanner.getCurrentElement(); Loader h = current.loader; current.push(); // tell the parent about the new child h.childElement(current,tagName); assert current.loader!=null; // the childElement should register this // and tell the new child that you are activated current.loader.startElement(current,tagName); } @Override public void text(CharSequence pcdata) throws SAXException { pushCoordinator(); try { if (current.elementDefaultValue != null) { if (pcdata.length() == 0) { // send the default value into the unmarshaller instead pcdata = current.elementDefaultValue; } } current.loader.text(current, pcdata); } finally { popCoordinator(); } } @Override public final void endElement(TagName tagName) throws SAXException { pushCoordinator(); try { State child = current; // tell the child that your time is up child.loader.leaveElement(child,tagName); // child.pop will erase them so store them now Object target = child.target; Receiver recv = child.receiver; Intercepter intercepter = child.intercepter; child.pop(); // then let the parent know if(intercepter!=null) target = intercepter.intercept(current,target); if(recv!=null) recv.receive(current,target); } finally { popCoordinator(); } } @Override public void endDocument() throws SAXException { runPatchers(); idResolver.endDocument(); isUnmarshalInProgress = false; currentElement = null; locator = DUMMY_INSTANCE; environmentNamespaceContext = null; // at the successful completion, scope must be all closed assert root==current; } /** * You should be always calling this through {@link TextPredictor}. */ @Deprecated @Override public boolean expectText() { return current.loader.expectText; } /** * You should be always getting {@link TextPredictor} from {@link XmlVisitor}. */ @Deprecated @Override public TextPredictor getPredictor() { return this; } @Override public UnmarshallingContext getContext() { return this; } /** * Gets the result of the unmarshalling */ public Object getResult() throws UnmarshalException { if(isUnmarshalInProgress) throw new IllegalStateException(); if(!aborted) return result; // there was an error. throw new UnmarshalException((String)null); } void clearResult() { if (isUnmarshalInProgress) { throw new IllegalStateException(); } result = null; } /** * Creates a new instance of the specified class. * In the unmarshaller, we need to check the user-specified factory class. */ public Object createInstance( Class clazz ) throws SAXException { if(!factories.isEmpty()) { Factory factory = factories.get(clazz); if(factory!=null) return factory.createInstance(); } return ClassFactory.create(clazz); } /** * Creates a new instance of the specified class. * In the unmarshaller, we need to check the user-specified factory class. */ public Object createInstance( JaxBeanInfo beanInfo ) throws SAXException { if(!factories.isEmpty()) { Factory factory = factories.get(beanInfo.jaxbType); if(factory!=null) return factory.createInstance(); } try { return beanInfo.createInstance(this); } catch (IllegalAccessException e) { Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false); } catch (InvocationTargetException e) { Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false); } catch (InstantiationException e) { Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false); } return null; // can never be here } // // // error handling // // /** * Reports an error to the user, and asks if s/he wants * to recover. If the canRecover flag is false, regardless * of the client instruction, an exception will be thrown. * * Only if the flag is true and the user wants to recover from an error, * the method returns normally. * * The thrown exception will be catched by the unmarshaller. */ public void handleEvent(ValidationEvent event, boolean canRecover ) throws SAXException { ValidationEventHandler eventHandler = parent.getEventHandler(); boolean recover = eventHandler.handleEvent(event); // if the handler says "abort", we will not return the object // from the unmarshaller.getResult() if(!recover) aborted = true; if( !canRecover || !recover ) throw new SAXParseException2( event.getMessage(), locator, new UnmarshalException( event.getMessage(), event.getLinkedException() ) ); } @Override 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( RuntimeException re ) { // if client event handler causes a runtime exception, then we // have to return false. return false; } } /** * Reports an exception found during the unmarshalling to the user. * This method is a convenience method that calls into * {@link #handleEvent(ValidationEvent, boolean)} */ public void handleError(Exception e) throws SAXException { handleError(e,true); } public void handleError(Exception e,boolean canRecover) throws SAXException { handleEvent(new ValidationEventImpl(ValidationEvent.ERROR,e.getMessage(),locator.getLocation(),e),canRecover); } public void handleError(String msg) { handleEvent(new ValidationEventImpl(ValidationEvent.ERROR,msg,locator.getLocation())); } @Override protected ValidationEventLocator getLocation() { return locator.getLocation(); } /** * Gets the current source location information in SAX {@link Locator}. *

* Sometimes the unmarshaller works against a different kind of XML source, * making this information meaningless. */ public LocatorEx getLocator() { return locator; } /** * Called when there's no corresponding ID value. */ public void errorUnresolvedIDREF(Object bean, String idref, LocatorEx loc) throws SAXException { handleEvent( new ValidationEventImpl( ValidationEvent.ERROR, Messages.UNRESOLVED_IDREF.format(idref), loc.getLocation()), true ); } // // // 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 Patcher[] patchers = null; private int patchersLen = 0; /** * Adds a job that will be executed at the last of the unmarshalling. * This method is used to support ID/IDREF feature, but it can be used * for other purposes as well. * * @param job * The run method of this object is called. */ public void addPatcher( Patcher job ) { // re-allocate buffer if necessary if( patchers==null ) patchers = new Patcher[32]; if( patchers.length == patchersLen ) { Patcher[] buf = new Patcher[patchersLen*2]; System.arraycopy(patchers,0,buf,0,patchersLen); patchers = buf; } patchers[patchersLen++] = job; } /** Executes all the patchers. */ private void runPatchers() throws SAXException { if( patchers!=null ) { for( int i=0; i * The exception thrown from {@link Callable#call()} means the unmarshaller should abort * right away. * * @see IDResolver#resolve(String, Class) */ public Callable getObjectFromId( String id, Class targetType ) throws SAXException { return idResolver.resolve(id,targetType); } // // // namespace binding maintainance // // private String[] nsBind = new String[16]; private int nsLen=0; @Override 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; } @Override public void endPrefixMapping( String prefix ) { nsLen-=2; } private String resolveNamespacePrefix( String prefix ) { if(prefix.equals("xml")) return XMLConstants.XML_NS_URI; for( int i=nsLen-2; i>=0; i-=2 ) { if(prefix.equals(nsBind[i])) return nsBind[i+1]; } if(environmentNamespaceContext!=null) // temporary workaround until Zephyr fixes 6337180 return environmentNamespaceContext.getNamespaceURI(prefix.intern()); // by default, the default ns is bound to "". // but allow environmentNamespaceContext to take precedence if(prefix.equals("")) return ""; // unresolved. error. return null; } /** * Returns a list of prefixes newly declared on the current element. * * @return * A possible zero-length array of prefixes. The default prefix * is represented by the empty string. */ public String[] getNewlyDeclaredPrefixes() { return getPrefixList( current.prev.numNsDecl ); } /** * Returns a list of all in-scope prefixes. * * @return * A possible zero-length array of prefixes. The default prefix * is represented by the empty string. */ public String[] getAllDeclaredPrefixes() { return getPrefixList(0); } private String[] getPrefixList( int startIndex ) { int size = (current.numNsDecl - startIndex)/2; String[] r = new String[size]; for( int i=0; i 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==null ) throw new IllegalArgumentException(); 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; } 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; } @Override public String getPrefix(String uri) { if( uri==null ) throw new IllegalArgumentException(); if( uri.equals(XMLConstants.XML_NS_URI) ) return XMLConstants.XML_NS_PREFIX; if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) ) return XMLConstants.XMLNS_ATTRIBUTE; 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. return nsBind[i]; if(environmentNamespaceContext!=null) return environmentNamespaceContext.getPrefix(uri); return null; } @Override public String getNamespaceURI(String prefix) { if (prefix == null) throw new IllegalArgumentException(); if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE)) return XMLConstants.XMLNS_ATTRIBUTE_NS_URI; return resolveNamespacePrefix(prefix); } // // // // scope management // // // private Scope[] scopes = new Scope[16]; /** * Points to the top of the scope stack (=size-1). */ private int scopeTop=0; { for( int i=0; i * This method allocates a specified number of fresh {@link Scope} objects. * They can be accessed by the {@link #getScope} method until the corresponding * {@link #endScope} method is invoked. * *

* A new scope will mask the currently active scope. Only one frame of {@link Scope}s * can be accessed at any given time. * * @param frameSize * The # of slots to be allocated. */ public void startScope(int frameSize) { scopeTop += frameSize; // reallocation if(scopeTop>=scopes.length) { Scope[] s = new Scope[Math.max(scopeTop+1,scopes.length*2)]; System.arraycopy(scopes,0,s,0,scopes.length); for( int i=scopes.length; i * If any packing in progress will be finalized by this method. * * @param frameSize * The same size that gets passed to the {@link #startScope(int)} * method. */ public void endScope(int frameSize) throws SAXException { try { for( ; frameSize>0; frameSize--, scopeTop-- ) scopes[scopeTop].finish(); } catch (AccessorException e) { handleError(e); // the error might have left scopes in inconsistent state, // so replace them by fresh ones for( ; frameSize>0; frameSize-- ) scopes[scopeTop--] = new Scope(this); } } /** * Gets the currently active {@link Scope}. * * @param offset * a number between [0,frameSize) * * @return * always a valid {@link Scope} object. */ public Scope getScope(int offset) { return scopes[scopeTop-offset]; } // // // // // // // private static final Loader DEFAULT_ROOT_LOADER = new DefaultRootLoader(); private static final Loader EXPECTED_TYPE_ROOT_LOADER = new ExpectedTypeRootLoader(); /** * Root loader that uses the tag name and possibly its @xsi:type * to decide how to start unmarshalling. */ private static final class DefaultRootLoader extends Loader implements Receiver { /** * Receives the root element and determines how to start * unmarshalling. */ @Override public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException { Loader loader = state.getContext().selectRootLoader(state,ea); if(loader!=null) { state.loader = loader; state.receiver = this; return; } // the registry doesn't know about this element. // try its xsi:type JaxBeanInfo beanInfo = XsiTypeLoader.parseXsiType(state, ea, null); if(beanInfo==null) { // we don't even know its xsi:type reportUnexpectedChildElement(ea,false); return; } state.loader = beanInfo.getLoader(null,false); state.prev.backup = new JAXBElement(ea.createQName(),Object.class,null); state.receiver = this; } @Override public Collection getExpectedChildElements() { return getInstance().getJAXBContext().getValidRootNames(); } @Override public void receive(State state, Object o) { if(state.backup!=null) { ((JAXBElement)state.backup).setValue(o); o = state.backup; } if (state.nil) { ((JAXBElement)o).setNil(true); } state.getContext().result = o; } } /** * Root loader that uses {@link UnmarshallingContext#expectedType} * to decide how to start unmarshalling. */ private static final class ExpectedTypeRootLoader extends Loader implements Receiver { /** * Receives the root element and determines how to start * unmarshalling. */ @Override public void childElement(UnmarshallingContext.State state, TagName ea) { UnmarshallingContext context = state.getContext(); // unmarshals the specified type QName qn = new QName(ea.uri,ea.local); state.prev.target = new JAXBElement(qn,context.expectedType.jaxbType,null,null); state.receiver = this; // this is bit wasteful, as in theory we should have each expectedType keep // nillable version --- but that increases the combination from two to four, // which adds the resident memory footprint. Since XsiNilLoader is small, // I intentionally allocate a new instance freshly. state.loader = new XsiNilLoader(context.expectedType.getLoader(null,true)); } @Override public void receive(State state, Object o) { JAXBElement e = (JAXBElement)state.target; e.setValue(o); state.getContext().recordOuterPeer(e); state.getContext().result = e; } } // // in-place unmarshalling related capabilities // /** * Notifies the context about the inner peer of the current element. * *

* If the unmarshalling is building the association, the context * will use this information. Otherwise it will be just ignored. */ public void recordInnerPeer(Object innerPeer) { if(assoc!=null) assoc.addInner(currentElement,innerPeer); } /** * Gets the inner peer JAXB object associated with the current element. * * @return * null if the current element doesn't have an inner peer, * or if we are not doing the in-place unmarshalling. */ public Object getInnerPeer() { if(assoc!=null && isInplaceMode) return assoc.getInnerPeer(currentElement); else return null; } /** * Notifies the context about the outer peer of the current element. * *

* If the unmarshalling is building the association, the context * will use this information. Otherwise it will be just ignored. */ public void recordOuterPeer(Object outerPeer) { if(assoc!=null) assoc.addOuter(currentElement,outerPeer); } /** * Gets the outer peer JAXB object associated with the current element. * * @return * null if the current element doesn't have an inner peer, * or if we are not doing the in-place unmarshalling. */ public Object getOuterPeer() { if(assoc!=null && isInplaceMode) return assoc.getOuterPeer(currentElement); else return null; } /** * Gets the xmime:contentType value for the current object. * * @see JAXBContextImpl#getXMIMEContentType(Object) */ public String getXMIMEContentType() { /* this won't work when the class is like class Foo { @XmlValue Image img; } because the target will return Foo, not the class enclosing Foo which will have xmime:contentType */ Object t = current.target; if(t==null) return null; return getJAXBContext().getXMIMEContentType(t); } /** * When called from within the realm of the unmarshaller, this method * returns the current {@link UnmarshallingContext} in charge. */ public static UnmarshallingContext getInstance() { return (UnmarshallingContext) Coordinator._getInstance(); } /** * Allows to access elements which are expected in current state. * Useful for getting elements for current parent. * * @return */ public Collection getCurrentExpectedElements() { pushCoordinator(); try { State s = getCurrentState(); Loader l = s.loader; return (l != null) ? l.getExpectedChildElements() : null; } finally { popCoordinator(); } } /** * Allows to access attributes which are expected in current state. * Useful for getting attributes for current parent. * * @return */ public Collection getCurrentExpectedAttributes() { pushCoordinator(); try { State s = getCurrentState(); Loader l = s.loader; return (l != null) ? l.getExpectedAttributes() : null; } finally { popCoordinator(); } } /** * Gets StructureLoader if used as loader. * Useful when determining if element is mixed or not. * */ public StructureLoader getStructureLoader() { if(current.loader instanceof StructureLoader) return (StructureLoader)current.loader; return null; } /** * Based on current {@link Logger} {@link Level} and errorCounter value determines if error should be reported. * * If the method called and return true it is expected that error will be reported. And that's why * errorCounter is automatically decremented during the check. * * NOT THREAD SAFE!!! In case of heave concurrency access several additional errors could be reported. It's not expected to be the * problem. Otherwise add synchronization here. * * @return true in case if {@link Level#FINEST} is set OR we haven't exceed errors reporting limit. */ public boolean shouldErrorBeReported() throws SAXException { if (logger.isLoggable(Level.FINEST)) return true; if (errorsCounter >= 0) { --errorsCounter; if (errorsCounter == 0) // it's possible to miss this because of concurrency. If required add synchronization here handleEvent(new ValidationEventImpl(ValidationEvent.WARNING, Messages.ERRORS_LIMIT_EXCEEDED.format(), getLocator().getLocation(), null), true); } return errorsCounter >= 0; } }