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

javax.el.BeanELResolver Maven / Gradle / Ivy

There is a newer version: 6.0-6
Show newest version
/*
 * 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 javax.el;

import java.beans.BeanInfo;
import java.beans.FeatureDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;


public class BeanELResolver extends ELResolver {

    private final boolean readOnly;

    private final ConcurrentCache cache = new ConcurrentCache(
            1000);

    public BeanELResolver() {
        this.readOnly = false;
    }

    public BeanELResolver(boolean readOnly) {
        this.readOnly = readOnly;
    }

    public Object getValue(ELContext context, Object base, Object property)
            throws NullPointerException, PropertyNotFoundException, ELException {
        if (context == null) {
            throw new NullPointerException();
        }
        if (base == null || property == null) {
            return null;
        }

        context.setPropertyResolved(true);
        Method m = this.property(context, base, property).read(context);
        try {
            return m.invoke(base, (Object[]) null);
        } catch (IllegalAccessException e) {
            throw new ELException(e);
        } catch (InvocationTargetException e) {
            throw new ELException(message(context, "propertyReadError",
                    new Object[] { base.getClass().getName(),
                            property.toString() }), e.getCause());
        } catch (Exception e) {
            throw new ELException(e);
        }
    }

    public Class getType(ELContext context, Object base, Object property)
            throws NullPointerException, PropertyNotFoundException, ELException {
        if (context == null) {
            throw new NullPointerException();
        }
        if (base == null || property == null) {
            return null;
        }

        context.setPropertyResolved(true);
        return this.property(context, base, property).getPropertyType();
    }

    public void setValue(ELContext context, Object base, Object property,
            Object value) throws NullPointerException,
            PropertyNotFoundException, PropertyNotWritableException,
            ELException {
        if (context == null) {
            throw new NullPointerException();
        }
        if (base == null || property == null) {
            return;
        }

        context.setPropertyResolved(true);

        if (this.readOnly) {
            throw new PropertyNotWritableException(message(context,
                    "resolverNotWriteable", new Object[] { base.getClass()
                            .getName() }));
        }

        Method m = this.property(context, base, property).write(context);
        try {
            m.invoke(base, value);
        } catch (IllegalAccessException e) {
            throw new ELException(e);
        } catch (InvocationTargetException e) {
            throw new ELException(message(context, "propertyWriteError",
                    new Object[] { base.getClass().getName(),
                            property.toString() }), e.getCause());
        } catch (Exception e) {
            throw new ELException(e);
        }
    }

    public boolean isReadOnly(ELContext context, Object base, Object property)
            throws NullPointerException, PropertyNotFoundException, ELException {
        if (context == null) {
            throw new NullPointerException();
        }
        if (base == null || property == null) {
            return false;
        }

        context.setPropertyResolved(true);
        return this.readOnly
                || this.property(context, base, property).isReadOnly();
    }

    public Iterator getFeatureDescriptors(ELContext context, Object base) {
        if (context == null) {
            throw new NullPointerException();
        }

        if (base == null) {
            return null;
        }

        try {
            BeanInfo info = Introspector.getBeanInfo(base.getClass());
            PropertyDescriptor[] pds = info.getPropertyDescriptors();
            for (PropertyDescriptor pd : pds) {
                pd.setValue(RESOLVABLE_AT_DESIGN_TIME, Boolean.TRUE);
                pd.setValue(TYPE, pd.getPropertyType());
            }
            return Arrays.asList((FeatureDescriptor[]) pds).iterator();
        } catch (IntrospectionException e) {
            //
        }

        return null;
    }

    public Class getCommonPropertyType(ELContext context, Object base) {
        if (context == null) {
            throw new NullPointerException();
        }

        if (base != null) {
            return Object.class;
        }

        return null;
    }

    protected final static class BeanProperties {
        private final Map properties;

        private final Class type;

        public BeanProperties(Class type) throws ELException {
            this.type = type;
            this.properties = new HashMap();
            try {
                BeanInfo info = Introspector.getBeanInfo(this.type);
                PropertyDescriptor[] pds = info.getPropertyDescriptors();
                for (PropertyDescriptor pd : pds) {
                    this.properties.put(pd.getName(), new BeanProperty(
                            type, pd));
                }
            } catch (IntrospectionException ie) {
                throw new ELException(ie);
            }
        }

        private BeanProperty get(ELContext ctx, String name) {
            BeanProperty property = this.properties.get(name);
            if (property == null) {
                throw new PropertyNotFoundException(message(ctx,
                        "propertyNotFound",
                        new Object[] { type.getName(), name }));
            }
            return property;
        }

        public BeanProperty getBeanProperty(String name) {
            return get(null, name);
        }

        private Class getType() {
            return type;
        }
    }

    protected final static class BeanProperty {
        private final Class type;

        private final Class owner;

        private final PropertyDescriptor descriptor;

        private Method read;

        private Method write;

        public BeanProperty(Class owner, PropertyDescriptor descriptor) {
            this.owner = owner;
            this.descriptor = descriptor;
            this.type = descriptor.getPropertyType();
        }

        public Class getPropertyType() {
            return this.type;
        }

        public boolean isReadOnly() {
            return this.write == null
                && (null == (this.write = getMethod(this.owner, descriptor.getWriteMethod())));
        }

        public Method getWriteMethod() {
            return write(null);
        }

        public Method getReadMethod() {
            return this.read(null);
        }

        private Method write(ELContext ctx) {
            if (this.write == null) {
                this.write = getMethod(this.owner, descriptor.getWriteMethod());
                if (this.write == null) {
                    throw new PropertyNotFoundException(message(ctx,
                            "propertyNotWritable", new Object[] {
                                    type.getName(), descriptor.getName() }));
                }
            }
            return this.write;
        }

        private Method read(ELContext ctx) {
            if (this.read == null) {
                this.read = getMethod(this.owner, descriptor.getReadMethod());
                if (this.read == null) {
                    throw new PropertyNotFoundException(message(ctx,
                            "propertyNotReadable", new Object[] {
                                    type.getName(), descriptor.getName() }));
                }
            }
            return this.read;
        }
    }

    private BeanProperty property(ELContext ctx, Object base,
            Object property) {
        Class type = base.getClass();
        String prop = property.toString();

        BeanProperties props = this.cache.get(type.getName());
        if (props == null || type != props.getType()) {
            props = new BeanProperties(type);
            this.cache.put(type.getName(), props);
        }

        return props.get(ctx, prop);
    }

    private static Method getMethod(Class type, Method m) {
        if (m == null || Modifier.isPublic(type.getModifiers())) {
            return m;
        }
        Class[] inf = type.getInterfaces();
        Method mp;
        for (Class anInf : inf) {
            try {
                mp = anInf.getMethod(m.getName(), m.getParameterTypes());
                mp = getMethod(mp.getDeclaringClass(), mp);
                if (mp != null) {
                    return mp;
                }
            } catch (NoSuchMethodException e) {
                //continue
            }
        }
        Class sup = type.getSuperclass();
        if (sup != null) {
            try {
                mp = sup.getMethod(m.getName(), m.getParameterTypes());
                mp = getMethod(mp.getDeclaringClass(), mp);
                if (mp != null) {
                    return mp;
                }
            } catch (NoSuchMethodException e) {
                //continue
            }
        }
        return null;
    }

    private final static class ConcurrentCache {

        private final int size;
        private final Map eden;
        private final Map longterm;

        public ConcurrentCache(int size) {
            this.size = size;
            this.eden = new ConcurrentHashMap(size);
            this.longterm = new WeakHashMap(size);
        }

        public V get(K key) {
            V value = this.eden.get(key);
            if (value == null) {
                value = this.longterm.get(key);
                if (value != null) {
                    this.eden.put(key, value);
                }
            }
            return value;
        }

        public void put(K key, V value) {
            if (this.eden.size() >= this.size) {
                this.longterm.putAll(this.eden);
                this.eden.clear();
            }
            this.eden.put(key, value);
        }

    }

    public Object invoke(ELContext context, Object base, Object method, Class[] paramTypes, Object[] params) {
        if (context == null) {
            throw new NullPointerException("ELContext could not be nulll");
        }
        // Why static invocation is not supported
        if(base == null || method == null) {
            return null;
        }
        if (params == null) {
            params = new Object[0];
        }
        ExpressionFactory expressionFactory = null;
        if (ELUtils.isCachedExpressionFactoryEnabled()) {
            expressionFactory = ELUtils.getCachedExpressionFactory();
        }
        if (expressionFactory == null) {
            expressionFactory = ExpressionFactory.newInstance();
        }
        String methodName = (String) expressionFactory.coerceToType(method, String.class);
        if (methodName.length() == 0) {
            throw new MethodNotFoundException("The parameter method could not be zero-length");
        }
        Class targetClass = base.getClass();
        if (methodName.equals("") || methodName.equals("")) {
            throw new MethodNotFoundException(method + " is not found in target class " + targetClass.getName());
        }
        Method targetMethod = null;
        if (paramTypes == null) {
            int paramsNumber = params.length;
            for (Method m : targetClass.getMethods()) {
                if (m.getName().equals(methodName) && m.getParameterTypes().length == paramsNumber) {
                    targetMethod = m;
                    break;
                }
            }
            if (targetMethod == null) {
                for (Method m : targetClass.getMethods()) {
                    if (m.getName().equals(methodName) && m.isVarArgs() && paramsNumber >= (m.getParameterTypes().length - 1)) {
                        targetMethod = m;
                        break;
                    }
                }
            }
        } else {
            try {
                targetMethod = targetClass.getMethod(methodName, paramTypes);
            } catch (SecurityException e) {
                throw new ELException(e);
            } catch (NoSuchMethodException e) {
                throw new MethodNotFoundException(e);
            }
        }
        if (targetMethod == null) {
            throw new MethodNotFoundException(method + " is not found in target class " + targetClass.getName());
        }
        if (paramTypes == null) {
            paramTypes = targetMethod.getParameterTypes();
        }
        //Initial check whether the types and parameter values length
        if (targetMethod.isVarArgs()) {
            if (paramTypes.length - 1 > params.length) {
                throw new IllegalArgumentException("Inconsistent number between argument types and values");
            }
        } else if (paramTypes.length != params.length) {
            throw new IllegalArgumentException("Inconsistent number between argument types and values");
        }
        try {
            Object[] finalParamValues = new Object[paramTypes.length];
            //Only do the parameter conversion while the method is not a non-parameter one
            if (paramTypes.length > 0) {
                int iCurrentIndex = 0;
                for (int iLoopSize = paramTypes.length - 1; iCurrentIndex < iLoopSize; iCurrentIndex++) {
                    finalParamValues[iCurrentIndex] = expressionFactory.coerceToType(params[iCurrentIndex], paramTypes[iCurrentIndex]);
                }
                /**
                 * Not sure it is over-designed. Do not find detailed description about how the parameter values are passed if the method is of variable arguments.
                 * It might be an array directly passed or each parameter value passed one by one.
                 */
                if (targetMethod.isVarArgs()) {
                    // varArgsClassType should be an array type
                    Class varArgsClassType = paramTypes[iCurrentIndex];
                    // 1. If there is no parameter value left for the variable argument, create a zero-length array
                    // 2. If there is only one parameter value left for the variable argument, and it has the same array type with the varArgsClass, pass in directly
                    // 3. Else, create an array of varArgsClass type, and add all the left coerced parameter values
                    if (iCurrentIndex == params.length) {
                        finalParamValues[iCurrentIndex] = Array.newInstance(varArgsClassType.getComponentType(), 0);
                    } else if (iCurrentIndex == params.length - 1 && varArgsClassType == params[iCurrentIndex].getClass()
                            && varArgsClassType.getClassLoader() == params[iCurrentIndex].getClass().getClassLoader()) {
                        finalParamValues[iCurrentIndex] = params[iCurrentIndex];
                    } else {
                        Object targetArray = Array.newInstance(varArgsClassType.getComponentType(), params.length - iCurrentIndex);
                        Class componentClassType = varArgsClassType.getComponentType();
                        for (int i = 0, iLoopSize = params.length - iCurrentIndex; i < iLoopSize; i++) {
                            Array.set(targetArray, i, expressionFactory.coerceToType(iCurrentIndex + i, componentClassType));
                        }
                        finalParamValues[iCurrentIndex] = targetArray;
                    }
                } else {
                    finalParamValues[iCurrentIndex] = expressionFactory.coerceToType(params[iCurrentIndex], paramTypes[iCurrentIndex]);
                }
            }
            Object retValue = targetMethod.invoke(base, finalParamValues);
            context.setPropertyResolved(true);
            return retValue;
        }  catch (IllegalAccessException e) {
            throw new ELException(e);
        } catch (InvocationTargetException e) {
            throw new ELException(e.getCause());
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy