All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.bval.util.PropertyAccess Maven / Gradle / Ivy

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.bval.util;

import org.apache.bval.util.reflection.Reflection;
import org.apache.commons.weaver.privilizer.Privilizing;
import org.apache.commons.weaver.privilizer.Privilizing.CallTo;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Description: Undefined dynamic strategy (FIELD or METHOD access). Uses Apache
 * Commons BeanUtils (if present) to support its {@code DynaBean} type. Otherwise the
 * {@code java.beans} APIs are used for Java bean property methods and we fall
 * back to accessing field values directly.
 */
@Privilizing(@CallTo(Reflection.class))
public class PropertyAccess extends AccessStrategy {
    private static final Logger log = Logger.getLogger(PropertyAccess.class.getName());
    private static final String BEANUTILS = "org.apache.commons.beanutils.BeanUtils";
    private static final String BEANUTILS_PROPERTY_ACCESS = "org.apache.bval.util.BeanUtilsPropertyAccess";
    private static final Constructor BEANUTILS_PROPERTY_ACCESS_CTOR;
    private static final ConcurrentMap, Map> PROPERTY_DESCRIPTORS =
        new ConcurrentHashMap, Map>();

    static {
        final ClassLoader cl = Reflection.getClassLoader(PropertyAccess.class);
        boolean useBeanUtils;
        try {
            Reflection.toClass(BEANUTILS, cl);
            useBeanUtils = true;
        } catch (Exception e) {
            useBeanUtils = false;
        }
        Constructor ctor;
        if (useBeanUtils) {
            try {
                final Class beanUtilsPropertyAccess = Reflection.toClass(BEANUTILS_PROPERTY_ACCESS, cl);

                ctor = Reflection.getDeclaredConstructor(beanUtilsPropertyAccess.asSubclass(PropertyAccess.class),
                    Class.class, String.class);

            } catch (Exception e) {
                ctor = null;
            }
        } else {
            ctor = null;
        }
        BEANUTILS_PROPERTY_ACCESS_CTOR = ctor;
    }

    /**
     * Obtain a {@link PropertyAccess} instance.
     * @param clazz
     * @param propertyName
     * @return PropertyAccess
     * @since 1.1.2
     */
    public static PropertyAccess getInstance(Class clazz, String propertyName) {
        if (BEANUTILS_PROPERTY_ACCESS_CTOR != null) {
            try {
                return BEANUTILS_PROPERTY_ACCESS_CTOR.newInstance(clazz, propertyName);
            } catch (Exception e) {
                log.log(Level.WARNING, String.format("Exception encountered attempting to instantiate %s(%s, %s)",
                    BEANUTILS_PROPERTY_ACCESS_CTOR, clazz, propertyName), e);
            }
        }
        return new PropertyAccess(clazz, propertyName);
    }

    private final Class beanClass;
    private final String propertyName;
    private Field rememberField;

    /**
     * Create a new PropertyAccess instance.
     * 
     * @param clazz
     * @param propertyName
     */
    @Deprecated
    // keep as protected
    public PropertyAccess(Class clazz, String propertyName) {
        this.beanClass = clazz;
        this.propertyName = propertyName;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ElementType getElementType() {
        return rememberField != null ? ElementType.FIELD : ElementType.METHOD;
    }

    protected Object getPublicProperty(Object bean)
        throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        if (bean instanceof Map) {
            return ((Map) bean).get(propertyName);
        }
        final Method readMethod = getPropertyReadMethod(propertyName, bean.getClass());
        if (readMethod == null) {
            throw new NoSuchMethodException(toString());
        }
        final boolean unset = Reflection.setAccessible(readMethod, true);
        try {
            return readMethod.invoke(bean);
        } finally {
            if (unset) {
                Reflection.setAccessible(readMethod, false);
            }
        }
    }

