/** * Copyright 2014 SAP AG * * 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.aim.mainagent.utils; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import javassist.ClassPool; import javassist.CtBehavior; import javassist.CtClass; import org.aim.api.exceptions.InstrumentationException; import org.lpe.common.util.LpeStringUtils; /** * Utility class for dynamic instrumentation. * * @author Alexander Wert * */ public final class Utils { private static final Map<String, Class<?>> primitiveTypes; static { primitiveTypes = new HashMap<>(); primitiveTypes.put("B", byte.class); primitiveTypes.put("C", char.class); primitiveTypes.put("D", double.class); primitiveTypes.put("F", float.class); primitiveTypes.put("I", int.class); primitiveTypes.put("J", long.class); primitiveTypes.put("S", short.class); primitiveTypes.put("Z", boolean.class); primitiveTypes.put("V", void.class); } /** * Private constructor due to utility class. */ private Utils() { } /** * * @param clazz * class to check * @return returns true, if given class is a normal class, meaning it is not * an interface, not an array, not an enum, etc. */ public static boolean isNormalClass(Class<?> clazz) { try { return !clazz.isLocalClass() && !clazz.isArray() && !clazz.isInterface() && !clazz.isPrimitive() && !clazz.isMemberClass() && !clazz.isEnum() && !clazz.isAnnotation() && !clazz.isAnonymousClass() && !clazz.isSynthetic() && clazz.getCanonicalName() != null; } catch (Throwable e) { return false; } } /** * * @param clazz * class to check * @return true if given class has been loaded by the bootstrap class loader */ public static boolean isBootstrapClass(Class<?> clazz) { try { return clazz.getClassLoader() == null; } catch (Throwable e) { return false; } } /** * * @param clazz * class to check * @return returns true, if given class is an interface */ public static boolean isInterface(Class<?> clazz) { try { return clazz.isInterface() && !clazz.isAnnotation() && clazz.getCanonicalName() != null; } catch (Throwable e) { return false; } } /** * * @param ctClass * class where to search the method * @param methodName * name of the method of interest * @return the CtMethod of the passed CtClass for the given methodName or * null if the given method could not have been found in the given * class * @throws InstrumentationException * if ctMethod cannot be retrieved */ public static CtBehavior getCtBehaviour(CtClass ctClass, String methodName) throws InstrumentationException { if (ctClass == null || ctClass.getDeclaredMethods() == null) { return null; } String shortMethodName = ""; try { methodName = methodName.trim(); int firstBraceIndex = methodName.indexOf('('); String parameterPart = methodName.substring(firstBraceIndex); String methodFirstPart = methodName.substring(0, firstBraceIndex); int lastDotIndex = methodFirstPart.lastIndexOf('.'); if (lastDotIndex >= 0) { shortMethodName += methodFirstPart.substring(lastDotIndex + 1) + parameterPart; } else { shortMethodName += methodFirstPart + parameterPart; } } catch (Exception e) { throw new InstrumentationException( "Invalid format of the passed method name!", e); } for (final CtBehavior method : ctClass.getDeclaredBehaviors()) { if (method.getLongName().endsWith(shortMethodName)) { return method; } } return null; } /** * Inserts local variables into the given method. * * @param ctBehaviour * the CtMethod where to inject local variable * @param variables * map of variables to inject. * @throws InstrumentationException * if variables cannot be inserted */ public static void insertMethodLocalVariables(CtBehavior ctBehaviour, Map<String, Class<?>> variables) throws InstrumentationException { try { for (String vName : variables.keySet()) { Class<?> type = variables.get(vName); ClassPool pool = ctBehaviour.getDeclaringClass().getClassPool(); if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Boolean")) { ctBehaviour.addLocalVariable(vName, CtClass.booleanType); } else if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Byte")) { ctBehaviour.addLocalVariable(vName, CtClass.byteType); } else if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Character")) { ctBehaviour.addLocalVariable(vName, CtClass.charType); } else if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Double")) { ctBehaviour.addLocalVariable(vName, CtClass.doubleType); } else if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Float")) { ctBehaviour.addLocalVariable(vName, CtClass.floatType); } else if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Integer")) { ctBehaviour.addLocalVariable(vName, CtClass.intType); } else if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Long")) { ctBehaviour.addLocalVariable(vName, CtClass.longType); } else if (type.getCanonicalName().equalsIgnoreCase( "java.lang.Short")) { ctBehaviour.addLocalVariable(vName, CtClass.shortType); } else { ctBehaviour.addLocalVariable(vName, pool.getCtClass(type.getCanonicalName())); } } } catch (Exception e) { throw new InstrumentationException( "failed inserting local variables!", e); } } /** * Returns the method signature for the given java reflection method * instance. * * @param method * method for which the signature shell be constructed * @param includeFullClassName * specifies whether to include the full qualified name of the * declaring class * @return string representation of the signature */ public static String getMethodSignature(Method method, boolean includeFullClassName) { String methodName = method.getName(); String className = ""; if (includeFullClassName) { className = method.getDeclaringClass().getName() + "."; } String parameterList = ""; boolean first = true; for (Class<?> pType : method.getParameterTypes()) { if (!first) { parameterList += ","; } else { first = false; } String arrayBraces = ""; while (pType.isArray()) { pType = pType.getComponentType(); arrayBraces += "[]"; } parameterList += pType.getName() + arrayBraces; } return className + methodName + "(" + parameterList + ")"; } /** * Returns the constructor signature for the given java reflection * constructor instance. * * @param constructor * constructor for which the signature shell be constructed * @param includeFullClassName * specifies whether to include the full qualified name of the * declaring class * @return string representation of the signature */ public static String getMethodSignature(Constructor<?> constructor, boolean includeFullClassName) { String methodName = constructor.getName(); String className = ""; if (includeFullClassName) { className = constructor.getDeclaringClass().getName() + "."; } String parameterList = ""; boolean first = true; for (Class<?> pType : constructor.getParameterTypes()) { if (!first) { parameterList += ","; } else { first = false; } parameterList += pType.getName(); } return className + methodName + "(" + parameterList + ")"; } /** * Extracts the parameter types of the given method signature. * * @param methodSignature * method signature to analyze. * @return array of classes representing the parameter types. * @throws ClassNotFoundException * if a parameter type class cannot be found */ public static Class<?>[] getParameterTypes(String methodSignature) throws ClassNotFoundException { methodSignature = methodSignature.replaceAll("\\s", ""); String parameterList = methodSignature.substring( methodSignature.indexOf('(') + 1, methodSignature.lastIndexOf(')')); if (parameterList.isEmpty()) { return new Class<?>[0]; } else { String[] parameters = parameterList.split(","); Class<?>[] parameterTypes = new Class<?>[parameters.length]; for (int i = 0; i < parameters.length; i++) { String parameterType = parameters[i]; String bytecodeNotationParameterType = LpeStringUtils .convertTypeToNativeFormat(parameterType); String bcNotationWithDots = bytecodeNotationParameterType.replace("/", "."); if (primitiveTypes.containsKey(bcNotationWithDots)) { // primitive type parameterTypes[i] = primitiveTypes .get(bcNotationWithDots); } else if (bcNotationWithDots.startsWith("[")) { // array type parameterTypes[i] = Class .forName(bcNotationWithDots); } else { parameterTypes[i] = Class.forName(parameterType); } } return parameterTypes; } } }