/*
* Copyright 2016, Stuart Douglas, and individual contributors as indicated
* by the @authors tag.
*
* 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 org.fakereplace.reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import javassist.bytecode.AccessFlag;
import javassist.bytecode.Descriptor;
import org.fakereplace.core.Constants;
import org.fakereplace.data.ClassData;
import org.fakereplace.data.ClassDataStore;
import org.fakereplace.data.MemberType;
import org.fakereplace.data.MethodData;
import org.fakereplace.data.ModifiedMethod;
import org.fakereplace.util.DescriptorUtils;
import sun.reflect.Reflection;
/**
* This class has some method related reflection calls delegated to it at
* runtime by bytecode manipulation
*
* @author stuart
*/
public class MethodReflection {
public static int getModifiers(Method method) {
if (method.isAnnotationPresent(ModifiedMethod.class)) {
return method.getModifiers() | Modifier.FINAL;
}
return method.getModifiers();
}
public static Object invoke(Method method, Object instance, Object[] args) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
if (!Modifier.isStatic(method.getModifiers())) {
MethodData info = ClassDataStore.instance().getMethodInformation(method.getDeclaringClass().getName());
try {
Method invoke = info.getMethodToInvoke(method.getDeclaringClass());
Object[] newAgrs = prependInstanceToParams(instance, args);
return invokeWithPermissionCheck(method, invoke, null, newAgrs);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
return invokeWithPermissionCheck(method, method, instance, args);
}
private static Object invokeWithPermissionCheck(final Method permissionCheckMethod, final Method method, final Object instance, final Object[] params) throws IllegalAccessException, InvocationTargetException {
if (!Modifier.isPublic(permissionCheckMethod.getModifiers()) && !permissionCheckMethod.isAccessible()) {
Class<?> caller = sun.reflect.Reflection.getCallerClass(2);
Reflection.ensureMemberAccess(caller, permissionCheckMethod.getDeclaringClass(), null, permissionCheckMethod.getModifiers());
try {
//we need to lookup a new method
//which is very yuck
final Method lookedUpMethod = method.getDeclaringClass().getDeclaredMethod(method.getName(), method.getParameterTypes());
lookedUpMethod.setAccessible(true);
return lookedUpMethod.invoke(instance, params);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
return method.invoke(instance, params);
}
}
public static Method[] getDeclaredMethods(Class<?> clazz) {
try {
ClassData cd = ClassDataStore.instance().getModifiedClassData(clazz.getClassLoader(), Descriptor.toJvmName(clazz.getName()));
if (cd == null || !cd.isReplaceable()) {
return clazz.getDeclaredMethods();
}
Method[] meth = clazz.getDeclaredMethods();
List<Method> visible = new ArrayList<Method>(meth.length);
for (int i = 0; i < meth.length; ++i) {
MethodData mData = cd.getData(meth[i]);
if (mData == null || mData.getType() == MemberType.NORMAL) {
visible.add(meth[i]);
}
}
for (MethodData i : cd.getMethods()) {
if (i.getType() == MemberType.FAKE) {
Class<?> c = clazz.getClassLoader().loadClass(i.getClassName());
visible.add(i.getMethod(c));
}
}
Method[] ret = new Method[visible.size()];
for (int i = 0; i < visible.size(); ++i) {
ret[i] = visible.get(i);
}
return ret;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Method[] getMethods(Class<?> clazz) {
try {
ClassData cd = ClassDataStore.instance().getModifiedClassData(clazz.getClassLoader(), Descriptor.toJvmName(clazz.getName()));
if (cd == null) {
return clazz.getMethods();
}
Method[] meth = clazz.getMethods();
List<Method> visible = new ArrayList<Method>(meth.length);
for (int i = 0; i < meth.length; ++i) {
MethodData mData = cd.getData(meth[i]);
if (mData == null || mData.getType() == MemberType.NORMAL) {
visible.add(meth[i]);
}
}
ClassData cta = cd;
while (cta != null) {
if (cta.isReplaceable()) {
for (MethodData i : cta.getMethods()) {
if (i.getType() == MemberType.FAKE && AccessFlag.isPublic(i.getAccessFlags())) {
Class<?> c = clazz.getClassLoader().loadClass(i.getClassName());
visible.add(i.getMethod(c));
} else if (i.getType() == MemberType.REMOVED) {
Class<?> c = clazz.getClassLoader().loadClass(i.getClassName());
visible.remove(i.getMethod(c));
}
}
}
cta = cta.getSuperClassInformation();
}
Method[] ret = new Method[visible.size()];
for (int i = 0; i < visible.size(); ++i) {
ret[i] = visible.get(i);
}
return ret;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static Method getMethod(Class<?> clazz, String name, Class<?>... parameters) throws NoSuchMethodException {
ClassData cd = ClassDataStore.instance().getModifiedClassData(clazz.getClassLoader(), Descriptor.toJvmName(clazz.getName()));
if (cd == null) {
Method meth = clazz.getMethod(name, parameters);
return meth;
}
String args = '(' + DescriptorUtils.classArrayToDescriptorString(parameters) + ')';
MethodData md = cd.getMethodData(name, args);
Class<?> superClass = clazz;
while (superClass.getSuperclass() != null && md == null && superClass != Object.class) {
superClass = superClass.getSuperclass();
cd = ClassDataStore.instance().getModifiedClassData(superClass.getClassLoader(), Descriptor.toJvmName(superClass.getName()));
if (cd != null) {
md = cd.getMethodData(name, args);
}
}
if (md == null) {
Method meth = clazz.getMethod(name, parameters);
return meth;
}
switch (md.getType()) {
case NORMAL:
Method meth = superClass.getMethod(name, parameters);
return meth;
case FAKE:
try {
if (!AccessFlag.isPublic(md.getAccessFlags())) {
throw new NoSuchMethodException(clazz.getName() + "." + name);
}
Class<?> c = superClass.getClassLoader().loadClass(md.getClassName());
meth = c.getMethod(name, parameters);
return meth;
} catch (NoSuchMethodException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
throw new NoSuchMethodException();
}
public static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameters) throws NoSuchMethodException {
ClassData cd = ClassDataStore.instance().getModifiedClassData(clazz.getClassLoader(), Descriptor.toJvmName(clazz.getName()));
if (cd == null || !cd.isReplaceable()) {
Method meth = clazz.getDeclaredMethod(name, parameters);
return meth;
}
String args;
if (parameters != null) {
args = '(' + DescriptorUtils.classArrayToDescriptorString(parameters) + ')';
} else {
args = "()";
}
MethodData md = cd.getMethodData(name, args);
if (md == null) {
Method meth = clazz.getDeclaredMethod(name, parameters);
return meth;
}
switch (md.getType()) {
case NORMAL:
Method meth = clazz.getDeclaredMethod(name, parameters);
return meth;
case FAKE:
try {
Class<?> c = clazz.getClassLoader().loadClass(md.getClassName());
meth = c.getDeclaredMethod(name, parameters);
return meth;
} catch (NoSuchMethodException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
throw new NoSuchMethodException();
}
public static Class<?> getDeclaringClass(Method m) {
Class<?> c = m.getDeclaringClass();
if (c.getName().startsWith(Constants.GENERATED_CLASS_PACKAGE)) {
return ClassDataStore.instance().getRealClassFromProxyName(c.getName());
}
return c;
}
public static boolean fakeCallRequired(Method method) {
return method.getDeclaringClass().getName().startsWith(Constants.GENERATED_CLASS_PACKAGE);
}
/**
* appends object to the start of the array
*/
public static Object[] prependInstanceToParams(Object object, Object[] array) {
int length = 0;
if (array != null) {
length = array.length;
}
Object[] ret = new Object[length + 1];
ret[0] = object;
for (int i = 0; i < length; ++i) {
ret[i + 1] = array[i];
}
return ret;
}
}