/* * 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
* 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
* 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
* 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