View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
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      // Auxiliary fields for handling method parameters:
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          // noinspection ReuseOfLocalVariable
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             // noinspection StringContatenationInLoop
236             result.append("[]");
237         }
238 
239         return result.toString();
240     }
241 
242     public void append(@NonNull String text) {
243         out.append(text);
244     }
245 }