ohair@286: /*
aefimov@650: * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
ohair@286: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
ohair@286: *
ohair@286: * This code is free software; you can redistribute it and/or modify it
ohair@286: * under the terms of the GNU General Public License version 2 only, as
ohair@286: * published by the Free Software Foundation. Oracle designates this
ohair@286: * particular file as subject to the "Classpath" exception as provided
ohair@286: * by Oracle in the LICENSE file that accompanied this code.
ohair@286: *
ohair@286: * This code is distributed in the hope that it will be useful, but WITHOUT
ohair@286: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
ohair@286: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
ohair@286: * version 2 for more details (a copy is included in the LICENSE file that
ohair@286: * accompanied this code).
ohair@286: *
ohair@286: * You should have received a copy of the GNU General Public License version
ohair@286: * 2 along with this work; if not, write to the Free Software Foundation,
ohair@286: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
ohair@286: *
ohair@286: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
ohair@286: * or visit www.oracle.com if you need additional information or have any
ohair@286: * questions.
ohair@286: */
ohair@286:
ohair@286: package com.sun.xml.internal.bind.v2.runtime.unmarshaller;
ohair@286:
ohair@286: import java.lang.reflect.InvocationTargetException;
ohair@286: import java.lang.reflect.Method;
ohair@286: import java.util.ArrayList;
ohair@286: import java.util.Collection;
ohair@286: import java.util.Collections;
ohair@286: import java.util.HashMap;
ohair@286: import java.util.Iterator;
ohair@286: import java.util.List;
ohair@286: import java.util.Map;
ohair@286: import java.util.concurrent.Callable;
ohair@286:
ohair@286: import javax.xml.XMLConstants;
ohair@286: import javax.xml.bind.JAXBElement;
ohair@286: import javax.xml.bind.UnmarshalException;
ohair@286: import javax.xml.bind.Unmarshaller;
ohair@286: import javax.xml.bind.ValidationEvent;
ohair@286: import javax.xml.bind.ValidationEventHandler;
ohair@286: import javax.xml.bind.ValidationEventLocator;
ohair@286: import javax.xml.bind.helpers.ValidationEventImpl;
ohair@286: import javax.xml.namespace.NamespaceContext;
ohair@286: import javax.xml.namespace.QName;
ohair@286:
ohair@286: import com.sun.istack.internal.NotNull;
ohair@286: import com.sun.istack.internal.Nullable;
ohair@286: import com.sun.istack.internal.SAXParseException2;
ohair@286: import com.sun.xml.internal.bind.IDResolver;
mkos@397: import com.sun.xml.internal.bind.Util;
ohair@286: import com.sun.xml.internal.bind.api.AccessorException;
ohair@286: import com.sun.xml.internal.bind.api.ClassResolver;
ohair@286: import com.sun.xml.internal.bind.unmarshaller.InfosetScanner;
ohair@286: import com.sun.xml.internal.bind.v2.ClassFactory;
ohair@286: import com.sun.xml.internal.bind.v2.runtime.AssociationMap;
ohair@286: import com.sun.xml.internal.bind.v2.runtime.Coordinator;
ohair@286: import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
ohair@286: import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
mkos@397: import java.util.logging.Level;
mkos@397: import java.util.logging.Logger;
ohair@286:
ohair@286: import org.xml.sax.ErrorHandler;
ohair@286: import org.xml.sax.SAXException;
ohair@286: import org.xml.sax.helpers.LocatorImpl;
ohair@286:
ohair@286: /**
ohair@286: * Center of the unmarshalling.
ohair@286: *
ohair@286: *
ohair@286: * This object is responsible for coordinating {@link Loader}s to
ohair@286: * perform the whole unmarshalling.
ohair@286: *
ohair@286: * @author Kohsuke Kawaguchi
ohair@286: */
ohair@286: public final class UnmarshallingContext extends Coordinator
ohair@286: implements NamespaceContext, ValidationEventHandler, ErrorHandler, XmlVisitor, XmlVisitor.TextPredictor {
ohair@286:
mkos@397: private static final Logger logger = Logger.getLogger(UnmarshallingContext.class.getName());
mkos@397:
ohair@286: /**
ohair@286: * Root state.
ohair@286: */
ohair@286: private final State root;
ohair@286:
ohair@286: /**
ohair@286: * The currently active state.
ohair@286: */
ohair@286: private State current;
ohair@286:
ohair@286: private static final LocatorEx DUMMY_INSTANCE;
ohair@286:
ohair@286: static {
ohair@286: LocatorImpl loc = new LocatorImpl();
ohair@286: loc.setPublicId(null);
ohair@286: loc.setSystemId(null);
ohair@286: loc.setLineNumber(-1);
ohair@286: loc.setColumnNumber(-1);
ohair@286: DUMMY_INSTANCE = new LocatorExWrapper(loc);
ohair@286: }
ohair@286:
ohair@286: private @NotNull LocatorEx locator = DUMMY_INSTANCE;
ohair@286:
ohair@286: /** Root object that is being unmarshalled. */
ohair@286: private Object result;
ohair@286:
ohair@286: /**
ohair@286: * If non-null, this unmarshaller will unmarshal {@code JAXBElement}
ohair@286: * regardless of the tag name, as opposed to deciding the root object by using
ohair@286: * the tag name.
ohair@286: *
ohair@286: * The property has a package-level access, because we cannot copy this value
ohair@286: * to {@link UnmarshallingContext} when it is created. The property
ohair@286: * on {@link Unmarshaller} could be changed after the handler is created.
ohair@286: */
ohair@286: private JaxBeanInfo expectedType;
ohair@286:
ohair@286: /**
ohair@286: * Handles ID/IDREF.
ohair@286: */
ohair@286: private IDResolver idResolver;
ohair@286:
ohair@286: /**
ohair@286: * This flag is set to true at the startDocument event
ohair@286: * and false at the endDocument event.
ohair@286: *
ohair@286: * Until the first document is unmarshalled, we don't
ohair@286: * want to return an object. So this variable is initialized
ohair@286: * to true.
ohair@286: */
ohair@286: private boolean isUnmarshalInProgress = true;
ohair@286: private boolean aborted = false;
ohair@286:
ohair@286: public final UnmarshallerImpl parent;
ohair@286:
ohair@286: /**
ohair@286: * If the unmarshaller is doing associative unmarshalling,
ohair@286: * this field is initialized to non-null.
ohair@286: */
ohair@286: private final AssociationMap assoc;
ohair@286:
ohair@286: /**
ohair@286: * Indicates whether we are doing in-place unmarshalling
ohair@286: * or not.
ohair@286: *
ohair@286: *
ohair@286: * This flag is unused when {@link #assoc}==null.
ohair@286: * If it's non-null, then true indicates
ohair@286: * that we are doing in-place associative unmarshalling.
ohair@286: * If false, then we are doing associative unmarshalling
ohair@286: * without object reuse.
ohair@286: */
ohair@286: private boolean isInplaceMode;
ohair@286:
ohair@286: /**
ohair@286: * This object is consulted to get the element object for
ohair@286: * the current element event.
ohair@286: *
ohair@286: * This is used when we are building an association map.
ohair@286: */
ohair@286: private InfosetScanner scanner;
ohair@286:
ohair@286: private Object currentElement;
ohair@286:
ohair@286: /**
ohair@286: * @see XmlVisitor#startDocument(LocatorEx, NamespaceContext)
ohair@286: */
ohair@286: private NamespaceContext environmentNamespaceContext;
ohair@286:
ohair@286: /**
ohair@286: * Used to discover additional classes when we hit unknown elements/types.
ohair@286: */
ohair@286: public @Nullable ClassResolver classResolver;
ohair@286:
ohair@286: /**
ohair@286: * User-supplied {@link ClassLoader} for converting name to {@link Class}.
ohair@286: * For backward compatibility, when null, use thread context classloader.
ohair@286: */
ohair@286: public @Nullable ClassLoader classLoader;
ohair@286:
ohair@286: /**
mkos@397: * The variable introduced to avoid reporting n^10 similar errors.
mkos@397: * After error is reported counter is decremented. When it became 0 - errors should not be reported any more.
mkos@397: *
mkos@397: * volatile is required to ensure that concurrent threads will see changed value
mkos@397: */
mkos@397: private static volatile int errorsCounter = 10;
mkos@397:
mkos@397: /**
ohair@286: * State information for each element.
ohair@286: */
ohair@286: public final class State {
ohair@286: /**
ohair@286: * Loader that owns this element.
ohair@286: */
aefimov@650: private Loader loader;
ohair@286: /**
ohair@286: * Once {@link #loader} is completed, this receiver
ohair@286: * receives the result.
ohair@286: */
aefimov@650: private Receiver receiver;
ohair@286:
aefimov@650: private Intercepter intercepter;
ohair@286:
ohair@286: /**
ohair@286: * Object being unmarshalled by this {@link #loader}.
ohair@286: */
aefimov@650: private Object target;
ohair@286:
ohair@286: /**
ohair@286: * Hack for making JAXBElement unmarshalling work.
ohair@286: *
ohair@286: *
ohair@286: * While the unmarshalling is in progress, the {@link #target} field stores the object being unmarshalled.
ohair@286: * This makes it convenient to keep track of the unmarshalling activity in context of XML infoset, but
ohair@286: * since there's only one {@link State} per element, this mechanism only works when there's one object
ohair@286: * per element, which breaks down when we have {@link JAXBElement}, since the presence of JAXBElement
ohair@286: * requires that we have two objects unmarshalled (a JAXBElement X and a value object Y bound to an XML type.)
ohair@286: *
ohair@286: *
ohair@286: * So to make room for storing both, this {@link #backup} field is used. When we create X instance
ohair@286: * in the above example, we set that to {@code state.prev.target} and displace its old value to
ohair@286: * {@code state.prev.backup} (where Y goes to {@code state.target}.) Upon the completion of the unmarshalling
ohair@286: * of Y, we revert this.
ohair@286: *
ohair@286: *
ohair@286: * While this attributes X incorrectly to its parent element, this preserves the parent/child
ohair@286: * relationship between unmarshalled objects and {@link State} parent/child relationship, and
ohair@286: * it thereby makes {@link Receiver} mechanism simpler.
ohair@286: *
ohair@286: *
ohair@286: * Yes, I know this is a hack, and no, I'm not proud of it.
ohair@286: *
ohair@286: * @see ElementBeanInfoImpl.IntercepterLoader#startElement(State, TagName)
ohair@286: * @see ElementBeanInfoImpl.IntercepterLoader#intercept(State, Object)
ohair@286: */
aefimov@650: private Object backup;
ohair@286:
ohair@286: /**
ohair@286: * Number of {@link UnmarshallingContext#nsBind}s declared thus far.
ohair@286: * (The value of {@link UnmarshallingContext#nsLen} when this state is pushed.
ohair@286: */
ohair@286: private int numNsDecl;
ohair@286:
ohair@286: /**
ohair@286: * If this element has an element default value.
ohair@286: *
ohair@286: * This should be set by either a parent {@link Loader} when
ohair@286: * {@link Loader#childElement(State, TagName)} is called
ohair@286: * or by a child {@link Loader} when
ohair@286: * {@link Loader#startElement(State, TagName)} is called.
ohair@286: */
aefimov@650: private String elementDefaultValue;
ohair@286:
ohair@286: /**
ohair@286: * {@link State} for the parent element
ohair@286: *
ohair@286: * {@link State} objects form a doubly linked list.
ohair@286: */
aefimov@650: private State prev;
ohair@286: private State next;
ohair@286:
aefimov@650: private boolean nil = false;
aefimov@650:
aefimov@650: /**
aefimov@650: * specifies that we are working with mixed content
aefimov@650: */
aefimov@650: private boolean mixed = false;
ohair@286:
ohair@286: /**
ohair@286: * Gets the context.
ohair@286: */
ohair@286: public UnmarshallingContext getContext() {
ohair@286: return UnmarshallingContext.this;
ohair@286: }
ohair@286:
mkos@397: @SuppressWarnings("LeakingThisInConstructor")
ohair@286: private State(State prev) {
ohair@286: this.prev = prev;
mkos@397: if (prev!=null) {
ohair@286: prev.next = this;
aefimov@650: if (prev.mixed) // parent is in mixed mode
aefimov@650: this.mixed = true;
mkos@397: }
ohair@286: }
ohair@286:
ohair@286: private void push() {
mkos@397: if (logger.isLoggable(Level.FINEST)) {
mkos@397: logger.log(Level.FINEST, "State.push");
mkos@397: }
mkos@397: if (next==null) {
mkos@397: assert current == this;
aefimov@650: next = new State(this);
mkos@397: }
mkos@397: nil = false;
ohair@286: State n = next;
ohair@286: n.numNsDecl = nsLen;
ohair@286: current = n;
ohair@286: }
ohair@286:
ohair@286: private void pop() {
mkos@397: if (logger.isLoggable(Level.FINEST)) {
mkos@397: logger.log(Level.FINEST, "State.pop");
mkos@397: }
ohair@286: assert prev!=null;
ohair@286: loader = null;
ohair@286: nil = false;
aefimov@650: mixed = false;
ohair@286: receiver = null;
ohair@286: intercepter = null;
ohair@286: elementDefaultValue = null;
ohair@286: target = null;
ohair@286: current = prev;
aefimov@650: next = null;
aefimov@650: }
aefimov@650:
aefimov@650: public boolean isMixed() {
aefimov@650: return mixed;
aefimov@650: }
aefimov@650:
aefimov@650: public Object getTarget() {
aefimov@650: return target;
aefimov@650: }
aefimov@650:
aefimov@650: public void setLoader(Loader loader) {
aefimov@650: if (loader instanceof StructureLoader) // set mixed mode
aefimov@650: mixed = !((StructureLoader)loader).getBeanInfo().hasElementOnlyContentModel();
aefimov@650: this.loader = loader;
aefimov@650: }
aefimov@650:
aefimov@650: public void setReceiver(Receiver receiver) {
aefimov@650: this.receiver = receiver;
aefimov@650: }
aefimov@650:
aefimov@650: public State getPrev() {
aefimov@650: return prev;
aefimov@650: }
aefimov@650:
aefimov@650: public void setIntercepter(Intercepter intercepter) {
aefimov@650: this.intercepter = intercepter;
aefimov@650: }
aefimov@650:
aefimov@650: public void setBackup(Object backup) {
aefimov@650: this.backup = backup;
aefimov@650: }
aefimov@650:
aefimov@650: public void setTarget(Object target) {
aefimov@650: this.target = target;
aefimov@650: }
aefimov@650:
aefimov@650: public Object getBackup() {
aefimov@650: return backup;
aefimov@650: }
aefimov@650:
aefimov@650: public boolean isNil() {
aefimov@650: return nil;
aefimov@650: }
aefimov@650:
aefimov@650: public void setNil(boolean nil) {
aefimov@650: this.nil = nil;
aefimov@650: }
aefimov@650:
aefimov@650: public Loader getLoader() {
aefimov@650: return loader;
aefimov@650: }
aefimov@650:
aefimov@650: public String getElementDefaultValue() {
aefimov@650: return elementDefaultValue;
aefimov@650: }
aefimov@650:
aefimov@650: public void setElementDefaultValue(String elementDefaultValue) {
aefimov@650: this.elementDefaultValue = elementDefaultValue;
ohair@286: }
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Stub to the user-specified factory method.
ohair@286: */
ohair@286: private static class Factory {
ohair@286: private final Object factorInstance;
ohair@286: private final Method method;
ohair@286:
ohair@286: public Factory(Object factorInstance, Method method) {
ohair@286: this.factorInstance = factorInstance;
ohair@286: this.method = method;
ohair@286: }
ohair@286:
ohair@286: public Object createInstance() throws SAXException {
ohair@286: try {
ohair@286: return method.invoke(factorInstance);
ohair@286: } catch (IllegalAccessException e) {
ohair@286: getInstance().handleError(e,false);
ohair@286: } catch (InvocationTargetException e) {
ohair@286: getInstance().handleError(e,false);
ohair@286: }
ohair@286: return null; // can never be executed
ohair@286: }
ohair@286: }
ohair@286:
ohair@286:
ohair@286: /**
ohair@286: * Creates a new unmarshaller.
ohair@286: *
ohair@286: * @param assoc
ohair@286: * Must be both non-null when the unmarshaller does the
ohair@286: * in-place unmarshalling. Otherwise must be both null.
ohair@286: */
ohair@286: public UnmarshallingContext( UnmarshallerImpl _parent, AssociationMap assoc) {
ohair@286: this.parent = _parent;
ohair@286: this.assoc = assoc;
ohair@286: this.root = this.current = new State(null);
ohair@286: }
ohair@286:
ohair@286: public void reset(InfosetScanner scanner,boolean isInplaceMode, JaxBeanInfo expectedType, IDResolver idResolver) {
ohair@286: this.scanner = scanner;
ohair@286: this.isInplaceMode = isInplaceMode;
ohair@286: this.expectedType = expectedType;
ohair@286: this.idResolver = idResolver;
ohair@286: }
ohair@286:
ohair@286: public JAXBContextImpl getJAXBContext() {
ohair@286: return parent.context;
ohair@286: }
ohair@286:
ohair@286: public State getCurrentState() {
ohair@286: return current;
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * On top of {@link JAXBContextImpl#selectRootLoader(State, TagName)},
ohair@286: * this method also consults {@link ClassResolver}.
ohair@286: *
ohair@286: * @throws SAXException
ohair@286: * if {@link ValidationEventHandler} reported a failure.
ohair@286: */
ohair@286: public Loader selectRootLoader(State state, TagName tag) throws SAXException {
ohair@286: try {
ohair@286: Loader l = getJAXBContext().selectRootLoader(state, tag);
ohair@286: if(l!=null) return l;
ohair@286:
ohair@286: if(classResolver!=null) {
ohair@286: Class> clazz = classResolver.resolveElementName(tag.uri, tag.local);
ohair@286: if(clazz!=null) {
ohair@286: JAXBContextImpl enhanced = getJAXBContext().createAugmented(clazz);
ohair@286: JaxBeanInfo> bi = enhanced.getBeanInfo(clazz);
ohair@286: return bi.getLoader(enhanced,true);
ohair@286: }
ohair@286: }
ohair@286: } catch (RuntimeException e) {
ohair@286: throw e;
ohair@286: } catch (Exception e) {
ohair@286: handleError(e);
ohair@286: }
ohair@286:
ohair@286: return null;
ohair@286: }
ohair@286:
ohair@286: public void clearStates() {
ohair@286: State last = current;
ohair@286: while (last.next != null) last = last.next;
ohair@286: while (last.prev != null) {
ohair@286: last.loader = null;
ohair@286: last.nil = false;
ohair@286: last.receiver = null;
ohair@286: last.intercepter = null;
ohair@286: last.elementDefaultValue = null;
ohair@286: last.target = null;
ohair@286: last = last.prev;
ohair@286: last.next.prev = null;
ohair@286: last.next = null;
ohair@286: }
ohair@286: current = last;
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * User-specified factory methods.
ohair@286: */
ohair@286: private final Map factories = new HashMap();
ohair@286:
ohair@286: public void setFactories(Object factoryInstances) {
ohair@286: factories.clear();
ohair@286: if(factoryInstances==null) {
ohair@286: return;
ohair@286: }
ohair@286: if(factoryInstances instanceof Object[]) {
ohair@286: for( Object factory : (Object[])factoryInstances ) {
ohair@286: // look for all the public methods inlcuding derived ones
ohair@286: addFactory(factory);
ohair@286: }
ohair@286: } else {
ohair@286: addFactory(factoryInstances);
ohair@286: }
ohair@286: }
ohair@286:
ohair@286: private void addFactory(Object factory) {
ohair@286: for( Method m : factory.getClass().getMethods() ) {
ohair@286: // look for methods whose signature is T createXXX()
ohair@286: if(!m.getName().startsWith("create"))
ohair@286: continue;
ohair@286: if(m.getParameterTypes().length>0)
ohair@286: continue;
ohair@286:
ohair@286: Class type = m.getReturnType();
ohair@286:
ohair@286: factories.put(type,new Factory(factory,m));
ohair@286: }
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public void startDocument(LocatorEx locator, NamespaceContext nsContext) throws SAXException {
ohair@286: if(locator!=null)
ohair@286: this.locator = locator;
ohair@286: this.environmentNamespaceContext = nsContext;
ohair@286: // reset the object
ohair@286: result = null;
ohair@286: current = root;
ohair@286:
ohair@286: patchersLen=0;
ohair@286: aborted = false;
ohair@286: isUnmarshalInProgress = true;
ohair@286: nsLen=0;
ohair@286:
ohair@286: if(expectedType!=null)
ohair@286: root.loader = EXPECTED_TYPE_ROOT_LOADER;
ohair@286: else
ohair@286: root.loader = DEFAULT_ROOT_LOADER;
ohair@286:
ohair@286: idResolver.startDocument(this);
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public void startElement(TagName tagName) throws SAXException {
ohair@286: pushCoordinator();
ohair@286: try {
ohair@286: _startElement(tagName);
ohair@286: } finally {
ohair@286: popCoordinator();
ohair@286: }
ohair@286: }
ohair@286:
ohair@286: private void _startElement(TagName tagName) throws SAXException {
ohair@286: // remember the current element if we are interested in it.
ohair@286: // because the inner peer might not be found while we consume
ohair@286: // the enter element token, we need to keep this information
ohair@286: // longer than this callback. That's why we assign it to a field.
ohair@286: if( assoc!=null )
ohair@286: currentElement = scanner.getCurrentElement();
ohair@286:
ohair@286: Loader h = current.loader;
ohair@286: current.push();
ohair@286:
ohair@286: // tell the parent about the new child
ohair@286: h.childElement(current,tagName);
ohair@286: assert current.loader!=null; // the childElement should register this
ohair@286: // and tell the new child that you are activated
ohair@286: current.loader.startElement(current,tagName);
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public void text(CharSequence pcdata) throws SAXException {
ohair@286: pushCoordinator();
ohair@286: try {
aefimov@650: if (current.elementDefaultValue != null) {
aefimov@650: if (pcdata.length() == 0) {
ohair@286: // send the default value into the unmarshaller instead
aefimov@650: pcdata = current.elementDefaultValue;
ohair@286: }
ohair@286: }
aefimov@650: current.loader.text(current, pcdata);
ohair@286: } finally {
ohair@286: popCoordinator();
ohair@286: }
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public final void endElement(TagName tagName) throws SAXException {
ohair@286: pushCoordinator();
ohair@286: try {
ohair@286: State child = current;
ohair@286:
ohair@286: // tell the child that your time is up
ohair@286: child.loader.leaveElement(child,tagName);
ohair@286:
ohair@286: // child.pop will erase them so store them now
ohair@286: Object target = child.target;
ohair@286: Receiver recv = child.receiver;
ohair@286: Intercepter intercepter = child.intercepter;
ohair@286: child.pop();
ohair@286:
ohair@286: // then let the parent know
ohair@286: if(intercepter!=null)
ohair@286: target = intercepter.intercept(current,target);
ohair@286: if(recv!=null)
ohair@286: recv.receive(current,target);
ohair@286: } finally {
ohair@286: popCoordinator();
ohair@286: }
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public void endDocument() throws SAXException {
ohair@286: runPatchers();
ohair@286: idResolver.endDocument();
ohair@286:
ohair@286: isUnmarshalInProgress = false;
ohair@286: currentElement = null;
ohair@286: locator = DUMMY_INSTANCE;
ohair@286: environmentNamespaceContext = null;
ohair@286:
ohair@286: // at the successful completion, scope must be all closed
ohair@286: assert root==current;
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * You should be always calling this through {@link TextPredictor}.
ohair@286: */
ohair@286: @Deprecated
mkos@397: @Override
ohair@286: public boolean expectText() {
ohair@286: return current.loader.expectText;
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * You should be always getting {@link TextPredictor} from {@link XmlVisitor}.
ohair@286: */
ohair@286: @Deprecated
mkos@397: @Override
ohair@286: public TextPredictor getPredictor() {
ohair@286: return this;
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public UnmarshallingContext getContext() {
ohair@286: return this;
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Gets the result of the unmarshalling
ohair@286: */
ohair@286: public Object getResult() throws UnmarshalException {
ohair@286: if(isUnmarshalInProgress)
ohair@286: throw new IllegalStateException();
ohair@286:
ohair@286: if(!aborted) return result;
ohair@286:
ohair@286: // there was an error.
ohair@286: throw new UnmarshalException((String)null);
ohair@286: }
ohair@286:
ohair@286: void clearResult() {
ohair@286: if (isUnmarshalInProgress) {
ohair@286: throw new IllegalStateException();
ohair@286: }
ohair@286: result = null;
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Creates a new instance of the specified class.
ohair@286: * In the unmarshaller, we need to check the user-specified factory class.
ohair@286: */
ohair@286: public Object createInstance( Class> clazz ) throws SAXException {
ohair@286: if(!factories.isEmpty()) {
ohair@286: Factory factory = factories.get(clazz);
ohair@286: if(factory!=null)
ohair@286: return factory.createInstance();
ohair@286: }
ohair@286: return ClassFactory.create(clazz);
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Creates a new instance of the specified class.
ohair@286: * In the unmarshaller, we need to check the user-specified factory class.
ohair@286: */
ohair@286: public Object createInstance( JaxBeanInfo beanInfo ) throws SAXException {
ohair@286: if(!factories.isEmpty()) {
ohair@286: Factory factory = factories.get(beanInfo.jaxbType);
ohair@286: if(factory!=null)
ohair@286: return factory.createInstance();
ohair@286: }
ohair@286: try {
ohair@286: return beanInfo.createInstance(this);
ohair@286: } catch (IllegalAccessException e) {
ohair@286: Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false);
ohair@286: } catch (InvocationTargetException e) {
ohair@286: Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false);
ohair@286: } catch (InstantiationException e) {
ohair@286: Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false);
ohair@286: }
ohair@286: return null; // can never be here
ohair@286: }
ohair@286:
ohair@286:
ohair@286:
ohair@286: //
ohair@286: //
ohair@286: // error handling
ohair@286: //
ohair@286: //
ohair@286:
ohair@286: /**
ohair@286: * Reports an error to the user, and asks if s/he wants
ohair@286: * to recover. If the canRecover flag is false, regardless
ohair@286: * of the client instruction, an exception will be thrown.
ohair@286: *
ohair@286: * Only if the flag is true and the user wants to recover from an error,
ohair@286: * the method returns normally.
ohair@286: *
ohair@286: * The thrown exception will be catched by the unmarshaller.
ohair@286: */
ohair@286: public void handleEvent(ValidationEvent event, boolean canRecover ) throws SAXException {
ohair@286: ValidationEventHandler eventHandler = parent.getEventHandler();
ohair@286:
ohair@286: boolean recover = eventHandler.handleEvent(event);
ohair@286:
ohair@286: // if the handler says "abort", we will not return the object
ohair@286: // from the unmarshaller.getResult()
ohair@286: if(!recover) aborted = true;
ohair@286:
ohair@286: if( !canRecover || !recover )
ohair@286: throw new SAXParseException2( event.getMessage(), locator,
ohair@286: new UnmarshalException(
ohair@286: event.getMessage(),
ohair@286: event.getLinkedException() ) );
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public boolean handleEvent(ValidationEvent event) {
ohair@286: try {
ohair@286: // if the handler says "abort", we will not return the object.
ohair@286: boolean recover = parent.getEventHandler().handleEvent(event);
ohair@286: if(!recover) aborted = true;
ohair@286: return recover;
ohair@286: } catch( RuntimeException re ) {
ohair@286: // if client event handler causes a runtime exception, then we
ohair@286: // have to return false.
ohair@286: return false;
ohair@286: }
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Reports an exception found during the unmarshalling to the user.
ohair@286: * This method is a convenience method that calls into
ohair@286: * {@link #handleEvent(ValidationEvent, boolean)}
ohair@286: */
ohair@286: public void handleError(Exception e) throws SAXException {
ohair@286: handleError(e,true);
ohair@286: }
ohair@286:
ohair@286: public void handleError(Exception e,boolean canRecover) throws SAXException {
ohair@286: handleEvent(new ValidationEventImpl(ValidationEvent.ERROR,e.getMessage(),locator.getLocation(),e),canRecover);
ohair@286: }
ohair@286:
ohair@286: public void handleError(String msg) {
ohair@286: handleEvent(new ValidationEventImpl(ValidationEvent.ERROR,msg,locator.getLocation()));
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: protected ValidationEventLocator getLocation() {
ohair@286: return locator.getLocation();
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Gets the current source location information in SAX {@link Locator}.
ohair@286: *
ohair@286: * Sometimes the unmarshaller works against a different kind of XML source,
ohair@286: * making this information meaningless.
ohair@286: */
ohair@286: public LocatorEx getLocator() { return locator; }
ohair@286:
ohair@286: /**
ohair@286: * Called when there's no corresponding ID value.
ohair@286: */
ohair@286: public void errorUnresolvedIDREF(Object bean, String idref, LocatorEx loc) throws SAXException {
ohair@286: handleEvent( new ValidationEventImpl(
ohair@286: ValidationEvent.ERROR,
ohair@286: Messages.UNRESOLVED_IDREF.format(idref),
ohair@286: loc.getLocation()), true );
ohair@286: }
ohair@286:
ohair@286:
ohair@286: //
ohair@286: //
ohair@286: // ID/IDREF related code
ohair@286: //
ohair@286: //
ohair@286: /**
ohair@286: * Submitted patchers in the order they've submitted.
ohair@286: * Many XML vocabulary doesn't use ID/IDREF at all, so we
ohair@286: * initialize it with null.
ohair@286: */
ohair@286: private Patcher[] patchers = null;
ohair@286: private int patchersLen = 0;
ohair@286:
ohair@286: /**
ohair@286: * Adds a job that will be executed at the last of the unmarshalling.
ohair@286: * This method is used to support ID/IDREF feature, but it can be used
ohair@286: * for other purposes as well.
ohair@286: *
ohair@286: * @param job
ohair@286: * The run method of this object is called.
ohair@286: */
ohair@286: public void addPatcher( Patcher job ) {
ohair@286: // re-allocate buffer if necessary
ohair@286: if( patchers==null )
ohair@286: patchers = new Patcher[32];
ohair@286: if( patchers.length == patchersLen ) {
ohair@286: Patcher[] buf = new Patcher[patchersLen*2];
ohair@286: System.arraycopy(patchers,0,buf,0,patchersLen);
ohair@286: patchers = buf;
ohair@286: }
ohair@286: patchers[patchersLen++] = job;
ohair@286: }
ohair@286:
ohair@286: /** Executes all the patchers. */
ohair@286: private void runPatchers() throws SAXException {
ohair@286: if( patchers!=null ) {
ohair@286: for( int i=0; i
ohair@286: * The exception thrown from {@link Callable#call()} means the unmarshaller should abort
ohair@286: * right away.
ohair@286: *
ohair@286: * @see IDResolver#resolve(String, Class)
ohair@286: */
ohair@286: public Callable getObjectFromId( String id, Class targetType ) throws SAXException {
ohair@286: return idResolver.resolve(id,targetType);
ohair@286: }
ohair@286:
ohair@286: //
ohair@286: //
ohair@286: // namespace binding maintainance
ohair@286: //
ohair@286: //
ohair@286: private String[] nsBind = new String[16];
ohair@286: private int nsLen=0;
ohair@286:
mkos@397: @Override
ohair@286: public void startPrefixMapping( String prefix, String uri ) {
ohair@286: if(nsBind.length==nsLen) {
ohair@286: // expand the buffer
ohair@286: String[] n = new String[nsLen*2];
ohair@286: System.arraycopy(nsBind,0,n,0,nsLen);
ohair@286: nsBind=n;
ohair@286: }
ohair@286: nsBind[nsLen++] = prefix;
ohair@286: nsBind[nsLen++] = uri;
ohair@286: }
mkos@397: @Override
ohair@286: public void endPrefixMapping( String prefix ) {
ohair@286: nsLen-=2;
ohair@286: }
ohair@286: private String resolveNamespacePrefix( String prefix ) {
ohair@286: if(prefix.equals("xml"))
ohair@286: return XMLConstants.XML_NS_URI;
ohair@286:
ohair@286: for( int i=nsLen-2; i>=0; i-=2 ) {
ohair@286: if(prefix.equals(nsBind[i]))
ohair@286: return nsBind[i+1];
ohair@286: }
ohair@286:
ohair@286: if(environmentNamespaceContext!=null)
ohair@286: // temporary workaround until Zephyr fixes 6337180
ohair@286: return environmentNamespaceContext.getNamespaceURI(prefix.intern());
ohair@286:
ohair@286: // by default, the default ns is bound to "".
ohair@286: // but allow environmentNamespaceContext to take precedence
ohair@286: if(prefix.equals(""))
ohair@286: return "";
ohair@286:
ohair@286: // unresolved. error.
ohair@286: return null;
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Returns a list of prefixes newly declared on the current element.
ohair@286: *
ohair@286: * @return
ohair@286: * A possible zero-length array of prefixes. The default prefix
ohair@286: * is represented by the empty string.
ohair@286: */
ohair@286: public String[] getNewlyDeclaredPrefixes() {
ohair@286: return getPrefixList( current.prev.numNsDecl );
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Returns a list of all in-scope prefixes.
ohair@286: *
ohair@286: * @return
ohair@286: * A possible zero-length array of prefixes. The default prefix
ohair@286: * is represented by the empty string.
ohair@286: */
ohair@286: public String[] getAllDeclaredPrefixes() {
ohair@286: return getPrefixList(0);
ohair@286: }
ohair@286:
ohair@286: private String[] getPrefixList( int startIndex ) {
ohair@286: int size = (current.numNsDecl - startIndex)/2;
ohair@286: String[] r = new String[size];
ohair@286: for( int i=0; i getPrefixes(String uri) {
ohair@286: // TODO: could be implemented much faster
ohair@286: // wrap it into unmodifiable list so that the remove method
ohair@286: // will throw UnsupportedOperationException.
ohair@286: return Collections.unmodifiableList(
ohair@286: getAllPrefixesInList(uri)).iterator();
ohair@286: }
ohair@286:
ohair@286: private List getAllPrefixesInList(String uri) {
ohair@286: List a = new ArrayList();
ohair@286:
ohair@286: if( uri==null )
ohair@286: throw new IllegalArgumentException();
ohair@286: if( uri.equals(XMLConstants.XML_NS_URI) ) {
ohair@286: a.add(XMLConstants.XML_NS_PREFIX);
ohair@286: return a;
ohair@286: }
ohair@286: if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) ) {
ohair@286: a.add(XMLConstants.XMLNS_ATTRIBUTE);
ohair@286: return a;
ohair@286: }
ohair@286:
ohair@286: for( int i=nsLen-2; i>=0; i-=2 )
ohair@286: if(uri.equals(nsBind[i+1]))
ohair@286: if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) )
ohair@286: // make sure that this prefix is still effective.
ohair@286: a.add(nsBind[i]);
ohair@286:
ohair@286: return a;
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public String getPrefix(String uri) {
ohair@286: if( uri==null )
ohair@286: throw new IllegalArgumentException();
ohair@286: if( uri.equals(XMLConstants.XML_NS_URI) )
ohair@286: return XMLConstants.XML_NS_PREFIX;
ohair@286: if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) )
ohair@286: return XMLConstants.XMLNS_ATTRIBUTE;
ohair@286:
ohair@286: for( int i=nsLen-2; i>=0; i-=2 )
ohair@286: if(uri.equals(nsBind[i+1]))
ohair@286: if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) )
ohair@286: // make sure that this prefix is still effective.
ohair@286: return nsBind[i];
ohair@286:
ohair@286: if(environmentNamespaceContext!=null)
ohair@286: return environmentNamespaceContext.getPrefix(uri);
ohair@286:
ohair@286: return null;
ohair@286: }
ohair@286:
mkos@397: @Override
ohair@286: public String getNamespaceURI(String prefix) {
ohair@286: if (prefix == null)
ohair@286: throw new IllegalArgumentException();
ohair@286: if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE))
ohair@286: return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
ohair@286:
ohair@286: return resolveNamespacePrefix(prefix);
ohair@286: }
ohair@286:
ohair@286: //
ohair@286: //
ohair@286: //
ohair@286: // scope management
ohair@286: //
ohair@286: //
ohair@286: //
ohair@286: private Scope[] scopes = new Scope[16];
ohair@286: /**
ohair@286: * Points to the top of the scope stack (=size-1).
ohair@286: */
ohair@286: private int scopeTop=0;
ohair@286:
ohair@286: {
ohair@286: for( int i=0; i
ohair@286: * This method allocates a specified number of fresh {@link Scope} objects.
ohair@286: * They can be accessed by the {@link #getScope} method until the corresponding
ohair@286: * {@link #endScope} method is invoked.
ohair@286: *
ohair@286: *
ohair@286: * A new scope will mask the currently active scope. Only one frame of {@link Scope}s
ohair@286: * can be accessed at any given time.
ohair@286: *
ohair@286: * @param frameSize
ohair@286: * The # of slots to be allocated.
ohair@286: */
ohair@286: public void startScope(int frameSize) {
ohair@286: scopeTop += frameSize;
ohair@286:
ohair@286: // reallocation
ohair@286: if(scopeTop>=scopes.length) {
ohair@286: Scope[] s = new Scope[Math.max(scopeTop+1,scopes.length*2)];
ohair@286: System.arraycopy(scopes,0,s,0,scopes.length);
ohair@286: for( int i=scopes.length; i
ohair@286: * If any packing in progress will be finalized by this method.
ohair@286: *
ohair@286: * @param frameSize
ohair@286: * The same size that gets passed to the {@link #startScope(int)}
ohair@286: * method.
ohair@286: */
ohair@286: public void endScope(int frameSize) throws SAXException {
ohair@286: try {
ohair@286: for( ; frameSize>0; frameSize--, scopeTop-- )
ohair@286: scopes[scopeTop].finish();
ohair@286: } catch (AccessorException e) {
ohair@286: handleError(e);
ohair@286:
ohair@286: // the error might have left scopes in inconsistent state,
ohair@286: // so replace them by fresh ones
ohair@286: for( ; frameSize>0; frameSize-- )
ohair@286: scopes[scopeTop--] = new Scope(this);
ohair@286: }
ohair@286: }
ohair@286:
ohair@286: /**
ohair@286: * Gets the currently active {@link Scope}.
ohair@286: *
ohair@286: * @param offset
ohair@286: * a number between [0,frameSize)
ohair@286: *
ohair@286: * @return
ohair@286: * always a valid {@link Scope} object.
ohair@286: */
ohair@286: public Scope getScope(int offset) {
ohair@286: return scopes[scopeTop-offset];
ohair@286: }
ohair@286:
ohair@286: //
ohair@286: //
ohair@286: //
ohair@286: //
ohair@286: //
ohair@286: //
ohair@286: //
ohair@286:
ohair@286: private static final Loader DEFAULT_ROOT_LOADER = new DefaultRootLoader();
ohair@286: private static final Loader EXPECTED_TYPE_ROOT_LOADER = new ExpectedTypeRootLoader();
ohair@286:
ohair@286: /**
ohair@286: * Root loader that uses the tag name and possibly its @xsi:type
ohair@286: * to decide how to start unmarshalling.
ohair@286: */
ohair@286: private static final class DefaultRootLoader extends Loader implements Receiver {
ohair@286: /**
ohair@286: * Receives the root element and determines how to start
ohair@286: * unmarshalling.
ohair@286: */
ohair@286: @Override
ohair@286: public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
ohair@286: Loader loader = state.getContext().selectRootLoader(state,ea);
ohair@286: if(loader!=null) {
ohair@286: state.loader = loader;
ohair@286: state.receiver = this;
ohair@286: return;
ohair@286: }
ohair@286:
ohair@286: // the registry doesn't know about this element.
ohair@286: // try its xsi:type
ohair@286: JaxBeanInfo beanInfo = XsiTypeLoader.parseXsiType(state, ea, null);
ohair@286: if(beanInfo==null) {
ohair@286: // we don't even know its xsi:type
ohair@286: reportUnexpectedChildElement(ea,false);
ohair@286: return;
ohair@286: }
ohair@286:
ohair@286: state.loader = beanInfo.getLoader(null,false);
ohair@286: state.prev.backup = new JAXBElement