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.runtime.property;
27
28import java.io.IOException;
29import java.lang.reflect.Modifier;
30import java.lang.reflect.Type;
31import java.util.HashMap;
32import java.util.Map;
33
34import javax.xml.namespace.QName;
35import javax.xml.stream.XMLStreamException;
36
37import com.sun.xml.internal.bind.api.AccessorException;
38import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
39import com.sun.xml.internal.bind.v2.model.core.TypeRef;
40import com.sun.xml.internal.bind.v2.model.runtime.RuntimeElementPropertyInfo;
41import com.sun.xml.internal.bind.v2.model.runtime.RuntimeTypeInfo;
42import com.sun.xml.internal.bind.v2.model.runtime.RuntimeTypeRef;
43import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
44import com.sun.xml.internal.bind.v2.runtime.JaxBeanInfo;
45import com.sun.xml.internal.bind.v2.runtime.Name;
46import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
47import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
48import com.sun.xml.internal.bind.v2.runtime.unmarshaller.ChildLoader;
49import com.sun.xml.internal.bind.v2.runtime.unmarshaller.DefaultValueLoaderDecorator;
50import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Loader;
51import com.sun.xml.internal.bind.v2.runtime.unmarshaller.XsiNilLoader;
52import com.sun.xml.internal.bind.v2.util.QNameMap;
53
54import javax.xml.bind.JAXBElement;
55import org.xml.sax.SAXException;
56
57/**
58 * @author Kohsuke Kawaguchi (kk@kohsuke.org)
59 */
60final class SingleElementNodeProperty<BeanT,ValueT> extends PropertyImpl<BeanT> {
61
62    private final Accessor<BeanT,ValueT> acc;
63
64    private final boolean nillable;
65
66    private final QName[] acceptedElements;
67
68    private final Map<Class,TagAndType> typeNames = new HashMap<Class,TagAndType>();
69
70    private RuntimeElementPropertyInfo prop;
71
72    /**
73     * The tag name used to produce xsi:nil. The first one in the list.
74     */
75    private final Name nullTagName;
76
77    public SingleElementNodeProperty(JAXBContextImpl context, RuntimeElementPropertyInfo prop) {
78        super(context,prop);
79        acc = prop.getAccessor().optimize(context);
80        this.prop = prop;
81
82        QName nt = null;
83        boolean nil = false;
84
85        acceptedElements = new QName[prop.getTypes().size()];
86        for( int i=0; i<acceptedElements.length; i++ )
87            acceptedElements[i] = prop.getTypes().get(i).getTagName();
88
89        for (RuntimeTypeRef e : prop.getTypes()) {
90            JaxBeanInfo beanInfo = context.getOrCreate(e.getTarget());
91            if(nt==null)    nt = e.getTagName();
92            typeNames.put( beanInfo.jaxbType, new TagAndType(
93                context.nameBuilder.createElementName(e.getTagName()),beanInfo) );
94            nil |= e.isNillable();
95        }
96
97        nullTagName = context.nameBuilder.createElementName(nt);
98
99        nillable = nil;
100    }
101
102    @Override
103    public void wrapUp() {
104        super.wrapUp();
105        prop = null;
106    }
107
108    public void reset(BeanT bean) throws AccessorException {
109        acc.set(bean,null);
110    }
111
112    public String getIdValue(BeanT beanT) {
113        return null;
114    }
115
116    @Override
117    public void serializeBody(BeanT o, XMLSerializer w, Object outerPeer) throws SAXException, AccessorException, IOException, XMLStreamException {
118        ValueT v = acc.get(o);
119        if (v!=null) {
120            Class vtype = v.getClass();
121            TagAndType tt=typeNames.get(vtype); // quick way that usually works
122
123            if(tt==null) {// slow way that always works
124                for (Map.Entry<Class,TagAndType> e : typeNames.entrySet()) {
125                    if(e.getKey().isAssignableFrom(vtype)) {
126                        tt = e.getValue();
127                        break;
128                    }
129                }
130            }
131
132            boolean addNilDecl = (o instanceof JAXBElement) && ((JAXBElement)o).isNil();
133            if(tt==null) {
134                // actually this is an error, because the actual type was not a sub-type
135                // of any of the types specified in the annotations,
136                // but for the purpose of experimenting with simple type substitution,
137                // it's convenient to marshal this anyway (for example so that classes
138                // generated from simple types like String can be marshalled as expected.)
139                w.startElement(typeNames.values().iterator().next().tagName,null);
140                w.childAsXsiType(v,fieldName,w.grammar.getBeanInfo(Object.class), addNilDecl && nillable);
141            } else {
142                w.startElement(tt.tagName,null);
143                w.childAsXsiType(v,fieldName,tt.beanInfo, addNilDecl && nillable);
144            }
145            w.endElement();
146        } else if (nillable) {
147            w.startElement(nullTagName,null);
148            w.writeXsiNilTrue();
149            w.endElement();
150        }
151    }
152
153    public void buildChildElementUnmarshallers(UnmarshallerChain chain, QNameMap<ChildLoader> handlers) {
154        JAXBContextImpl context = chain.context;
155
156        for (TypeRef<Type,Class> e : prop.getTypes()) {
157            JaxBeanInfo bi = context.getOrCreate((RuntimeTypeInfo) e.getTarget());
158            // if the expected Java type is already final, type substitution won't really work anyway.
159            // this also traps cases like trying to substitute xsd:long element with xsi:type='xsd:int'
160            Loader l = bi.getLoader(context,!Modifier.isFinal(bi.jaxbType.getModifiers()));
161            if(e.getDefaultValue()!=null)
162                l = new DefaultValueLoaderDecorator(l,e.getDefaultValue());
163            if(nillable || chain.context.allNillable)
164                l = new XsiNilLoader.Single(l,acc);
165            handlers.put( e.getTagName(), new ChildLoader(l,acc));
166        }
167    }
168
169    public PropertyKind getKind() {
170        return PropertyKind.ELEMENT;
171    }
172
173    @Override
174    public Accessor getElementPropertyAccessor(String nsUri, String localName) {
175        for( QName n : acceptedElements) {
176            if(n.getNamespaceURI().equals(nsUri) && n.getLocalPart().equals(localName))
177                return acc;
178        }
179        return null;
180    }
181
182}
183