| /* |
| * 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.commons.jexl3.internal.introspection; |
| |
| import org.apache.commons.jexl3.JexlArithmetic; |
| import org.apache.commons.jexl3.JexlEngine; |
| import org.apache.commons.jexl3.JexlOperator; |
| import org.apache.commons.jexl3.introspection.JexlMethod; |
| import org.apache.commons.jexl3.introspection.JexlPermissions; |
| import org.apache.commons.jexl3.introspection.JexlPropertyGet; |
| import org.apache.commons.jexl3.introspection.JexlPropertySet; |
| import org.apache.commons.jexl3.introspection.JexlUberspect; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.commons.logging.LogFactory; |
| |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| |
| import java.util.EnumSet; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.concurrent.atomic.AtomicInteger; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.util.List; |
| |
| /** |
| * Implementation of Uberspect to provide the default introspective |
| * functionality of JEXL. |
| * <p> |
| * This is the class to derive to customize introspection.</p> |
| * |
| * @since 1.0 |
| */ |
| public class Uberspect implements JexlUberspect { |
| /** Publicly exposed special failure object returned by tryInvoke. */ |
| public static final Object TRY_FAILED = JexlEngine.TRY_FAILED; |
| /** The logger to use for all warnings and errors. */ |
| protected final Log logger; |
| /** The resolver strategy. */ |
| private final JexlUberspect.ResolverStrategy strategy; |
| /** The permissions. */ |
| private final JexlPermissions permissions; |
| /** The introspector version. */ |
| private final AtomicInteger version; |
| /** The soft reference to the introspector currently in use. */ |
| private volatile Reference<Introspector> ref; |
| /** The class loader reference; used to recreate the introspector when necessary. */ |
| private volatile Reference<ClassLoader> loader; |
| /** |
| * The map from arithmetic classes to overloaded operator sets. |
| * <p> |
| * This map keeps track of which operator methods are overloaded per JexlArithmetic classes |
| * allowing a fail fast test during interpretation by avoiding seeking a method when there is none. |
| */ |
| private final Map<Class<? extends JexlArithmetic>, Set<JexlOperator>> operatorMap; |
| |
| /** |
| * Creates a new Uberspect. |
| * @param runtimeLogger the logger used for all logging needs |
| * @param sty the resolver strategy |
| */ |
| public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty) { |
| this(runtimeLogger, sty, null); |
| } |
| |
| /** |
| * Creates a new Uberspect. |
| * @param runtimeLogger the logger used for all logging needs |
| * @param sty the resolver strategy |
| * @param perms the introspector permissions |
| */ |
| public Uberspect(final Log runtimeLogger, final JexlUberspect.ResolverStrategy sty, final JexlPermissions perms) { |
| logger = runtimeLogger == null? LogFactory.getLog(JexlEngine.class) : runtimeLogger; |
| strategy = sty == null? JexlUberspect.JEXL_STRATEGY : sty; |
| permissions = perms == null? JexlPermissions.RESTRICTED : perms; |
| ref = new SoftReference<>(null); |
| loader = new SoftReference<>(getClass().getClassLoader()); |
| operatorMap = new ConcurrentHashMap<>(); |
| version = new AtomicInteger(0); |
| } |
| |
| /** |
| * Gets the current introspector base. |
| * <p> |
| * If the reference has been collected, this method will recreate the underlying introspector.</p> |
| * @return the introspector |
| */ |
| // CSOFF: DoubleCheckedLocking |
| protected final Introspector base() { |
| Introspector intro = ref.get(); |
| if (intro == null) { |
| // double-checked locking is ok (fixed by Java 5 memory model). |
| synchronized (this) { |
| intro = ref.get(); |
| if (intro == null) { |
| intro = new Introspector(logger, loader.get(), permissions); |
| ref = new SoftReference<>(intro); |
| loader = new SoftReference<>(intro.getLoader()); |
| version.incrementAndGet(); |
| } |
| } |
| } |
| return intro; |
| } |
| // CSON: DoubleCheckedLocking |
| |
| @Override |
| public void setClassLoader(final ClassLoader nloader) { |
| synchronized (this) { |
| Introspector intro = ref.get(); |
| if (intro != null) { |
| intro.setLoader(nloader); |
| } else { |
| intro = new Introspector(logger, nloader, permissions); |
| ref = new SoftReference<>(intro); |
| } |
| loader = new SoftReference<>(intro.getLoader()); |
| operatorMap.clear(); |
| version.incrementAndGet(); |
| } |
| } |
| |
| @Override |
| public ClassLoader getClassLoader() { |
| return loader.get(); |
| } |
| |
| @Override |
| public int getVersion() { |
| return version.intValue(); |
| } |
| |
| /** |
| * Gets a class by name through this introspector class loader. |
| * @param className the class name |
| * @return the class instance or null if it could not be found |
| */ |
| @Override |
| public final Class<?> getClassByName(final String className) { |
| return base().getClassByName(className); |
| } |
| |
| /** |
| * Gets the field named by |
| * <code>key</code> for the class |
| * <code>c</code>. |
| * |
| * @param c Class in which the field search is taking place |
| * @param key Name of the field being searched for |
| * @return a {@link java.lang.reflect.Field} or null if it does not exist or is not accessible |
| */ |
| public final Field getField(final Class<?> c, final String key) { |
| return base().getField(c, key); |
| } |
| |
| /** |
| * Gets the accessible field names known for a given class. |
| * @param c the class |
| * @return the class field names |
| */ |
| public final String[] getFieldNames(final Class<?> c) { |
| return base().getFieldNames(c); |
| } |
| |
| /** |
| * Gets the method defined by |
| * <code>name</code> and |
| * <code>params</code> for the Class |
| * <code>c</code>. |
| * |
| * @param c Class in which the method search is taking place |
| * @param name Name of the method being searched for |
| * @param params An array of Objects (not Classes) that describe the parameters |
| * |
| * @return a {@link java.lang.reflect.Method} |
| * or null if no unambiguous method could be found through introspection. |
| */ |
| public final Method getMethod(final Class<?> c, final String name, final Object[] params) { |
| return base().getMethod(c, new MethodKey(name, params)); |
| } |
| |
| /** |
| * Gets the method defined by |
| * <code>key</code> and for the Class |
| * <code>c</code>. |
| * |
| * @param c Class in which the method search is taking place |
| * @param key MethodKey of the method being searched for |
| * |
| * @return a {@link java.lang.reflect.Method} |
| * or null if no unambiguous method could be found through introspection. |
| */ |
| public final Method getMethod(final Class<?> c, final MethodKey key) { |
| return base().getMethod(c, key); |
| } |
| |
| /** |
| * Gets the accessible methods names known for a given class. |
| * @param c the class |
| * @return the class method names |
| */ |
| public final String[] getMethodNames(final Class<?> c) { |
| return base().getMethodNames(c); |
| } |
| |
| /** |
| * Gets all the methods with a given name from this map. |
| * @param c the class |
| * @param methodName the seeked methods name |
| * @return the array of methods |
| */ |
| public final Method[] getMethods(final Class<?> c, final String methodName) { |
| return base().getMethods(c, methodName); |
| } |
| |
| @Override |
| public JexlMethod getMethod(final Object obj, final String method, final Object... args) { |
| return MethodExecutor.discover(base(), obj, method, args); |
| } |
| |
| @Override |
| public List<PropertyResolver> getResolvers(final JexlOperator op, final Object obj) { |
| return strategy.apply(op, obj); |
| } |
| |
| @Override |
| public JexlPropertyGet getPropertyGet(final Object obj, final Object identifier) { |
| return getPropertyGet(null, obj, identifier); |
| } |
| |
| @Override |
| public JexlPropertyGet getPropertyGet( |
| final List<PropertyResolver> resolvers, final Object obj, final Object identifier |
| ) { |
| final Class<?> claz = obj.getClass(); |
| final String property = AbstractExecutor.castString(identifier); |
| final Introspector is = base(); |
| final List<PropertyResolver> r = resolvers == null? strategy.apply(null, obj) : resolvers; |
| JexlPropertyGet executor = null; |
| for (final PropertyResolver resolver : r) { |
| if (resolver instanceof JexlResolver) { |
| switch ((JexlResolver) resolver) { |
| case PROPERTY: |
| // first try for a getFoo() type of property (also getfoo() ) |
| executor = PropertyGetExecutor.discover(is, claz, property); |
| if (executor == null) { |
| executor = BooleanGetExecutor.discover(is, claz, property); |
| } |
| break; |
| case MAP: |
| // let's see if we are a map... |
| executor = MapGetExecutor.discover(is, claz, identifier); |
| break; |
| case LIST: |
| // let's see if this is a list or array |
| final Integer index = AbstractExecutor.castInteger(identifier); |
| if (index != null) { |
| executor = ListGetExecutor.discover(is, claz, index); |
| } |
| break; |
| case DUCK: |
| // if that didn't work, look for get(foo) |
| executor = DuckGetExecutor.discover(is, claz, identifier); |
| if (executor == null && property != null && property != identifier) { |
| // look for get("foo") if we did not try yet (just above) |
| executor = DuckGetExecutor.discover(is, claz, property); |
| } |
| break; |
| case FIELD: |
| // a field may be? (can not be a number) |
| executor = FieldGetExecutor.discover(is, claz, property); |
| // static class fields (enums included) |
| if (obj instanceof Class<?>) { |
| executor = FieldGetExecutor.discover(is, (Class<?>) obj, property); |
| } |
| break; |
| case CONTAINER: |
| // or an indexed property? |
| executor = IndexedType.discover(is, obj, property); |
| break; |
| default: |
| continue; // in case we add new ones in enum |
| } |
| } else { |
| executor = resolver.getPropertyGet(this, obj, identifier); |
| } |
| if (executor != null) { |
| return executor; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public JexlPropertySet getPropertySet(final Object obj, final Object identifier, final Object arg) { |
| return getPropertySet(null, obj, identifier, arg); |
| } |
| |
| @Override |
| public JexlPropertySet getPropertySet( |
| final List<PropertyResolver> resolvers, final Object obj, final Object identifier, final Object arg |
| ) { |
| final Class<?> claz = obj.getClass(); |
| final String property = AbstractExecutor.castString(identifier); |
| final Introspector is = base(); |
| final List<PropertyResolver> actual = resolvers == null? strategy.apply(null, obj) : resolvers; |
| JexlPropertySet executor = null; |
| for (final PropertyResolver resolver : actual) { |
| if (resolver instanceof JexlResolver) { |
| switch ((JexlResolver) resolver) { |
| case PROPERTY: |
| // first try for a setFoo() type of property (also setfoo() ) |
| executor = PropertySetExecutor.discover(is, claz, property, arg); |
| break; |
| case MAP: |
| // let's see if we are a map... |
| executor = MapSetExecutor.discover(is, claz, identifier, arg); |
| break; |
| case LIST: |
| // let's see if we can convert the identifier to an int, |
| // if obj is an array or a list, we can still do something |
| final Integer index = AbstractExecutor.castInteger(identifier); |
| if (index != null) { |
| executor = ListSetExecutor.discover(is, claz, identifier, arg); |
| } |
| break; |
| case DUCK: |
| // if that didn't work, look for set(foo) |
| executor = DuckSetExecutor.discover(is, claz, identifier, arg); |
| if (executor == null && property != null && property != identifier) { |
| executor = DuckSetExecutor.discover(is, claz, property, arg); |
| } |
| break; |
| case FIELD: |
| // a field may be? |
| executor = FieldSetExecutor.discover(is, claz, property, arg); |
| break; |
| case CONTAINER: |
| default: |
| continue; // in case we add new ones in enum |
| } |
| } else { |
| executor = resolver.getPropertySet(this, obj, identifier, arg); |
| } |
| if (executor != null) { |
| return executor; |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public Iterator<?> getIterator(final Object obj) { |
| if (!permissions.allow(obj.getClass())) { |
| return null; |
| } |
| if (obj instanceof Iterator<?>) { |
| return ((Iterator<?>) obj); |
| } |
| if (obj.getClass().isArray()) { |
| return new ArrayIterator(obj); |
| } |
| if (obj instanceof Map<?, ?>) { |
| return ((Map<?, ?>) obj).values().iterator(); |
| } |
| if (obj instanceof Enumeration<?>) { |
| return new EnumerationIterator<>((Enumeration<Object>) obj); |
| } |
| if (obj instanceof Iterable<?>) { |
| return ((Iterable<?>) obj).iterator(); |
| } |
| try { |
| // look for an iterator() method to support the JDK5 Iterable |
| // interface or any user tools/DTOs that want to work in |
| // foreach without implementing the Collection interface |
| final JexlMethod it = getMethod(obj, "iterator", (Object[]) null); |
| if (it != null && Iterator.class.isAssignableFrom(it.getReturnType())) { |
| return (Iterator<Object>) it.invoke(obj, (Object[]) null); |
| } |
| } catch (final Exception xany) { |
| if (logger != null && logger.isDebugEnabled()) { |
| logger.info("unable to solve iterator()", xany); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public JexlMethod getConstructor(final Object ctorHandle, final Object... args) { |
| return ConstructorMethod.discover(base(), ctorHandle, args); |
| } |
| |
| /** |
| * The concrete uberspect Arithmetic class. |
| */ |
| protected class ArithmeticUberspect implements JexlArithmetic.Uberspect { |
| /** The arithmetic instance being analyzed. */ |
| private final JexlArithmetic arithmetic; |
| /** The set of overloaded operators. */ |
| private final Set<JexlOperator> overloads; |
| |
| /** |
| * Creates an instance. |
| * @param theArithmetic the arithmetic instance |
| * @param theOverloads the overloaded operators |
| */ |
| ArithmeticUberspect(final JexlArithmetic theArithmetic, final Set<JexlOperator> theOverloads) { |
| this.arithmetic = theArithmetic; |
| this.overloads = theOverloads; |
| } |
| |
| @Override |
| public JexlMethod getOperator(final JexlOperator operator, final Object... args) { |
| return overloads.contains(operator) && args != null |
| ? getMethod(arithmetic, operator.getMethodName(), args) |
| : null; |
| } |
| |
| @Override |
| public boolean overloads(final JexlOperator operator) { |
| return overloads.contains(operator); |
| } |
| } |
| |
| @Override |
| public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) { |
| JexlArithmetic.Uberspect jau = null; |
| if (arithmetic != null) { |
| final Class<? extends JexlArithmetic> aclass = arithmetic.getClass(); |
| Set<JexlOperator> ops = operatorMap.get(aclass); |
| if (ops == null) { |
| ops = EnumSet.noneOf(JexlOperator.class); |
| // deal only with derived classes |
| if (!JexlArithmetic.class.equals(aclass)) { |
| for (final JexlOperator op : JexlOperator.values()) { |
| final Method[] methods = getMethods(arithmetic.getClass(), op.getMethodName()); |
| if (methods != null) { |
| for (final Method method : methods) { |
| final Class<?>[] parms = method.getParameterTypes(); |
| if (parms.length != op.getArity()) { |
| continue; |
| } |
| // filter method that is an actual overload: |
| // - not inherited (not declared by base class) |
| // - nor overridden (not present in base class) |
| if (!JexlArithmetic.class.equals(method.getDeclaringClass())) { |
| try { |
| JexlArithmetic.class.getMethod(method.getName(), method.getParameterTypes()); |
| } catch (final NoSuchMethodException xmethod) { |
| // method was not found in JexlArithmetic; this is an operator definition |
| ops.add(op); |
| } |
| } |
| } |
| } |
| } |
| } |
| // register this arithmetic class in the operator map |
| operatorMap.put(aclass, ops); |
| } |
| jau = new ArithmeticUberspect(arithmetic, ops); |
| } |
| return jau; |
| } |
| } |