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  */
25 
26 package com.sun.xml.internal.bind.v2.runtime.unmarshaller;
27 
28 import java.util.Collection;
29 import java.util.HashMap;
30 import java.util.Map;
31 
32 import javax.xml.namespace.QName;
33 
34 import com.sun.xml.internal.bind.api.AccessorException;
35 import com.sun.xml.internal.bind.v2.WellKnownNamespace;
36 import com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl;
37 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
38 import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
39 import com.sun.xml.internal.bind.v2.runtime.property.AttributeProperty;
40 import com.sun.xml.internal.bind.v2.runtime.property.Property;
41 import com.sun.xml.internal.bind.v2.runtime.property.StructureLoaderBuilder;
42 import com.sun.xml.internal.bind.v2.runtime.property.UnmarshallerChain;
43 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
44 import com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor;
45 import com.sun.xml.internal.bind.v2.util.QNameMap;
46 
47 import java.util.Iterator;
48 import org.xml.sax.Attributes;
49 import org.xml.sax.SAXException;
50 
51 /**
52  * Loads children of an element.
53  *
54  * <p>
55  * This loader works with a single {@link JaxBeanInfo} and handles
56  * attributes, child elements, or child text.
57  *
58  * @author Kohsuke Kawaguchi
59  */
60 public final class StructureLoader extends Loader {
61     /**
62      * This map statically stores information of the
63      * unmarshaller loader and can be used while unmarshalling
64      * Since creating new QNames is expensive use this optimized
65      * version of the map
66      */
67     private final QNameMap<ChildLoader> childUnmarshallers = new QNameMap<ChildLoader>();
68 
69     /**
70      * Loader that processes elements that didn't match anf of the {@link #childUnmarshallers}.
71      * Can be null.
72      */
73     private /*final*/ ChildLoader catchAll;
74 
75     /**
76      * If we have a loader for processing text. Otherwise null.
77      */
78     private /*final*/ ChildLoader textHandler;
79 
80     /**
81      * Unmarshallers for attribute values.
82      * May be null if no attribute is expected and {@link #attCatchAll}==null.
83      */
84     private /*final*/ QNameMap<TransducedAccessor> attUnmarshallers;
85 
86     /**
87      * This will receive all the attributes
88      * that were not processed. Never be null.
89      */
90     private /*final*/ Accessor<Object,Map<QName,String>> attCatchAll;
91 
92     private final JaxBeanInfo beanInfo;
93 
94     /**
95      * The number of scopes this dispatcher needs to keep active.
96      */
97     private /*final*/ int frameSize;
98 
99     // this class is potentially useful for general audience, not just for ClassBeanInfoImpl,
100     // but since right now that is the only user, we make the construction code very specific
101     // to ClassBeanInfoImpl. See rev.1.5 of this file for the original general purpose definition.
StructureLoader(ClassBeanInfoImpl beanInfo)102     public StructureLoader(ClassBeanInfoImpl beanInfo) {
103         super(true);
104         this.beanInfo = beanInfo;
105     }
106 
107     /**
108      * Completes the initialization.
109      *
110      * <p>
111      * To fix the cyclic reference issue, the main part of the initialization needs to be done
112      * after a {@link StructureLoader} is set to {@link ClassBeanInfoImpl#loader}.
113      */
init( JAXBContextImpl context, ClassBeanInfoImpl beanInfo, Accessor<?,Map<QName,String>> attWildcard)114     public void init( JAXBContextImpl context, ClassBeanInfoImpl beanInfo, Accessor<?,Map<QName,String>> attWildcard) {
115         UnmarshallerChain chain = new UnmarshallerChain(context);
116         for (ClassBeanInfoImpl bi = beanInfo; bi != null; bi = bi.superClazz) {
117             for (int i = bi.properties.length - 1; i >= 0; i--) {
118                 Property p = bi.properties[i];
119 
120                 switch(p.getKind()) {
121                 case ATTRIBUTE:
122                     if(attUnmarshallers==null)
123                         attUnmarshallers = new QNameMap<TransducedAccessor>();
124                     AttributeProperty ap = (AttributeProperty) p;
125                     attUnmarshallers.put(ap.attName.toQName(),ap.xacc);
126                     break;
127                 case ELEMENT:
128                 case REFERENCE:
129                 case MAP:
130                 case VALUE:
131                     p.buildChildElementUnmarshallers(chain,childUnmarshallers);
132                     break;
133                 }
134             }
135         }
136 
137         this.frameSize = chain.getScopeSize();
138 
139         textHandler = childUnmarshallers.get(StructureLoaderBuilder.TEXT_HANDLER);
140         catchAll = childUnmarshallers.get(StructureLoaderBuilder.CATCH_ALL);
141 
142         if(attWildcard!=null) {
143             attCatchAll = (Accessor<Object,Map<QName,String>>) attWildcard;
144             // we use attUnmarshallers==null as a sign to skip the attribute processing
145             // altogether, so if we have an att wildcard we need to have an empty qname map.
146             if(attUnmarshallers==null)
147                 attUnmarshallers = EMPTY;
148         } else {
149             attCatchAll = null;
150         }
151     }
152 
153     @Override
startElement(UnmarshallingContext.State state, TagName ea)154     public void startElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
155         UnmarshallingContext context = state.getContext();
156 
157         // create the object to unmarshal
158         Object child;
159         assert !beanInfo.isImmutable();
160 
161         // let's see if we can reuse the existing peer object
162         child = context.getInnerPeer();
163 
164         if(child != null && beanInfo.jaxbType!=child.getClass())
165             child = null;   // unexpected type.
166 
167         if(child != null)
168             beanInfo.reset(child,context);
169 
170         if(child == null)
171             child = context.createInstance(beanInfo);
172 
173         context.recordInnerPeer(child);
174 
175         state.setTarget(child);
176 
177         fireBeforeUnmarshal(beanInfo, child, state);
178 
179 
180         context.startScope(frameSize);
181 
182         if(attUnmarshallers!=null) {
183             Attributes atts = ea.atts;
184             for (int i = 0; i < atts.getLength(); i ++){
185                 String auri = atts.getURI(i);
186                 // may be empty string based on parser settings
187                 String alocal = atts.getLocalName(i);
188                 if ("".equals(alocal)) {
189                     alocal = atts.getQName(i);
190                 }
191                 String avalue = atts.getValue(i);
192                 TransducedAccessor xacc = attUnmarshallers.get(auri, alocal);
193                 try {
194                     if(xacc!=null) {
195                         xacc.parse(child,avalue);
196                     } else if (attCatchAll!=null) {
197                         String qname = atts.getQName(i);
198                         if(atts.getURI(i).equals(WellKnownNamespace.XML_SCHEMA_INSTANCE))
199                             continue;   // xsi:* attributes are meant to be processed by us, not by user apps.
200                         Object o = state.getTarget();
201                         Map<QName,String> map = attCatchAll.get(o);
202                         if(map==null) {
203                             // TODO: use  ClassFactory.inferImplClass(sig,knownImplClasses)
204 
205                             // if null, create a new map.
206                             if(attCatchAll.valueType.isAssignableFrom(HashMap.class))
207                                 map = new HashMap<QName,String>();
208                             else {
209                                 // we don't know how to create a map for this.
210                                 // report an error and back out
211                                 context.handleError(Messages.UNABLE_TO_CREATE_MAP.format(attCatchAll.valueType));
212                                 return;
213                             }
214                             attCatchAll.set(o,map);
215                         }
216 
217                         String prefix;
218                         int idx = qname.indexOf(':');
219                         if(idx<0)   prefix="";
220                         else        prefix=qname.substring(0,idx);
221 
222                         map.put(new QName(auri,alocal,prefix),avalue);
223                     }
224                 } catch (AccessorException e) {
225                    handleGenericException(e,true);
226                 }
227             }
228         }
229     }
230 
231     @Override
childElement(UnmarshallingContext.State state, TagName arg)232     public void childElement(UnmarshallingContext.State state, TagName arg) throws SAXException {
233         ChildLoader child = childUnmarshallers.get(arg.uri,arg.local);
234         if (child == null) {
235             child = catchAll;
236             if (child==null) {
237                 super.childElement(state,arg);
238                 return;
239             }
240         }
241 
242         state.setLoader(child.loader);
243         state.setReceiver(child.receiver);
244     }
245 
246     @Override
getExpectedChildElements()247     public Collection<QName> getExpectedChildElements() {
248         return childUnmarshallers.keySet();
249     }
250 
251     @Override
getExpectedAttributes()252     public Collection<QName> getExpectedAttributes() {
253         return attUnmarshallers.keySet();
254     }
255 
256     @Override
text(UnmarshallingContext.State state, CharSequence text)257     public void text(UnmarshallingContext.State state, CharSequence text) throws SAXException {
258         if(textHandler!=null)
259             textHandler.loader.text(state,text);
260     }
261 
262     @Override
leaveElement(UnmarshallingContext.State state, TagName ea)263     public void leaveElement(UnmarshallingContext.State state, TagName ea) throws SAXException {
264         state.getContext().endScope(frameSize);
265         fireAfterUnmarshal(beanInfo, state.getTarget(), state.getPrev());
266     }
267 
268     private static final QNameMap<TransducedAccessor> EMPTY = new QNameMap<TransducedAccessor>();
269 
getBeanInfo()270     public JaxBeanInfo getBeanInfo() {
271         return beanInfo;
272     }
273 }
274