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

com.redislabs.riot.gen.ReflectivePropertyAccessor Maven / Gradle / Ivy

The newest version!
package com.redislabs.riot.gen;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.asm.MethodVisitor;
import org.springframework.core.MethodParameter;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.expression.AccessException;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.EvaluationException;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.CodeFlow;
import org.springframework.expression.spel.CompilablePropertyAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;

public class ReflectivePropertyAccessor implements PropertyAccessor {

    static {
        Set> booleanTypes = new HashSet<>(4);
        booleanTypes.add(Boolean.class);
        booleanTypes.add(Boolean.TYPE);
    }

    private final boolean allowWrite;
    private final Map readerCache = new ConcurrentHashMap<>(64);
    private final Map writerCache = new ConcurrentHashMap<>(64);
    private final Map typeDescriptorCache = new ConcurrentHashMap<>(64);
    @Nullable
    private volatile InvokerPair lastReadInvokerPair;

    /**
     * Create a new property accessor for reading as well writing.
     *
     * @see #ReflectivePropertyAccessor(boolean)
     */
    public ReflectivePropertyAccessor() {
        this.allowWrite = true;
    }

    /**
     * Create a new property accessor for reading and possibly writing.
     *
     * @param allowWrite whether to also allow for write operations
     * @see #canWrite
     * @since 4.3.15
     */
    public ReflectivePropertyAccessor(boolean allowWrite) {
        this.allowWrite = allowWrite;
    }

    /**
     * Returns {@code null} which means this is a general purpose accessor.
     */
    @Override
    @Nullable
    public Class[] getSpecificTargetClasses() {
        return null;
    }

