Sun, 31 Aug 2014 16:14:36 +0400
8036981: JAXB not preserving formatting for xsd:any Mixed content
Reviewed-by: lancea, mkos
1 /*
2 * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
26 package com.sun.xml.internal.bind.v2.runtime.unmarshaller;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.Collections;
33 import java.util.HashMap;
34 import java.util.Iterator;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.concurrent.Callable;
39 import javax.xml.XMLConstants;
40 import javax.xml.bind.JAXBElement;
41 import javax.xml.bind.UnmarshalException;
42 import javax.xml.bind.Unmarshaller;
43 import javax.xml.bind.ValidationEvent;
44 import javax.xml.bind.ValidationEventHandler;
45 import javax.xml.bind.ValidationEventLocator;
46 import javax.xml.bind.helpers.ValidationEventImpl;
47 import javax.xml.namespace.NamespaceContext;
48 import javax.xml.namespace.QName;
50 import com.sun.istack.internal.NotNull;
51 import com.sun.istack.internal.Nullable;
52 import com.sun.istack.internal.SAXParseException2;
53 import com.sun.xml.internal.bind.IDResolver;
54 import com.sun.xml.internal.bind.Util;
55 import com.sun.xml.internal.bind.api.AccessorException;
56 import com.sun.xml.internal.bind.api.ClassResolver;
57 import com.sun.xml.internal.bind.unmarshaller.InfosetScanner;
58 import com.sun.xml.internal.bind.v2.ClassFactory;
59 import com.sun.xml.internal.bind.v2.runtime.AssociationMap;
60 import com.sun.xml.internal.bind.v2.runtime.Coordinator;
61 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
62 import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
63 import java.util.logging.Level;
64 import java.util.logging.Logger;
66 import org.xml.sax.ErrorHandler;
67 import org.xml.sax.SAXException;
68 import org.xml.sax.helpers.LocatorImpl;
70 /**
71 * Center of the unmarshalling.
72 *
73 * <p>
74 * This object is responsible for coordinating {@link Loader}s to
75 * perform the whole unmarshalling.
76 *
77 * @author Kohsuke Kawaguchi
78 */
79 public final class UnmarshallingContext extends Coordinator
80 implements NamespaceContext, ValidationEventHandler, ErrorHandler, XmlVisitor, XmlVisitor.TextPredictor {
82 private static final Logger logger = Logger.getLogger(UnmarshallingContext.class.getName());
84 /**
85 * Root state.
86 */
87 private final State root;
89 /**
90 * The currently active state.
91 */
92 private State current;
94 private static final LocatorEx DUMMY_INSTANCE;
96 static {
97 LocatorImpl loc = new LocatorImpl();
98 loc.setPublicId(null);
99 loc.setSystemId(null);
100 loc.setLineNumber(-1);
101 loc.setColumnNumber(-1);
102 DUMMY_INSTANCE = new LocatorExWrapper(loc);
103 }
105 private @NotNull LocatorEx locator = DUMMY_INSTANCE;
107 /** Root object that is being unmarshalled. */
108 private Object result;
110 /**
111 * If non-null, this unmarshaller will unmarshal {@code JAXBElement<EXPECTEDTYPE>}
112 * regardless of the tag name, as opposed to deciding the root object by using
113 * the tag name.
114 *
115 * The property has a package-level access, because we cannot copy this value
116 * to {@link UnmarshallingContext} when it is created. The property
117 * on {@link Unmarshaller} could be changed after the handler is created.
118 */
119 private JaxBeanInfo expectedType;
121 /**
122 * Handles ID/IDREF.
123 */
124 private IDResolver idResolver;
126 /**
127 * This flag is set to true at the startDocument event
128 * and false at the endDocument event.
129 *
130 * Until the first document is unmarshalled, we don't
131 * want to return an object. So this variable is initialized
132 * to true.
133 */
134 private boolean isUnmarshalInProgress = true;
135 private boolean aborted = false;
137 public final UnmarshallerImpl parent;
139 /**
140 * If the unmarshaller is doing associative unmarshalling,
141 * this field is initialized to non-null.
142 */
143 private final AssociationMap assoc;
145 /**
146 * Indicates whether we are doing in-place unmarshalling
147 * or not.
148 *
149 * <p>
150 * This flag is unused when {@link #assoc}==null.
151 * If it's non-null, then <tt>true</tt> indicates
152 * that we are doing in-place associative unmarshalling.
153 * If <tt>false</tt>, then we are doing associative unmarshalling
154 * without object reuse.
155 */
156 private boolean isInplaceMode;
158 /**
159 * This object is consulted to get the element object for
160 * the current element event.
161 *
162 * This is used when we are building an association map.
163 */
164 private InfosetScanner scanner;
166 private Object currentElement;
168 /**
169 * @see XmlVisitor#startDocument(LocatorEx, NamespaceContext)
170 */
171 private NamespaceContext environmentNamespaceContext;
173 /**
174 * Used to discover additional classes when we hit unknown elements/types.
175 */
176 public @Nullable ClassResolver classResolver;
178 /**
179 * User-supplied {@link ClassLoader} for converting name to {@link Class}.
180 * For backward compatibility, when null, use thread context classloader.
181 */
182 public @Nullable ClassLoader classLoader;
184 /**
185 * The variable introduced to avoid reporting n^10 similar errors.
186 * After error is reported counter is decremented. When it became 0 - errors should not be reported any more.
187 *
188 * volatile is required to ensure that concurrent threads will see changed value
189 */
190 private static volatile int errorsCounter = 10;
192 /**
193 * State information for each element.
194 */
195 public final class State {
196 /**
197 * Loader that owns this element.
198 */
199 private Loader loader;
200 /**
201 * Once {@link #loader} is completed, this receiver
202 * receives the result.
203 */
204 private Receiver receiver;
206 private Intercepter intercepter;
208 /**
209 * Object being unmarshalled by this {@link #loader}.
210 */
211 private Object target;
213 /**
214 * Hack for making JAXBElement unmarshalling work.
215 *
216 * <p>
217 * While the unmarshalling is in progress, the {@link #target} field stores the object being unmarshalled.
218 * This makes it convenient to keep track of the unmarshalling activity in context of XML infoset, but
219 * since there's only one {@link State} per element, this mechanism only works when there's one object
220 * per element, which breaks down when we have {@link JAXBElement}, since the presence of JAXBElement
221 * requires that we have two objects unmarshalled (a JAXBElement X and a value object Y bound to an XML type.)
222 *
223 * <p>
224 * So to make room for storing both, this {@link #backup} field is used. When we create X instance
225 * in the above example, we set that to {@code state.prev.target} and displace its old value to
226 * {@code state.prev.backup} (where Y goes to {@code state.target}.) Upon the completion of the unmarshalling
227 * of Y, we revert this.
228 *
229 * <p>
230 * While this attributes X incorrectly to its parent element, this preserves the parent/child
231 * relationship between unmarshalled objects and {@link State} parent/child relationship, and
232 * it thereby makes {@link Receiver} mechanism simpler.
233 *
234 * <p>
235 * Yes, I know this is a hack, and no, I'm not proud of it.
236 *
237 * @see ElementBeanInfoImpl.IntercepterLoader#startElement(State, TagName)
238 * @see ElementBeanInfoImpl.IntercepterLoader#intercept(State, Object)
239 */
240 private Object backup;
242 /**
243 * Number of {@link UnmarshallingContext#nsBind}s declared thus far.
244 * (The value of {@link UnmarshallingContext#nsLen} when this state is pushed.
245 */
246 private int numNsDecl;
248 /**
249 * If this element has an element default value.
250 *
251 * This should be set by either a parent {@link Loader} when
252 * {@link Loader#childElement(State, TagName)} is called
253 * or by a child {@link Loader} when
254 * {@link Loader#startElement(State, TagName)} is called.
255 */
256 private String elementDefaultValue;
258 /**
259 * {@link State} for the parent element
260 *
261 * {@link State} objects form a doubly linked list.
262 */
263 private State prev;
264 private State next;
266 private boolean nil = false;
268 /**
269 * specifies that we are working with mixed content
270 */
271 private boolean mixed = false;
273 /**
274 * Gets the context.
275 */
276 public UnmarshallingContext getContext() {
277 return UnmarshallingContext.this;
278 }
280 @SuppressWarnings("LeakingThisInConstructor")
281 private State(State prev) {
282 this.prev = prev;
283 if (prev!=null) {
284 prev.next = this;
285 if (prev.mixed) // parent is in mixed mode
286 this.mixed = true;
287 }
288 }
290 private void push() {
291 if (logger.isLoggable(Level.FINEST)) {
292 logger.log(Level.FINEST, "State.push");
293 }
294 if (next==null) {
295 assert current == this;
296 next = new State(this);
297 }
298 nil = false;
299 State n = next;
300 n.numNsDecl = nsLen;
301 current = n;
302 }
304 private void pop() {
305 if (logger.isLoggable(Level.FINEST)) {
306 logger.log(Level.FINEST, "State.pop");
307 }
308 assert prev!=null;
309 loader = null;
310 nil = false;
311 mixed = false;
312 receiver = null;
313 intercepter = null;
314 elementDefaultValue = null;
315 target = null;
316 current = prev;
317 next = null;
318 }
320 public boolean isMixed() {
321 return mixed;
322 }
324 public Object getTarget() {
325 return target;
326 }
328 public void setLoader(Loader loader) {
329 if (loader instanceof StructureLoader) // set mixed mode
330 mixed = !((StructureLoader)loader).getBeanInfo().hasElementOnlyContentModel();
331 this.loader = loader;
332 }
334 public void setReceiver(Receiver receiver) {
335 this.receiver = receiver;
336 }
338 public State getPrev() {
339 return prev;
340 }
342 public void setIntercepter(Intercepter intercepter) {
343 this.intercepter = intercepter;
344 }
346 public void setBackup(Object backup) {
347 this.backup = backup;
348 }
350 public void setTarget(Object target) {
351 this.target = target;
352 }
354 public Object getBackup() {
355 return backup;
356 }
358 public boolean isNil() {
359 return nil;
360 }
362 public void setNil(boolean nil) {
363 this.nil = nil;
364 }
366 public Loader getLoader() {
367 return loader;
368 }
370 public String getElementDefaultValue() {
371 return elementDefaultValue;
372 }
374 public void setElementDefaultValue(String elementDefaultValue) {
375 this.elementDefaultValue = elementDefaultValue;
376 }
377 }
379 /**
380 * Stub to the user-specified factory method.
381 */
382 private static class Factory {
383 private final Object factorInstance;
384 private final Method method;
386 public Factory(Object factorInstance, Method method) {
387 this.factorInstance = factorInstance;
388 this.method = method;
389 }
391 public Object createInstance() throws SAXException {
392 try {
393 return method.invoke(factorInstance);
394 } catch (IllegalAccessException e) {
395 getInstance().handleError(e,false);
396 } catch (InvocationTargetException e) {
397 getInstance().handleError(e,false);
398 }
399 return null; // can never be executed
400 }
401 }
404 /**
405 * Creates a new unmarshaller.
406 *
407 * @param assoc
408 * Must be both non-null when the unmarshaller does the
409 * in-place unmarshalling. Otherwise must be both null.
410 */
411 public UnmarshallingContext( UnmarshallerImpl _parent, AssociationMap assoc) {
412 this.parent = _parent;
413 this.assoc = assoc;
414 this.root = this.current = new State(null);
415 }
417 public void reset(InfosetScanner scanner,boolean isInplaceMode, JaxBeanInfo expectedType, IDResolver idResolver) {
418 this.scanner = scanner;
419 this.isInplaceMode = isInplaceMode;
420 this.expectedType = expectedType;
421 this.idResolver = idResolver;
422 }
424 public JAXBContextImpl getJAXBContext() {
425 return parent.context;
426 }
428 public State getCurrentState() {
429 return current;
430 }
432 /**
433 * On top of {@link JAXBContextImpl#selectRootLoader(State, TagName)},
434 * this method also consults {@link ClassResolver}.
435 *
436 * @throws SAXException
437 * if {@link ValidationEventHandler} reported a failure.
438 */
439 public Loader selectRootLoader(State state, TagName tag) throws SAXException {
440 try {
441 Loader l = getJAXBContext().selectRootLoader(state, tag);
442 if(l!=null) return l;
444 if(classResolver!=null) {
445 Class<?> clazz = classResolver.resolveElementName(tag.uri, tag.local);
446 if(clazz!=null) {
447 JAXBContextImpl enhanced = getJAXBContext().createAugmented(clazz);
448 JaxBeanInfo<?> bi = enhanced.getBeanInfo(clazz);
449 return bi.getLoader(enhanced,true);
450 }
451 }
452 } catch (RuntimeException e) {
453 throw e;
454 } catch (Exception e) {
455 handleError(e);
456 }
458 return null;
459 }
461 public void clearStates() {
462 State last = current;
463 while (last.next != null) last = last.next;
464 while (last.prev != null) {
465 last.loader = null;
466 last.nil = false;
467 last.receiver = null;
468 last.intercepter = null;
469 last.elementDefaultValue = null;
470 last.target = null;
471 last = last.prev;
472 last.next.prev = null;
473 last.next = null;
474 }
475 current = last;
476 }
478 /**
479 * User-specified factory methods.
480 */
481 private final Map<Class,Factory> factories = new HashMap<Class, Factory>();
483 public void setFactories(Object factoryInstances) {
484 factories.clear();
485 if(factoryInstances==null) {
486 return;
487 }
488 if(factoryInstances instanceof Object[]) {
489 for( Object factory : (Object[])factoryInstances ) {
490 // look for all the public methods inlcuding derived ones
491 addFactory(factory);
492 }
493 } else {
494 addFactory(factoryInstances);
495 }
496 }
498 private void addFactory(Object factory) {
499 for( Method m : factory.getClass().getMethods() ) {
500 // look for methods whose signature is T createXXX()
501 if(!m.getName().startsWith("create"))
502 continue;
503 if(m.getParameterTypes().length>0)
504 continue;
506 Class type = m.getReturnType();
508 factories.put(type,new Factory(factory,m));
509 }
510 }
512 @Override
513 public void startDocument(LocatorEx locator, NamespaceContext nsContext) throws SAXException {
514 if(locator!=null)
515 this.locator = locator;
516 this.environmentNamespaceContext = nsContext;
517 // reset the object
518 result = null;
519 current = root;
521 patchersLen=0;
522 aborted = false;
523 isUnmarshalInProgress = true;
524 nsLen=0;
526 if(expectedType!=null)
527 root.loader = EXPECTED_TYPE_ROOT_LOADER;
528 else
529 root.loader = DEFAULT_ROOT_LOADER;
531 idResolver.startDocument(this);
532 }
534 @Override
535 public void startElement(TagName tagName) throws SAXException {
536 pushCoordinator();
537 try {
538 _startElement(tagName);
539 } finally {
540 popCoordinator();
541 }
542 }
544 private void _startElement(TagName tagName) throws SAXException {
545 // remember the current element if we are interested in it.
546 // because the inner peer might not be found while we consume
547 // the enter element token, we need to keep this information
548 // longer than this callback. That's why we assign it to a field.
549 if( assoc!=null )
550 currentElement = scanner.getCurrentElement();
552 Loader h = current.loader;
553 current.push();
555 // tell the parent about the new child
556 h.childElement(current,tagName);
557 assert current.loader!=null; // the childElement should register this
558 // and tell the new child that you are activated
559 current.loader.startElement(current,tagName);
560 }
562 @Override
563 public void text(CharSequence pcdata) throws SAXException {
564 pushCoordinator();
565 try {
566 if (current.elementDefaultValue != null) {
567 if (pcdata.length() == 0) {
568 // send the default value into the unmarshaller instead
569 pcdata = current.elementDefaultValue;
570 }
571 }
572 current.loader.text(current, pcdata);
573 } finally {
574 popCoordinator();
575 }
576 }
578 @Override
579 public final void endElement(TagName tagName) throws SAXException {
580 pushCoordinator();
581 try {
582 State child = current;
584 // tell the child that your time is up
585 child.loader.leaveElement(child,tagName);
587 // child.pop will erase them so store them now
588 Object target = child.target;
589 Receiver recv = child.receiver;
590 Intercepter intercepter = child.intercepter;
591 child.pop();
593 // then let the parent know
594 if(intercepter!=null)
595 target = intercepter.intercept(current,target);
596 if(recv!=null)
597 recv.receive(current,target);
598 } finally {
599 popCoordinator();
600 }
601 }
603 @Override
604 public void endDocument() throws SAXException {
605 runPatchers();
606 idResolver.endDocument();
608 isUnmarshalInProgress = false;
609 currentElement = null;
610 locator = DUMMY_INSTANCE;
611 environmentNamespaceContext = null;
613 // at the successful completion, scope must be all closed
614 assert root==current;
615 }
617 /**
618 * You should be always calling this through {@link TextPredictor}.
619 */
620 @Deprecated
621 @Override
622 public boolean expectText() {
623 return current.loader.expectText;
624 }
626 /**
627 * You should be always getting {@link TextPredictor} from {@link XmlVisitor}.
628 */
629 @Deprecated
630 @Override
631 public TextPredictor getPredictor() {
632 return this;
633 }
635 @Override
636 public UnmarshallingContext getContext() {
637 return this;
638 }
640 /**
641 * Gets the result of the unmarshalling
642 */
643 public Object getResult() throws UnmarshalException {
644 if(isUnmarshalInProgress)
645 throw new IllegalStateException();
647 if(!aborted) return result;
649 // there was an error.
650 throw new UnmarshalException((String)null);
651 }
653 void clearResult() {
654 if (isUnmarshalInProgress) {
655 throw new IllegalStateException();
656 }
657 result = null;
658 }
660 /**
661 * Creates a new instance of the specified class.
662 * In the unmarshaller, we need to check the user-specified factory class.
663 */
664 public Object createInstance( Class<?> clazz ) throws SAXException {
665 if(!factories.isEmpty()) {
666 Factory factory = factories.get(clazz);
667 if(factory!=null)
668 return factory.createInstance();
669 }
670 return ClassFactory.create(clazz);
671 }
673 /**
674 * Creates a new instance of the specified class.
675 * In the unmarshaller, we need to check the user-specified factory class.
676 */
677 public Object createInstance( JaxBeanInfo beanInfo ) throws SAXException {
678 if(!factories.isEmpty()) {
679 Factory factory = factories.get(beanInfo.jaxbType);
680 if(factory!=null)
681 return factory.createInstance();
682 }
683 try {
684 return beanInfo.createInstance(this);
685 } catch (IllegalAccessException e) {
686 Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false);
687 } catch (InvocationTargetException e) {
688 Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false);
689 } catch (InstantiationException e) {
690 Loader.reportError("Unable to create an instance of "+beanInfo.jaxbType.getName(),e,false);
691 }
692 return null; // can never be here
693 }
697 //
698 //
699 // error handling
700 //
701 //
703 /**
704 * Reports an error to the user, and asks if s/he wants
705 * to recover. If the canRecover flag is false, regardless
706 * of the client instruction, an exception will be thrown.
707 *
708 * Only if the flag is true and the user wants to recover from an error,
709 * the method returns normally.
710 *
711 * The thrown exception will be catched by the unmarshaller.
712 */
713 public void handleEvent(ValidationEvent event, boolean canRecover ) throws SAXException {
714 ValidationEventHandler eventHandler = parent.getEventHandler();
716 boolean recover = eventHandler.handleEvent(event);
718 // if the handler says "abort", we will not return the object
719 // from the unmarshaller.getResult()
720 if(!recover) aborted = true;
722 if( !canRecover || !recover )
723 throw new SAXParseException2( event.getMessage(), locator,
724 new UnmarshalException(
725 event.getMessage(),
726 event.getLinkedException() ) );
727 }
729 @Override
730 public boolean handleEvent(ValidationEvent event) {
731 try {
732 // if the handler says "abort", we will not return the object.
733 boolean recover = parent.getEventHandler().handleEvent(event);
734 if(!recover) aborted = true;
735 return recover;
736 } catch( RuntimeException re ) {
737 // if client event handler causes a runtime exception, then we
738 // have to return false.
739 return false;
740 }
741 }
743 /**
744 * Reports an exception found during the unmarshalling to the user.
745 * This method is a convenience method that calls into
746 * {@link #handleEvent(ValidationEvent, boolean)}
747 */
748 public void handleError(Exception e) throws SAXException {
749 handleError(e,true);
750 }
752 public void handleError(Exception e,boolean canRecover) throws SAXException {
753 handleEvent(new ValidationEventImpl(ValidationEvent.ERROR,e.getMessage(),locator.getLocation(),e),canRecover);
754 }
756 public void handleError(String msg) {
757 handleEvent(new ValidationEventImpl(ValidationEvent.ERROR,msg,locator.getLocation()));
758 }
760 @Override
761 protected ValidationEventLocator getLocation() {
762 return locator.getLocation();
763 }
765 /**
766 * Gets the current source location information in SAX {@link Locator}.
767 * <p>
768 * Sometimes the unmarshaller works against a different kind of XML source,
769 * making this information meaningless.
770 */
771 public LocatorEx getLocator() { return locator; }
773 /**
774 * Called when there's no corresponding ID value.
775 */
776 public void errorUnresolvedIDREF(Object bean, String idref, LocatorEx loc) throws SAXException {
777 handleEvent( new ValidationEventImpl(
778 ValidationEvent.ERROR,
779 Messages.UNRESOLVED_IDREF.format(idref),
780 loc.getLocation()), true );
781 }
784 //
785 //
786 // ID/IDREF related code
787 //
788 //
789 /**
790 * Submitted patchers in the order they've submitted.
791 * Many XML vocabulary doesn't use ID/IDREF at all, so we
792 * initialize it with null.
793 */
794 private Patcher[] patchers = null;
795 private int patchersLen = 0;
797 /**
798 * Adds a job that will be executed at the last of the unmarshalling.
799 * This method is used to support ID/IDREF feature, but it can be used
800 * for other purposes as well.
801 *
802 * @param job
803 * The run method of this object is called.
804 */
805 public void addPatcher( Patcher job ) {
806 // re-allocate buffer if necessary
807 if( patchers==null )
808 patchers = new Patcher[32];
809 if( patchers.length == patchersLen ) {
810 Patcher[] buf = new Patcher[patchersLen*2];
811 System.arraycopy(patchers,0,buf,0,patchersLen);
812 patchers = buf;
813 }
814 patchers[patchersLen++] = job;
815 }
817 /** Executes all the patchers. */
818 private void runPatchers() throws SAXException {
819 if( patchers!=null ) {
820 for( int i=0; i<patchersLen; i++ ) {
821 patchers[i].run();
822 patchers[i] = null; // free memory
823 }
824 }
825 }
827 /**
828 * Adds the object which is currently being unmarshalled
829 * to the ID table.
830 *
831 * @return
832 * Returns the value passed as the parameter.
833 * This is a hack, but this makes it easier for ID
834 * transducer to do its job.
835 */
836 // TODO: what shall we do if the ID is already declared?
837 //
838 // throwing an exception is one way. Overwriting the previous one
839 // is another way. The latter allows us to process invalid documents,
840 // while the former makes it impossible to handle them.
841 //
842 // I prefer to be flexible in terms of invalid document handling,
843 // so chose not to throw an exception.
844 //
845 // I believe this is an implementation choice, not the spec issue.
846 // -kk
847 public String addToIdTable( String id ) throws SAXException {
848 // Hmm...
849 // in cases such as when ID is used as an attribute, or as @XmlValue
850 // the target wilil be current.target.
851 // but in some other cases, such as when ID is used as a child element
852 // or a value of JAXBElement, it's current.prev.target.
853 // I don't know if this detection logic is complete
854 Object o = current.target;
855 if(o==null)
856 o = current.prev.target;
857 idResolver.bind(id,o);
858 return id;
859 }
861 /**
862 * Looks up the ID table and gets associated object.
863 *
864 * <p>
865 * The exception thrown from {@link Callable#call()} means the unmarshaller should abort
866 * right away.
867 *
868 * @see IDResolver#resolve(String, Class)
869 */
870 public Callable getObjectFromId( String id, Class targetType ) throws SAXException {
871 return idResolver.resolve(id,targetType);
872 }
874 //
875 //
876 // namespace binding maintainance
877 //
878 //
879 private String[] nsBind = new String[16];
880 private int nsLen=0;
882 @Override
883 public void startPrefixMapping( String prefix, String uri ) {
884 if(nsBind.length==nsLen) {
885 // expand the buffer
886 String[] n = new String[nsLen*2];
887 System.arraycopy(nsBind,0,n,0,nsLen);
888 nsBind=n;
889 }
890 nsBind[nsLen++] = prefix;
891 nsBind[nsLen++] = uri;
892 }
893 @Override
894 public void endPrefixMapping( String prefix ) {
895 nsLen-=2;
896 }
897 private String resolveNamespacePrefix( String prefix ) {
898 if(prefix.equals("xml"))
899 return XMLConstants.XML_NS_URI;
901 for( int i=nsLen-2; i>=0; i-=2 ) {
902 if(prefix.equals(nsBind[i]))
903 return nsBind[i+1];
904 }
906 if(environmentNamespaceContext!=null)
907 // temporary workaround until Zephyr fixes 6337180
908 return environmentNamespaceContext.getNamespaceURI(prefix.intern());
910 // by default, the default ns is bound to "".
911 // but allow environmentNamespaceContext to take precedence
912 if(prefix.equals(""))
913 return "";
915 // unresolved. error.
916 return null;
917 }
919 /**
920 * Returns a list of prefixes newly declared on the current element.
921 *
922 * @return
923 * A possible zero-length array of prefixes. The default prefix
924 * is represented by the empty string.
925 */
926 public String[] getNewlyDeclaredPrefixes() {
927 return getPrefixList( current.prev.numNsDecl );
928 }
930 /**
931 * Returns a list of all in-scope prefixes.
932 *
933 * @return
934 * A possible zero-length array of prefixes. The default prefix
935 * is represented by the empty string.
936 */
937 public String[] getAllDeclaredPrefixes() {
938 return getPrefixList(0);
939 }
941 private String[] getPrefixList( int startIndex ) {
942 int size = (current.numNsDecl - startIndex)/2;
943 String[] r = new String[size];
944 for( int i=0; i<r.length; i++ )
945 r[i] = nsBind[startIndex+i*2];
946 return r;
947 }
949 // NamespaceContext2 implementation
950 //
951 @Override
952 public Iterator<String> getPrefixes(String uri) {
953 // TODO: could be implemented much faster
954 // wrap it into unmodifiable list so that the remove method
955 // will throw UnsupportedOperationException.
956 return Collections.unmodifiableList(
957 getAllPrefixesInList(uri)).iterator();
958 }
960 private List<String> getAllPrefixesInList(String uri) {
961 List<String> a = new ArrayList<String>();
963 if( uri==null )
964 throw new IllegalArgumentException();
965 if( uri.equals(XMLConstants.XML_NS_URI) ) {
966 a.add(XMLConstants.XML_NS_PREFIX);
967 return a;
968 }
969 if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) ) {
970 a.add(XMLConstants.XMLNS_ATTRIBUTE);
971 return a;
972 }
974 for( int i=nsLen-2; i>=0; i-=2 )
975 if(uri.equals(nsBind[i+1]))
976 if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) )
977 // make sure that this prefix is still effective.
978 a.add(nsBind[i]);
980 return a;
981 }
983 @Override
984 public String getPrefix(String uri) {
985 if( uri==null )
986 throw new IllegalArgumentException();
987 if( uri.equals(XMLConstants.XML_NS_URI) )
988 return XMLConstants.XML_NS_PREFIX;
989 if( uri.equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI) )
990 return XMLConstants.XMLNS_ATTRIBUTE;
992 for( int i=nsLen-2; i>=0; i-=2 )
993 if(uri.equals(nsBind[i+1]))
994 if( getNamespaceURI(nsBind[i]).equals(nsBind[i+1]) )
995 // make sure that this prefix is still effective.
996 return nsBind[i];
998 if(environmentNamespaceContext!=null)
999 return environmentNamespaceContext.getPrefix(uri);
1001 return null;
1002 }
1004 @Override
1005 public String getNamespaceURI(String prefix) {
1006 if (prefix == null)
1007 throw new IllegalArgumentException();
1008 if (prefix.equals(XMLConstants.XMLNS_ATTRIBUTE))
1009 return XMLConstants.XMLNS_ATTRIBUTE_NS_URI;
1011 return resolveNamespacePrefix(prefix);
1012 }
1014 //
1015 //
1016 //
1017 // scope management
1018 //
1019 //
1020 //
1021 private Scope[] scopes = new Scope[16];
1022 /**
1023 * Points to the top of the scope stack (=size-1).
1024 */
1025 private int scopeTop=0;
1027 {
1028 for( int i=0; i<scopes.length; i++ )
1029 scopes[i] = new Scope(this);
1030 }
1032 /**
1033 * Starts a new packing scope.
1034 *
1035 * <p>
1036 * This method allocates a specified number of fresh {@link Scope} objects.
1037 * They can be accessed by the {@link #getScope} method until the corresponding
1038 * {@link #endScope} method is invoked.
1039 *
1040 * <p>
1041 * A new scope will mask the currently active scope. Only one frame of {@link Scope}s
1042 * can be accessed at any given time.
1043 *
1044 * @param frameSize
1045 * The # of slots to be allocated.
1046 */
1047 public void startScope(int frameSize) {
1048 scopeTop += frameSize;
1050 // reallocation
1051 if(scopeTop>=scopes.length) {
1052 Scope[] s = new Scope[Math.max(scopeTop+1,scopes.length*2)];
1053 System.arraycopy(scopes,0,s,0,scopes.length);
1054 for( int i=scopes.length; i<s.length; i++ )
1055 s[i] = new Scope(this);
1056 scopes = s;
1057 }
1058 }
1060 /**
1061 * Ends the current packing scope.
1062 *
1063 * <p>
1064 * If any packing in progress will be finalized by this method.
1065 *
1066 * @param frameSize
1067 * The same size that gets passed to the {@link #startScope(int)}
1068 * method.
1069 */
1070 public void endScope(int frameSize) throws SAXException {
1071 try {
1072 for( ; frameSize>0; frameSize--, scopeTop-- )
1073 scopes[scopeTop].finish();
1074 } catch (AccessorException e) {
1075 handleError(e);
1077 // the error might have left scopes in inconsistent state,
1078 // so replace them by fresh ones
1079 for( ; frameSize>0; frameSize-- )
1080 scopes[scopeTop--] = new Scope(this);
1081 }
1082 }
1084 /**
1085 * Gets the currently active {@link Scope}.
1086 *
1087 * @param offset
1088 * a number between [0,frameSize)
1089 *
1090 * @return
1091 * always a valid {@link Scope} object.
1092 */
1093 public Scope getScope(int offset) {
1094 return scopes[scopeTop-offset];
1095 }
1097 //
1098 //
1099 //
1100 //
1101 //
1102 //
1103 //
1105 private static final Loader DEFAULT_ROOT_LOADER = new DefaultRootLoader();
1106 private static final Loader EXPECTED_TYPE_ROOT_LOADER = new ExpectedTypeRootLoader();
1108 /**
1109 * Root loader that uses the tag name and possibly its @xsi:type
1110 * to decide how to start unmarshalling.
1111 */
1112 private static final class DefaultRootLoader extends Loader implements Receiver {
1113 /**
1114 * Receives the root element and determines how to start
1115 * unmarshalling.
1116 */
1117 @Override
1118 public void childElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
1119 Loader loader = state.getContext().selectRootLoader(state,ea);
1120 if(loader!=null) {
1121 state.loader = loader;
1122 state.receiver = this;
1123 return;
1124 }
1126 // the registry doesn't know about this element.
1127 // try its xsi:type
1128 JaxBeanInfo beanInfo = XsiTypeLoader.parseXsiType(state, ea, null);
1129 if(beanInfo==null) {
1130 // we don't even know its xsi:type
1131 reportUnexpectedChildElement(ea,false);
1132 return;
1133 }
1135 state.loader = beanInfo.getLoader(null,false);
1136 state.prev.backup = new JAXBElement<Object>(ea.createQName(),Object.class,null);
1137 state.receiver = this;
1138 }
1140 @Override
1141 public Collection<QName> getExpectedChildElements() {
1142 return getInstance().getJAXBContext().getValidRootNames();
1143 }
1145 @Override
1146 public void receive(State state, Object o) {
1147 if(state.backup!=null) {
1148 ((JAXBElement<Object>)state.backup).setValue(o);
1149 o = state.backup;
1150 }
1151 if (state.nil) {
1152 ((JAXBElement<Object>)o).setNil(true);
1153 }
1154 state.getContext().result = o;
1155 }
1156 }
1158 /**
1159 * Root loader that uses {@link UnmarshallingContext#expectedType}
1160 * to decide how to start unmarshalling.
1161 */
1162 private static final class ExpectedTypeRootLoader extends Loader implements Receiver {
1163 /**
1164 * Receives the root element and determines how to start
1165 * unmarshalling.
1166 */
1167 @Override
1168 public void childElement(UnmarshallingContext.State state, TagName ea) {
1169 UnmarshallingContext context = state.getContext();
1171 // unmarshals the specified type
1172 QName qn = new QName(ea.uri,ea.local);
1173 state.prev.target = new JAXBElement(qn,context.expectedType.jaxbType,null,null);
1174 state.receiver = this;
1175 // this is bit wasteful, as in theory we should have each expectedType keep
1176 // nillable version --- but that increases the combination from two to four,
1177 // which adds the resident memory footprint. Since XsiNilLoader is small,
1178 // I intentionally allocate a new instance freshly.
1179 state.loader = new XsiNilLoader(context.expectedType.getLoader(null,true));
1180 }
1182 @Override
1183 public void receive(State state, Object o) {
1184 JAXBElement e = (JAXBElement)state.target;
1185 e.setValue(o);
1186 state.getContext().recordOuterPeer(e);
1187 state.getContext().result = e;
1188 }
1189 }
1191 //
1192 // in-place unmarshalling related capabilities
1193 //
1194 /**
1195 * Notifies the context about the inner peer of the current element.
1196 *
1197 * <p>
1198 * If the unmarshalling is building the association, the context
1199 * will use this information. Otherwise it will be just ignored.
1200 */
1201 public void recordInnerPeer(Object innerPeer) {
1202 if(assoc!=null)
1203 assoc.addInner(currentElement,innerPeer);
1204 }
1206 /**
1207 * Gets the inner peer JAXB object associated with the current element.
1208 *
1209 * @return
1210 * null if the current element doesn't have an inner peer,
1211 * or if we are not doing the in-place unmarshalling.
1212 */
1213 public Object getInnerPeer() {
1214 if(assoc!=null && isInplaceMode)
1215 return assoc.getInnerPeer(currentElement);
1216 else
1217 return null;
1218 }
1220 /**
1221 * Notifies the context about the outer peer of the current element.
1222 *
1223 * <p>
1224 * If the unmarshalling is building the association, the context
1225 * will use this information. Otherwise it will be just ignored.
1226 */
1227 public void recordOuterPeer(Object outerPeer) {
1228 if(assoc!=null)
1229 assoc.addOuter(currentElement,outerPeer);
1230 }
1232 /**
1233 * Gets the outer peer JAXB object associated with the current element.
1234 *
1235 * @return
1236 * null if the current element doesn't have an inner peer,
1237 * or if we are not doing the in-place unmarshalling.
1238 */
1239 public Object getOuterPeer() {
1240 if(assoc!=null && isInplaceMode)
1241 return assoc.getOuterPeer(currentElement);
1242 else
1243 return null;
1244 }
1246 /**
1247 * Gets the xmime:contentType value for the current object.
1248 *
1249 * @see JAXBContextImpl#getXMIMEContentType(Object)
1250 */
1251 public String getXMIMEContentType() {
1252 /*
1253 this won't work when the class is like
1255 class Foo {
1256 @XmlValue Image img;
1257 }
1259 because the target will return Foo, not the class enclosing Foo
1260 which will have xmime:contentType
1261 */
1262 Object t = current.target;
1263 if(t==null) return null;
1264 return getJAXBContext().getXMIMEContentType(t);
1265 }
1267 /**
1268 * When called from within the realm of the unmarshaller, this method
1269 * returns the current {@link UnmarshallingContext} in charge.
1270 */
1271 public static UnmarshallingContext getInstance() {
1272 return (UnmarshallingContext) Coordinator._getInstance();
1273 }
1275 /**
1276 * Allows to access elements which are expected in current state.
1277 * Useful for getting elements for current parent.
1278 *
1279 * @return
1280 */
1281 public Collection<QName> getCurrentExpectedElements() {
1282 pushCoordinator();
1283 try {
1284 State s = getCurrentState();
1285 Loader l = s.loader;
1286 return (l != null) ? l.getExpectedChildElements() : null;
1287 } finally {
1288 popCoordinator();
1289 }
1290 }
1292 /**
1293 * Allows to access attributes which are expected in current state.
1294 * Useful for getting attributes for current parent.
1295 *
1296 * @return
1297 */
1298 public Collection<QName> getCurrentExpectedAttributes() {
1299 pushCoordinator();
1300 try {
1301 State s = getCurrentState();
1302 Loader l = s.loader;
1303 return (l != null) ? l.getExpectedAttributes() : null;
1304 } finally {
1305 popCoordinator();
1306 }
1307 }
1309 /**
1310 * Gets StructureLoader if used as loader.
1311 * Useful when determining if element is mixed or not.
1312 *
1313 */
1314 public StructureLoader getStructureLoader() {
1315 if(current.loader instanceof StructureLoader)
1316 return (StructureLoader)current.loader;
1318 return null;
1319 }
1321 /**
1322 * Based on current {@link Logger} {@link Level} and errorCounter value determines if error should be reported.
1323 *
1324 * If the method called and return true it is expected that error will be reported. And that's why
1325 * errorCounter is automatically decremented during the check.
1326 *
1327 * NOT THREAD SAFE!!! In case of heave concurrency access several additional errors could be reported. It's not expected to be the
1328 * problem. Otherwise add synchronization here.
1329 *
1330 * @return true in case if {@link Level#FINEST} is set OR we haven't exceed errors reporting limit.
1331 */
1332 public boolean shouldErrorBeReported() throws SAXException {
1333 if (logger.isLoggable(Level.FINEST))
1334 return true;
1336 if (errorsCounter >= 0) {
1337 --errorsCounter;
1338 if (errorsCounter == 0) // it's possible to miss this because of concurrency. If required add synchronization here
1339 handleEvent(new ValidationEventImpl(ValidationEvent.WARNING, Messages.ERRORS_LIMIT_EXCEEDED.format(),
1340 getLocator().getLocation(), null), true);
1341 }
1342 return errorsCounter >= 0;
1343 }
1344 }