1
2
3
4
5 package mockit.internal.util;
6
7 import edu.umd.cs.findbugs.annotations.NonNull;
8 import edu.umd.cs.findbugs.annotations.Nullable;
9
10 import java.util.ArrayList;
11 import java.util.List;
12 import java.util.regex.Pattern;
13
14 import mockit.internal.state.ParameterNames;
15
16 public final class MethodFormatter {
17 private static final Pattern CONSTRUCTOR_NAME = Pattern.compile("<init>");
18 private static final Pattern JAVA_LANG_PREFIX = Pattern.compile("java/lang/");
19
20 @NonNull
21 private final StringBuilder out;
22 @NonNull
23 private final List<String> parameterTypes;
24 @NonNull
25 private final String classDesc;
26 @NonNull
27 private String methodDesc;
28
29
30 private int parameterIndex;
31 private int typeDescPos;
32 private char typeCode;
33 private int arrayDimensions;
34
35 public MethodFormatter(@Nullable String classDesc) {
36 out = new StringBuilder();
37 parameterTypes = new ArrayList<>(5);
38 this.classDesc = classDesc;
39 methodDesc = "";
40 }
41
42 public MethodFormatter(@NonNull String classDesc, @NonNull String methodNameAndDesc) {
43 this(classDesc, methodNameAndDesc, true);
44 }
45
46 public MethodFormatter(@NonNull String classDesc, @NonNull String methodNameAndDesc,
47 boolean withParametersAppended) {
48 out = new StringBuilder();
49 parameterTypes = new ArrayList<>(5);
50 this.classDesc = classDesc;
51 methodDesc = "";
52 methodDesc = methodNameAndDesc;
53 appendFriendlyMethodSignature(withParametersAppended);
54 }
55
56 @Override
57 public String toString() {
58 return out.toString();
59 }
60
61 @NonNull
62 public List<String> getParameterTypes() {
63 return parameterTypes;
64 }
65
66 private void appendFriendlyMethodSignature(boolean withParametersAppended) {
67 String className = classDesc.replace('/', '.');
68 out.append(className).append('#');
69
70 String constructorName = getConstructorName(className);
71 String friendlyDesc = CONSTRUCTOR_NAME.matcher(methodDesc).replaceFirst(constructorName);
72
73 int leftParenNextPos = friendlyDesc.indexOf('(') + 1;
74 int rightParenPos = friendlyDesc.indexOf(')');
75
76 if (leftParenNextPos < rightParenPos) {
77 out.append(friendlyDesc, 0, leftParenNextPos);
78
79 String concatenatedParameterTypes = friendlyDesc.substring(leftParenNextPos, rightParenPos);
80
81 if (withParametersAppended) {
82 parameterIndex = 0;
83 appendParameterTypesAndNames(concatenatedParameterTypes);
84 out.append(')');
85 } else {
86 addParameterTypes(concatenatedParameterTypes);
87 }
88 } else {
89 out.append(friendlyDesc, 0, rightParenPos + 1);
90 }
91 }
92
93 @NonNull
94 private static String getConstructorName(@NonNull String className) {
95 int p = className.lastIndexOf('.');
96 String constructorName = p < 0 ? className : className.substring(p + 1);
97
98
99 p = constructorName.lastIndexOf('$');
100
101 if (p > 0) {
102 constructorName = constructorName.substring(p + 1);
103 }
104
105 return constructorName;
106 }
107
108 private void appendParameterTypesAndNames(@NonNull String typeDescs) {
109 String sep = "";
110
111 for (String typeDesc : typeDescs.split(";")) {
112 out.append(sep);
113
114 if (typeDesc.charAt(0) == 'L') {
115 appendParameterType(friendlyReferenceType(typeDesc));
116 appendParameterName();
117 } else {
118 appendPrimitiveParameterTypesAndNames(typeDesc);
119 }
120
121 sep = ", ";
122 }
123 }
124
125 @NonNull
126 private static String friendlyReferenceType(@NonNull String typeDesc) {
127 return JAVA_LANG_PREFIX.matcher(typeDesc.substring(1)).replaceAll("").replace('/', '.');
128 }
129
130 private void appendParameterType(@NonNull String friendlyTypeDesc) {
131 out.append(friendlyTypeDesc);
132 parameterTypes.add(friendlyTypeDesc);
133 }
134
135 private void appendParameterName() {
136 String name = ParameterNames.getName(classDesc, methodDesc, parameterIndex);
137
138 if (name != null) {
139 out.append(' ').append(name);
140 }
141
142 parameterIndex++;
143 }
144
145 private void appendPrimitiveParameterTypesAndNames(@NonNull String typeDesc) {
146 String sep = "";
147
148 for (typeDescPos = 0; typeDescPos < typeDesc.length(); typeDescPos++) {
149 typeCode = typeDesc.charAt(typeDescPos);
150 advancePastArrayDimensionsIfAny(typeDesc);
151
152 out.append(sep);
153
154 String paramType = getTypeNameForTypeDesc(typeDesc) + getArrayBrackets();
155 appendParameterType(paramType);
156 appendParameterName();
157
158 sep = ", ";
159 }
160 }
161
162 private void addParameterTypes(@NonNull String typeDescs) {
163 for (String typeDesc : typeDescs.split(";")) {
164 if (typeDesc.charAt(0) == 'L') {
165 parameterTypes.add(friendlyReferenceType(typeDesc));
166 } else {
167 addPrimitiveParameterTypes(typeDesc);
168 }
169 }
170 }
171
172 private void addPrimitiveParameterTypes(@NonNull String typeDesc) {
173 for (typeDescPos = 0; typeDescPos < typeDesc.length(); typeDescPos++) {
174 typeCode = typeDesc.charAt(typeDescPos);
175 advancePastArrayDimensionsIfAny(typeDesc);
176
177 String paramType = getTypeNameForTypeDesc(typeDesc) + getArrayBrackets();
178 parameterTypes.add(paramType);
179 }
180 }
181
182 @NonNull
183 @SuppressWarnings("OverlyComplexMethod")
184 private String getTypeNameForTypeDesc(@NonNull String typeDesc) {
185 String paramType;
186
187 switch (typeCode) {
188 case 'B':
189 return "byte";
190 case 'C':
191 return "char";
192 case 'D':
193 return "double";
194 case 'F':
195 return "float";
196 case 'I':
197 return "int";
198 case 'J':
199 return "long";
200 case 'S':
201 return "short";
202 case 'V':
203 return "void";
204 case 'Z':
205 return "boolean";
206 case 'L':
207 paramType = friendlyReferenceType(typeDesc.substring(typeDescPos));
208 typeDescPos = typeDesc.length();
209 break;
210 default:
211 paramType = typeDesc.substring(typeDescPos);
212 typeDescPos = typeDesc.length();
213 }
214
215 return paramType;
216 }
217
218 private void advancePastArrayDimensionsIfAny(@NonNull String param) {
219 arrayDimensions = 0;
220
221 while (typeCode == '[') {
222 typeDescPos++;
223 typeCode = param.charAt(typeDescPos);
224 arrayDimensions++;
225 }
226 }
227
228 @NonNull
229 private String getArrayBrackets() {
230 @SuppressWarnings("NonConstantStringShouldBeStringBuffer")
231 StringBuilder result = new StringBuilder();
232
233 for (int i = 0; i < arrayDimensions; i++) {
234
235 result.append("[]");
236 }
237
238 return result.toString();
239 }
240
241 public void append(@NonNull String text) {
242 out.append(text);
243 }
244 }