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 
  26 package com.sun.xml.internal.bind.v2.runtime.reflect;
  27 
  28 import java.io.IOException;
  29 import java.util.concurrent.Callable;
  30 
  31 import javax.xml.bind.JAXBException;
  32 import javax.xml.bind.annotation.XmlValue;
  33 import javax.xml.stream.XMLStreamException;
  34 
  35 import com.sun.istack.internal.NotNull;
  36 import com.sun.istack.internal.Nullable;
  37 import com.sun.istack.internal.SAXException2;
  38 import com.sun.xml.internal.bind.WhiteSpaceProcessor;
  39 import com.sun.xml.internal.bind.api.AccessorException;
  40 import com.sun.xml.internal.bind.v2.model.core.ID;
  41 import com.sun.xml.internal.bind.v2.model.impl.RuntimeModelBuilder;
  42 import com.sun.xml.internal.bind.v2.model.runtime.RuntimeNonElementRef;
  43 import com.sun.xml.internal.bind.v2.model.runtime.RuntimePropertyInfo;
  44 import com.sun.xml.internal.bind.v2.runtime.Name;
  45 import com.sun.xml.internal.bind.v2.runtime.Transducer;
  46 import com.sun.xml.internal.bind.v2.runtime.XMLSerializer;
  47 import com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl;
  48 import com.sun.xml.internal.bind.v2.runtime.reflect.opt.OptimizedTransducedAccessorFactory;
  49 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Patcher;
  50 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.UnmarshallingContext;
  51 import com.sun.xml.internal.bind.v2.runtime.unmarshaller.LocatorEx;
  52 
  53 import org.xml.sax.SAXException;
  54 
  55 /**
  56  * {@link Accessor} and {@link Transducer} combined into one object.
  57  *
  58  * <p>
  59  * This allows efficient conversions between primitive values and
  60  * String without using boxing.
  61  *
  62  * <p>
  63  * This abstraction only works for a single-value property.
  64  *
  65  * <p>
  66  * An instance of {@link TransducedAccessor} implicitly holds a
  67  * field of the {@code BeanT} that the accessors access.
  68  *
  69  * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
  70  */
  71 public abstract class TransducedAccessor<BeanT> {
  72 
  73     /**
  74      * @see Transducer#useNamespace()
  75      */
  76     public boolean useNamespace() {
  77         return false;
  78     }
  79 
  80     /**
  81      * Obtain the value of the field and declares the namespace URIs used in
  82      * the value.
  83      *
  84      * @see Transducer#declareNamespace(Object, XMLSerializer)
  85      */
  86     public void declareNamespace( BeanT o, XMLSerializer w ) throws AccessorException, SAXException {
  87     }
  88 
  89     /**
  90      * Prints the responsible field of the given bean to the writer.
  91      *
  92      * <p>
  93      * Use {@link XMLSerializer#getInstance()} to access to the namespace bindings
  94      *
  95      * @return
  96      *      if the accessor didn't yield a value, return null.
  97      */
  98     public abstract @Nullable CharSequence print(@NotNull BeanT o) throws AccessorException, SAXException;
  99 
 100     /**
 101      * Parses the text value into the responsible field of the given bean.
 102      *
 103      * <p>
 104      * Use {@link UnmarshallingContext#getInstance()} to access to the namespace bindings
 105      *
 106      * @throws AccessorException
 107      *      if the transducer is used to parse an user bean that uses {@link XmlValue},
 108      *      then this exception may occur when it tries to set the leaf value to the bean.
 109      * @throws RuntimeException
 110      *      if the lexical form is incorrect. The method may throw a RuntimeException,
 111      *      but it shouldn't cause the entire unmarshalling to fail.
 112      * @throws SAXException
 113      *      if the parse method found an error, the error is reported, and then
 114      *      the processing is aborted.
 115      */
 116     public abstract void parse(BeanT o, CharSequence lexical) throws AccessorException, SAXException;
 117 
 118     /**
 119      * Checks if the field has a value.
 120      */
 121     public abstract boolean hasValue(BeanT o) throws AccessorException;
 122 
 123 
 124 
 125 
 126 
 127 
 128 
 129 
 130 
 131 
 132 
 133     /**
 134      * Gets the {@link TransducedAccessor} appropriately configured for
 135      * the given property.
 136      *
 137      * <p>
 138      * This allows the implementation to use an optimized code.
 139      */
 140     public static <T> TransducedAccessor<T> get(JAXBContextImpl context, RuntimeNonElementRef ref) {
 141         Transducer xducer = RuntimeModelBuilder.createTransducer(ref);
 142         RuntimePropertyInfo prop = ref.getSource();
 143 
 144         if(prop.isCollection()) {
 145             return new ListTransducedAccessorImpl(xducer,prop.getAccessor(),
 146                     Lister.create(Utils.REFLECTION_NAVIGATOR.erasure(prop.getRawType()), prop.id(), prop.getAdapter()));
 147         }
 148 
 149         if(prop.id()==ID.IDREF)
 150             return new IDREFTransducedAccessorImpl(prop.getAccessor());
 151 
 152         if(xducer.isDefault() && context != null && !context.fastBoot) {
 153             TransducedAccessor xa = OptimizedTransducedAccessorFactory.get(prop);
 154             if(xa!=null)    return xa;
 155         }
 156 
 157         if(xducer.useNamespace())
 158             return new CompositeContextDependentTransducedAccessorImpl( context, xducer, prop.getAccessor() );
 159         else
 160             return new CompositeTransducedAccessorImpl( context, xducer, prop.getAccessor() );
 161     }
 162 
 163     /**
 164      * Convenience method to write the value as a text inside an element
 165      * without any attributes.
 166      * Can be overridden for improved performance.
 167      *
 168      * <p>
 169      * The callee assumes that there's an associated value in the field.
 170      * No @xsi:type handling is expected.
 171      */
 172     public abstract void writeLeafElement(XMLSerializer w, Name tagName, BeanT o, String fieldName) throws SAXException, AccessorException, IOException, XMLStreamException;
 173 
 174     /**
 175      * Invokes one of the {@link XMLSerializer#text(String, String)} method
 176      * with the representation of data bested suited for this transduced accessor.
 177      */
 178     public abstract void writeText(XMLSerializer w, BeanT o, String fieldName) throws AccessorException, SAXException, IOException, XMLStreamException;
 179 
 180     static class CompositeContextDependentTransducedAccessorImpl<BeanT,ValueT> extends CompositeTransducedAccessorImpl<BeanT,ValueT> {
 181         public CompositeContextDependentTransducedAccessorImpl(JAXBContextImpl context,Transducer<ValueT> xducer, Accessor<BeanT,ValueT> acc) {
 182             super(context,xducer,acc);
 183             assert xducer.useNamespace();
 184         }
 185 
 186         @Override
 187         public boolean useNamespace() {
 188             return true;
 189         }
 190 
 191         @Override
 192         public void declareNamespace(BeanT bean, XMLSerializer w) throws AccessorException {
 193             ValueT o = acc.get(bean);
 194             if(o!=null)
 195                 xducer.declareNamespace(o,w);
 196         }
 197 
 198         @Override
 199         public void writeLeafElement(XMLSerializer w, Name tagName, BeanT o, String fieldName) throws SAXException, AccessorException, IOException, XMLStreamException {
 200             w.startElement(tagName,null);
 201             declareNamespace(o,w);
 202             w.endNamespaceDecls(null);
 203             w.endAttributes();
 204             xducer.writeText(w,acc.get(o),fieldName);
 205             w.endElement();
 206         }
 207     }
 208 
 209 
 210     /**
 211      * Implementation of {@link TransducedAccessor} that
 212      * simply combines a {@link Transducer} and {@link Accessor}.
 213      */
 214     public static class CompositeTransducedAccessorImpl<BeanT,ValueT> extends TransducedAccessor<BeanT> {
 215         protected final Transducer<ValueT> xducer;
 216         protected final Accessor<BeanT,ValueT> acc;
 217 
 218         public CompositeTransducedAccessorImpl(JAXBContextImpl context, Transducer<ValueT> xducer, Accessor<BeanT,ValueT> acc) {
 219             this.xducer = xducer;
 220             this.acc = acc.optimize(context);
 221         }
 222 
 223         public CharSequence print(BeanT bean) throws AccessorException {
 224             ValueT o = acc.get(bean);
 225             if(o==null)     return null;
 226             return xducer.print(o);
 227         }
 228 
 229         public void parse(BeanT bean, CharSequence lexical) throws AccessorException, SAXException {
 230             acc.set(bean,xducer.parse(lexical));
 231         }
 232 
 233         public boolean hasValue(BeanT bean) throws AccessorException {
 234             return acc.getUnadapted(bean)!=null;
 235         }
 236 
 237         @Override
 238         public void writeLeafElement(XMLSerializer w, Name tagName, BeanT o, String fieldName) throws SAXException, AccessorException, IOException, XMLStreamException {
 239             xducer.writeLeafElement(w,tagName,acc.get(o),fieldName);
 240         }
 241 
 242         @Override
 243         public void writeText(XMLSerializer w, BeanT o, String fieldName) throws AccessorException, SAXException, IOException, XMLStreamException {
 244             xducer.writeText(w,acc.get(o),fieldName);
 245         }
 246     }
 247 
 248     /**
 249      * {@link TransducedAccessor} for IDREF.
 250      *
 251      * BeanT: the type of the bean that contains this the IDREF field.
 252      * TargetT: the type of the bean pointed by IDREF.
 253      */
 254     private static final class IDREFTransducedAccessorImpl<BeanT,TargetT> extends DefaultTransducedAccessor<BeanT> {
 255         private final Accessor<BeanT,TargetT> acc;
 256         /**
 257          * The object that an IDREF resolves to should be
 258          * assignable to this type.
 259          */
 260         private final Class<TargetT> targetType;
 261 
 262         public IDREFTransducedAccessorImpl(Accessor<BeanT, TargetT> acc) {
 263             this.acc = acc;
 264             this.targetType = acc.getValueType();
 265         }
 266 
 267         public String print(BeanT bean) throws AccessorException, SAXException {
 268             TargetT target = acc.get(bean);
 269             if(target==null)    return null;
 270 
 271             XMLSerializer w = XMLSerializer.getInstance();
 272             try {
 273                 String id = w.grammar.getBeanInfo(target,true).getId(target,w);
 274                 if(id==null)
 275                     w.errorMissingId(target);
 276                 return id;
 277             } catch (JAXBException e) {
 278                 w.reportError(null,e);
 279                 return null;
 280             }
 281         }
 282 
 283         private void assign( BeanT bean, TargetT t, UnmarshallingContext context ) throws AccessorException {
 284             if(!targetType.isInstance(t))
 285                 context.handleError(Messages.UNASSIGNABLE_TYPE.format(targetType,t.getClass()));
 286             else
 287                 acc.set(bean,t);
 288         }
 289 
 290         public void parse(final BeanT bean, CharSequence lexical) throws AccessorException, SAXException {
 291             final String idref = WhiteSpaceProcessor.trim(lexical).toString();
 292             final UnmarshallingContext context = UnmarshallingContext.getInstance();
 293 
 294             final Callable callable = context.getObjectFromId(idref,acc.valueType);
 295             if(callable==null) {
 296                 // the IDResolver decided to abort it now
 297                 context.errorUnresolvedIDREF(bean,idref,context.getLocator());
 298                 return;
 299             }
 300 
 301             TargetT t;
 302             try {
 303                 t = (TargetT)callable.call();
 304             } catch (SAXException e) {// from callable.call
 305                 throw e;
 306             } catch (RuntimeException e) {// from callable.call
 307                 throw e;
 308             } catch (Exception e) {// from callable.call
 309                 throw new SAXException2(e);
 310             }
 311             if(t!=null) {
 312                 assign(bean,t,context);
 313             } else {
 314                 // try again later
 315                 final LocatorEx loc = new LocatorEx.Snapshot(context.getLocator());
 316                 context.addPatcher(new Patcher() {
 317                     public void run() throws SAXException {
 318                         try {
 319                             TargetT t = (TargetT)callable.call();
 320                             if(t==null) {
 321                                 context.errorUnresolvedIDREF(bean,idref,loc);
 322                             } else {
 323                                 assign(bean,t,context);
 324                             }
 325                         } catch (AccessorException e) {
 326                             context.handleError(e);
 327                         } catch (SAXException e) {// from callable.call
 328                             throw e;
 329                         } catch (RuntimeException e) {// from callable.call
 330                             throw e;
 331                         } catch (Exception e) {// from callable.call
 332                             throw new SAXException2(e);
 333                         }
 334                     }
 335                 });
 336             }
 337         }
 338 
 339         public boolean hasValue(BeanT bean) throws AccessorException {
 340             return acc.get(bean)!=null;
 341         }
 342     }
 343 }