• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2014, Google Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  * notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  *     * Neither the name of Google Inc. nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package org.jf.smalidea.util;
33 
34 import com.google.common.collect.ImmutableMap;
35 import com.intellij.openapi.project.DumbService;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.psi.*;
38 import com.intellij.psi.impl.ResolveScopeManager;
39 import com.intellij.psi.search.GlobalSearchScope;
40 import org.jetbrains.annotations.NotNull;
41 import org.jetbrains.annotations.Nullable;
42 
43 import java.util.Map;
44 
45 public class NameUtils {
46     private static final Map<String, String> javaToSmaliPrimitiveTypes = ImmutableMap.<String, String>builder()
47             .put("boolean", "Z")
48             .put("byte", "B")
49             .put("char", "C")
50             .put("short", "S")
51             .put("int", "I")
52             .put("long", "J")
53             .put("float", "F")
54             .put("double", "D")
55             .build();
56 
57     @NotNull
javaToSmaliType(@otNull PsiType psiType)58     public static String javaToSmaliType(@NotNull PsiType psiType) {
59         if (psiType instanceof PsiClassType) {
60             PsiClass psiClass = ((PsiClassType)psiType).resolve();
61             if (psiClass != null) {
62                 return javaToSmaliType(psiClass);
63             }
64         }
65         return javaToSmaliType(psiType.getCanonicalText());
66     }
67 
68     @NotNull
javaToSmaliType(@otNull PsiClass psiClass)69     public static String javaToSmaliType(@NotNull PsiClass psiClass) {
70         String qualifiedName = psiClass.getQualifiedName();
71         if (qualifiedName == null) {
72             throw new IllegalArgumentException("This method does not support anonymous classes");
73         }
74         PsiClass parent = psiClass.getContainingClass();
75         if (parent != null) {
76             int offset = qualifiedName.lastIndexOf('.');
77             String parentName = qualifiedName.substring(0, offset);
78             assert parentName.equals(parent.getQualifiedName());
79             String className = qualifiedName.substring(offset+1, qualifiedName.length());
80             assert className.equals(psiClass.getName());
81             return javaToSmaliType(parentName + '$' + className);
82         } else {
83             return javaToSmaliType(psiClass.getQualifiedName());
84         }
85     }
86 
87     @NotNull
javaToSmaliType(@otNull String javaType)88     public static String javaToSmaliType(@NotNull String javaType) {
89         if (javaType.charAt(javaType.length()-1) == ']') {
90             int dimensions = 0;
91             int firstArrayChar = -1;
92             for (int i=0; i<javaType.length(); i++) {
93                 if (javaType.charAt(i) == '[') {
94                     if (firstArrayChar == -1) {
95                         firstArrayChar = i;
96                     }
97                     dimensions++;
98                 }
99             }
100             if (dimensions > 0) {
101                 StringBuilder sb = new StringBuilder(firstArrayChar + 2 + dimensions);
102                 for (int i=0; i<dimensions; i++) {
103                     sb.append('[');
104                 }
105                 convertSimpleJavaToSmaliType(javaType.substring(0, firstArrayChar), sb);
106                 return sb.toString();
107             }
108         }
109 
110         return simpleJavaToSmaliType(javaType);
111     }
112 
convertSimpleJavaToSmaliType(@otNull String javaType, @NotNull StringBuilder dest)113     private static void convertSimpleJavaToSmaliType(@NotNull String javaType, @NotNull StringBuilder dest) {
114         String smaliType = javaToSmaliPrimitiveTypes.get(javaType);
115         if (smaliType != null) {
116             dest.append(smaliType);
117         } else {
118             dest.append('L');
119             for (int i=0; i<javaType.length(); i++) {
120                 char c = javaType.charAt(i);
121                 if (c == '.') {
122                     dest.append('/');
123                 } else {
124                     dest.append(c);
125                 }
126             }
127             dest.append(';');
128         }
129     }
130 
resolveSmaliType(@otNull Project project, @NotNull GlobalSearchScope scope, @NotNull String smaliType)131     public static PsiClass resolveSmaliType(@NotNull Project project, @NotNull GlobalSearchScope scope,
132                                             @NotNull String smaliType) {
133         if (DumbService.isDumb(project)) {
134             return null;
135         }
136 
137         JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
138 
139         String javaType = NameUtils.smaliToJavaType(smaliType);
140 
141         PsiClass psiClass = facade.findClass(javaType, scope);
142         if (psiClass != null) {
143             return psiClass;
144         }
145 
146         int offset = javaType.lastIndexOf('.');
147         if (offset < 0) {
148             offset = 0;
149         }
150         // find the first $ after the last .
151         offset = javaType.indexOf('$', offset+1);
152         if (offset < 0) {
153             return null;
154         }
155 
156         while (offset > 0 && offset < javaType.length()-1) {
157             String left = javaType.substring(0, offset);
158             psiClass = facade.findClass(left, scope);
159             if (psiClass != null) {
160                 psiClass = findInnerClass(psiClass, javaType.substring(offset+1, javaType.length()), facade, scope);
161                 if (psiClass != null) {
162                     return psiClass;
163                 }
164             }
165             offset = javaType.indexOf('$', offset+1);
166         }
167         return null;
168     }
169 
170     @Nullable
resolveSmaliType(@otNull PsiElement element, @NotNull String smaliType)171     public static PsiClass resolveSmaliType(@NotNull PsiElement element, @NotNull String smaliType) {
172         GlobalSearchScope scope = ResolveScopeManager.getElementResolveScope(element);
173         return resolveSmaliType(element.getProject(), scope, smaliType);
174     }
175 
176     @Nullable
findInnerClass(@otNull PsiClass outerClass, String innerText, JavaPsiFacade facade, GlobalSearchScope scope)177     public static PsiClass findInnerClass(@NotNull PsiClass outerClass, String innerText, JavaPsiFacade facade,
178                                           GlobalSearchScope scope) {
179         int offset = innerText.indexOf('$');
180         if (offset < 0) {
181             offset = innerText.length();
182         }
183 
184         while (offset > 0 && offset <= innerText.length()) {
185             String left = innerText.substring(0, offset);
186             String nextInner = outerClass.getQualifiedName() + "." + left;
187             PsiClass psiClass = facade.findClass(nextInner, scope);
188             if (psiClass != null) {
189                 if (offset < innerText.length()) {
190                     psiClass = findInnerClass(psiClass, innerText.substring(offset+1, innerText.length()), facade,
191                             scope);
192                     if (psiClass != null) {
193                         return psiClass;
194                     }
195                 } else {
196                     return psiClass;
197                 }
198             }
199             if (offset >= innerText.length()) {
200                 break;
201             }
202             offset = innerText.indexOf('$', offset+1);
203             if (offset < 0) {
204                 offset = innerText.length();
205             }
206         }
207         return null;
208     }
209 
simpleJavaToSmaliType(@otNull String simpleJavaType)210     private static String simpleJavaToSmaliType(@NotNull String simpleJavaType) {
211         StringBuilder sb = new StringBuilder(simpleJavaType.length() + 2);
212         convertSimpleJavaToSmaliType(simpleJavaType, sb);
213         sb.trimToSize();
214         return sb.toString();
215     }
216 
217     @NotNull
smaliToJavaType(@otNull String smaliType)218     public static String smaliToJavaType(@NotNull String smaliType) {
219         if (smaliType.charAt(0) == '[') {
220             return convertSmaliArrayToJava(smaliType);
221         } else {
222             StringBuilder sb = new StringBuilder(smaliType.length());
223             convertAndAppendNonArraySmaliTypeToJava(smaliType, sb);
224             return sb.toString();
225         }
226     }
227 
228     @NotNull
resolveSmaliToJavaType(@otNull Project project, @NotNull GlobalSearchScope scope, @NotNull String smaliType)229     public static String resolveSmaliToJavaType(@NotNull Project project, @NotNull GlobalSearchScope scope,
230                                                 @NotNull String smaliType) {
231         // First, try to resolve the type and get its qualified name, so that we can make sure
232         // to use the correct name for inner classes
233         PsiClass resolvedType = resolveSmaliType(project, scope, smaliType);
234         if (resolvedType != null) {
235             String qualifiedName = resolvedType.getQualifiedName();
236             if (qualifiedName != null) {
237                 return qualifiedName;
238             }
239         }
240 
241         // if we can't find it, just do a textual conversion of the name
242         return smaliToJavaType(smaliType);
243     }
244 
245     @NotNull
resolveSmaliToJavaType(@otNull PsiElement element, @NotNull String smaliType)246     public static String resolveSmaliToJavaType(@NotNull PsiElement element, @NotNull String smaliType) {
247         return resolveSmaliToJavaType(element.getProject(), element.getResolveScope(), smaliType);
248     }
249 
250     @NotNull
resolveSmaliToPsiType(@otNull PsiElement element, @NotNull String smaliType)251     public static PsiType resolveSmaliToPsiType(@NotNull PsiElement element, @NotNull String smaliType) {
252         PsiClass resolvedType = resolveSmaliType(element, smaliType);
253         if (resolvedType != null) {
254             PsiElementFactory factory = JavaPsiFacade.getInstance(element.getProject()).getElementFactory();
255             return factory.createType(resolvedType);
256         }
257 
258         String javaType = NameUtils.smaliToJavaType(smaliType);
259         PsiElementFactory factory = JavaPsiFacade.getInstance(element.getProject()).getElementFactory();
260         return factory.createTypeFromText(javaType, element);
261     }
262 
263     @NotNull
convertSmaliArrayToJava(@otNull String smaliType)264     private static String convertSmaliArrayToJava(@NotNull String smaliType) {
265         int dimensions=0;
266         while (smaliType.charAt(dimensions) == '[') {
267             dimensions++;
268         }
269 
270         StringBuilder sb = new StringBuilder(smaliType.length() + dimensions);
271         convertAndAppendNonArraySmaliTypeToJava(smaliType.substring(dimensions), sb);
272         for (int i=0; i<dimensions; i++) {
273             sb.append("[]");
274         }
275         return sb.toString();
276     }
277 
convertAndAppendNonArraySmaliTypeToJava(@otNull String smaliType, @NotNull StringBuilder dest)278     private static void convertAndAppendNonArraySmaliTypeToJava(@NotNull String smaliType, @NotNull StringBuilder dest) {
279         switch (smaliType.charAt(0)) {
280             case 'Z':
281                 dest.append("boolean");
282                 return;
283             case 'B':
284                 dest.append("byte");
285                 return;
286             case 'C':
287                 dest.append("char");
288                 return;
289             case 'S':
290                 dest.append("short");
291                 return;
292             case 'I':
293                 dest.append("int");
294                 return;
295             case 'J':
296                 dest.append("long");
297                 return;
298             case 'F':
299                 dest.append("float");
300                 return;
301             case 'D':
302                 dest.append("double");
303             case 'L':
304                 for (int i=1; i<smaliType.length()-1; i++) {
305                     char c = smaliType.charAt(i);
306                     if (c == '/') {
307                         dest.append('.');
308                     } else {
309                         dest.append(c);
310                     }
311                 }
312                 return;
313             case 'V':
314                 dest.append("void");
315                 return;
316             case 'U':
317                 if (smaliType.equals("Ujava/lang/Object;")) {
318                     dest.append("java.lang.Object");
319                     return;
320                 }
321                 // fall through
322             default:
323                 throw new RuntimeException("Invalid smali type: " + smaliType);
324         }
325     }
326 
327     @Nullable
shortNameFromQualifiedName(@ullable String qualifiedName)328     public static String shortNameFromQualifiedName(@Nullable String qualifiedName) {
329         if (qualifiedName == null) {
330             return null;
331         }
332 
333         int index = qualifiedName.lastIndexOf('.');
334         if (index == -1) {
335             return qualifiedName;
336         }
337         return qualifiedName.substring(index+1);
338     }
339 }
340