/*
* 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 Loader
s to perform the whole unmarshalling.
Author: Kohsuke Kawaguchi
/**
* Center of the unmarshalling.
*
* <p>
* 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.
/**
* Root state.
*/
private final State root;
The currently active state.
/**
* 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. /** Root object that is being unmarshalled. */
private Object result;
If non-null, this unmarshaller will unmarshal JAXBElement<EXPECTEDTYPE>
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 UnmarshallingContext
when it is created. The property on Unmarshaller
could be changed after the handler is created. /**
* If non-null, this unmarshaller will unmarshal {@code JAXBElement<EXPECTEDTYPE>}
* 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.
/**
* 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.
/**
* 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.
/**
* 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 assoc
==null. If it's non-null, then true
indicates that we are doing in-place associative unmarshalling. If false
, then we are doing associative unmarshalling without object reuse.
/**
* Indicates whether we are doing in-place unmarshalling
* or not.
*
* <p>
* 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.
/**
* 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 Also: - startDocument.startDocument(LocatorEx, NamespaceContext)
/**
* @see XmlVisitor#startDocument(LocatorEx, NamespaceContext)
*/
private NamespaceContext environmentNamespaceContext;
Used to discover additional classes when we hit unknown elements/types.
/**
* Used to discover additional classes when we hit unknown elements/types.
*/
public @Nullable ClassResolver classResolver;
User-supplied ClassLoader
for converting name to Class
. For backward compatibility, when null, use thread context classloader. /**
* 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
/**
* 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.
/**
* State information for each element.
*/
public final class State {
Loader that owns this element.
/**
* Loader that owns this element.
*/
private Loader loader;
Once loader
is completed, this receiver receives the result. /**
* Once {@link #loader} is completed, this receiver
* receives the result.
*/
private Receiver receiver;
private Intercepter intercepter;
Object being unmarshalled by this loader
. /**
* Object being unmarshalled by this {@link #loader}.
*/
private Object target;
Hack for making JAXBElement unmarshalling work.
While the unmarshalling is in progress, the 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 State
per element, this mechanism only works when there's one object per element, which breaks down when we have 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 backup
field is used. When we create X instance in the above example, we set that to state.prev.target
and displace its old value to state.prev.backup
(where Y goes to 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 State
parent/child relationship, and it thereby makes Receiver
mechanism simpler.
Yes, I know this is a hack, and no, I'm not proud of it.
See Also: - startElement.startElement(State, TagName)
- intercept.intercept(State, Object)
/**
* Hack for making JAXBElement unmarshalling work.
*
* <p>
* 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.)
*
* <p>
* 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.
*
* <p>
* 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.
*
* <p>
* 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 UnmarshallingContext.nsBind
s declared thus far. (The value of UnmarshallingContext.nsLen
when this state is pushed. /**
* 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 Loader
when Loader.childElement(State, TagName)
is called or by a child Loader
when Loader.startElement(State, TagName)
is called. /**
* 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
/**
* specifies that we are working with mixed content
*/
private boolean mixed = false;
Gets the context.
/**
* 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.
/**
* 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.
Params: - assoc –
Must be both non-null when the unmarshaller does the
in-place unmarshalling. Otherwise must be both null.
/**
* 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 JAXBContextImpl.selectRootLoader(State, TagName)
, this method also consults ClassResolver
. Throws: - SAXException – if
ValidationEventHandler
reported a failure.
/**
* 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.
/**
* User-specified factory methods.
*/
private final Map<Class,Factory> factories = new HashMap<Class, Factory>();
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 TextPredictor
. /**
* You should be always calling this through {@link TextPredictor}.
*/
@Deprecated
@Override
public boolean expectText() {
return current.loader.expectText;
}
You should be always getting TextPredictor
from XmlVisitor
. /**
* 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
/**
* 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.
/**
* 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.
/**
* 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.
/**
* 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 handleEvent(ValidationEvent, boolean)
/**
* 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 Locator
.
Sometimes the unmarshaller works against a different kind of XML source,
making this information meaningless.
/**
* Gets the current source location information in SAX {@link Locator}.
* <p>
* 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.
/**
* 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.
/**
* 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.
Params: - job –
The run method of this object is called.
/**
* 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. /** Executes all the patchers. */
private void runPatchers() throws SAXException {
if( patchers!=null ) {
for( int i=0; i<patchersLen; i++ ) {
patchers[i].run();
patchers[i] = null; // free memory
}
}
}
Adds the object which is currently being unmarshalled
to the ID table.
Returns:
Returns the value passed as the parameter.
This is a hack, but this makes it easier for ID
transducer to do its job.
/**
* Adds the object which is currently being unmarshalled
* to the ID table.
*
* @return
* Returns the value passed as the parameter.
* This is a hack, but this makes it easier for ID
* transducer to do its job.
*/
// TODO: what shall we do if the ID is already declared?
//
// throwing an exception is one way. Overwriting the previous one
// is another way. The latter allows us to process invalid documents,
// while the former makes it impossible to handle them.
//
// I prefer to be flexible in terms of invalid document handling,
// so chose not to throw an exception.
//
// I believe this is an implementation choice, not the spec issue.
// -kk
public String addToIdTable( String id ) throws SAXException {
// Hmm...
// in cases such as when ID is used as an attribute, or as @XmlValue
// the target wilil be current.target.
// but in some other cases, such as when ID is used as a child element
// or a value of JAXBElement, it's current.prev.target.
// I don't know if this detection logic is complete
Object o = current.target;
if(o==null)
o = current.prev.target;
idResolver.bind(id,o);
return id;
}
Looks up the ID table and gets associated object.
The exception thrown from Callable.call()
means the unmarshaller should abort right away.
See Also:
/**
* Looks up the ID table and gets associated object.
*
* <p>
* 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.
Returns:
A possible zero-length array of prefixes. The default prefix
is represented by the empty string.
/**
* 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.
Returns:
A possible zero-length array of prefixes. The default prefix
is represented by the empty string.
/**
* 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<r.length; i++ )
r[i] = nsBind[startIndex+i*2];
return r;
}
// NamespaceContext2 implementation
//
@Override
public Iterator<String> 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<String> getAllPrefixesInList(String uri) {
List<String> a = new ArrayList<String>();
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).
/**
* Points to the top of the scope stack (=size-1).
*/
private int scopeTop=0;
{
for( int i=0; i<scopes.length; i++ )
scopes[i] = new Scope(this);
}
Starts a new packing scope.
This method allocates a specified number of fresh Scope
objects. They can be accessed by the getScope
method until the corresponding endScope
method is invoked.
A new scope will mask the currently active scope. Only one frame of Scope
s can be accessed at any given time.
Params: - frameSize –
The # of slots to be allocated.
/**
* Starts a new packing scope.
*
* <p>
* 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.
*
* <p>
* 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<s.length; i++ )
s[i] = new Scope(this);
scopes = s;
}
}
Ends the current packing scope.
If any packing in progress will be finalized by this method.
Params: - frameSize – The same size that gets passed to the
startScope(int)
method.
/**
* Ends the current packing scope.
*
* <p>
* 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 Scope
. Params: - offset –
a number between [0,frameSize)
Returns: always a valid Scope
object.
/**
* 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.
/**
* 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.
/**
* 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<Object>(ea.createQName(),Object.class,null);
state.receiver = this;
}
@Override
public Collection<QName> getExpectedChildElements() {
return getInstance().getJAXBContext().getValidRootNames();
}
@Override
public void receive(State state, Object o) {
if(state.backup!=null) {
((JAXBElement<Object>)state.backup).setValue(o);
o = state.backup;
}
if (state.nil) {
((JAXBElement<Object>)o).setNil(true);
}
state.getContext().result = o;
}
}
Root loader that uses UnmarshallingContext.expectedType
to decide how to start unmarshalling. /**
* 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.
/**
* 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.
/**
* Notifies the context about the inner peer of the current element.
*
* <p>
* 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.
Returns:
null if the current element doesn't have an inner peer,
or if we are not doing the in-place unmarshalling.
/**
* 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.
/**
* Notifies the context about the outer peer of the current element.
*
* <p>
* 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.
Returns:
null if the current element doesn't have an inner peer,
or if we are not doing the in-place unmarshalling.
/**
* 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 Also: - getXMIMEContentType.getXMIMEContentType(Object)
/**
* 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 UnmarshallingContext
in charge. /**
* 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.
Returns:
/**
* Allows to access elements which are expected in current state.
* Useful for getting elements for current parent.
*
* @return
*/
public Collection<QName> 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.
Returns:
/**
* Allows to access attributes which are expected in current state.
* Useful for getting attributes for current parent.
*
* @return
*/
public Collection<QName> 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.
/**
* 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 Logger
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. Returns: true in case if Level.FINEST
is set OR we haven't exceed errors reporting limit.
/**
* 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;
}
}