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
26package com.sun.xml.internal.bind.v2.runtime;
27
28import java.io.IOException;
29import java.lang.reflect.InvocationTargetException;
30import java.lang.reflect.Method;
31import java.lang.reflect.Modifier;
32import java.util.Collection;
33import java.util.Collections;
34import java.util.List;
35import java.util.Map;
36import java.util.logging.Level;
37import java.util.logging.Logger;
38
39import javax.xml.bind.ValidationEvent;
40import javax.xml.bind.annotation.XmlRootElement;
41import javax.xml.bind.helpers.ValidationEventImpl;
42import javax.xml.namespace.QName;
43import javax.xml.stream.XMLStreamException;
44
45import com.sun.istack.internal.FinalArrayList;
46import com.sun.xml.internal.bind.Util;
47import com.sun.xml.internal.bind.api.AccessorException;
48import com.sun.xml.internal.bind.v2.ClassFactory;
49import com.sun.xml.internal.bind.v2.WellKnownNamespace;
50import com.sun.xml.internal.bind.v2.model.core.ID;
51import com.sun.xml.internal.bind.v2.model.runtime.RuntimeClassInfo;
52import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo;
53import com.sun.xml.internal.bind.v2.runtime.property.AttributeProperty;
54import com.sun.xml.internal.bind.v2.runtime.property.Property;
55import com.sun.xml.internal.bind.v2.runtime.property.PropertyFactory;
56import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
57import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader;
58import com.sun.xml.internal.bind.v2.runtime.unmarshaller.StructureLoader;
59import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
60import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiTypeLoader;
61
62import org.xml.sax.Locator;
63import org.xml.sax.SAXException;
64import org.xml.sax.helpers.LocatorImpl;
65
66/**
67 * {@link JaxBeanInfo} implementation for j2s bean.
68 *
69 * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
70 */
71public final class ClassBeanInfoImpl<BeanT> extends JaxBeanInfo<BeanT> implements AttributeAccessor<BeanT> {
72
73    /**
74     * Properties of this bean class but not its ancestor classes.
75     */
76    public final Property<BeanT>[] properties;
77
78    /**
79     * Non-null if this bean has an ID property.
80     */
81    private Property<? super BeanT> idProperty;
82
83    /**
84     * Immutable configured loader for this class.
85     *
86     * <p>
87     * Set from the link method, but considered final.
88     */
89    private Loader loader;
90    private Loader loaderWithTypeSubst;
91
92    /**
93     * Set only until the link phase to avoid leaking memory.
94     */
95    private RuntimeClassInfo ci;
96
97    private final Accessor<? super BeanT,Map<QName,String>> inheritedAttWildcard;
98    private final Transducer<BeanT> xducer;
99
100    /**
101     * {@link ClassBeanInfoImpl} that represents the super class of {@link #jaxbType}.
102     */
103    public final ClassBeanInfoImpl<? super BeanT> superClazz;
104
105    private final Accessor<? super BeanT,Locator> xmlLocatorField;
106
107    private final Name tagName;
108
109    private boolean retainPropertyInfo = false;
110
111    /**
112     * The {@link AttributeProperty}s for this type and all its ancestors.
113     * If {@link JAXBContextImpl#c14nSupport} is true, this is sorted alphabetically.
114     */
115    private /*final*/ AttributeProperty<BeanT>[] attributeProperties;
116
117    /**
118     * {@link Property}s that need to receive {@link Property#serializeURIs(Object, XMLSerializer)} callback.
119     */
120    private /*final*/ Property<BeanT>[] uriProperties;
121
122    private final Method factoryMethod;
123
124    /*package*/ ClassBeanInfoImpl(JAXBContextImpl owner, RuntimeClassInfo ci) {
125        super(owner,ci,ci.getClazz(),ci.getTypeName(),ci.isElement(),false,true);
126
127        this.ci = ci;
128        this.inheritedAttWildcard = ci.getAttributeWildcard();
129        this.xducer = ci.getTransducer();
130        this.factoryMethod = ci.getFactoryMethod();
131        this.retainPropertyInfo = owner.retainPropertyInfo;
132
133        // make the factory accessible
134        if(factoryMethod!=null) {
135            int classMod = factoryMethod.getDeclaringClass().getModifiers();
136
137            if(!Modifier.isPublic(classMod) || !Modifier.isPublic(factoryMethod.getModifiers())) {
138                // attempt to make it work even if the constructor is not accessible
139                try {
140                    factoryMethod.setAccessible(true);
141                } catch(SecurityException e) {
142                    // but if we don't have a permission to do so, work gracefully.
143                    logger.log(Level.FINE,"Unable to make the method of "+factoryMethod+" accessible",e);
144                    throw e;
145                }
146            }
147        }
148
149
150        if(ci.getBaseClass()==null)
151            this.superClazz = null;
152        else
153            this.superClazz = owner.getOrCreate(ci.getBaseClass());
154
155        if(superClazz!=null && superClazz.xmlLocatorField!=null)
156            xmlLocatorField = superClazz.xmlLocatorField;
157        else
158            xmlLocatorField = ci.getLocatorField();
159
160        // create property objects
161        Collection<? extends RuntimePropertyInfo> ps = ci.getProperties();
162        this.properties = new Property[ps.size()];
163        int idx=0;
164        boolean elementOnly = true;
165        for( RuntimePropertyInfo info : ps ) {
166            Property p = PropertyFactory.create(owner,info);
167            if(info.id()==ID.ID)
168                idProperty = p;
169            properties[idx++] = p;
170            elementOnly &= info.elementOnlyContent();
171            checkOverrideProperties(p);
172        }
173        // super class' idProperty might not be computed at this point,
174        // so check that later
175
176        hasElementOnlyContentModel( elementOnly );
177        // again update this value later when we know that of the super class
178
179        if(ci.isElement())
180            tagName = owner.nameBuilder.createElementName(ci.getElementName());
181        else
182            tagName = null;
183
184        setLifecycleFlags();
185    }
186
187    private void checkOverrideProperties(Property p) {
188        ClassBeanInfoImpl bi = this;
189        while ((bi = bi.superClazz) != null) {
190            Property[] props = bi.properties;
191            if (props == null) break;
192            for (Property superProperty : props) {
193                if (superProperty != null) {
194                    String spName = superProperty.getFieldName();
195                    if ((spName != null) && (spName.equals(p.getFieldName()))) {
196                        superProperty.setHiddenByOverride(true);
197                    }
198                }
199            }
200        }
201    }
202
203    @Override
204    protected void link(JAXBContextImpl grammar) {
205        if(uriProperties!=null)
206            return; // avoid linking twice
207
208        super.link(grammar);
209
210        if(superClazz!=null)
211            superClazz.link(grammar);
212
213        getLoader(grammar,true);    // make sure to build the loader if we haven't done so.
214
215        // propagate values from super class
216        if(superClazz!=null) {
217            if(idProperty==null)
218                idProperty = superClazz.idProperty;
219
220            if(!superClazz.hasElementOnlyContentModel())
221                hasElementOnlyContentModel(false);
222        }
223
224        // create a list of attribute/URI handlers
225        List<AttributeProperty> attProps = new FinalArrayList<AttributeProperty>();
226        List<Property> uriProps = new FinalArrayList<Property>();
227        for (ClassBeanInfoImpl bi = this; bi != null; bi = bi.superClazz) {
228            for (int i = 0; i < bi.properties.length; i++) {
229                Property p = bi.properties[i];
230                if(p instanceof AttributeProperty)
231                    attProps.add((AttributeProperty) p);
232                if(p.hasSerializeURIAction())
233                    uriProps.add(p);
234            }
235        }
236        if(grammar.c14nSupport)
237            Collections.sort(attProps);
238
239        if(attProps.isEmpty())
240            attributeProperties = EMPTY_PROPERTIES;
241        else
242            attributeProperties = attProps.toArray(new AttributeProperty[attProps.size()]);
243
244        if(uriProps.isEmpty())
245            uriProperties = EMPTY_PROPERTIES;
246        else
247            uriProperties = uriProps.toArray(new Property[uriProps.size()]);
248    }
249
250    @Override
251    public void wrapUp() {
252        for (Property p : properties)
253            p.wrapUp();
254        ci = null;
255        super.wrapUp();
256    }
257
258    public String getElementNamespaceURI(BeanT bean) {
259        return tagName.nsUri;
260    }
261
262    public String getElementLocalName(BeanT bean) {
263        return tagName.localName;
264    }
265
266    public BeanT createInstance(UnmarshallingContext context) throws IllegalAccessException, InvocationTargetException, InstantiationException, SAXException {
267
268        BeanT bean = null;
269        if (factoryMethod == null){
270           bean = ClassFactory.create0(jaxbType);
271        }else {
272            Object o = ClassFactory.create(factoryMethod);
273            if( jaxbType.isInstance(o) ){
274                bean = (BeanT)o;
275            } else {
276                throw new InstantiationException("The factory method didn't return a correct object");
277            }
278        }
279
280        if(xmlLocatorField!=null)
281            // need to copy because Locator is mutable
282            try {
283                xmlLocatorField.set(bean,new LocatorImpl(context.getLocator()));
284            } catch (AccessorException e) {
285                context.handleError(e);
286            }
287        return bean;
288    }
289
290    public boolean reset(BeanT bean, UnmarshallingContext context) throws SAXException {
291        try {
292            if(superClazz!=null)
293                superClazz.reset(bean,context);
294            for( Property<BeanT> p : properties )
295                p.reset(bean);
296            return true;
297        } catch (AccessorException e) {
298            context.handleError(e);
299            return false;
300        }
301    }
302
303    public String getId(BeanT bean, XMLSerializer target) throws SAXException {
304        if(idProperty!=null) {
305            try {
306                return idProperty.getIdValue(bean);
307            } catch (AccessorException e) {
308                target.reportError(null,e);
309            }
310        }
311        return null;
312    }
313
314    public void serializeRoot(BeanT bean, XMLSerializer target) throws SAXException, IOException, XMLStreamException {
315        if(tagName==null) {
316            Class beanClass = bean.getClass();
317            String message;
318            if (beanClass.isAnnotationPresent(XmlRootElement.class)) {
319                message = Messages.UNABLE_TO_MARSHAL_UNBOUND_CLASS.format(beanClass.getName());
320            } else {
321                message = Messages.UNABLE_TO_MARSHAL_NON_ELEMENT.format(beanClass.getName());
322            }
323            target.reportError(new ValidationEventImpl(ValidationEvent.ERROR,message,null, null));
324        } else {
325            target.startElement(tagName,bean);
326            target.childAsSoleContent(bean,null);
327            target.endElement();
328            if (retainPropertyInfo) {
329                target.currentProperty.remove();
330            }
331        }
332    }
333
334    public void serializeBody(BeanT bean, XMLSerializer target) throws SAXException, IOException, XMLStreamException {
335        if (superClazz != null) {
336            superClazz.serializeBody(bean, target);
337        }
338        try {
339            for (Property<BeanT> p : properties) {
340                if (retainPropertyInfo) {
341                    target.currentProperty.set(p);
342                }
343                boolean isThereAnOverridingProperty = p.isHiddenByOverride();
344                if (!isThereAnOverridingProperty || bean.getClass().equals(jaxbType)) {
345                    p.serializeBody(bean, target, null);
346                } else if (isThereAnOverridingProperty) {
347                    // need to double check the override - it should be safe to do after the model has been created because it's targeted to override properties only
348                    Class beanClass = bean.getClass();
349                    if (Utils.REFLECTION_NAVIGATOR.getDeclaredField(beanClass, p.getFieldName()) == null) {
350                        p.serializeBody(bean, target, null);
351                    }
352                }
353            }
354        } catch (AccessorException e) {
355            target.reportError(null, e);
356        }
357    }
358
359    public void serializeAttributes(BeanT bean, XMLSerializer target) throws SAXException, IOException, XMLStreamException {
360        for( AttributeProperty<BeanT> p : attributeProperties )
361            try {
362                if (retainPropertyInfo) {
363                final Property parentProperty = target.getCurrentProperty();
364                target.currentProperty.set(p);
365                p.serializeAttributes(bean,target);
366                target.currentProperty.set(parentProperty);
367                } else {
368                    p.serializeAttributes(bean,target);
369                }
370                if (p.attName.equals(WellKnownNamespace.XML_SCHEMA_INSTANCE, "nil")) {
371                    isNilIncluded = true;
372                }
373            } catch (AccessorException e) {
374                target.reportError(null,e);
375            }
376
377        try {
378            if(inheritedAttWildcard!=null) {
379                Map<QName,String> map = inheritedAttWildcard.get(bean);
380                target.attWildcardAsAttributes(map,null);
381            }
382        } catch (AccessorException e) {
383            target.reportError(null,e);
384        }
385    }
386
387    public void serializeURIs(BeanT bean, XMLSerializer target) throws SAXException {
388        try {
389            if (retainPropertyInfo) {
390            final Property parentProperty = target.getCurrentProperty();
391            for( Property<BeanT> p : uriProperties ) {
392                target.currentProperty.set(p);
393                p.serializeURIs(bean,target);
394            }
395            target.currentProperty.set(parentProperty);
396            } else {
397                for( Property<BeanT> p : uriProperties ) {
398                    p.serializeURIs(bean,target);
399                }
400            }
401            if(inheritedAttWildcard!=null) {
402                Map<QName,String> map = inheritedAttWildcard.get(bean);
403                target.attWildcardAsURIs(map,null);
404            }
405        } catch (AccessorException e) {
406            target.reportError(null,e);
407        }
408    }
409
410    public Loader getLoader(JAXBContextImpl context, boolean typeSubstitutionCapable) {
411        if(loader==null) {
412            // these variables have to be set before they are initialized,
413            // because the initialization may build other loaders and they may refer to this.
414            StructureLoader sl = new StructureLoader(this);
415            loader = sl;
416            if(ci.hasSubClasses())
417                loaderWithTypeSubst = new XsiTypeLoader(this);
418            else
419                // optimization. we know there can be no @xsi:type
420                loaderWithTypeSubst = loader;
421
422
423            sl.init(context,this,ci.getAttributeWildcard());
424        }
425        if(typeSubstitutionCapable)
426            return loaderWithTypeSubst;
427        else
428            return loader;
429    }
430
431    public Transducer<BeanT> getTransducer() {
432        return xducer;
433    }
434
435    private static final AttributeProperty[] EMPTY_PROPERTIES = new AttributeProperty[0];
436
437    private static final Logger logger = Util.getClassLogger();
438
439}
440