src/share/jaxws_classes/com/sun/xml/internal/bind/v2/runtime/unmarshaller/UnmarshallingContext.java

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

mercurial