/*
* Copyright (C) 2012 uPhyca Inc.
*
* Licensed 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 com.uphyca.testing;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public abstract class DelegateFactory {
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface DeclaredIn {
String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Parameter {
boolean isStatic() default false;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Delegate {
}
private static final class InvocationHandlerImpl implements InvocationHandler {
final Object mDelegate;
final Class<?> mClass;
final Map<Method, Method> mMethodCache;
final Map<Method, Field> mFieldCache;
final Map<Method, Object> mNegativeCache;
private final static Object UNRESOLVED = new Object();
InvocationHandlerImpl(Class<?> receiver,
Class<?> delegateClass,
Object delegateInstance) {
mDelegate = delegateInstance;
mClass = delegateClass;
mMethodCache = new ConcurrentHashMap<Method, Method>();
mFieldCache = new ConcurrentHashMap<Method, Field>();
mNegativeCache = new ConcurrentHashMap<Method, Object>();
}
public final Object invoke(Object target,
Method method,
Object[] args) throws Throwable {
if (isParameter(method)) {
return obtainField(method,
args);
}
Method delegateMethod = getMethodFromCache(method);
if (!hasDelegateMethod(delegateMethod)) {
return unresolved(method.getReturnType());
}
Object receiver = null;
boolean staticMethod = isMethodStatic(delegateMethod);
if (!staticMethod) {
receiver = mDelegate;
}
boolean invoke = canInvoke(staticMethod,
receiver);
if (!invoke) {
return null;
}
Object res = delegateMethod.invoke(receiver,
args);
if (method.getReturnType().isAnnotationPresent(Delegate.class)) {
return DelegateFactory.create(method.getReturnType(),
res);
}
return res;
}
private Object obtainField(Method method,
Object[] args) throws IllegalArgumentException, IllegalAccessException {
Field field = getFieldFromCache(method);
if (field == null)
return null;
if (args == null || args.length == 0) {
Object f = field.get(mDelegate);
if (method.getReturnType().isAnnotationPresent(Delegate.class)) {
return DelegateFactory.create(method.getReturnType(),
f);
}
return f;
}
field.set(mDelegate,
args[0]);
return null;
}
private Object unresolved(Class<?> returnType) {
if (!returnType.isPrimitive())
return null;
if (returnType.equals(Boolean.TYPE))
return false;
if (returnType.equals(Byte.TYPE))
return (byte) 0;
if (returnType.equals(Short.TYPE))
return (short) 0;
if (returnType.equals(Integer.TYPE))
return 0;
if (returnType.equals(Long.TYPE))
return 0L;
if (returnType.equals(Float.TYPE))
return 0F;
if (returnType.equals(Double.TYPE))
return 0D;
return null;
}
private Method getMethodFromCache(Method method) throws SecurityException {
Method delegate = mMethodCache.get(method);
if (delegate != null)
return delegate;
if (mNegativeCache.containsKey(method)) {
return null;
}
delegate = getDelegateMethod(method,
mClass);
if (delegate == null) {
mNegativeCache.put(method,
UNRESOLVED);
return null;
}
makeAccessible(delegate);
mMethodCache.put(method,
delegate);
return delegate;
}
private Field getFieldFromCache(Method method) throws SecurityException {
Field field = mFieldCache.get(method);
if (field != null)
return field;
if (mNegativeCache.containsKey(method)) {
return null;
}
field = getField(method,
mClass);
if (field == null) {
mNegativeCache.put(method,
UNRESOLVED);
return null;
}
makeAccessible(field);
mFieldCache.put(method,
field);
return field;
}
}
static final boolean canInvoke(boolean staticMethod,
Object receiver) {
if (staticMethod) {
return true;
}
boolean b = receiver != null;
return b;
}
static final <T> T create(Class<T> type,
Class<?> delegateClass,
Object delegateInstance) {
if (type == null) {
throw new IllegalArgumentException("type must not be null");
}
if (delegateClass == null) {
throw new IllegalArgumentException("delegateClass must not be null");
}
InvocationHandler handler = new InvocationHandlerImpl(type, delegateClass, delegateInstance);
ClassLoader classLoader = type.getClassLoader();
Class<?>[] types = new Class[] { type };
Object proxy = Proxy.newProxyInstance(classLoader,
types,
handler);
T t = type.cast(proxy);
return t;
}
public static final <T> T create(Class<T> type,
Object delegate) {
if (delegate == null) {
throw new IllegalArgumentException("delegate must not be null");
}
T t = create(type,
delegate.getClass(),
delegate);
return t;
}
public static final <T> T create(Class<T> type,
String delegateClassName,
Object delegate) throws ClassNotFoundException {
return create(type,
delegateClassName,
type.getClassLoader(),
delegate);
}
public static final <T> T create(Class<T> type,
String delegateClassName,
ClassLoader classLoader,
Object delegate) throws ClassNotFoundException {
if (delegateClassName == null) {
throw new IllegalArgumentException("delegateClassName must not be null");
}
Class<?> delegateClass = classLoader.loadClass(delegateClassName);
T t = create(type,
delegateClass,
delegate);
return t;
}
static final Method getDeclaredMethod(Class<?> type,
String name,
Class<?>[] params) {
for (Class<?> c = type; c != null; c = c.getSuperclass()) {
Method[] methods = c.getDeclaredMethods();
Method m = getMostMatchMethod(name,
params,
methods);
boolean b = m != null;
if (b) {
return m;
}
}
return null;
}
static final Method getDelegateMethod(Method method,
Class<?> clazz) throws SecurityException {
String name = method.getName();
Class<?>[] params = method.getParameterTypes();
Method delegateMethod;
try {
delegateMethod = clazz.getMethod(name,
params);
return delegateMethod;
} catch (NoSuchMethodException e) {
}
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
for (int i = 0, num = parameterAnnotations.length; i < num; ++i) {
Annotation[] annotations = parameterAnnotations[i];
if (annotations.length <= 0)
continue;
for (int j = 0, jnum = annotations.length; j < jnum; ++j) {
Annotation annon = annotations[j];
Class<?> annonType = annon.annotationType();
if (!DeclaredIn.class.equals(annonType))
continue;
DeclaredIn t = (DeclaredIn) annon;
try {
Class<?> type = DelegateFactory.class.getClassLoader().loadClass(t.value());
params[i] = type;
continue;
} catch (ClassNotFoundException e) {
params[i] = Object.class;
//throw new RuntimeException(e);
}
}
}
delegateMethod = getDeclaredMethod(clazz,
name,
params);
return delegateMethod;
}
static final Field getField(Method method,
Class<?> clazz) throws SecurityException {
String name = method.getName();
String fieldName;
if (name.startsWith("set")) {
fieldName = name.substring(3);
} else if (name.startsWith("is")) {
fieldName = name.substring(2);
} else if (name.startsWith("get")) {
fieldName = name.substring(3);
} else {
throw new IllegalArgumentException();
}
Field field = null;
if (method.getAnnotation(Parameter.class).isStatic()) {
try {
field = clazz.getField(fieldName);
return field;
} catch (NoSuchFieldException e) {
}
return null;
} else {
String canonicalName = fieldName.substring(0,
1).toLowerCase() + fieldName.substring(1);
for (Class<?> cls = clazz; cls != null; cls = cls.getSuperclass()) {
try {
field = clazz.getDeclaredField(canonicalName);
} catch (NoSuchFieldException e) {
}
if (field != null) {
return field;
}
}
}
return null;
}
static final Method getMostMatchMethod(String name,
Class<?>[] params,
Method[] methods) {
for (Method m : methods) {
boolean b = isSignatureMatches(m,
name,
params);
if (b) {
return m;
}
}
return null;
}
static final boolean hasDelegateMethod(Method m) {
return m != null;
}
static final boolean isParameter(Method m) {
return m.isAnnotationPresent(Parameter.class);
}
static final boolean isMethodStatic(Method m) {
int modifiers = m.getModifiers();
boolean b = Modifier.isStatic(modifiers);
return b;
}
static final boolean isSignatureMatches(Method m,
String name,
Class<?>[] params) {
boolean b;
String n = m.getName();
b = n.equals(name);
if (!b) {
return false;
}
Class<?>[] delegateParams = m.getParameterTypes();
b = isParameterMatches(params,
delegateParams);
return b;
}
static final boolean isParameterMatches(Class<?>[] params,
Class<?>[] delegateParams) {
// if (Arrays.equals(params, delegateParams))
// return true;
if (params.length != delegateParams.length) {
return false;
}
for (int i = 0, num = params.length; i < num; ++i) {
if (params[i].isPrimitive()) {
if (!params[i].equals(delegateParams[i])) {
return false;
}
} else if (!delegateParams[i].isAssignableFrom(params[i])) {
return false;
}
}
return true;
}
static final void makeAccessible(Method m) {
if (m == null) {
return;
}
if (m.isAccessible()) {
return;
}
m.setAccessible(true);
}
static final void makeAccessible(Field f) {
if (f == null) {
return;
}
if (f.isAccessible()) {
return;
}
f.setAccessible(true);
}
}