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