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 
  26 package com.sun.xml.internal.bind.v2.model.impl;
  27 
  28 import java.io.IOException;
  29 import java.lang.annotation.Annotation;
  30 import java.lang.reflect.Field;
  31 import java.lang.reflect.Method;
  32 import java.lang.reflect.Modifier;
  33 import java.lang.reflect.Type;
  34 import java.util.List;
  35 import java.util.Map;
  36 
  37 import javax.xml.bind.JAXBException;
  38 import javax.xml.namespace.QName;
  39 import javax.xml.stream.XMLStreamException;
  40 
  41 import com.sun.istack.internal.NotNull;
  42 import com.sun.xml.internal.bind.AccessorFactory;
  43 import com.sun.xml.internal.bind.AccessorFactoryImpl;
  44 import com.sun.xml.internal.bind.InternalAccessorFactory;
  45 import com.sun.xml.internal.bind.XmlAccessorFactory;
  46 import com.sun.xml.internal.bind.annotation.XmlLocation;
  47 import com.sun.xml.internal.bind.api.AccessorException;
  48 import com.sun.xml.internal.bind.v2.ClassFactory;
  49 import com.sun.xml.internal.bind.v2.model.annotation.Locatable;
  50 import com.sun.xml.internal.bind.v2.model.core.PropertyKind;
  51 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeClassInfo;
  52 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeElement;
  53 import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo;
  54 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeValuePropertyInfo;
  55 import com.sun.xml.internal.bind.v2.runtime.IllegalAnnotationException;
  56 import com.sun.xml.internal.bind.v2.runtime.Location;
  57 import com.sun.xml.internal.bind.v2.runtime.Name;
  58 import com.sun.xml.internal.bind.v2.runtime.Transducer;
  59 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  60 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
  61 import com.sun.xml.internal.bind.v2.runtime.reflect.Accessor;
  62 import com.sun.xml.internal.bind.v2.runtime.reflect.TransducedAccessor;
  63 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
  64 
  65 import org.xml.sax.Locator;
  66 import org.xml.sax.SAXException;
  67 
  68 /**
  69  * @author Kohsuke Kawaguchi (kk@kohsuke.org)
  70  */
  71 class 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 }