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

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

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

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

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

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

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

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

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

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

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