diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ByteCodeConst.java b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ByteCodeConst.java new file mode 100644 index 0000000..aeb97bb --- /dev/null +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/constant/ByteCodeConst.java @@ -0,0 +1,19 @@ +package com.alibaba.testable.agent.constant; + +public class ByteCodeConst { + + public static final byte TYPE_BYTE = 'B'; + public static final byte TYPE_CHAR = 'C'; + public static final byte TYPE_DOUBLE = 'D'; + public static final byte TYPE_FLOAT = 'F'; + public static final byte TYPE_INT = 'I'; + public static final byte TYPE_LONG = 'J'; + public static final byte TYPE_CLASS = 'L'; + public static final byte TYPE_SHORT = 'S'; + public static final byte TYPE_BOOL = 'Z'; + public static final byte TYPE_VOID = 'V'; + public static final byte PARAM_END = ')'; + public static final byte CLASS_END = ';'; + public static final byte TYPE_ARRAY = '['; + +} diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java index 84ea5c0..78c6847 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/MockClassHandler.java @@ -1,5 +1,6 @@ package com.alibaba.testable.agent.handler; +import com.alibaba.testable.agent.constant.ByteCodeConst; import com.alibaba.testable.agent.constant.ConstPool; import com.alibaba.testable.agent.tool.ImmutablePair; import com.alibaba.testable.agent.util.AnnotationUtil; @@ -92,7 +93,7 @@ public class MockClassHandler extends BaseClassWithContextHandler { if (targetClassName != null) { // must get label before method description changed ImmutablePair labels = getStartAndEndLabel(mn); - mn.desc = ClassUtil.addParameterAtBegin(mn.desc, targetClassName); + mn.desc = MethodUtil.addParameterAtBegin(mn.desc, targetClassName); int parameterOffset = MethodUtil.isStaticMethod(mn) ? 0 : 1; mn.localVariables.add(parameterOffset, new LocalVariableNode("__self", targetClassName, null, labels.left, labels.right, parameterOffset)); @@ -121,7 +122,7 @@ public class MockClassHandler extends BaseClassWithContextHandler { break; } } - if (ClassUtil.extractParameters(mn.desc).isEmpty()) { + if (MethodUtil.extractParameters(mn.desc).isEmpty()) { // for method without parameter, should manually add a ending label endLabel = new LabelNode(new Label()); mn.instructions.add(endLabel); @@ -158,14 +159,13 @@ public class MockClassHandler extends BaseClassWithContextHandler { private InsnList invokeOriginalMethod(MethodNode mn) { InsnList il = new InsnList(); - mn.maxStack += 3; ImmutablePair target = getTargetClassAndMethodName(mn); il.add(new LdcInsnNode(target.left)); il.add(new LdcInsnNode(target.right)); il.add(duplicateParameters(mn)); il.add(new MethodInsnNode(INVOKESTATIC, CLASS_MOCK_ASSOCIATION_UTIL, METHOD_INVOKE_ORIGIN, SIGNATURE_INVOKE_ORIGIN, false)); - String returnType = ClassUtil.getReturnType(mn.desc); + String returnType = MethodUtil.getReturnType(mn.desc); if (VOID_RES.equals(returnType)) { il.add(new InsnNode(POP)); il.add(new InsnNode(RETURN)); @@ -197,9 +197,9 @@ public class MockClassHandler extends BaseClassWithContextHandler { } } if (methodName.equals(CONSTRUCTOR)) { - className = Type.getType(ClassUtil.getReturnType(mn.desc)); + className = Type.getType(MethodUtil.getReturnType(mn.desc)); } else { - className = Type.getType(ClassUtil.getFirstParameter(mn.desc)); + className = Type.getType(MethodUtil.getFirstParameter(mn.desc)); } return ImmutablePair.of(className, methodName); } @@ -233,7 +233,6 @@ public class MockClassHandler extends BaseClassWithContextHandler { private void injectInvokeRecorder(MethodNode mn) { InsnList il = new InsnList(); - mn.maxStack += 2; il.add(duplicateParameters(mn)); if (isMockForConstructor(mn)) { il.add(new InsnNode(ICONST_1)); @@ -243,17 +242,17 @@ public class MockClassHandler extends BaseClassWithContextHandler { il.add(new MethodInsnNode(INVOKESTATIC, CLASS_INVOKE_RECORD_UTIL, METHOD_RECORD_MOCK_INVOKE, SIGNATURE_RECORDER_METHOD_INVOKE, false)); mn.instructions.insertBefore(mn.instructions.getFirst(), il); + mn.maxStack += (2 + MethodUtil.getParameterTypes(mn.desc).size() * 3); } private InsnList duplicateParameters(MethodNode mn) { InsnList il = new InsnList(); - List types = ClassUtil.getParameterTypes(mn.desc); + List types = MethodUtil.getParameterTypes(mn.desc); int size = types.size(); il.add(getIntInsn(size)); il.add(new TypeInsnNode(ANEWARRAY, ClassUtil.CLASS_OBJECT)); int parameterOffset = MethodUtil.isStaticMethod(mn) ? 0 : 1; for (int i = 0; i < size; i++) { - mn.maxStack += 3; il.add(new InsnNode(DUP)); il.add(getIntInsn(i)); ImmutablePair code = getLoadParameterByteCode(types.get(i)); @@ -286,17 +285,17 @@ public class MockClassHandler extends BaseClassWithContextHandler { private static ImmutablePair getLoadParameterByteCode(Byte type) { switch (type) { - case ClassUtil.TYPE_BYTE: - case ClassUtil.TYPE_CHAR: - case ClassUtil.TYPE_SHORT: - case ClassUtil.TYPE_INT: - case ClassUtil.TYPE_BOOL: + case ByteCodeConst.TYPE_BYTE: + case ByteCodeConst.TYPE_CHAR: + case ByteCodeConst.TYPE_SHORT: + case ByteCodeConst.TYPE_INT: + case ByteCodeConst.TYPE_BOOL: return ImmutablePair.of(ILOAD, 1); - case ClassUtil.TYPE_DOUBLE: + case ByteCodeConst.TYPE_DOUBLE: return ImmutablePair.of(DLOAD, 2); - case ClassUtil.TYPE_FLOAT: + case ByteCodeConst.TYPE_FLOAT: return ImmutablePair.of(FLOAD, 1); - case ClassUtil.TYPE_LONG: + case ByteCodeConst.TYPE_LONG: return ImmutablePair.of(LLOAD, 2); default: return ImmutablePair.of(ALOAD, 1); diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java index c5ea318..417bd20 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/handler/SourceClassHandler.java @@ -4,6 +4,7 @@ import com.alibaba.testable.agent.model.MethodInfo; import com.alibaba.testable.agent.model.ModifiedInsnNodes; import com.alibaba.testable.agent.util.BytecodeUtil; import com.alibaba.testable.agent.util.ClassUtil; +import com.alibaba.testable.agent.util.MethodUtil; import com.alibaba.testable.core.util.LogUtil; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.*; @@ -115,7 +116,7 @@ public class SourceClassHandler extends BaseClassHandler { String nodeOwner = ClassUtil.fitCompanionClassName(node.owner); String nodeName = ClassUtil.fitKotlinAccessorName(node.name); // Kotlin accessor method will append a extra type parameter - String nodeDesc = nodeName.equals(node.name) ? node.desc : ClassUtil.removeFirstParameter(node.desc); + String nodeDesc = nodeName.equals(node.name) ? node.desc : MethodUtil.removeFirstParameter(node.desc); if (m.getClazz().equals(nodeOwner) && m.getName().equals(nodeName) && m.getDesc().equals(nodeDesc)) { return m; } @@ -161,7 +162,7 @@ public class SourceClassHandler extends BaseClassHandler { } private int getInitialStackLevel(MethodInsnNode instruction) { - int stackLevel = ClassUtil.getParameterTypes((instruction).desc).size(); + int stackLevel = MethodUtil.getParameterTypes((instruction).desc).size(); switch (instruction.getOpcode()) { case Opcodes.INVOKESPECIAL: case Opcodes.INVOKEVIRTUAL: @@ -194,7 +195,7 @@ public class SourceClassHandler extends BaseClassHandler { } private int stackEffectOfInvocation(String desc) { - return ClassUtil.getParameterTypes(desc).size() - (ClassUtil.getReturnType(desc).equals(VOID_RES) ? 0 : 1); + return MethodUtil.getParameterTypes(desc).size() - (MethodUtil.getReturnType(desc).equals(VOID_RES) ? 0 : 1); } private ModifiedInsnNodes replaceNewOps(MethodNode mn, MethodInfo newOperatorInjectMethod, diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java index ce13bd1..80c06fc 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/transformer/MockClassParser.java @@ -6,6 +6,7 @@ import com.alibaba.testable.agent.tool.ImmutablePair; import com.alibaba.testable.agent.util.AnnotationUtil; import com.alibaba.testable.agent.util.ClassUtil; import com.alibaba.testable.agent.util.DiagnoseUtil; +import com.alibaba.testable.agent.util.MethodUtil; import com.alibaba.testable.core.util.LogUtil; import com.alibaba.testable.core.util.MockAssociationUtil; import org.objectweb.asm.Type; @@ -92,7 +93,7 @@ public class MockClassParser { String fullClassName = toDotSeparateFullClassName(an.desc); if (fullClassName.equals(ConstPool.MOCK_CONSTRUCTOR)) { LogUtil.verbose(" Mock constructor \"%s\" as \"(%s)V\" for \"%s\"", mn.name, - ClassUtil.extractParameters(mn.desc), ClassUtil.getReturnType(mn.desc)); + MethodUtil.extractParameters(mn.desc), MethodUtil.getReturnType(mn.desc)); addMockConstructor(methodInfos, cn, mn); } else if (fullClassName.equals(ConstPool.MOCK_METHOD)) { LogUtil.verbose(" Mock method \"%s\" as \"%s\"", mn.name, getTargetMethodDesc(mn, an)); @@ -114,7 +115,7 @@ public class MockClassParser { private String getTargetMethodDesc(MethodNode mn, AnnotationNode mockMethodAnnotation) { Type type = AnnotationUtil.getAnnotationParameter(mockMethodAnnotation, ConstPool.FIELD_TARGET_CLASS, null, Type.class); - return type == null ? ClassUtil.removeFirstParameter(mn.desc) : mn.desc; + return type == null ? MethodUtil.removeFirstParameter(mn.desc) : mn.desc; } private MethodInfo getMethodInfo(MethodNode mn, AnnotationNode an, String targetMethod) { @@ -131,7 +132,7 @@ public class MockClassParser { // "targetClass" found, use it as target class type String slashSeparatedName = ClassUtil.toSlashSeparatedName(targetType.getClassName()); return new MethodInfo(slashSeparatedName, targetMethod, mn.desc, mn.name, - ClassUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)), isStatic); + MethodUtil.addParameterAtBegin(mn.desc, ClassUtil.toByteCodeClassName(slashSeparatedName)), isStatic); } } diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java b/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java index c9cce27..bd9f38f 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/util/ClassUtil.java @@ -7,11 +7,10 @@ import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.MethodInsnNode; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; +import static com.alibaba.testable.agent.constant.ByteCodeConst.*; import static com.alibaba.testable.core.constant.ConstPool.MOCK_POSTFIX; import static com.alibaba.testable.core.constant.ConstPool.TEST_POSTFIX; import static org.objectweb.asm.Opcodes.*; @@ -21,20 +20,6 @@ import static org.objectweb.asm.Opcodes.*; */ public class ClassUtil { - public static final byte TYPE_BYTE = 'B'; - public static final byte TYPE_CHAR = 'C'; - public static final byte TYPE_DOUBLE = 'D'; - public static final byte TYPE_FLOAT = 'F'; - public static final byte TYPE_INT = 'I'; - public static final byte TYPE_LONG = 'J'; - public static final byte TYPE_CLASS = 'L'; - public static final byte TYPE_SHORT = 'S'; - public static final byte TYPE_BOOL = 'Z'; - public static final byte TYPE_VOID = 'V'; - private static final byte PARAM_END = ')'; - private static final byte CLASS_END = ';'; - private static final byte TYPE_ARRAY = '['; - public static final String CLASS_OBJECT = "java/lang/Object"; private static final String CLASS_BYTE = "java/lang/Byte"; private static final String CLASS_CHARACTER = "java/lang/Character"; @@ -150,69 +135,6 @@ public class ClassUtil { return testClassName.substring(0, testClassName.length() - TEST_POSTFIX.length()); } - /** - * parse method desc, fetch parameter types - * @param desc method description - * @return list of parameter types - */ - public static List getParameterTypes(String desc) { - List parameterTypes = new ArrayList(); - boolean travelingClass = false; - boolean travelingArray = false; - for (byte b : desc.getBytes()) { - if (travelingClass) { - if (b == CLASS_END) { - travelingClass = false; - travelingArray = false; - } - } else { - if (isPrimaryType(b)) { - // should treat primary array as class (issue-48) - parameterTypes.add(travelingArray ? TYPE_CLASS : b); - travelingArray = false; - } else if (b == TYPE_CLASS) { - travelingClass = true; - parameterTypes.add(b); - } else if (b == TYPE_ARRAY) { - travelingArray = true; - } else if (b == PARAM_END) { - break; - } - } - } - return parameterTypes; - } - - /** - * extract parameter part of method desc - * @param desc method description - * @return parameter value - */ - public static String extractParameters(String desc) { - int returnTypeEdge = desc.lastIndexOf(PARAM_END); - return desc.substring(1, returnTypeEdge); - } - - /** - * parse method desc, fetch return value types - * @param desc method description - * @return types of return value - */ - public static String getReturnType(String desc) { - int returnTypeEdge = desc.lastIndexOf(PARAM_END); - return desc.substring(returnTypeEdge + 1); - } - - /** - * parse method desc, fetch first parameter type - * @param desc method description - * @return types of first parameter - */ - public static String getFirstParameter(String desc) { - int typeEdge = desc.indexOf(CLASS_END); - return desc.substring(1, typeEdge + 1); - } - /** * get wrapper class of specified private type * @param primaryType byte code of private type @@ -279,15 +201,6 @@ public class ClassUtil { return (char)TYPE_CLASS + toSlashSeparatedName(className) + (char)CLASS_END; } - /** - * convert byte code class name to slash separated human readable name - * @param className original name - * @return converted name - */ - public static String toSlashSeparateFullClassName(String className) { - return className.substring(1, className.length() - 1); - } - /** * convert byte code class name to dot separated human readable name * @param className original name @@ -297,25 +210,6 @@ public class ClassUtil { return toDotSeparatedName(className).substring(1, className.length() - 1); } - /** - * remove first parameter from method descriptor - * @param desc original descriptor - * @return descriptor without first parameter - */ - public static String removeFirstParameter(String desc) { - return "(" + desc.substring(desc.indexOf(";") + 1); - } - - /** - * add extra parameter to the beginning of method descriptor - * @param desc original descriptor - * @param type byte code class name - * @return descriptor with specified parameter at begin - */ - public static String addParameterAtBegin(String desc, String type) { - return "(" + type + desc.substring(1); - } - /** * Read class from current context * @param className class name @@ -335,8 +229,4 @@ public class ClassUtil { return "(" + (char)type.byteValue() + ")L" + objectType + ";"; } - private static boolean isPrimaryType(byte b) { - return b == TYPE_BYTE || b == TYPE_CHAR || b == TYPE_DOUBLE || b == TYPE_FLOAT - || b == TYPE_INT || b == TYPE_LONG || b == TYPE_SHORT || b == TYPE_BOOL; - } } diff --git a/testable-agent/src/main/java/com/alibaba/testable/agent/util/MethodUtil.java b/testable-agent/src/main/java/com/alibaba/testable/agent/util/MethodUtil.java index c15575b..0dfd46d 100644 --- a/testable-agent/src/main/java/com/alibaba/testable/agent/util/MethodUtil.java +++ b/testable-agent/src/main/java/com/alibaba/testable/agent/util/MethodUtil.java @@ -2,12 +2,107 @@ package com.alibaba.testable.agent.util; import org.objectweb.asm.tree.MethodNode; +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.testable.agent.constant.ByteCodeConst.*; import static org.objectweb.asm.Opcodes.ACC_STATIC; public class MethodUtil { + /** + * Judge whether a method is static + * @param mn method to check + * @return is static or not + */ public static boolean isStaticMethod(MethodNode mn) { return (mn.access & ACC_STATIC) != 0; } + /** + * parse method desc, fetch parameter types + * @param desc method description + * @return list of parameter types + */ + public static List getParameterTypes(String desc) { + List parameterTypes = new ArrayList(); + boolean travelingClass = false; + boolean travelingArray = false; + for (byte b : desc.getBytes()) { + if (travelingClass) { + if (b == CLASS_END) { + travelingClass = false; + travelingArray = false; + } + } else { + if (isPrimaryType(b)) { + // should treat primary array as class (issue-48) + parameterTypes.add(travelingArray ? TYPE_CLASS : b); + travelingArray = false; + } else if (b == TYPE_CLASS) { + travelingClass = true; + parameterTypes.add(b); + } else if (b == TYPE_ARRAY) { + travelingArray = true; + } else if (b == PARAM_END) { + break; + } + } + } + return parameterTypes; + } + + /** + * extract parameter part of method desc + * @param desc method description + * @return parameter value + */ + public static String extractParameters(String desc) { + int returnTypeEdge = desc.lastIndexOf(PARAM_END); + return desc.substring(1, returnTypeEdge); + } + + /** + * parse method desc, fetch return value types + * @param desc method description + * @return types of return value + */ + public static String getReturnType(String desc) { + int returnTypeEdge = desc.lastIndexOf(PARAM_END); + return desc.substring(returnTypeEdge + 1); + } + + /** + * parse method desc, fetch first parameter type + * @param desc method description + * @return types of first parameter + */ + public static String getFirstParameter(String desc) { + int typeEdge = desc.indexOf(CLASS_END); + return desc.substring(1, typeEdge + 1); + } + + /** + * remove first parameter from method descriptor + * @param desc original descriptor + * @return descriptor without first parameter + */ + public static String removeFirstParameter(String desc) { + return "(" + desc.substring(desc.indexOf(";") + 1); + } + + /** + * add extra parameter to the beginning of method descriptor + * @param desc original descriptor + * @param type byte code class name + * @return descriptor with specified parameter at begin + */ + public static String addParameterAtBegin(String desc, String type) { + return "(" + type + desc.substring(1); + } + + private static boolean isPrimaryType(byte b) { + return b == TYPE_BYTE || b == TYPE_CHAR || b == TYPE_DOUBLE || b == TYPE_FLOAT + || b == TYPE_INT || b == TYPE_LONG || b == TYPE_SHORT || b == TYPE_BOOL; + } } diff --git a/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java b/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java index 2e58ce0..04d8eda 100644 --- a/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java +++ b/testable-agent/src/test/java/com/alibaba/testable/agent/util/ClassUtilTest.java @@ -2,39 +2,10 @@ package com.alibaba.testable.agent.util; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; class ClassUtilTest { - @Test - void should_able_to_get_parameter_count() { - assertEquals(0, ClassUtil.getParameterTypes("()V").size()); - assertEquals(1, ClassUtil.getParameterTypes("(Ljava/lang/String;)V").size()); - assertEquals(6, ClassUtil.getParameterTypes("(Ljava/lang/String;IDLjava/lang/String;ZLjava/net/URL;)V").size()); - assertEquals(10, ClassUtil.getParameterTypes("(ZLjava/lang/String;IJFDCSBZ)V").size()); - assertEquals(3, ClassUtil.getParameterTypes("(Ljava/lang/String;[I[Ljava/lang/String;)V").size()); - } - - @Test - void should_able_to_extract_parameter() { - assertEquals("", ClassUtil.extractParameters("()I")); - assertEquals("Ljava/lang/String;", ClassUtil.extractParameters("(Ljava/lang/String;)I")); - } - - @Test - void should_able_to_get_return_type() { - assertEquals("V", ClassUtil.getReturnType("(Ljava/lang/String;)V")); - assertEquals("I", ClassUtil.getReturnType("(Ljava/lang/String;)I")); - assertEquals("[I", ClassUtil.getReturnType("(Ljava/lang/String;)[I")); - assertEquals("Ljava/lang/String;", ClassUtil.getReturnType("(Ljava/lang/String;)Ljava/lang/String;")); - assertEquals("[Ljava/lang/String;", ClassUtil.getReturnType("(Ljava/lang/String;)[Ljava/lang/String;")); - } - - @Test - void should_able_to_get_first_parameter() { - assertEquals("Ljava/lang/String;", ClassUtil.getFirstParameter("(Ljava/lang/String;Ljava/lang/Object;I)V")); - } - @Test void should_able_to_convert_class_name() { assertEquals("Ljava/lang/String;", ClassUtil.toByteCodeClassName("java.lang.String")); diff --git a/testable-agent/src/test/java/com/alibaba/testable/agent/util/MethodUtilTest.java b/testable-agent/src/test/java/com/alibaba/testable/agent/util/MethodUtilTest.java new file mode 100644 index 0000000..53fdd87 --- /dev/null +++ b/testable-agent/src/test/java/com/alibaba/testable/agent/util/MethodUtilTest.java @@ -0,0 +1,39 @@ +package com.alibaba.testable.agent.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class MethodUtilTest { + + @Test + void should_able_to_get_parameter_count() { + assertEquals(0, MethodUtil.getParameterTypes("()V").size()); + assertEquals(1, MethodUtil.getParameterTypes("(Ljava/lang/String;)V").size()); + assertEquals(6, MethodUtil.getParameterTypes("(Ljava/lang/String;IDLjava/lang/String;ZLjava/net/URL;)V").size()); + assertEquals(10, MethodUtil.getParameterTypes("(ZLjava/lang/String;IJFDCSBZ)V").size()); + assertEquals(3, MethodUtil.getParameterTypes("(Ljava/lang/String;[I[Ljava/lang/String;)V").size()); + } + + @Test + void should_able_to_extract_parameter() { + assertEquals("", MethodUtil.extractParameters("()I")); + assertEquals("Ljava/lang/String;", MethodUtil.extractParameters("(Ljava/lang/String;)I")); + } + + @Test + void should_able_to_get_return_type() { + assertEquals("V", MethodUtil.getReturnType("(Ljava/lang/String;)V")); + assertEquals("I", MethodUtil.getReturnType("(Ljava/lang/String;)I")); + assertEquals("[I", MethodUtil.getReturnType("(Ljava/lang/String;)[I")); + assertEquals("Ljava/lang/String;", MethodUtil.getReturnType("(Ljava/lang/String;)Ljava/lang/String;")); + assertEquals("[Ljava/lang/String;", MethodUtil.getReturnType("(Ljava/lang/String;)[Ljava/lang/String;")); + } + + @Test + void should_able_to_get_first_parameter() { + assertEquals("Ljava/lang/String;", MethodUtil.getFirstParameter("(Ljava/lang/String;Ljava/lang/Object;I)V")); + } + +} +