    @Override
    public boolean canRead(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
        if (target == null) {
            return false;
        }

        Class type = (target instanceof Class ? (Class) target : target.getClass());
        if (type.isArray() && name.equals("length")) {
            return true;
        }

        PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
        if (this.readerCache.containsKey(cacheKey)) {
            return true;
        }
        Method method = getMethod(name, type);
        if (method != null) {
            TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1));
            this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor));
            this.typeDescriptorCache.put(cacheKey, typeDescriptor);
            return true;
        } else {
            Field field = findField(name, type, target);
            if (field != null) {
                TypeDescriptor typeDescriptor = new TypeDescriptor(field);
                this.readerCache.put(cacheKey, new InvokerPair(field, typeDescriptor));
                this.typeDescriptorCache.put(cacheKey, typeDescriptor);
                return true;
            }
        }

        return false;
    }

    private Method getMethod(String name, Class type) {
        try {
            return type.getMethod(name);
        } catch (NoSuchMethodException | SecurityException e) {
            return null;
        }
    }

    @Override
    public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
        Assert.state(target != null, "Target must not be null");
        Class type = (target instanceof Class ? (Class) target : target.getClass());

        if (type.isArray() && name.equals("length")) {
            if (target instanceof Class) {
                throw new AccessException("Cannot access length on array class itself");
            }
            return new TypedValue(Array.getLength(target));
        }

        PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
        InvokerPair invoker = this.readerCache.get(cacheKey);
        this.lastReadInvokerPair = invoker;

        if (invoker == null || invoker.member instanceof Method) {
            Method method = (Method) (invoker != null ? invoker.member : null);
            if (method == null) {
                method = getMethod(name, type);
                if (method != null) {
                    // Treat it like a property...
                    // The readerCache will only contain gettable properties (let's not worry about
                    // setters for now).
                    TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1));
                    invoker = new InvokerPair(method, typeDescriptor);
                    this.lastReadInvokerPair = invoker;
                    this.readerCache.put(cacheKey, invoker);
                }
            }
            if (method != null) {
                try {
                    ReflectionUtils.makeAccessible(method);
                    Object value = method.invoke(target);
                    return new TypedValue(value, invoker.typeDescriptor.narrow(value));
                } catch (Exception ex) {
                    throw new AccessException("Unable to access property '" + name + "' through getter method", ex);
                }
            }
        }

        if (invoker == null || invoker.member instanceof Field) {
            Field field = (Field) (invoker == null ? null : invoker.member);
            if (field == null) {
                field = findField(name, type, target);
                if (field != null) {
                    invoker = new InvokerPair(field, new TypeDescriptor(field));
                    this.lastReadInvokerPair = invoker;
                    this.readerCache.put(cacheKey, invoker);
                }
            }
            if (field != null) {
                try {
                    ReflectionUtils.makeAccessible(field);
                    Object value = field.get(target);
                    return new TypedValue(value, invoker.typeDescriptor.narrow(value));
                } catch (Exception ex) {
                    throw new AccessException("Unable to access field '" + name + "'", ex);
                }
            }
        }

        throw new AccessException("Neither getter method nor field found for property '" + name + "'");
    }

    @Override
    public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) throws AccessException {
        if (!this.allowWrite || target == null) {
            return false;
        }

        Class type = (target instanceof Class ? (Class) target : target.getClass());
        PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
        if (this.writerCache.containsKey(cacheKey)) {
            return true;
        }

        Method method = getMethod(name, type);
        if (method != null) {
            TypeDescriptor typeDescriptor = new TypeDescriptor(new MethodParameter(method, -1));
            this.writerCache.put(cacheKey, method);
            this.typeDescriptorCache.put(cacheKey, typeDescriptor);
            return true;
        } else {
            Field field = findField(name, type, target);
            if (field != null) {
                this.writerCache.put(cacheKey, field);
                this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field));
                return true;
            }
        }

        return false;
    }

    @Override
    public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) throws AccessException {

        if (!this.allowWrite) {
            throw new AccessException("PropertyAccessor for property '" + name + "' on target [" + target + "] does not allow write operations");
        }

        Assert.state(target != null, "Target must not be null");
        Class type = (target instanceof Class ? (Class) target : target.getClass());

        Object possiblyConvertedNewValue = newValue;
        TypeDescriptor typeDescriptor = getTypeDescriptor(context, target, name);
        if (typeDescriptor != null) {
            try {
                possiblyConvertedNewValue = context.getTypeConverter().convertValue(newValue, TypeDescriptor.forObject(newValue), typeDescriptor);
            } catch (EvaluationException evaluationException) {
                throw new AccessException("Type conversion failure", evaluationException);
            }
        }

        PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
        Member cachedMember = this.writerCache.get(cacheKey);

        if (cachedMember == null || cachedMember instanceof Method) {
            Method method = (Method) cachedMember;
            if (method != null) {
                try {
                    ReflectionUtils.makeAccessible(method);
                    method.invoke(target, possiblyConvertedNewValue);
                    return;
                } catch (Exception ex) {
                    throw new AccessException("Unable to access property '" + name + "' through setter method", ex);
                }
            }
        }

        if (cachedMember == null || cachedMember instanceof Field) {
            Field field = (Field) cachedMember;
            if (field == null) {
                field = findField(name, type, target);
                if (field != null) {
                    cachedMember = field;
                    this.writerCache.put(cacheKey, cachedMember);
                }
            }
            if (field != null) {
                try {
                    ReflectionUtils.makeAccessible(field);
                    field.set(target, possiblyConvertedNewValue);
                    return;
                } catch (Exception ex) {
                    throw new AccessException("Unable to access field '" + name + "'", ex);
                }
            }
        }

        throw new AccessException("Neither setter method nor field found for property '" + name + "'");
    }

    /**
     * Get the last read invoker pair.
     *
     * @deprecated as of 4.3.15 since it is not used within the framework anymore
     */
    @Deprecated
    @Nullable
    public Member getLastReadInvokerPair() {
        InvokerPair lastReadInvoker = this.lastReadInvokerPair;
        return (lastReadInvoker != null ? lastReadInvoker.member : null);
    }

    @Nullable
    private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) {
        Class type = (target instanceof Class ? (Class) target : target.getClass());

        if (type.isArray() && name.equals("length")) {
            return TypeDescriptor.valueOf(Integer.TYPE);
        }
        PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class);
        TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey);
        if (typeDescriptor == null) {
            // Attempt to populate the cache entry
            try {
                if (canRead(context, target, name) || canWrite(context, target, name)) {
                    typeDescriptor = this.typeDescriptorCache.get(cacheKey);
                }
            } catch (AccessException ex) {
                // Continue with null type descriptor
            }
        }
        return typeDescriptor;
    }

    /**
     * Determine whether the given {@code Method} is a candidate for property access
     * on an instance of the given target class.
     * 

* The default implementation considers any method as a candidate, even for * non-user-declared properties on the {@link Object} base class. * * @param method the Method to evaluate * @param targetClass the concrete target class that is being introspected * @since 4.3.15 */ protected boolean isCandidateForProperty(Method method, Class targetClass) { return true; } @Nullable private Field findField(String name, Class clazz, Object target) { Field field = findField(name, clazz, target instanceof Class); if (field == null && target instanceof Class) { field = findField(name, target.getClass(), false); } return field; } /** * Find a field of a certain name on a specified class. */ @Nullable protected Field findField(String name, Class clazz, boolean mustBeStatic) { Field[] fields = clazz.getFields(); for (Field field : fields) { if (field.getName().equals(name) && (!mustBeStatic || Modifier.isStatic(field.getModifiers()))) { return field; } } // We'll search superclasses and implemented interfaces explicitly, // although it shouldn't be necessary - however, see SPR-10125. if (clazz.getSuperclass() != null) { Field field = findField(name, clazz.getSuperclass(), mustBeStatic); if (field != null) { return field; } } for (Class implementedInterface : clazz.getInterfaces()) { Field field = findField(name, implementedInterface, mustBeStatic); if (field != null) { return field; } } return null; } /** * Attempt to create an optimized property accessor tailored for a property of a * particular name on a particular class. The general ReflectivePropertyAccessor * will always work but is not optimal due to the need to lookup which * reflective member (method/field) to use each time read() is called. This * method will just return the ReflectivePropertyAccessor instance if it is * unable to build a more optimal accessor. *

* Note: An optimal accessor is currently only usable for read attempts. Do not * call this method if you need a read-write accessor. * * @see OptimalPropertyAccessor */ public PropertyAccessor createOptimalAccessor(@Nullable Object target, String name) { // Don't be clever for arrays or a null target... if (target == null) { return this; } Class clazz = (target instanceof Class ? (Class) target : target.getClass()); if (clazz.isArray()) { return this; } PropertyCacheKey cacheKey = new PropertyCacheKey(clazz, name, target instanceof Class); InvokerPair invocationTarget = this.readerCache.get(cacheKey); if (invocationTarget == null || invocationTarget.member instanceof Method) { Method method = (Method) (invocationTarget != null ? invocationTarget.member : null); if (method == null) { method = getMethod(name, clazz); if (method != null) { invocationTarget = new InvokerPair(method, new TypeDescriptor(new MethodParameter(method, -1))); ReflectionUtils.makeAccessible(method); this.readerCache.put(cacheKey, invocationTarget); } } if (method != null) { return new OptimalPropertyAccessor(invocationTarget); } } if (invocationTarget == null || invocationTarget.member instanceof Field) { Field field = (invocationTarget != null ? (Field) invocationTarget.member : null); if (field == null) { field = findField(name, clazz, target instanceof Class); if (field != null) { invocationTarget = new InvokerPair(field, new TypeDescriptor(field)); ReflectionUtils.makeAccessible(field); this.readerCache.put(cacheKey, invocationTarget); } } if (field != null) { return new OptimalPropertyAccessor(invocationTarget); } } return this; } /** * Captures the member (method/field) to call reflectively to access a property * value and the type descriptor for the value returned by the reflective call. */ private static class InvokerPair { final Member member; final TypeDescriptor typeDescriptor; public InvokerPair(Member member, TypeDescriptor typeDescriptor) { this.member = member; this.typeDescriptor = typeDescriptor; } } private static final class PropertyCacheKey implements Comparable { private final Class clazz; private final String property; private final boolean targetIsClass; public PropertyCacheKey(Class clazz, String name, boolean targetIsClass) { this.clazz = clazz; this.property = name; this.targetIsClass = targetIsClass; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof PropertyCacheKey)) { return false; } PropertyCacheKey otherKey = (PropertyCacheKey) other; return (this.clazz == otherKey.clazz && this.property.equals(otherKey.property) && this.targetIsClass == otherKey.targetIsClass); } @Override public int hashCode() { return (this.clazz.hashCode() * 29 + this.property.hashCode()); } @Override public String toString() { return "CacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property + ", " + this.property + ", targetIsClass=" + this.targetIsClass + "]"; } @Override public int compareTo(PropertyCacheKey other) { int result = this.clazz.getName().compareTo(other.clazz.getName()); if (result == 0) { result = this.property.compareTo(other.property); } return result; } } /** * An optimized form of a PropertyAccessor that will use reflection but only * knows how to access a particular property on a particular class. This is * unlike the general ReflectivePropertyResolver which manages a cache of * methods/fields that may be invoked to access different properties on * different classes. This optimal accessor exists because looking up the * appropriate reflective object by class/name on each read is not cheap. */ public static class OptimalPropertyAccessor implements CompilablePropertyAccessor { /** * The member being accessed. */ public final Member member; private final TypeDescriptor typeDescriptor; OptimalPropertyAccessor(InvokerPair target) { this.member = target.member; this.typeDescriptor = target.typeDescriptor; } @Override @Nullable public Class[] getSpecificTargetClasses() { throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); } @Override public boolean canRead(EvaluationContext context, @Nullable Object target, String name) { if (target == null) { return false; } Class type = (target instanceof Class ? (Class) target : target.getClass()); if (type.isArray()) { return false; } if (this.member instanceof Method) { Method method = (Method) this.member; return name.equals(method.getName()); } else { Field field = (Field) this.member; return field.getName().equals(name); } } @Override public TypedValue read(EvaluationContext context, @Nullable Object target, String name) throws AccessException { if (this.member instanceof Method) { Method method = (Method) this.member; try { ReflectionUtils.makeAccessible(method); Object value = method.invoke(target); return new TypedValue(value, this.typeDescriptor.narrow(value)); } catch (Exception ex) { throw new AccessException("Unable to access property '" + name + "' through getter method", ex); } } else { Field field = (Field) this.member; try { ReflectionUtils.makeAccessible(field); Object value = field.get(target); return new TypedValue(value, this.typeDescriptor.narrow(value)); } catch (Exception ex) { throw new AccessException("Unable to access field '" + name + "'", ex); } } } @Override public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) { throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); } @Override public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) { throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); } @Override public boolean isCompilable() { return (Modifier.isPublic(this.member.getModifiers()) && Modifier.isPublic(this.member.getDeclaringClass().getModifiers())); } @Override public Class getPropertyType() { if (this.member instanceof Method) { return ((Method) this.member).getReturnType(); } else { return ((Field) this.member).getType(); } } @Override public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) { boolean isStatic = Modifier.isStatic(this.member.getModifiers()); String descriptor = cf.lastDescriptor(); String classDesc = this.member.getDeclaringClass().getName().replace('.', '/'); if (!isStatic) { if (descriptor == null) { cf.loadTarget(mv); } if (descriptor == null || !classDesc.equals(descriptor.substring(1))) { mv.visitTypeInsn(CHECKCAST, classDesc); } } else { if (descriptor != null) { // A static field/method call will not consume what is on the stack, // it needs to be popped off. mv.visitInsn(POP); } } if (this.member instanceof Method) { mv.visitMethodInsn((isStatic ? INVOKESTATIC : INVOKEVIRTUAL), classDesc, this.member.getName(), CodeFlow.createSignatureDescriptor((Method) this.member), false); } else { mv.visitFieldInsn((isStatic ? GETSTATIC : GETFIELD), classDesc, this.member.getName(), CodeFlow.toJvmDescriptor(((Field) this.member).getType())); } } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy