Home | History | Annotate | Download | only in util
      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
     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
     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
     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 
    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 
    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
    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
    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 
    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
    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
    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
    246     public static String resolveSmaliToJavaType(@NotNull PsiElement element, @NotNull String smaliType) {
    247         return resolveSmaliToJavaType(element.getProject(), element.getResolveScope(), smaliType);
    248     }
    249 
    250     @NotNull
    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
    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 
    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
    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