/*
* Copyright (c) 2009-2010 Ken Wenzel and Mathias Doenitz
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.parboiled.transform;
import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.parboiled.BaseParser;
import org.parboiled.ContextAware;
import org.parboiled.support.Var;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
class AsmUtils {
private static final ConcurrentMap<String, Class<?>> classForDesc = new ConcurrentHashMap<String, Class<?>>();
public static ClassReader createClassReader(@NotNull Class<?> clazz) throws IOException {
String classFilename = clazz.getName().replace('.', '/') + ".class";
InputStream inputStream = clazz.getClassLoader().getResourceAsStream(classFilename);
return new ClassReader(inputStream);
}
public static String getExtendedParserClassName(@NotNull String parserClassName) {
return parserClassName + "$$parboiled";
}
public static Class<?> getClassForInternalName(@NotNull String classDesc) {
Class<?> clazz = classForDesc.get(classDesc);
if (clazz == null) {
if (classDesc.charAt(0) == '[') {
Class<?> compType = getClassForType(Type.getType(classDesc.substring(1)));
clazz = Array.newInstance(compType, 0).getClass();
} else {
String className = classDesc.replace('/', '.');
try {
clazz = Thread.currentThread().getContextClassLoader().loadClass(className);
} catch (ClassNotFoundException e) {
throw new RuntimeException("Error loading class '" + className + "' for rule method analysis", e);
}
}
classForDesc.put(classDesc, clazz);
}
return clazz;
}
public static Class<?> getClassForType(@NotNull Type type) {
switch (type.getSort()) {
case Type.BOOLEAN:
return boolean.class;
case Type.BYTE:
return byte.class;
case Type.CHAR:
return char.class;
case Type.DOUBLE:
return double.class;
case Type.FLOAT:
return float.class;
case Type.INT:
return int.class;
case Type.LONG:
return long.class;
case Type.SHORT:
return short.class;
case Type.VOID:
return void.class;
case Type.OBJECT:
case Type.ARRAY:
return getClassForInternalName(type.getInternalName());
}
throw new IllegalStateException(); // should be unreachable
}
public static Field getClassField(@NotNull String classInternalName, @NotNull String fieldName) {
Class<?> clazz = getClassForInternalName(classInternalName);
Class<?> current = clazz;
while (true) {
try {
return current.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
current = current.getSuperclass();
if (Object.class.equals(current)) {
throw new RuntimeException(
"Field '" + fieldName + "' not found in '" + clazz + "\' or any superclass", e);
}
}
}
}
public static Method getClassMethod(@NotNull String classInternalName, @NotNull String methodName,
@NotNull String methodDesc) {
Class<?> clazz = getClassForInternalName(classInternalName);
Type[] types = Type.getArgumentTypes(methodDesc);
Class<?>[] argTypes = new Class<?>[types.length];
for (int i = 0; i < types.length; i++) {
argTypes[i] = getClassForType(types[i]);
}
Class<?> current = clazz;
while (true) {
try {
return current.getDeclaredMethod(methodName, argTypes);
} catch (NoSuchMethodException e) {
current = current.getSuperclass();
if (Object.class.equals(current)) {
throw new RuntimeException("Method '" + methodName + "' with descriptor '" +
methodDesc + "' not found in '" + clazz + "\' or any superclass", e);
}
}
}
}
public static Constructor getClassConstructor(@NotNull String classInternalName, @NotNull String constructorDesc) {
Class<?> clazz = getClassForInternalName(classInternalName);
Type[] types = Type.getArgumentTypes(constructorDesc);
Class<?>[] argTypes = new Class<?>[types.length];
for (int i = 0; i < types.length; i++) {
argTypes[i] = getClassForType(types[i]);
}
try {
return clazz.getDeclaredConstructor(argTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException("Constructor with descriptor '" + constructorDesc + "' not found in '" +
clazz, e);
}
}
/**
* Returns the class with the given name if it has already been loaded by the given class loader.
* Otherwise the method returns null.
*
* @param className the full name of the class to be loaded
* @param classLoader the class loader to use
* @return the class instance or null
*/
public static Class<?> findLoadedClass(@NotNull String className, @NotNull ClassLoader classLoader) {
try {
Class<?> classLoaderBaseClass = Class.forName("java.lang.ClassLoader");
Method findLoadedClassMethod = classLoaderBaseClass.getDeclaredMethod("findLoadedClass", String.class);
// protected method invocation
findLoadedClassMethod.setAccessible(true);
try {
return (Class<?>) findLoadedClassMethod.invoke(classLoader, className);
} finally {
findLoadedClassMethod.setAccessible(false);
}
} catch (Exception e) {
throw new RuntimeException("Could not determine whether class '" + className +
"' has already been loaded", e);
}
}
/**
* Loads the class defined with the given name and bytecode using the given class loader.
* Since package and class idendity includes the ClassLoader instance used to load a class we use reflection
* on the given class loader to define generated classes. If we used our own class loader (in order to be able
* to access the protected "defineClass" method) we would likely still be able to load generated classes,
* however, they would not have access to package-private classes and members of their super classes.
*
* @param className the full name of the class to be loaded
* @param code the bytecode of the class to load
* @param classLoader the class loader to use
* @return the class instance
*/
public static Class<?> loadClass(@NotNull String className, @NotNull byte[] code,
@NotNull ClassLoader classLoader) {
try {
Class<?> classLoaderBaseClass = Class.forName("java.lang.ClassLoader");
Method defineClassMethod = classLoaderBaseClass.getDeclaredMethod("defineClass",
String.class, byte[].class, int.class, int.class);
// protected method invocation
defineClassMethod.setAccessible(true);
try {
return (Class<?>) defineClassMethod.invoke(classLoader, className, code, 0, code.length);
} finally {
defineClassMethod.setAccessible(false);
}
} catch (Exception e) {
throw new RuntimeException("Could not load class '" + className + '\'', e);
}
}
public static InsnList createArgumentLoaders(@NotNull String methodDescriptor) {
InsnList instructions = new InsnList();
Type[] types = Type.getArgumentTypes(methodDescriptor);
for (int i = 0; i < types.length; i++) {
instructions.add(new VarInsnNode(getLoadingOpcode(types[i]), i + 1));
}
return instructions;
}
public static int getLoadingOpcode(@NotNull Type argType) {
switch (argType.getSort()) {
case Type.BOOLEAN:
case Type.BYTE:
case Type.CHAR:
case Type.SHORT:
case Type.INT:
return Opcodes.ILOAD;
case Type.DOUBLE:
return Opcodes.DLOAD;
case Type.FLOAT:
return Opcodes.FLOAD;
case Type.LONG:
return Opcodes.LLOAD;
case Type.OBJECT:
case Type.ARRAY:
return Opcodes.ALOAD;
default:
throw new IllegalStateException();
}
}
/**
* Determines whether the class with the given descriptor is assignable to the given type.
*
* @param classInternalName the class descriptor
* @param type the type
* @return true if the class with the given descriptor is assignable to the given type
*/
public static boolean isAssignableTo(@NotNull String classInternalName, @NotNull Class<?> type) {
return type.isAssignableFrom(getClassForInternalName(classInternalName));
}
public static boolean isBooleanValueOfZ(@NotNull AbstractInsnNode insn) {
if (insn.getOpcode() != Opcodes.INVOKESTATIC) return false;
MethodInsnNode mi = (MethodInsnNode) insn;
return isBooleanValueOfZ(mi.owner, mi.name, mi.desc);
}
public static boolean isBooleanValueOfZ(@NotNull String methodOwner, @NotNull String methodName,
@NotNull String methodDesc) {
return "java/lang/Boolean".equals(methodOwner) && "valueOf".equals(methodName) &&
"(Z)Ljava/lang/Boolean;".equals(methodDesc);
}
public static boolean isActionRoot(@NotNull AbstractInsnNode insn) {
if (insn.getOpcode() != Opcodes.INVOKESTATIC) return false;
MethodInsnNode mi = (MethodInsnNode) insn;
return isActionRoot(mi.owner, mi.name);
}
public static boolean isActionRoot(@NotNull String methodOwner, @NotNull String methodName) {
return "ACTION".equals(methodName) && isAssignableTo(methodOwner, BaseParser.class);
}
public static boolean isVarRoot(@NotNull AbstractInsnNode insn) {
if (insn.getOpcode() != Opcodes.INVOKESPECIAL) return false;
MethodInsnNode mi = (MethodInsnNode) insn;
return isVarRoot(mi.owner, mi.name, mi.desc);
}
public static boolean isVarRoot(@NotNull String methodOwner, @NotNull String methodName,
@NotNull String methodDesc) {
return "<init>".equals(methodName) && "(Ljava/lang/Object;)V".equals(methodDesc) &&
isAssignableTo(methodOwner, Var.class);
}
public static boolean isCallOnContextAware(@NotNull AbstractInsnNode insn) {
if (insn.getOpcode() != Opcodes.INVOKEVIRTUAL && insn.getOpcode() != Opcodes.INVOKEINTERFACE) return false;
MethodInsnNode mi = (MethodInsnNode) insn;
return isAssignableTo(mi.owner, ContextAware.class);
}
}