
org.apache.commons.jexl3.internal.introspection.Uberspect Maven / Gradle / Ivy
Show all versions of commons-jexl3 Show documentation
/*
* 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 java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.jexl3.JexlArithmetic;
import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlOperator;
import org.apache.commons.jexl3.internal.Operator;
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;
/**
* Implements Uberspect to provide the default introspective
* functionality of JEXL.
*
* This is the class to derive to customize introspection.
*
* @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 ref;
/** The class loader reference; used to recreate the introspector when necessary. */
private volatile Reference loader;
/**
* The map from arithmetic classes to overloaded operator sets.
*
* 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, Set> 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();
}
/**
* Gets the current introspector base.
*
* If the reference has been collected, this method will recreate the underlying introspector.
* @return the introspector
*/
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;
}
/**
* Computes which operators have an overload implemented in the arithmetic.
* This is used to speed up resolution and avoid introspection when possible.
* @param arithmetic the arithmetic instance
* @return the set of overloaded operators
*/
Set getOverloads(final JexlArithmetic arithmetic) {
final Class extends JexlArithmetic> aclass = arithmetic.getClass();
return operatorMap.computeIfAbsent(aclass, k -> {
final Set newOps = 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
newOps.add(op);
}
}
}
}
}
}
return newOps;
});
}
@Override
public JexlArithmetic.Uberspect getArithmetic(final JexlArithmetic arithmetic) {
final Set operators = arithmetic == null ? Collections.emptySet() : getOverloads(arithmetic);
return operators.isEmpty()? null : new Operator(this, arithmetic, operators);
}
@Override
public Operator getOperator(final JexlArithmetic arithmetic) {
final Set operators = arithmetic == null ? Collections.emptySet() : getOverloads(arithmetic);
return new Operator(this, arithmetic, operators);
}
/**
* 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);
}
@Override
public ClassLoader getClassLoader() {
synchronized (this) {
return loader.get();
}
}
@Override
public JexlMethod getConstructor(final Object ctorHandle, final Object... args) {
return ConstructorMethod.discover(base(), ctorHandle, args);
}
/**
* Gets the field named by
* {@code key} for the class
* {@code c}.
*
* @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);
}
@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