| /* |
| * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. |
| * |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Distribution License v. 1.0, which is available at |
| * http://www.eclipse.org/org/documents/edl-v10.php. |
| * |
| * SPDX-License-Identifier: BSD-3-Clause |
| */ |
| |
| package org.glassfish.jaxb.runtime.v2.runtime.reflect; |
| |
| import com.sun.istack.Nullable; |
| import org.glassfish.jaxb.runtime.api.AccessorException; |
| import org.glassfish.jaxb.runtime.api.JAXBRIContext; |
| import org.glassfish.jaxb.core.v2.model.core.Adapter; |
| import org.glassfish.jaxb.runtime.v2.model.impl.RuntimeModelBuilder; |
| import org.glassfish.jaxb.runtime.v2.runtime.JAXBContextImpl; |
| import org.glassfish.jaxb.runtime.v2.runtime.reflect.opt.OptimizedAccessorFactory; |
| import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.Loader; |
| import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.Receiver; |
| import org.glassfish.jaxb.runtime.v2.runtime.unmarshaller.UnmarshallingContext; |
| import jakarta.xml.bind.JAXBElement; |
| import jakarta.xml.bind.annotation.adapters.XmlAdapter; |
| import org.xml.sax.SAXException; |
| |
| import java.lang.reflect.*; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Accesses a particular property of a bean. |
| * <p> |
| * <p> |
| * This interface encapsulates the access to the actual data store. |
| * The intention is to generate implementations for a particular bean |
| * and a property to improve the performance. |
| * <p> |
| * <p> |
| * Accessor can be used as a receiver. Upon receiving an object |
| * it sets that to the field. |
| * |
| * @author Kohsuke Kawaguchi (kk@kohsuke.org) |
| * @see Accessor.FieldReflection |
| * @see TransducedAccessor |
| */ |
| public abstract class Accessor<BeanT, ValueT> implements Receiver { |
| |
| public final Class<ValueT> valueType; |
| |
| public Class<ValueT> getValueType() { |
| return valueType; |
| } |
| |
| protected Accessor(Class<ValueT> valueType) { |
| this.valueType = valueType; |
| } |
| |
| /** |
| * Returns the optimized version of the same accessor. |
| * |
| * @param context The {@link JAXBContextImpl} that owns the whole thing. |
| * (See {@link RuntimeModelBuilder#context}.) |
| * @return At least the implementation can return {@code this}. |
| */ |
| public Accessor<BeanT, ValueT> optimize(@Nullable JAXBContextImpl context) { |
| return this; |
| } |
| |
| |
| /** |
| * Gets the value of the property of the given bean object. |
| * |
| * @param bean must not be null. |
| * @throws AccessorException if failed to set a value. For example, the getter method |
| * may throw an exception. |
| * @since 2.0 EA1 |
| */ |
| public abstract ValueT get(BeanT bean) throws AccessorException; |
| |
| /** |
| * Sets the value of the property of the given bean object. |
| * |
| * @param bean must not be null. |
| * @param value the value to be set. Setting value to null means resetting |
| * to the VM default value (even for primitive properties.) |
| * @throws AccessorException if failed to set a value. For example, the setter method |
| * may throw an exception. |
| * @since 2.0 EA1 |
| */ |
| public abstract void set(BeanT bean, ValueT value) throws AccessorException; |
| |
| |
| /** |
| * Sets the value without adapting the value. |
| * <p> |
| * This ugly entry point is only used by JAX-WS. |
| * See {@link JAXBRIContext#getElementPropertyAccessor} |
| */ |
| public Object getUnadapted(BeanT bean) throws AccessorException { |
| return get(bean); |
| } |
| |
| /** |
| * Returns true if this accessor wraps an adapter. |
| * <p> |
| * This method needs to be used with care, but it helps some optimization. |
| */ |
| public boolean isAdapted() { |
| return false; |
| } |
| |
| /** |
| * Sets the value without adapting the value. |
| * <p> |
| * This ugly entry point is only used by JAX-WS. |
| * See {@link JAXBRIContext#getElementPropertyAccessor} |
| */ |
| public void setUnadapted(BeanT bean, Object value) throws AccessorException { |
| set(bean, (ValueT) value); |
| } |
| |
| public void receive(UnmarshallingContext.State state, Object o) throws SAXException { |
| try { |
| set((BeanT) state.getTarget(), (ValueT) o); |
| } catch (AccessorException e) { |
| Loader.handleGenericException(e, true); |
| } catch (IllegalAccessError iae) { |
| // throw UnmarshalException instead IllegalAccesssError | Issue 475 |
| Loader.handleGenericError(iae); |
| } |
| } |
| |
| private static List<Class> nonAbstractableClasses = Arrays.asList(new Class[]{ |
| Object.class, |
| java.util.Calendar.class, |
| javax.xml.datatype.Duration.class, |
| javax.xml.datatype.XMLGregorianCalendar.class, |
| java.awt.Image.class, |
| jakarta.activation.DataHandler.class, |
| javax.xml.transform.Source.class, |
| java.util.Date.class, |
| java.io.File.class, |
| java.net.URI.class, |
| java.net.URL.class, |
| Class.class, |
| String.class, |
| javax.xml.transform.Source.class} |
| ); |
| |
| public boolean isValueTypeAbstractable() { |
| return !nonAbstractableClasses.contains(getValueType()); |
| } |
| |
| /** |
| * Checks if it is not builtin jaxb class |
| * @param clazz to be checked |
| * @return true if it is NOT builtin class |
| */ |
| public boolean isAbstractable(Class clazz) { |
| return !nonAbstractableClasses.contains(clazz); |
| } |
| |
| /** |
| * Wraps this {@link Accessor} into another {@link Accessor} |
| * and performs the type adaption as necessary. |
| */ |
| public final <T> Accessor<BeanT, T> adapt(Class<T> targetType, final Class<? extends XmlAdapter<T, ValueT>> adapter) { |
| return new AdaptedAccessor<BeanT, ValueT, T>(targetType, this, adapter); |
| } |
| |
| public final <T> Accessor<BeanT, T> adapt(Adapter<Type, Class> adapter) { |
| return new AdaptedAccessor<BeanT, ValueT, T>( |
| (Class<T>) Utils.REFLECTION_NAVIGATOR.erasure(adapter.defaultType), |
| this, |
| adapter.adapterType); |
| } |
| |
| /** |
| * Flag that will be set to true after issueing a warning |
| * about the lack of permission to access non-public fields. |
| */ |
| private static boolean accessWarned = false; |
| |
| |
| /** |
| * {@link Accessor} that uses Java reflection to access a field. |
| */ |
| public static class FieldReflection<BeanT, ValueT> extends Accessor<BeanT, ValueT> { |
| public final Field f; |
| |
| private static final Logger logger = org.glassfish.jaxb.core.Utils.getClassLogger(); |
| |
| public FieldReflection(Field f) { |
| this(f, false); |
| } |
| |
| public FieldReflection(Field f, boolean supressAccessorWarnings) { |
| super((Class<ValueT>) f.getType()); |
| this.f = f; |
| |
| int mod = f.getModifiers(); |
| if (!Modifier.isPublic(mod) || Modifier.isFinal(mod) || !Modifier.isPublic(f.getDeclaringClass().getModifiers())) { |
| try { |
| // attempt to make it accessible, but do so in the security context of the calling application. |
| // don't do this in the doPrivilege block |
| f.setAccessible(true); |
| } catch (SecurityException e) { |
| if ((!accessWarned) && (!supressAccessorWarnings)) { |
| // this happens when we don't have enough permission. |
| logger.log(Level.WARNING, Messages.UNABLE_TO_ACCESS_NON_PUBLIC_FIELD.format( |
| f.getDeclaringClass().getName(), |
| f.getName()), |
| e); |
| } |
| accessWarned = true; |
| } |
| } |
| } |
| |
| public ValueT get(BeanT bean) { |
| try { |
| return (ValueT) f.get(bean); |
| } catch (IllegalAccessException e) { |
| throw new IllegalAccessError(e.getMessage()); |
| } |
| } |
| |
| public void set(BeanT bean, ValueT value) { |
| try { |
| if (value == null) |
| value = (ValueT) uninitializedValues.get(valueType); |
| f.set(bean, value); |
| } catch (IllegalAccessException e) { |
| throw new IllegalAccessError(e.getMessage()); |
| } |
| } |
| |
| @Override |
| public Accessor<BeanT, ValueT> optimize(JAXBContextImpl context) { |
| if (context != null && context.fastBoot) { |
| // let's not waste time on doing this for the sake of faster boot. |
| return this; |
| } |
| Accessor<BeanT, ValueT> acc = OptimizedAccessorFactory.get(f); |
| if (acc != null) { |
| return acc; |
| } |
| return this; |
| } |
| } |
| |
| /** |
| * Read-only access to {@link Field}. Used to handle a static field. |
| */ |
| public static final class ReadOnlyFieldReflection<BeanT, ValueT> extends FieldReflection<BeanT, ValueT> { |
| public ReadOnlyFieldReflection(Field f, boolean supressAccessorWarnings) { |
| super(f, supressAccessorWarnings); |
| } |
| public ReadOnlyFieldReflection(Field f) { |
| super(f); |
| } |
| |
| @Override |
| public void set(BeanT bean, ValueT value) { |
| // noop |
| } |
| |
| @Override |
| public Accessor<BeanT, ValueT> optimize(JAXBContextImpl context) { |
| return this; |
| } |
| } |
| |
| |
| /** |
| * {@link Accessor} that uses Java reflection to access a getter and a setter. |
| */ |
| public static class GetterSetterReflection<BeanT, ValueT> extends Accessor<BeanT, ValueT> { |
| public final Method getter; |
| public final Method setter; |
| |
| private static final Logger logger = org.glassfish.jaxb.core.Utils.getClassLogger(); |
| |
| public GetterSetterReflection(Method getter, Method setter) { |
| super( |
| (Class<ValueT>) (getter != null ? |
| getter.getReturnType() : |
| setter.getParameterTypes()[0])); |
| this.getter = getter; |
| this.setter = setter; |
| |
| if (getter != null) |
| makeAccessible(getter); |
| if (setter != null) |
| makeAccessible(setter); |
| } |
| |
| private void makeAccessible(Method m) { |
| if (!Modifier.isPublic(m.getModifiers()) || !Modifier.isPublic(m.getDeclaringClass().getModifiers())) { |
| try { |
| m.setAccessible(true); |
| } catch (SecurityException e) { |
| if (!accessWarned) |
| // this happens when we don't have enough permission. |
| logger.log(Level.WARNING, Messages.UNABLE_TO_ACCESS_NON_PUBLIC_FIELD.format( |
| m.getDeclaringClass().getName(), |
| m.getName()), |
| e); |
| accessWarned = true; |
| } |
| } |
| } |
| |
| public ValueT get(BeanT bean) throws AccessorException { |
| try { |
| return (ValueT) getter.invoke(bean); |
| } catch (IllegalAccessException e) { |
| throw new IllegalAccessError(e.getMessage()); |
| } catch (InvocationTargetException e) { |
| throw handleInvocationTargetException(e); |
| } |
| } |
| |
| public void set(BeanT bean, ValueT value) throws AccessorException { |
| try { |
| if (value == null) |
| value = (ValueT) uninitializedValues.get(valueType); |
| setter.invoke(bean, value); |
| } catch (IllegalAccessException e) { |
| throw new IllegalAccessError(e.getMessage()); |
| } catch (InvocationTargetException e) { |
| throw handleInvocationTargetException(e); |
| } |
| } |
| |
| private AccessorException handleInvocationTargetException(InvocationTargetException e) { |
| // don't block a problem in the user code |
| Throwable t = e.getTargetException(); |
| if (t instanceof RuntimeException) |
| throw (RuntimeException) t; |
| if (t instanceof Error) |
| throw (Error) t; |
| |
| // otherwise it's a checked exception. |
| // I'm not sure how to handle this. |
| // we can throw a checked exception from here, |
| // but because get/set would be called from so many different places, |
| // the handling would be tedious. |
| return new AccessorException(t); |
| } |
| |
| @Override |
| public Accessor<BeanT, ValueT> optimize(JAXBContextImpl context) { |
| if (getter == null || setter == null) { |
| // if we aren't complete, OptimizedAccessor won't always work |
| return this; |
| } |
| if (context != null && context.fastBoot) { |
| // let's not waste time on doing this for the sake of faster boot. |
| return this; |
| } |
| |
| Accessor<BeanT, ValueT> acc = OptimizedAccessorFactory.get(getter, setter); |
| if (acc != null) { |
| return acc; |
| } |
| |
| return this; |
| } |
| } |
| |
| /** |
| * A version of {@link GetterSetterReflection} that doesn't have any setter. |
| * <p> |
| * <p> |
| * This provides a user-friendly error message. |
| */ |
| public static class GetterOnlyReflection<BeanT, ValueT> extends GetterSetterReflection<BeanT, ValueT> { |
| public GetterOnlyReflection(Method getter) { |
| super(getter, null); |
| } |
| |
| @Override |
| public void set(BeanT bean, ValueT value) throws AccessorException { |
| throw new AccessorException(Messages.NO_SETTER.format(getter.toString())); |
| } |
| } |
| |
| /** |
| * A version of {@link GetterSetterReflection} thaat doesn't have any getter. |
| * <p> |
| * <p> |
| * This provides a user-friendly error message. |
| */ |
| public static class SetterOnlyReflection<BeanT, ValueT> extends GetterSetterReflection<BeanT, ValueT> { |
| public SetterOnlyReflection(Method setter) { |
| super(null, setter); |
| } |
| |
| @Override |
| public ValueT get(BeanT bean) throws AccessorException { |
| throw new AccessorException(Messages.NO_GETTER.format(setter.toString())); |
| } |
| } |
| |
| /** |
| * Gets the special {@link Accessor} used to recover from errors. |
| */ |
| @SuppressWarnings("unchecked") |
| public static <A, B> Accessor<A, B> getErrorInstance() { |
| return ERROR; |
| } |
| |
| private static final Accessor ERROR = new Accessor<Object, Object>(Object.class) { |
| public Object get(Object o) { |
| return null; |
| } |
| |
| public void set(Object o, Object o1) { |
| } |
| }; |
| |
| /** |
| * {@link Accessor} for {@link JAXBElement#getValue()}. |
| */ |
| public static final Accessor<JAXBElement, Object> JAXB_ELEMENT_VALUE = new Accessor<JAXBElement, Object>(Object.class) { |
| public Object get(JAXBElement jaxbElement) { |
| return jaxbElement.getValue(); |
| } |
| |
| public void set(JAXBElement jaxbElement, Object o) { |
| jaxbElement.setValue(o); |
| } |
| }; |
| |
| /** |
| * Uninitialized map keyed by their classes. |
| */ |
| private static final Map<Class, Object> uninitializedValues = new HashMap<Class, Object>(); |
| |
| static { |
| /* |
| static byte default_value_byte = 0; |
| static boolean default_value_boolean = false; |
| static char default_value_char = 0; |
| static float default_value_float = 0; |
| static double default_value_double = 0; |
| static int default_value_int = 0; |
| static long default_value_long = 0; |
| static short default_value_short = 0; |
| */ |
| uninitializedValues.put(byte.class, Byte.valueOf((byte) 0)); |
| uninitializedValues.put(boolean.class, false); |
| uninitializedValues.put(char.class, Character.valueOf((char) 0)); |
| uninitializedValues.put(float.class, Float.valueOf(0)); |
| uninitializedValues.put(double.class, Double.valueOf(0)); |
| uninitializedValues.put(int.class, Integer.valueOf(0)); |
| uninitializedValues.put(long.class, Long.valueOf(0)); |
| uninitializedValues.put(short.class, Short.valueOf((short) 0)); |
| } |
| |
| } |