1/*
2 * Copyright (c) 1997, 2012, 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.model.impl;
27
28import java.io.IOException;
29import java.lang.annotation.Annotation;
30import java.lang.reflect.Field;
31import java.lang.reflect.Method;
32import java.lang.reflect.Modifier;
33import java.lang.reflect.Type;
34import java.util.List;
35import java.util.Map;
36
37import javax.xml.bind.JAXBException;
38import javax.xml.namespace.QName;
39import javax.xml.stream.XMLStreamException;
40
41import com.sun.istack.internal.NotNull;
42import com.sun.xml.internal.bind.AccessorFactory;
43import com.sun.xml.internal.bind.AccessorFactoryImpl;
44import com.sun.xml.internal.bind.InternalAccessorFactory;
45import com.sun.xml.internal.bind.XmlAccessorFactory;
46import com.sun.xml.internal.bind.annotation.XmlLocation;
47import com.sun.xml.internal.bind.api.AccessorException;
48import com.sun.xml.internal.bind.v2.ClassFactory;
49import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
50import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
51import com.sun.xml.internal.bind.v2.model.runtime.RuntimeClassInfo;
52import com.sun.xml.internal.bind.v2.model.runtime.RuntimeElement;
53import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo;
54import com.sun.xml.internal.bind.v2.model.runtime.RuntimeValuePropertyInfo;
55import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
56import com.sun.xml.internal.bind.v2.runtime.Location;
57import com.sun.xml.internal.bind.v2.runtime.Name;
58import com.sun.xml.internal.bind.v2.runtime.Transducer;
59import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
60import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
61import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
62import com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor;
63import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
64
65import org.xml.sax.Locator;
66import org.xml.sax.SAXException;
67
68/**
69 * @author Kohsuke Kawaguchi (kk@kohsuke.org)
70 */
71class RuntimeClassInfoImpl extends ClassInfoImpl<Type,Class,Field,Method>
72        implements RuntimeClassInfo, RuntimeElement {
73
74    /**
75     * If this class has a property annotated with {@link XmlLocation},
76     * this field will get the accessor for it.
77     *
78     * TODO: support method based XmlLocation
79     */
80    private Accessor<?,Locator> xmlLocationAccessor;
81
82    private AccessorFactory accessorFactory;
83
84    private boolean supressAccessorWarnings = false;
85
86    public RuntimeClassInfoImpl(RuntimeModelBuilder modelBuilder, Locatable upstream, Class clazz) {
87        super(modelBuilder, upstream, clazz);
88        accessorFactory = createAccessorFactory(clazz);
89    }
90
91    protected AccessorFactory createAccessorFactory(Class clazz) {
92        XmlAccessorFactory factoryAnn;
93        AccessorFactory accFactory = null;
94
95        // user providing class to be used.
96        JAXBContextImpl context = ((RuntimeModelBuilder) builder).context;
97        if (context!=null) {
98            this.supressAccessorWarnings = context.supressAccessorWarnings;
99            if (context.xmlAccessorFactorySupport) {
100                factoryAnn = findXmlAccessorFactoryAnnotation(clazz);
101                if (factoryAnn != null) {
102                    try {
103                        accFactory = factoryAnn.value().newInstance();
104                    } catch (InstantiationException e) {
105                        builder.reportError(new IllegalAnnotationException(
106                                Messages.ACCESSORFACTORY_INSTANTIATION_EXCEPTION.format(
107                                factoryAnn.getClass().getName(), nav().getClassName(clazz)), this));
108                    } catch (IllegalAccessException e) {
109                        builder.reportError(new IllegalAnnotationException(
110                                Messages.ACCESSORFACTORY_ACCESS_EXCEPTION.format(
111                                factoryAnn.getClass().getName(), nav().getClassName(clazz)),this));
112                    }
113                }
114            }
115        }
116
117
118        // Fall back to local AccessorFactory when no
119        // user not providing one or as error recovery.
120        if (accFactory == null){
121            accFactory = AccessorFactoryImpl.getInstance();
122        }
123        return accFactory;
124    }
125
126    protected XmlAccessorFactory findXmlAccessorFactoryAnnotation(Class clazz) {
127        XmlAccessorFactory factoryAnn = reader().getClassAnnotation(XmlAccessorFactory.class,clazz,this);
128        if (factoryAnn == null) {
129            factoryAnn = reader().getPackageAnnotation(XmlAccessorFactory.class,clazz,this);
130        }
131        return factoryAnn;
132    }
133
134
135    public Method getFactoryMethod(){
136        return super.getFactoryMethod();
137    }
138
139    public final RuntimeClassInfoImpl getBaseClass() {
140        return (RuntimeClassInfoImpl)super.getBaseClass();
141    }
142
143    @Override
144    protected ReferencePropertyInfoImpl createReferenceProperty(PropertySeed<Type,Class,Field,Method> seed) {
145        return new RuntimeReferencePropertyInfoImpl(this,seed);
146    }
147
148    @Override
149    protected AttributePropertyInfoImpl createAttributeProperty(PropertySeed<Type,Class,Field,Method> seed) {
150        return new RuntimeAttributePropertyInfoImpl(this,seed);
151    }
152
153    @Override
154    protected ValuePropertyInfoImpl createValueProperty(PropertySeed<Type,Class,Field,Method> seed) {
155        return new RuntimeValuePropertyInfoImpl(this,seed);
156    }
157
158    @Override
159    protected ElementPropertyInfoImpl createElementProperty(PropertySeed<Type,Class,Field,Method> seed) {
160        return new RuntimeElementPropertyInfoImpl(this,seed);
161    }
162
163    @Override
164    protected MapPropertyInfoImpl createMapProperty(PropertySeed<Type,Class,Field,Method> seed) {
165        return new RuntimeMapPropertyInfoImpl(this,seed);
166    }
167
168
169    @Override
170    public List<? extends RuntimePropertyInfo> getProperties() {
171        return (List<? extends RuntimePropertyInfo>)super.getProperties();
172    }
173
174    @Override
175    public RuntimePropertyInfo getProperty(String name) {
176        return (RuntimePropertyInfo)super.getProperty(name);
177    }
178
179
180    public void link() {
181        getTransducer();    // populate the transducer
182        super.link();
183    }
184
185    private Accessor<?,Map<QName,String>> attributeWildcardAccessor;
186
187    public <B> Accessor<B,Map<QName,String>> getAttributeWildcard() {
188        for( RuntimeClassInfoImpl c=this; c!=null; c=c.getBaseClass() ) {
189            if(c.attributeWildcard!=null) {
190                if(c.attributeWildcardAccessor==null)
191                    c.attributeWildcardAccessor = c.createAttributeWildcardAccessor();
192                return (Accessor<B,Map<QName,String>>)c.attributeWildcardAccessor;
193            }
194        }
195        return null;
196    }
197
198    private boolean computedTransducer = false;
199    private Transducer xducer = null;
200
201    public Transducer getTransducer() {
202        if(!computedTransducer) {
203            computedTransducer = true;
204            xducer = calcTransducer();
205        }
206        return xducer;
207    }
208
209    /**
210     * Creates a transducer if this class is bound to a text in XML.
211     */
212    private Transducer calcTransducer() {
213        RuntimeValuePropertyInfo valuep=null;
214        if(hasAttributeWildcard())
215            return null;        // has attribute wildcard. Can't be handled as a leaf
216        for (RuntimeClassInfoImpl ci = this; ci != null; ci = ci.getBaseClass()) {
217            for( RuntimePropertyInfo pi : ci.getProperties() )
218                if(pi.kind()==PropertyKind.VALUE) {
219                    valuep = (RuntimeValuePropertyInfo)pi;
220                } else {
221                    // this bean has something other than a value
222                    return null;
223                }
224        }
225        if(valuep==null)
226            return null;
227        if( !valuep.getTarget().isSimpleType() )
228            return null;    // if there's an error, recover from it by returning null.
229
230        return new TransducerImpl(getClazz(),TransducedAccessor.get(
231                ((RuntimeModelBuilder)builder).context,valuep));
232    }
233
234    /**
235     * Creates
236     */
237    private Accessor<?,Map<QName,String>> createAttributeWildcardAccessor() {
238        assert attributeWildcard!=null;
239        return ((RuntimePropertySeed)attributeWildcard).getAccessor();
240    }
241
242    @Override
243    protected RuntimePropertySeed createFieldSeed(Field field) {
244       final boolean readOnly = Modifier.isStatic(field.getModifiers());
245        Accessor acc;
246        try {
247            if (supressAccessorWarnings) {
248                acc = ((InternalAccessorFactory)accessorFactory).createFieldAccessor(clazz, field, readOnly, supressAccessorWarnings);
249            } else {
250                acc = accessorFactory.createFieldAccessor(clazz, field, readOnly);
251            }
252        } catch(JAXBException e) {
253            builder.reportError(new IllegalAnnotationException(
254                    Messages.CUSTOM_ACCESSORFACTORY_FIELD_ERROR.format(
255                    nav().getClassName(clazz), e.toString()), this ));
256            acc = Accessor.getErrorInstance(); // error recovery
257        }
258        return new RuntimePropertySeed(super.createFieldSeed(field), acc );
259    }
260
261    @Override
262    public RuntimePropertySeed createAccessorSeed(Method getter, Method setter) {
263        Accessor acc;
264        try {
265            acc = accessorFactory.createPropertyAccessor(clazz, getter, setter);
266        } catch(JAXBException e) {
267            builder.reportError(new IllegalAnnotationException(
268                Messages.CUSTOM_ACCESSORFACTORY_PROPERTY_ERROR.format(
269                nav().getClassName(clazz), e.toString()), this ));
270            acc = Accessor.getErrorInstance(); // error recovery
271        }
272        return new RuntimePropertySeed( super.createAccessorSeed(getter,setter),
273          acc );
274    }
275
276    @Override
277    protected void checkFieldXmlLocation(Field f) {
278        if(reader().hasFieldAnnotation(XmlLocation.class,f))
279            // TODO: check for XmlLocation signature
280            // TODO: check a collision with the super class
281            xmlLocationAccessor = new Accessor.FieldReflection<Object,Locator>(f);
282    }
283
284    public Accessor<?,Locator> getLocatorField() {
285        return xmlLocationAccessor;
286    }
287
288    static final class RuntimePropertySeed implements PropertySeed<Type,Class,Field,Method> {
289        /**
290         * @see #getAccessor()
291         */
292        private final Accessor acc;
293
294        private final PropertySeed<Type,Class,Field,Method> core;
295
296        public RuntimePropertySeed(PropertySeed<Type,Class,Field,Method> core, Accessor acc) {
297            this.core = core;
298            this.acc = acc;
299        }
300
301        public String getName() {
302            return core.getName();
303        }
304
305        public <A extends Annotation> A readAnnotation(Class<A> annotationType) {
306            return core.readAnnotation(annotationType);
307        }
308
309        public boolean hasAnnotation(Class<? extends Annotation> annotationType) {
310            return core.hasAnnotation(annotationType);
311        }
312
313        public Type getRawType() {
314            return core.getRawType();
315        }
316
317        public Location getLocation() {
318            return core.getLocation();
319        }
320
321        public Locatable getUpstream() {
322            return core.getUpstream();
323        }
324
325        public Accessor getAccessor() {
326            return acc;
327        }
328    }
329
330
331
332    /**
333     * {@link Transducer} implementation used when this class maps to PCDATA in XML.
334     *
335     * TODO: revisit the exception handling
336     */
337    private static final class TransducerImpl<BeanT> implements Transducer<BeanT> {
338        private final TransducedAccessor<BeanT> xacc;
339        private final Class<BeanT> ownerClass;
340
341        public TransducerImpl(Class<BeanT> ownerClass,TransducedAccessor<BeanT> xacc) {
342            this.xacc = xacc;
343            this.ownerClass = ownerClass;
344        }
345
346        public boolean useNamespace() {
347            return xacc.useNamespace();
348        }
349
350        public boolean isDefault() {
351            return false;
352        }
353
354        public void declareNamespace(BeanT bean, XMLSerializer w) throws AccessorException {
355            try {
356                xacc.declareNamespace(bean,w);
357            } catch (SAXException e) {
358                throw new AccessorException(e);
359            }
360        }
361
362        public @NotNull CharSequence print(BeanT o) throws AccessorException {
363            try {
364                CharSequence value = xacc.print(o);
365                if(value==null)
366                    throw new AccessorException(Messages.THERE_MUST_BE_VALUE_IN_XMLVALUE.format(o));
367                return value;
368            } catch (SAXException e) {
369                throw new AccessorException(e);
370            }
371        }
372
373        public BeanT parse(CharSequence lexical) throws AccessorException, SAXException {
374            UnmarshallingContext ctxt = UnmarshallingContext.getInstance();
375            BeanT inst;
376            if(ctxt!=null)
377                inst = (BeanT)ctxt.createInstance(ownerClass);
378            else
379                // when this runs for parsing enum constants,
380                // there's no UnmarshallingContext.
381                inst = ClassFactory.create(ownerClass);
382
383            xacc.parse(inst,lexical);
384            return inst;
385        }
386
387        public void writeText(XMLSerializer w, BeanT o, String fieldName) throws IOException, SAXException, XMLStreamException, AccessorException {
388            if(!xacc.hasValue(o))
389                throw new AccessorException(Messages.THERE_MUST_BE_VALUE_IN_XMLVALUE.format(o));
390            xacc.writeText(w,o,fieldName);
391        }
392
393        public void writeLeafElement(XMLSerializer w, Name tagName, BeanT o, String fieldName) throws IOException, SAXException, XMLStreamException, AccessorException {
394            if(!xacc.hasValue(o))
395                throw new AccessorException(Messages.THERE_MUST_BE_VALUE_IN_XMLVALUE.format(o));
396            xacc.writeLeafElement(w,tagName,o,fieldName);
397        }
398
399        public QName getTypeName(BeanT instance) {
400            return null;
401        }
402    }
403}
404