com.gitee.l0km.aocache.AocacheUtils Maven / Gradle / Ivy
Show all versions of aocache Show documentation
package com.gitee.l0km.aocache;
import static com.gitee.l0km.aocache.guava.common.base.Preconditions.checkArgument;
import static com.gitee.l0km.aocache.guava.common.base.Preconditions.checkNotNull;
import static com.gitee.l0km.aocache.ObjectUtils.STRING_VALUEOF_FUN;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.Executable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ConcurrentMap;
import com.gitee.l0km.aocache.annotations.AoCacheable;
import com.gitee.l0km.aocache.config.CacheConfig;
import com.gitee.l0km.aocache.guava.common.base.CharMatcher;
import com.gitee.l0km.aocache.guava.common.base.Function;
import com.gitee.l0km.aocache.guava.common.base.Joiner;
import com.gitee.l0km.aocache.guava.common.base.Predicates;
import com.gitee.l0km.aocache.guava.common.base.Strings;
import com.gitee.l0km.aocache.guava.common.collect.FluentIterable;
import com.gitee.l0km.aocache.guava.common.collect.Iterables;
import com.gitee.l0km.aocache.guava.common.collect.Lists;
class AocacheUtils implements AocacheConstant{
// --------------------------------------------------------- Private Members
static V computeIfAbsent(ConcurrentMapmap, K key,
ThrowingFunction mappingFunction) throws E {
Objects.requireNonNull(mappingFunction);
V v, newValue;
return ((v = map.get(key)) == null &&
(newValue = mappingFunction.apply(key)) != null &&
(v = map.putIfAbsent(key, newValue)) == null) ? newValue : v;
}
/**
* 根据输入参数查找类型({@code clazz} )的方法或构造方法({@link Executable})
* @param clazz 目标类型
* @param methodName 方法名,为空则查找类的构造方法(Constructor)
* @param parameterTypes 方法或构造方法的参数类型数组
* @throws NoSuchMethodException
*/
static Executable getMatchingAccessibleExecutable(Class>clazz,String methodName, Class>[]parameterTypes) throws NoSuchMethodException {
Executable executable;
if (null == parameterTypes) {
parameterTypes = EMPTY_CLASS_PARAMETERS;
}
if(Strings.isNullOrEmpty(methodName)) {
executable = getMatchingAccessibleConstructor(clazz,parameterTypes);
}else {
executable = getMatchingAccessibleMethod(clazz,methodName,parameterTypes);
}
if (null == executable) {
throw new NoSuchMethodException(
"No such accessible constructor on object: " + clazz.getName()+toMemberString(null,parameterTypes));
}
return executable;
}
/**
* Returns new instance of klazz
created using the actual arguments args
.
* The formal parameter types are inferred from the actual values of args
.
* See {@link #invokeExactConstructor(Class, Object[], Class[])} for more details.
*
* The signatures should be assignment compatible.
*
* @param the type of the object to be constructed
* @param klass the class to be constructed.
* @param args actual argument array. May be null (this will result in calling the default constructor).
* @return new instance of klazz
*
* @throws NoSuchMethodException If the constructor cannot be found
* @throws IllegalAccessException If an error occurs accessing the constructor
* @throws InvocationTargetException If an error occurs invoking the constructor
* @throws InstantiationException If an error occurs instantiating the class
*
* @see #invokeConstructor(java.lang.Class, java.lang.Object[], java.lang.Class[])
*/
public static T invokeConstructor(Class klass, Object[] args)
throws
NoSuchMethodException,
IllegalAccessException,
InvocationTargetException,
InstantiationException {
if (null == args) {
args = EMPTY_OBJECT_ARRAY;
}
int arguments = args.length;
Class> parameterTypes[] = new Class>[arguments];
for (int i = 0; i < arguments; i++) {
parameterTypes[i] = args[i].getClass();
}
return invokeConstructor(klass, args, parameterTypes);
}
/**
* Returns new instance of klazz
created using constructor
* with signature parameterTypes
and actual arguments args
.
*
* The signatures should be assignment compatible.
*
* @param the type of the object to be constructed
* @param klass the class to be constructed.
* @param args actual argument array. May be null (this will result in calling the default constructor).
* @param parameterTypes parameter types array
* @return new instance of klazz
*
* @throws NoSuchMethodException if matching constructor cannot be found
* @throws IllegalAccessException thrown on the constructor's invocation
* @throws InvocationTargetException thrown on the constructor's invocation
* @throws InstantiationException thrown on the constructor's invocation
* @see Constructor#newInstance
*/
public static T invokeConstructor(
Class klass,
Object[] args,
Class>[] parameterTypes)
throws
NoSuchMethodException,
IllegalAccessException,
InvocationTargetException,
InstantiationException {
if (parameterTypes == null) {
parameterTypes = EMPTY_CLASS_PARAMETERS;
}
if (args == null) {
args = EMPTY_OBJECT_ARRAY;
}
Constructor ctor =
getMatchingAccessibleConstructor(klass, parameterTypes);
if (null == ctor) {
throw new NoSuchMethodException(
"No such accessible constructor on object: " + klass.getName()+toMemberString(null,parameterTypes));
}
return ctor.newInstance(args);
}
// -------------------------------------------------------- Private Methods
/**
* Find an accessible constructor with compatible parameters.
* Compatible parameters mean that every method parameter is assignable from
* the given parameters. In other words, it finds constructor that will take
* the parameters given.
*
* First it checks if there is constructor matching the exact signature.
* If no such, all the constructors of the class are tested if their signatures
* are assignment compatible with the parameter types.
* The first matching constructor is returned.
*
* @param the type of the class to be inspected
* @param clazz find constructor for this class
* @param parameterTypes find method with compatible parameters
* @return a valid Constructor object. If there's no matching constructor, returns null
.
*/
static Constructor getMatchingAccessibleConstructor(
Class clazz,
Class>[] parameterTypes) {
// see if we can find the method directly
// most of the time this works and it's much faster
try {
Constructor ctor = clazz.getConstructor(parameterTypes);
try {
//
// XXX Default access superclass workaround
//
// When a public class has a default access superclass
// with public methods, these methods are accessible.
// Calling them from compiled code works fine.
//
// Unfortunately, using reflection to invoke these methods
// seems to (wrongly) to prevent access even when the method
// modifer is public.
//
// The following workaround solves the problem but will only
// work from sufficiently privilages code.
//
// Better workarounds would be greatfully accepted.
//
ctor.setAccessible(true);
} catch (SecurityException se) {
/* SWALLOW, if workaround fails don't fret. */
}
return ctor;
} catch (NoSuchMethodException e) { /* SWALLOW */
}
// search through all methods
int paramSize = parameterTypes.length;
Constructor>[] ctors = clazz.getDeclaredConstructors();
for (int i = 0, size = ctors.length; i < size; i++) {
// compare parameters
Class>[] ctorParams = ctors[i].getParameterTypes();
int ctorParamSize = ctorParams.length;
if (ctorParamSize == paramSize) {
boolean match = true;
for (int n = 0; n < ctorParamSize; n++) {
if (!isAssignmentCompatible(
ctorParams[n],
parameterTypes[n])) {
match = false;
break;
}
}
if (match) {
// get accessible version of method
Constructor> ctor = ctors[i];
if (ctor != null) {
try {
ctor.setAccessible(true);
} catch (SecurityException se) {
/* Swallow SecurityException
* TODO: Why?
*/
}
@SuppressWarnings("unchecked")
// Class.getConstructors() actually returns constructors
// of type T, so it is safe to cast.
Constructor typedCtor = (Constructor) ctor;
return typedCtor;
}
}
}
}
return null;
}
/**
* Determine whether a type can be used as a parameter in a method invocation.
* This method handles primitive conversions correctly.
*
* In order words, it will match a Boolean
to a boolean
,
* a Long
to a long
,
* a Float
to a float
,
* a Integer
to a int
,
* and a Double
to a double
.
* Now logic widening matches are allowed.
* For example, a Long
will not match a int
.
*
* @param parameterType the type of parameter accepted by the method
* @param parameterization the type of parameter being tested
*
* @return true if the assignment is compatible.
*/
private static final boolean isAssignmentCompatible(Class> parameterType, Class> parameterization) {
// try plain assignment
if (parameterType.isAssignableFrom(parameterization)) {
return true;
}
if (parameterType.isPrimitive()) {
// this method does *not* do widening - you must specify exactly
// is this the right behaviour?
Class> parameterWrapperClazz = getPrimitiveWrapper(parameterType);
if (parameterWrapperClazz != null) {
return parameterWrapperClazz.equals(parameterization);
}
}
return false;
}
/**
*
Invoke a named method whose parameter type matches the object type.
*
* The behaviour of this method is less deterministic
* than {@link #invokeExactMethod(Object object,String methodName,Object [] args)}.
* It loops through all methods with names that match
* and then executes the first it finds with compatible parameters.
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
* This is a convenient wrapper for
* {@link #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
*
*
* @param object invoke method on this object
* @param methodName get method with this name
* @param args use these arguments - treat null as empty array (passing null will
* result in calling the parameterless method with name {@code methodName}).
* @return The value returned by the invoked method
*
* @throws NoSuchMethodException if there is no such accessible method
* @throws InvocationTargetException wraps an exception thrown by the
* method invoked
* @throws IllegalAccessException if the requested method is not accessible
* via reflection
*/
public static Object invokeMethod(
Object object,
String methodName,
Object[] args)
throws
NoSuchMethodException,
IllegalAccessException,
InvocationTargetException {
if (args == null) {
args = EMPTY_OBJECT_ARRAY;
}
int arguments = args.length;
Class>[] parameterTypes = new Class[arguments];
for (int i = 0; i < arguments; i++) {
parameterTypes[i] = args[i].getClass();
}
return invokeMethod(object, methodName, args, parameterTypes);
}
/**
* Invoke a named method whose parameter type matches the object type.
*
* The behaviour of this method is less deterministic
* than {@link
* #invokeExactMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
* It loops through all methods with names that match
* and then executes the first it finds with compatible parameters.
*
* This method supports calls to methods taking primitive parameters
* via passing in wrapping classes. So, for example, a Boolean
class
* would match a boolean
primitive.
*
*
* @param object invoke method on this object
* @param methodName get method with this name
* @param args use these arguments - treat null as empty array (passing null will
* result in calling the parameterless method with name {@code methodName}).
* @param parameterTypes match these parameters - treat null as empty array
* @return The value returned by the invoked method
*
* @throws NoSuchMethodException if there is no such accessible method
* @throws InvocationTargetException wraps an exception thrown by the
* method invoked
* @throws IllegalAccessException if the requested method is not accessible
* via reflection
*/
public static Object invokeMethod(
Object object,
String methodName,
Object[] args,
Class>[] parameterTypes)
throws
NoSuchMethodException,
IllegalAccessException,
InvocationTargetException {
if (parameterTypes == null) {
parameterTypes = EMPTY_CLASS_PARAMETERS;
}
if (args == null) {
args = EMPTY_OBJECT_ARRAY;
}
Method method = getMatchingAccessibleMethod(
object.getClass(),
methodName,
parameterTypes);
if (method == null) {
throw new NoSuchMethodException("No such accessible method: " +
methodName + "() on object: " + object.getClass().getName()+" with matched parameter type: "+toMemberString(methodName,parameterTypes));
}
return method.invoke(object, args);
}
/**
* Find an accessible method that matches the given name and has compatible parameters.
* Compatible parameters mean that every method parameter is assignable from
* the given parameters.
* In other words, it finds a method with the given name
* that will take the parameters given.
*
*
This method is slightly undeterministic since it loops
* through methods names and return the first matching method.
*
* This method is used by
* {@link
* #invokeMethod(Object object,String methodName,Object [] args,Class[] parameterTypes)}.
*
*
This method can match primitive parameter by passing in wrapper classes.
* For example, a Boolean
will match a primitive boolean
* parameter.
*
* @param clazz find method in this class
* @param methodName find method with this name
* @param parameterTypes find method with compatible parameters
* @return The accessible method
*/
static Method getMatchingAccessibleMethod(
Class> clazz,
String methodName,
Class>[] parameterTypes) {
// see if we can find the method directly
// most of the time this works and it's much faster
try {
Method method = clazz.getMethod(methodName, parameterTypes);
setMethodAccessible(method); // Default access superclass workaround
return method;
} catch (NoSuchMethodException e) { /* SWALLOW */ }
// search through all methods
int paramSize = parameterTypes.length;
Method bestMatch = null;
Method[] methods = allDeclaredMethodOf(clazz);
float bestMatchCost = Float.MAX_VALUE;
float myCost = Float.MAX_VALUE;
for (int i = 0, size = methods.length; i < size ; i++) {
if (methods[i].getName().equals(methodName)) {
// compare parameters
Class>[] methodsParams = methods[i].getParameterTypes();
int methodParamSize = methodsParams.length;
if (methodParamSize == paramSize) {
boolean match = true;
for (int n = 0 ; n < methodParamSize; n++) {
if (!isAssignmentCompatible(methodsParams[n], parameterTypes[n])) {
match = false;
break;
}
}
if (match) {
// get accessible version of method
Method method = methods[i];
if (method != null) {
setMethodAccessible(method); // Default access superclass workaround
myCost = getTotalTransformationCost(parameterTypes,method.getParameterTypes());
if ( myCost < bestMatchCost ) {
bestMatch = method;
bestMatchCost = myCost;
}
}
}
}
}
}
return bestMatch;
}
// Superclasses/Superinterfaces
// ----------------------------------------------------------------------
/**
*
Gets a {@code List} of superclasses for the given class.
*
* @param cls the class to look up, may be {@code null}
* @return the {@code List} of superclasses in order going up from this one
* {@code null} if null input
*/
private static List> getAllSuperclasses(final Class> cls) {
if (cls == null) {
return null;
}
final List> classes = new ArrayList<>();
Class> superclass = cls.getSuperclass();
while (superclass != null) {
classes.add(superclass);
superclass = superclass.getSuperclass();
}
return classes;
}
private static Method[] allDeclaredMethodOf(Class> clazz) {
// Address methods in superclasses
FluentIterable methods = FluentIterable.from(Lists.newArrayList(clazz.getDeclaredMethods()));
for (final Class> superClass : getAllSuperclasses(clazz)) {
methods = methods.append(superClass.getDeclaredMethods());
}
return methods.toArray(Method.class);
}
/**
* Try to make the method accessible
* @param method The source arguments
*/
private static void setMethodAccessible(Method method) {
if (!method.isAccessible()) {
method.setAccessible(true);
}
}
/**
* Returns the sum of the object transformation cost for each class in the source
* argument list.
* @param srcArgs The source arguments
* @param destArgs The destination arguments
* @return The total transformation cost
*/
private static float getTotalTransformationCost(Class>[] srcArgs, Class>[] destArgs) {
float totalCost = 0.0f;
for (int i = 0; i < srcArgs.length; i++) {
Class> srcClass, destClass;
srcClass = srcArgs[i];
destClass = destArgs[i];
totalCost += getObjectTransformationCost(srcClass, destClass);
}
return totalCost;
}
/**
* Gets the number of steps required needed to turn the source class into the
* destination class. This represents the number of steps in the object hierarchy
* graph.
* @param srcClass The source class
* @param destClass The destination class
* @return The cost of transforming an object
*/
private static float getObjectTransformationCost(Class> srcClass, Class> destClass) {
float cost = 0.0f;
while (srcClass != null && !destClass.equals(srcClass)) {
if (destClass.isPrimitive()) {
Class> destClassWrapperClazz = getPrimitiveWrapper(destClass);
if (destClassWrapperClazz != null && destClassWrapperClazz.equals(srcClass)) {
cost += 0.25f;
break;
}
}
if (destClass.isInterface() && isAssignmentCompatible(destClass,srcClass)) {
// slight penalty for interface match.
// we still want an exact match to override an interface match, but
// an interface match should override anything where we have to get a
// superclass.
cost += 0.25f;
break;
}
cost++;
srcClass = srcClass.getSuperclass();
}
/*
* If the destination class is null, we've travelled all the way up to
* an Object match. We'll penalize this by adding 1.5 to the cost.
*/
if (srcClass == null) {
cost += 1.5f;
}
return cost;
}
/**
* Gets the wrapper object class for the given primitive type class.
* For example, passing boolean.class
returns Boolean.class
* @param primitiveType the primitive type class for which a match is to be found
* @return the wrapper type associated with the given primitive
* or null if no match is found
*/
private static Class> getPrimitiveWrapper(Class> primitiveType) {
// does anyone know a better strategy than comparing names?
if (boolean.class.equals(primitiveType)) {
return Boolean.class;
} else if (float.class.equals(primitiveType)) {
return Float.class;
} else if (long.class.equals(primitiveType)) {
return Long.class;
} else if (int.class.equals(primitiveType)) {
return Integer.class;
} else if (short.class.equals(primitiveType)) {
return Short.class;
} else if (byte.class.equals(primitiveType)) {
return Byte.class;
} else if (double.class.equals(primitiveType)) {
return Double.class;
} else if (char.class.equals(primitiveType)) {
return Character.class;
} else {
return null;
}
}
private static String typeNameOf(Class> clazz) {
if (clazz.isArray()) {
try {
Class> cl = clazz;
int dimensions = 0;
while (cl.isArray()) {
dimensions++;
cl = cl.getComponentType();
}
StringBuilder sb = new StringBuilder();
sb.append(cl.getName());
for (int i = 0; i < dimensions; i++) {
sb.append("[]");
}
return sb.toString();
} catch (Throwable e) { /*FALLTHRU*/ }
}
return clazz.getName();
}
static String toMemberString(String name, Class>[]parameterTypes) {
try {
StringBuilder sb = new StringBuilder();
if(null!=name) {
sb.append(name);
}
sb.append('(');
for (int j = 0; j < parameterTypes.length; j++) {
sb.append(typeNameOf(parameterTypes[j]));
if (j < (parameterTypes.length - 1))
sb.append(",");
}
sb.append(')');
return sb.toString();
} catch (Exception e) {
return "<" + e + ">";
}
}
/**
* Assert that the given String contains valid text content; that is, it must not
* be {@code null} and must contain at least one non-whitespace character.
* Assert.hasText(name, "'name' must not be empty");
* @param text the String to check
* @param message the exception message to use if the assertion fails
* @throws IllegalArgumentException if the text does not contain valid text content
* @see StringUtils#hasText
*/
static void hasText(String str, String message) {
checkArgument((str != null && !str.isEmpty() && CharMatcher.whitespace().negate().indexIn(str)>=0));
}
/**
* Convert a {@code String} array into a delimited {@code String} (e.g. CSV).
* Useful for {@code toString()} implementations.
* @param arr the array to display (potentially {@code null} or empty)
* @param delim the delimiter to use (typically a ",")
* @return the delimited {@code String}
*/
static String arrayToDelimitedString(Object[] arr, String delim) {
if ((arr == null || arr.length == 0)) {
return "";
}
if (arr.length == 1) {
return ObjectUtils.nullSafeToString(arr[0]);
}
return Joiner.on(delim).join(Iterables.transform(Arrays.asList(arr), STRING_VALUEOF_FUN));
}
/**
* Convert a {@code String} array into a comma delimited {@code String}
* (i.e., CSV).
*
Useful for {@code toString()} implementations.
* @param arr the array to display (potentially {@code null} or empty)
* @return the delimited {@code String}
*/
static String arrayToCommaDelimitedString(Object[] arr) {
return arrayToDelimitedString(arr, ",");
}
static Class>[] nonnullTypesOf(Object... args){
if(null != args) {
Class>[] argTypes = new Class>[args.length];
for(int i=0;i[] nonnullTypesOf(Class>...argTypes){
if(null != argTypes) {
for(int i=0;i
* 优先查找 {@link AoCacheable}注解,如果没有找到,
* 则查找所有注解,
* 如果找到第一个将{@link AoCacheable}作为元注解的注解,
* 则将之与元注解一起合成为{@link AoCacheable}注解返回
* 仍然没有找到则返回{@code null}
* @param element 可注解实例
*/
static AoCacheable readAoCacheable(AnnotatedElement element) {
/** 从注解中获取缓存配置 */
AoCacheable annot = READ_AOCACHEABLE_FUN.apply(element);
if(null != annot) {
return annot;
}
return FluentIterable.from(element.getAnnotations())
.transform(READ_AOCACHEABLE_FUN)
.filter(Predicates.notNull())
.first()
.orNull();
}
private static final Function