    /**
     * Get a named property from bean.
     * 
     * @param bean
     * @param propertyName
     * @return Object found
     * @throws InvocationTargetException
     * @throws NoSuchMethodException
     * @throws IllegalAccessException
     */
    public static Object getProperty(Object bean, String propertyName)
        throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        return getInstance(bean.getClass(), propertyName).get(bean);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        return "Property{" + beanClass.getName() + '.' + propertyName + '}';
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Type getJavaType() {
        Type result = getTypeInner();
        return result == null ? Object.class : result;
    }

    /**
     * Learn whether this {@link PropertyAccess} references a known property.
     * 
     * @return boolean
     */
    public boolean isKnown() {
        return getTypeInner() != null;
    }

    /**
     * Find out what, if any, type can be calculated.
     * 
     * @return type found or null
     */
    private Type getTypeInner() {
        if (rememberField != null) {
            return rememberField.getGenericType();
        }
        Method readMethod = getPropertyReadMethod(propertyName, beanClass);
        if (readMethod != null) {
            return readMethod.getGenericReturnType();
        }
        Field fld = getField(propertyName, beanClass);
        if (fld != null) {
            cacheField(fld);
            return rememberField.getGenericType();
        }
        return null;
    }

    private static Method getPropertyReadMethod(String propertyName, Class beanClass) {
        final Map propertyDescriptors = getPropertyDescriptors(beanClass);
        if (propertyDescriptors.containsKey(propertyName)) {
            return propertyDescriptors.get(propertyName).getReadMethod();
        }
        return null;
    }

    private static Field getField(String propertyName, Class beanClass) {
        try { // try public field
            return beanClass.getField(propertyName);
        } catch (NoSuchFieldException ex2) {
            // search for private/protected field up the hierarchy
            Class theClass = beanClass;
            while (theClass != null) {
                try {
                    return theClass.getDeclaredField(propertyName);
                } catch (NoSuchFieldException ex3) {
                    // do nothing
                }
                theClass = theClass.getSuperclass();
            }
        }
        return null;
    }

    private static Object readField(Field field, Object bean) throws IllegalAccessException {
        final boolean mustUnset = Reflection.setAccessible(field, true);
        try {
            return field.get(bean);
        } finally {
            if (mustUnset) {
                Reflection.setAccessible(field, false);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getPropertyName() {
        return propertyName;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object get(Object bean) {
        try {
            if (rememberField != null) { // cache field of previous access
                return readField(rememberField, bean);
            }
            try { // try public method
                return getPublicProperty(bean);
            } catch (NoSuchMethodException ex) {
                return getFieldValue(bean);
            }
        } catch (IllegalArgumentException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalArgumentException("cannot access " + propertyName, e);
        }
    }

    private Object getFieldValue(Object bean) throws IllegalAccessException {
        Field field = getField(propertyName, beanClass);
        if (field != null) {
            cacheField(field);
            return readField(rememberField, bean);
        }
        throw new IllegalArgumentException("cannot access field " + propertyName);
    }

    private void cacheField(Field field) {
        this.rememberField = field;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        PropertyAccess that = (PropertyAccess) o;

        return beanClass.equals(that.beanClass) && propertyName.equals(that.propertyName);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public int hashCode() {
        int result;
        result = beanClass.hashCode();
        result = 31 * result + propertyName.hashCode();
        return result;
    }

    private static Map getPropertyDescriptors(Class type) {
        if (PROPERTY_DESCRIPTORS.containsKey(type)) {
            return PROPERTY_DESCRIPTORS.get(type);
        }
        Map m;
        try {
            final PropertyDescriptor[] propertyDescriptors = Introspector.getBeanInfo(type).getPropertyDescriptors();
            if (propertyDescriptors == null) {
                m = Collections.emptyMap();
            } else {
                m = new HashMap();
                for (PropertyDescriptor pd : propertyDescriptors) {
                    m.put(pd.getName(), pd);
                }
            }
        } catch (IntrospectionException e) {
            log.log(Level.SEVERE, String.format("Cannot locate %s for ", BeanInfo.class.getSimpleName(), type), e);
            m = Collections.emptyMap();
        }
        final Map faster = PROPERTY_DESCRIPTORS.putIfAbsent(type, m);
        return faster == null ? m : faster;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy