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 }