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(ea.createQName(),Object.class,null); ohair@286: state.receiver = this; ohair@286: } ohair@286: ohair@286: @Override ohair@286: public Collection getExpectedChildElements() { ohair@286: return getInstance().getJAXBContext().getValidRootNames(); ohair@286: } ohair@286: mkos@397: @Override ohair@286: public void receive(State state, Object o) { ohair@286: if(state.backup!=null) { ohair@286: ((JAXBElement)state.backup).setValue(o); ohair@286: o = state.backup; ohair@286: } ohair@286: if (state.nil) { ohair@286: ((JAXBElement)o).setNil(true); ohair@286: } ohair@286: state.getContext().result = o; ohair@286: } ohair@286: } ohair@286: ohair@286: /** ohair@286: * Root loader that uses {@link UnmarshallingContext#expectedType} ohair@286: * to decide how to start unmarshalling. ohair@286: */ ohair@286: private static final class ExpectedTypeRootLoader 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) { ohair@286: UnmarshallingContext context = state.getContext(); ohair@286: ohair@286: // unmarshals the specified type ohair@286: QName qn = new QName(ea.uri,ea.local); ohair@286: state.prev.target = new JAXBElement(qn,context.expectedType.jaxbType,null,null); ohair@286: state.receiver = this; ohair@286: // this is bit wasteful, as in theory we should have each expectedType keep ohair@286: // nillable version --- but that increases the combination from two to four, ohair@286: // which adds the resident memory footprint. Since XsiNilLoader is small, ohair@286: // I intentionally allocate a new instance freshly. ohair@286: state.loader = new XsiNilLoader(context.expectedType.getLoader(null,true)); ohair@286: } ohair@286: mkos@397: @Override ohair@286: public void receive(State state, Object o) { ohair@286: JAXBElement e = (JAXBElement)state.target; ohair@286: e.setValue(o); ohair@286: state.getContext().recordOuterPeer(e); ohair@286: state.getContext().result = e; ohair@286: } ohair@286: } ohair@286: ohair@286: // ohair@286: // in-place unmarshalling related capabilities ohair@286: // ohair@286: /** ohair@286: * Notifies the context about the inner peer of the current element. ohair@286: * ohair@286: *

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

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