package org.simpleflatmapper.reflect.asm;
import org.simpleflatmapper.ow2asm.MethodVisitor;
import org.simpleflatmapper.ow2asm.Opcodes;
import org.simpleflatmapper.ow2asm.signature.SignatureReader;
import org.simpleflatmapper.ow2asm.signature.SignatureVisitor;
import org.simpleflatmapper.util.Predicate;
import org.simpleflatmapper.util.TypeHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.simpleflatmapper.ow2asm.Opcodes.BIPUSH;
import static org.simpleflatmapper.ow2asm.Opcodes.DCONST_0;
import static org.simpleflatmapper.ow2asm.Opcodes.DLOAD;
import static org.simpleflatmapper.ow2asm.Opcodes.DRETURN;
import static org.simpleflatmapper.ow2asm.Opcodes.FCONST_0;
import static org.simpleflatmapper.ow2asm.Opcodes.FLOAD;
import static org.simpleflatmapper.ow2asm.Opcodes.FRETURN;
import static org.simpleflatmapper.ow2asm.Opcodes.ICONST_0;
import static org.simpleflatmapper.ow2asm.Opcodes.ICONST_1;
import static org.simpleflatmapper.ow2asm.Opcodes.ICONST_2;
import static org.simpleflatmapper.ow2asm.Opcodes.ICONST_3;
import static org.simpleflatmapper.ow2asm.Opcodes.ICONST_4;
import static org.simpleflatmapper.ow2asm.Opcodes.ICONST_5;
import static org.simpleflatmapper.ow2asm.Opcodes.ILOAD;
import static org.simpleflatmapper.ow2asm.Opcodes.IRETURN;
import static org.simpleflatmapper.ow2asm.Opcodes.LCONST_0;
import static org.simpleflatmapper.ow2asm.Opcodes.LLOAD;
import static org.simpleflatmapper.ow2asm.Opcodes.LRETURN;
import static org.simpleflatmapper.ow2asm.Opcodes.SIPUSH;
public class AsmUtils {
public static final String ASM_DUMP_TARGET_DIR = "asm.dump.target.dir";
public static final Type[] EMPTY_TYPE_ARRAY = new Type[0];
static final Map<Class<?>, Class<?>> wrappers = new HashMap<Class<?>, Class<?>>();
static {
wrappers.put(boolean.class, Boolean.class);
wrappers.put(byte.class, Byte.class);
wrappers.put(char.class, Character.class);
wrappers.put(double.class, Double.class);
wrappers.put(float.class, Float.class);
wrappers.put(int.class, Integer.class);
wrappers.put(long.class, Long.class);
wrappers.put(short.class, Short.class);
wrappers.put(void.class, Void.class);
}
static final Map<Class<?>, String> primitivesType = new HashMap<Class<?>, String>();
static {
primitivesType.put(boolean.class, "Z");
primitivesType.put(byte.class, "B");
primitivesType.put(char.class, "C");
primitivesType.put(double.class, "D");
primitivesType.put(float.class, "F");
primitivesType.put(int.class, "I");
primitivesType.put(long.class, "J");
primitivesType.put(short.class, "S");
primitivesType.put(void.class, "V");
}
static final Map<String, String> stringToPrimitivesType = new HashMap<String, String>();
static {
stringToPrimitivesType.put("Boolean", "Z");
stringToPrimitivesType.put("Byte", "B");
stringToPrimitivesType.put("Character", "C");
stringToPrimitivesType.put("Double", "D");
stringToPrimitivesType.put("Float", "F");
stringToPrimitivesType.put("Int", "I");
stringToPrimitivesType.put("Long", "J");
stringToPrimitivesType.put("Short", "S");
}
static final Map<Class<?>, Integer> loadOps = new HashMap<Class<?>, Integer>();
static {
loadOps.put(boolean.class, ILOAD);
loadOps.put(byte.class, ILOAD);
loadOps.put(char.class, ILOAD);
loadOps.put(double.class, DLOAD);
loadOps.put(float.class, FLOAD);
loadOps.put(int.class, ILOAD);
loadOps.put(long.class, LLOAD);
loadOps.put(short.class, ILOAD);
}
static final Map<Class<?>, Integer> returnOps = new HashMap<Class<?>, Integer>();
static {
returnOps.put(boolean.class, IRETURN);
returnOps.put(byte.class, IRETURN);
returnOps.put(char.class, IRETURN);
returnOps.put(double.class, DRETURN);
returnOps.put(float.class, FRETURN);
returnOps.put(int.class, IRETURN);
returnOps.put(long.class, LRETURN);
returnOps.put(short.class, IRETURN);
}
static final Map<Class<?>, Integer> defaultValue = new HashMap<Class<?>, Integer>();
static {
defaultValue.put(boolean.class, ICONST_0);
defaultValue.put(byte.class, ICONST_0);
defaultValue.put(char.class, ICONST_0);
defaultValue.put(double.class, DCONST_0);
defaultValue.put(float.class, FCONST_0);
defaultValue.put(int.class, ICONST_0);
defaultValue.put(long.class, LCONST_0);
defaultValue.put(short.class, ICONST_0);
}
static final Set<Class<?>> primitivesClassAndWrapper = new HashSet<Class<?>>();
static {
primitivesClassAndWrapper.addAll(wrappers.keySet());
primitivesClassAndWrapper.addAll(wrappers.values());
}
static File targetDir = null;
static {
String targetDirStr = System.getProperty(ASM_DUMP_TARGET_DIR);
if (targetDirStr != null) {
targetDir = new File(targetDirStr);
targetDir.mkdirs();
}
}
public static String toAsmType(final String name) {
return name.replace('.', '/');
}
public static String toAsmType(final Type type) {
if (TypeHelper.isPrimitive(type)) {
return primitivesType.get(TypeHelper.toClass(type));
}
return toAsmType(TypeHelper.toClass(type).getName());
}
public static String toTargetTypeDeclaration(Type targetType) {
if (TypeHelper.isPrimitive(targetType)) {
return primitivesType.get(TypeHelper.toClass(targetType));
}
return toTargetTypeDeclaration(AsmUtils.toAsmType(targetType));
}
public static String toTargetTypeDeclaration(String targetType) {
if (targetType.startsWith("[")) {
return targetType;
} else {
return "L" + targetType+ ";";
}
}
public static String toGenericAsmType(final Type type) {
StringBuilder sb = new StringBuilder();
sb.append(toAsmType(type));
Type[] typeParameters = null;
if (type instanceof ParameterizedType) {
typeParameters = ((ParameterizedType) type).getActualTypeArguments();
}
if (typeParameters != null && typeParameters.length > 0) {
sb.append("<");
for(Type t : typeParameters) {
sb.append(toTargetTypeDeclaration(toGenericAsmType(t)));
}
sb.append(">");
}
return sb.toString();
}
public static byte[] writeClassToFile (final String className, final byte[] bytes) throws IOException {
return writeClassToFileInDir(className, bytes, AsmUtils.targetDir);
}
public static byte[] writeClassToFileInDir(String className, byte[] bytes, File targetDir) throws IOException {
if (targetDir != null) {
_writeClassToFileInDir(className, bytes, targetDir);
}
return bytes;
}
private static void _writeClassToFileInDir(String className, byte[] bytes, File targetDir) throws IOException {
final int lastIndex = className.lastIndexOf('.');
final String filename = className.substring(lastIndex + 1) + ".class";
final String directory = className.substring(0, lastIndex).replace('.', '/');
final File packageDir = new File(targetDir, directory);
packageDir.mkdirs();
final FileOutputStream fos = new FileOutputStream(new File(packageDir, filename ));
try {
fos.write(bytes);
} finally {
fos.close();
}
}
public static Type toGenericType(String sig, List<String> genericTypeNames, Type target) throws ClassNotFoundException {
if (sig.length() == 1) {
switch (sig.charAt(0)) {
case 'Z': return boolean.class;
case 'B': return byte.class;
case 'C': return char.class;
case 'D': return double.class;
case 'F': return float.class;
case 'I': return int.class;
case 'J': return long.class;
case 'S': return short.class;
}
}
if (sig.startsWith("+")) {
sig = sig.substring(1);
}
if (sig.startsWith("[")) {
Type componentType = toGenericType(sig.substring(1), genericTypeNames, target);
Class<?> componentClass = TypeHelper.toClass(componentType);
return Array.newInstance(componentClass, 0).getClass();
} else if (sig.startsWith("L")) {
sig = sig.substring(1);
if (sig.endsWith(";")) {
sig = sig.substring(0, sig.length() - 1);
}
} else if (sig.startsWith("T")) {
String templateType = sig.substring(1, sig.length() - (sig.endsWith(";") ? 1 : 0));
int indexOfParam = genericTypeNames.indexOf(templateType);
if (target instanceof ParameterizedType) {
return ((ParameterizedType) target).getActualTypeArguments()[indexOfParam];
} else {
// meethod parameter
return null;
}
}
int indexOf = sig.indexOf('<');
ClassLoader classLoader = TypeHelper.getClassLoader(target, Thread.currentThread().getContextClassLoader());
if (indexOf == -1) {
return Class.forName(sig.replace('/','.'), true, classLoader);
} else {
final Class<?> rawType = Class.forName(sig.substring(0, indexOf).replace('/','.'), true, classLoader);
final Type[] types = parseTypes(sig.substring(indexOf+ 1, sig.length() - 1), genericTypeNames, target);
return new ParameterizedTypeImpl(rawType, types);
}
}
private static ClassLoader getClassLoader(Type target) {
if (target == null)
return Thread.currentThread().getContextClassLoader();
Class<Object> aClass = TypeHelper.toClass(target);
if (aClass == null)
return Thread.currentThread().getContextClassLoader();
return aClass.getClassLoader();
}
public static Type findClosestPublicTypeExposing(Type type, Class<?> expose) {
return findTypeInHierarchy(type, new TypeIsPublicAndImplement(expose));
}
public static Type findTypeInHierarchy(Type type, Predicate<Type> predicate) {
if (predicate.test(type)) {
return type;
}
// check interfaces
Class<Object> targetClass = TypeHelper.toClass(type);
for(Type i : targetClass.getGenericInterfaces()) {
if (predicate.test(i)) {
return i;
}
}
Type st = targetClass.getGenericSuperclass();
if (st != null) {
return findTypeInHierarchy(st, predicate);
}
return null;
}
public static void invoke(MethodVisitor mv, Type target, Method method) {
invoke(mv, target, method.getName(), toSignature(method));
}
public static void invoke(MethodVisitor mv, Type target,
String method, String sig) {
Type publicClass = findTypeInHierarchy(target, new TypeIsPublicAndHasMethodMethod(method));
boolean isInterface = TypeHelper.toClass(publicClass).isInterface();
mv.visitMethodInsn(isInterface ? Opcodes.INVOKEINTERFACE : Opcodes.INVOKEVIRTUAL, toAsmType(publicClass), method, sig, isInterface);
}
public static Class<?> toWrapperClass(Type type) {
final Class<?> clazz = TypeHelper.toClass(type);
if (clazz.isPrimitive()) {
return wrappers.get(clazz);
} else return clazz;
}
public static String toWrapperType(Type type) {
return toAsmType(toWrapperClass(type));
}
public static List<String> extractGenericTypeNames(String sig) {
List<String> types = new ArrayList<String>();
boolean nameDetected = false;
int currentStart = -1;
for(int i = 0; i < sig.length(); i++) {
char c = sig.charAt(i);
switch(c) {
case '<' :
case ';' :
if (!nameDetected) {
nameDetected = true;
currentStart = i + 1;
}
break;
case ':' :
types.add(sig.substring(currentStart, i));
nameDetected = false;
break;
}
}
return types;
}
private static Type[] parseTypes(String sig, List<String> genericTypeNames, Type target) throws ClassNotFoundException {
List<Type> types = new ArrayList<Type>();
int genericLevel = 0;
int currentStart = 0;
for(int i = 0; i < sig.length(); i++) {
char c = sig.charAt(i);
switch(c) {
case '<': genericLevel ++; break;
case '>': genericLevel --; break;
case ';' :
if (genericLevel == 0) {
types.add(toGenericType(sig.substring(currentStart, i), genericTypeNames, target));
currentStart = i + 1;
}
break;
}
}
return types.toArray(EMPTY_TYPE_ARRAY);
}
public static List<String> extractTypeNames(String sig) {
final List<String> types = new ArrayList<String>();
SignatureReader reader = new SignatureReader(sig);
reader.accept(new SignatureVisitor(Opcodes.ASM5) {
//TypeSignature =
// visitBaseType | visitTypeVariable | visitArrayType | ( visitClassType visitTypeArgument* ( visitInnerClassType visitTypeArgument* )* visitEnd ) )
class AppendType extends SignatureVisitor {
StringBuilder sb = new StringBuilder();
int l = 0;
public AppendType() {
super(Opcodes.ASM5);
}
@Override
public void visitBaseType(char descriptor) {
if (descriptor != 'V') {
sb.append(descriptor);
visitEnd();
}
}
@Override
public void visitTypeVariable(String name) {
sb.append("T");
sb.append(name);
visitEnd();
}
@Override
public SignatureVisitor visitArrayType() {
sb.append("[");
return this;
}
@Override
public void visitClassType(String name) {
sb.append("L");
sb.append(name);
visitEnd();
}
@Override
public void visitInnerClassType(String name) {
visitClassType(name);
}
@Override
public void visitTypeArgument() {
}
@Override
public SignatureVisitor visitTypeArgument(char wildcard) {
l++;
if (sb.length() == 0) {
String t = types.remove(types.size() - 1);
if (t.endsWith(";")) {
t = t.substring(0, t.length() -1);
}
sb.append(t);
sb.append("<");
}
if (wildcard != '=') {
sb.append(wildcard);
}
return this;
}
@Override
public void visitEnd() {
if (l == 0) {
flush();
} else {
sb.append(";>");
l--;
}
}
private void flush() {
if (sb.length() >0) {
if (sb.charAt(0) == 'L' || sb.charAt(0) == 'T') {
sb.append(";");
}
types.add(sb.toString());
sb = new StringBuilder();
}
}
}
@Override
public void visitFormalTypeParameter(String name) {
}
@Override
public SignatureVisitor visitClassBound() {
return super.visitInterfaceBound();
}
@Override
public SignatureVisitor visitInterfaceBound() {
return super.visitInterfaceBound();
}
@Override
public SignatureVisitor visitParameterType() {
return new AppendType();
}
@Override
public SignatureVisitor visitReturnType() {
return new AppendType();
}
@Override
public SignatureVisitor visitExceptionType() {
return new AppendType();
}
});
return types;
}
public static String toSignature(Method exec) {
StringBuilder sb = new StringBuilder();
sb.append("(");
for(Class<?> clazz : exec.getParameterTypes()) {
sb.append(AsmUtils.toTargetTypeDeclaration(clazz));
}
sb.append(")");
sb.append(AsmUtils.toTargetTypeDeclaration(exec.getReturnType()));
return sb.toString();
}
private static class ParameterizedTypeImpl implements ParameterizedType {
private final Class<?> rawType;
private final Type[] types;
public ParameterizedTypeImpl(Class<?> rawType, Type[] types) {
this.rawType = rawType;
this.types = types;
}
@Override
public Type getRawType() {
return rawType;
}
@Override
public Type getOwnerType() {
return null;
}
@Override
public Type[] getActualTypeArguments() {
return types;
}
@Override
public String toString() {
return "ParameterizedTypeImpl{" +
"rawType=" + rawType +
", types=" + Arrays.toString(types) +
'}';
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ParameterizedType)) return false;
ParameterizedType that = (ParameterizedType) o;
if (!rawType.equals(that.getRawType())) return false;
// Probably incorrect - comparing Object[] arrays with Arrays.equals
return Arrays.equals(types, that.getActualTypeArguments()) && that.getOwnerType() == null;
}
@Override
public int hashCode() {
int result = rawType.hashCode();
result = 31 * result + Arrays.hashCode(types);
return result;
}
}
public static void addIndex(MethodVisitor mv, int i) {
switch(i) {
case 0:
mv.visitInsn(ICONST_0);
return;
case 1:
mv.visitInsn(ICONST_1);
return;
case 2:
mv.visitInsn(ICONST_2);
return;
case 3:
mv.visitInsn(ICONST_3);
return;
case 4:
mv.visitInsn(ICONST_4);
return;
case 5:
mv.visitInsn(ICONST_5);
return;
default:
if (i <= Byte.MAX_VALUE) {
mv.visitIntInsn(BIPUSH, i);
} else if (i <= Short.MAX_VALUE) {
mv.visitIntInsn(SIPUSH, i);
} else {
mv.visitLdcInsn(i);
}
}
}
private static class TypeIsPublicAndHasMethodMethod implements Predicate<Type> {
private final String method;
public TypeIsPublicAndHasMethodMethod(String method) {
this.method = method;
}
@Override
public boolean test(Type type) {
Class<?> clazz = TypeHelper.toClass(type);
if (!Modifier.isPublic(clazz.getModifiers())) {
return false;
}
for(Method m : clazz.getMethods()) {
if (m.getName().equals(method)) {
return true;
}
}
return false;
}
}
private static class TypeIsPublicAndImplement implements Predicate<Type> {
private final Class<?> expose;
public TypeIsPublicAndImplement(Class<?> expose) {
this.expose = expose;
}
@Override
public boolean test(Type type) {
Class<Object> targetClass = TypeHelper.toClass(type);
return Modifier.isPublic(targetClass.getModifiers()) && expose.isAssignableFrom(targetClass);
}
}
}