/* * Copyright 2014 NAVER Corp. * * 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.navercorp.pinpoint.profiler.util; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import com.navercorp.pinpoint.common.util.StringUtils; import javassist.CtBehavior; import javassist.CtClass; import javassist.Modifier; import javassist.bytecode.AttributeInfo; import javassist.bytecode.CodeAttribute; import javassist.bytecode.LocalVariableAttribute; import javassist.bytecode.MethodInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author emeroad */ public final class JavaAssistUtils { private final static String EMTPY_ARRAY = "()"; private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final String ARRAY = "[]"; private static final Logger logger = LoggerFactory.getLogger(JavaAssistUtils.class); private static final Pattern PARAMETER_SIGNATURE_PATTERN = Pattern.compile("\\[*L[^;]+;|\\[*[ZBCSIFDJ]|[ZBCSIFDJ]"); private static final Map<String, String> PRIMITIVE_JAVA_TO_JVM = createPrimitiveJavaToJvmMap(); private static Map<String, String> createPrimitiveJavaToJvmMap() { final Map<String, String> primitiveJavaToJvm = new HashMap<String, String>(); primitiveJavaToJvm.put("byte", "B"); primitiveJavaToJvm.put("char", "C"); primitiveJavaToJvm.put("double", "D"); primitiveJavaToJvm.put("float", "F"); primitiveJavaToJvm.put("int", "I"); primitiveJavaToJvm.put("long", "J"); primitiveJavaToJvm.put("short", "S"); primitiveJavaToJvm.put("void", "V"); primitiveJavaToJvm.put("boolean", "Z"); return primitiveJavaToJvm; } private JavaAssistUtils() { } /** * example return value: (int, java.lang.String) * * @param params * @return */ public static String getParameterDescription(CtClass[] params) { if (params == null) { return EMTPY_ARRAY; } StringBuilder sb = new StringBuilder(64); sb.append('('); int end = params.length - 1; for (int i = 0; i < params.length; i++) { sb.append(params[i].getName()); if (i < end) { sb.append(", "); } } sb.append(')'); return sb.toString(); } public static String javaTypeToJvmSignature(String[] javaTypeArray, String returnType) { if (returnType == null) { throw new NullPointerException("returnType must not be null"); } final String parameterSignature = javaTypeToJvmSignature(javaTypeArray); final StringBuilder sb = new StringBuilder(parameterSignature.length() + 8); sb.append(parameterSignature); sb.append(toJvmSignature(returnType)); return sb.toString(); } public static String javaTypeToJvmSignature(String[] javaTypeArray) { if (com.navercorp.pinpoint.common.util.ArrayUtils.isEmpty(javaTypeArray)) { return "()"; } final StringBuilder buffer = new StringBuilder(); buffer.append('('); for (String javaType : javaTypeArray) { final String jvmSignature = toJvmSignature(javaType); buffer.append(jvmSignature); } buffer.append(')'); return buffer.toString(); } public static String toJvmSignature(String javaType) { if (javaType == null) { throw new NullPointerException("javaType must not be null"); } if (javaType.isEmpty()) { throw new IllegalArgumentException("invalid javaType. \"\""); } final int javaObjectArraySize = getJavaObjectArraySize(javaType); final int javaArrayLength = javaObjectArraySize * 2; String pureJavaType; if (javaObjectArraySize != 0) { // pure java pureJavaType = javaType.substring(0, javaType.length() - javaArrayLength); } else { pureJavaType = javaType; } final String signature = PRIMITIVE_JAVA_TO_JVM.get(pureJavaType); if (signature != null) { // primitive type return appendJvmArray(signature, javaObjectArraySize); } return toJvmObject(javaObjectArraySize, pureJavaType); } private static String toJvmObject(int javaObjectArraySize, String pureJavaType) { // "java.lang.String[][]"->"[[Ljava.lang.String;" final StringBuilder buffer = new StringBuilder(pureJavaType.length() + javaObjectArraySize + 2); for (int i = 0; i < javaObjectArraySize; i++) { buffer.append('['); } buffer.append('L'); buffer.append(javaNameToJvmName(pureJavaType)); buffer.append(';'); return buffer.toString(); } /** * java.lang.String -> java/lang/String * @param javaName * @return */ public static String javaNameToJvmName(String javaName) { if (javaName == null) { throw new NullPointerException("javaName must not be null"); } return javaName.replace('.', '/'); } /** * java/lang/String -> java.lang.String * @param jvmName * @return */ public static String jvmNameToJavaName(String jvmName) { if (jvmName == null) { throw new NullPointerException("jvmName must not be null"); } return jvmName.replace('/', '.'); } /** * java/lang/String -> java.lang.String * @param jvmNameArray * @return */ public static List<String> jvmNameToJavaName(List<String> jvmNameArray) { if (jvmNameArray == null) { return Collections.emptyList(); } List<String> list = new ArrayList<String>(jvmNameArray.size()); for (String jvmName : jvmNameArray) { list.add(jvmNameToJavaName(jvmName)); } return list; } private static String appendJvmArray(String signature, int javaObjectArraySize) { if (javaObjectArraySize == 0) { return signature; } StringBuilder sb = new StringBuilder(signature.length() + javaObjectArraySize); for (int i = 0; i < javaObjectArraySize; i++) { sb.append('['); } sb.append(signature); return sb.toString(); } static int getJavaObjectArraySize(String javaType) { if (javaType == null) { throw new NullPointerException("javaType must not be null"); } if (javaType.isEmpty()) { return 0; } final int endIndex = javaType.length() - 1; final char checkEndArrayExist = javaType.charAt(endIndex); if (checkEndArrayExist != ']') { return 0; } int arraySize = 0; for (int i = endIndex; i > 0; i = i - 2) { final char arrayEnd = javaType.charAt(i); final char arrayStart = javaType.charAt(i - 1); if (arrayStart == '[' && arrayEnd == ']') { arraySize++; } else { return arraySize; } } return arraySize; } public static String[] parseParameterSignature(String signature) { if (signature == null) { throw new NullPointerException("signature must not be null"); } final List<String> parameterSignatureList = splitParameterSignature(signature); if (parameterSignatureList.isEmpty()) { return EMPTY_STRING_ARRAY; } final String[] objectType = new String[parameterSignatureList.size()]; for (int i = 0; i < parameterSignatureList.size(); i++) { final String parameterSignature = parameterSignatureList.get(i); objectType[i] = byteCodeSignatureToObjectType(parameterSignature, 0); } return objectType; } public static String javaClassNameToObjectName(String javaClassName) { final char scheme = javaClassName.charAt(0); switch (scheme) { case '[': return toArrayType(javaClassName); default: return javaClassName; } } // to variable name. // '.' '$' '[' ']' => '_' public static String javaClassNameToVariableName(String javaClassName) { if (javaClassName == null) { throw new NullPointerException("java class name must not be null"); } return javaClassName.replace('.', '_').replace('$', '_').replace('[', '_').replace(']', '_'); } private static String byteCodeSignatureToObjectType(String signature, int startIndex) { final char scheme = signature.charAt(startIndex); switch (scheme) { case 'B': return "byte"; case 'C': return "char"; case 'D': return "double"; case 'F': return "float"; case 'I': return "int"; case 'J': return "long"; case 'S': return "short"; case 'V': return "void"; case 'Z': return "boolean"; case 'L': return toObjectType(signature, startIndex + 1); case '[': { return toArrayType(signature); } } throw new IllegalArgumentException("invalid signature :" + signature); } private static String toArrayType(String description) { final int arraySize = getArraySize(description); final String objectType = byteCodeSignatureToObjectType(description, arraySize); return arrayType(objectType, arraySize); } private static String arrayType(String objectType, int arraySize) { final int arrayStringLength = ARRAY.length() * arraySize; StringBuilder sb = new StringBuilder(objectType.length() + arrayStringLength); sb.append(objectType); for (int i = 0; i < arraySize; i++) { sb.append(ARRAY); } return sb.toString(); } private static int getArraySize(String description) { if (StringUtils.isEmpty(description)) { return 0; } int arraySize = 0; for (int i = 0; i < description.length(); i++) { final char c = description.charAt(i); if (c == '[') { arraySize++; } else { break; } } return arraySize; } private static String toObjectType(String signature, int startIndex) { // Ljava/lang/String; final String assistClass = signature.substring(startIndex, signature.length() - 1); final String objectName = jvmNameToJavaName(assistClass); if (objectName.isEmpty()) { throw new IllegalArgumentException("invalid signature. objectName not found :" + signature); } return objectName; } private static List<String> splitParameterSignature(String signature) { final String parameterSignature = getParameterSignature(signature); if (parameterSignature.isEmpty()) { return Collections.emptyList(); } final Matcher matcher = PARAMETER_SIGNATURE_PATTERN.matcher(parameterSignature); final List<String> parameterTypeList = new ArrayList<String>(); while (matcher.find()) { parameterTypeList.add(matcher.group()); } return parameterTypeList; } private static String getParameterSignature(String signature) { int start = signature.indexOf('('); if (start == -1) { throw new IllegalArgumentException("'(' not found. signature:" + signature); } final int end = signature.indexOf(')', start + 1); if (end == -1) { throw new IllegalArgumentException("')' not found. signature:" + signature); } start = start + 1; if (start == end) { return ""; } return signature.substring(start, end); } public static String[] getParameterType(Class[] paramsClass) { if (paramsClass == null) { return null; } String[] paramsString = new String[paramsClass.length]; for (int i = 0; i < paramsClass.length; i++) { paramsString[i] = paramsClass[i].getName(); } return paramsString; } public static String[] toPinpointParameterType(Class<?>[] paramClasses) { if (paramClasses == null) { return null; } String[] paramsString = new String[paramClasses.length]; for (int i = 0; i < paramClasses.length; i++) { paramsString[i] = toPinpointParameterType(paramClasses[i]); } return paramsString; } public static String toPinpointParameterType(Class<?> type) { if (type.isArray()) { return toPinpointParameterType(type.getComponentType()) + "[]"; } else { return type.getName(); } } @Deprecated static String[] getParameterType(CtClass[] paramsClass) { if (paramsClass == null) { return null; } String[] paramsString = new String[paramsClass.length]; for (int i = 0; i < paramsClass.length; i++) { paramsString[i] = paramsClass[i].getName(); } return paramsString; } @Deprecated public static String getParameterDescription(Class[] params) { if (params == null) { return EMTPY_ARRAY; } StringBuilder sb = new StringBuilder(64); sb.append('('); int end = params.length - 1; for (int i = 0; i < params.length; i++) { sb.append(params[i].getName()); if (i < end) { sb.append(", "); } } sb.append(')'); return sb.toString(); } public static String getParameterDescription(String[] params) { if (params == null) { return EMTPY_ARRAY; } StringBuilder sb = new StringBuilder(64); sb.append('('); int end = params.length - 1; for (int i = 0; i < params.length; i++) { sb.append(params[i]); if (i < end) { sb.append(", "); } } sb.append(')'); return sb.toString(); } public static int getLineNumber(CtBehavior method) { if (method == null) { return -1; } return method.getMethodInfo().getLineNumber(0); } public static boolean isStaticBehavior(CtBehavior behavior) { if (behavior == null) { throw new NullPointerException("behavior must not be null"); } int modifiers = behavior.getModifiers(); return Modifier.isStatic(modifiers); } public static String[] getParameterVariableName(CtBehavior method) { if (method == null) { throw new NullPointerException("method must not be null"); } LocalVariableAttribute localVariableAttribute = lookupLocalVariableAttribute(method); if (localVariableAttribute == null) { return getParameterDefaultVariableName(method); } return getParameterVariableName(method, localVariableAttribute); } /** * get LocalVariableAttribute * * @param method * @return null if the class is not compiled with debug option */ public static LocalVariableAttribute lookupLocalVariableAttribute(CtBehavior method) { if (method == null) { throw new NullPointerException("method must not be null"); } MethodInfo methodInfo = method.getMethodInfo2(); CodeAttribute codeAttribute = methodInfo.getCodeAttribute(); if (codeAttribute == null) { return null; } AttributeInfo localVariableTable = codeAttribute.getAttribute(LocalVariableAttribute.tag); LocalVariableAttribute local = (LocalVariableAttribute) localVariableTable; return local; } public static String[] getParameterVariableName(CtBehavior method, LocalVariableAttribute localVariableAttribute) { // Inspired by // http://www.jarvana.com/jarvana/view/org/jboss/weld/servlet/weld-servlet/1.0.1-Final/weld-servlet-1.0.1-Final-sources.jar!/org/slf4j/instrumentation/JavassistHelper.java?format=ok // http://grepcode.com/file/repo1.maven.org/maven2/jp.objectfanatics/assertion-weaver/0.0.30/jp/objectfanatics/commons/javassist/JavassistUtils.java if (localVariableAttribute == null) { // null means that the class is not compiled with debug option. return null; } dump(localVariableAttribute); String[] parameterTypes = JavaAssistUtils.parseParameterSignature(method.getSignature()); if (parameterTypes.length == 0) { return EMPTY_STRING_ARRAY; } String[] parameterVariableNames = new String[parameterTypes.length]; boolean thisExist = thisExist(method); int paramIndex = 0; for (int i = 0; i < localVariableAttribute.tableLength(); i++) { // if start pc is not 0, it's not a parameter. if (localVariableAttribute.startPc(i) != 0) { continue; } int index = localVariableAttribute.index(i); if (index == 0 && thisExist) { // variable this. skip. continue; } String variablename = localVariableAttribute.variableName(i); parameterVariableNames[paramIndex++] = variablename; if (paramIndex == parameterTypes.length) { break; } } return parameterVariableNames; } private static boolean thisExist(CtBehavior method) { int modifiers = method.getModifiers(); if (Modifier.isStatic(modifiers)) { return false; } else { return true; } } private static void dump(LocalVariableAttribute lva) { if (logger.isDebugEnabled()) { StringBuilder buffer = new StringBuilder(1024); for (int i = 0; i < lva.tableLength(); i++) { buffer.append("\n"); buffer.append(i); buffer.append(" start_pc:"); buffer.append(lva.startPc(i)); buffer.append(" index:"); buffer.append(lva.index(i)); buffer.append(" name:"); buffer.append(lva.variableName(i)); buffer.append(" nameIndex:"); buffer.append(lva.nameIndex(i)); } logger.debug(buffer.toString()); } } public static String[] getParameterDefaultVariableName(CtBehavior method) { if (method == null) { throw new NullPointerException("method must not be null"); } String[] parameterTypes = JavaAssistUtils.parseParameterSignature(method.getSignature()); String[] variableName = new String[parameterTypes.length]; for (int i = 0; i < variableName.length; i++) { variableName[i] = getSimpleName(parameterTypes[i]).toLowerCase(); } return variableName; } private static String getSimpleName(String parameterName) { final int findIndex = parameterName.lastIndexOf('.'); if (findIndex == -1) { return parameterName; } else { return parameterName.substring(findIndex + 1); } } }