View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
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      // Auxiliary fields for handling method parameters:
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          // noinspection ReuseOfLocalVariable
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             // noinspection StringContatenationInLoop
235             result.append("[]");
236         }
237 
238         return result.toString();
239     }
240 
241     public void append(@NonNull String text) {
242         out.append(text);
243     }
244 }