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.Project;
     36 import com.intellij.psi.*;
     37 import com.intellij.psi.impl.ResolveScopeManager;
     38 import com.intellij.psi.search.GlobalSearchScope;
     39 import org.jetbrains.annotations.NotNull;
     40 import org.jetbrains.annotations.Nullable;
     41 
     42 import java.util.Map;
     43 
     44 public class NameUtils {
     45     private static final Map<String, String> javaToSmaliPrimitiveTypes = ImmutableMap.<String, String>builder()
     46             .put("boolean", "Z")
     47             .put("byte", "B")
     48             .put("char", "C")
     49             .put("short", "S")
     50             .put("int", "I")
     51             .put("long", "J")
     52             .put("float", "F")
     53             .put("double", "D")
     54             .build();
     55 
     56     @NotNull
     57     public static String javaToSmaliType(@NotNull PsiType psiType) {
     58         if (psiType instanceof PsiClassType) {
     59             PsiClass psiClass = ((PsiClassType)psiType).resolve();
     60             if (psiClass != null) {
     61                 return javaToSmaliType(psiClass);
     62             }
     63         }
     64         return javaToSmaliType(psiType.getCanonicalText());
     65     }
     66 
     67     @NotNull
     68     public static String javaToSmaliType(@NotNull PsiClass psiClass) {
     69         String qualifiedName = psiClass.getQualifiedName();
     70         if (qualifiedName == null) {
     71             throw new IllegalArgumentException("This method does not support anonymous classes");
     72         }
     73         PsiClass parent = psiClass.getContainingClass();
     74         if (parent != null) {
     75             int offset = qualifiedName.lastIndexOf('.');
     76             String parentName = qualifiedName.substring(0, offset);
     77             assert parentName.equals(parent.getQualifiedName());
     78             String className = qualifiedName.substring(offset+1, qualifiedName.length());
     79             assert className.equals(psiClass.getName());
     80             return javaToSmaliType(parentName + '$' + className);
     81         } else {
     82             return javaToSmaliType(psiClass.getQualifiedName());
     83         }
     84     }
     85 
     86     @NotNull
     87     public static String javaToSmaliType(@NotNull String javaType) {
     88         if (javaType.charAt(javaType.length()-1) == ']') {
     89             int dimensions = 0;
     90             int firstArrayChar = -1;
     91             for (int i=0; i<javaType.length(); i++) {
     92                 if (javaType.charAt(i) == '[') {
     93                     if (firstArrayChar == -1) {
     94                         firstArrayChar = i;
     95                     }
     96                     dimensions++;
     97                 }
     98             }
     99             if (dimensions > 0) {
    100                 StringBuilder sb = new StringBuilder(firstArrayChar + 2 + dimensions);
    101                 for (int i=0; i<dimensions; i++) {
    102                     sb.append('[');
    103                 }
    104                 convertSimpleJavaToSmaliType(javaType.substring(0, firstArrayChar), sb);
    105                 return sb.toString();
    106             }
    107         }
    108 
    109         return simpleJavaToSmaliType(javaType);
    110     }
    111 
    112     private static void convertSimpleJavaToSmaliType(@NotNull String javaType, @NotNull StringBuilder dest) {
    113         String smaliType = javaToSmaliPrimitiveTypes.get(javaType);
    114         if (smaliType != null) {
    115             dest.append(smaliType);
    116         } else {
    117             dest.append('L');
    118             for (int i=0; i<javaType.length(); i++) {
    119                 char c = javaType.charAt(i);
    120                 if (c == '.') {
    121                     dest.append('/');
    122                 } else {
    123                     dest.append(c);
    124                 }
    125             }
    126             dest.append(';');
    127         }
    128     }
    129 
    130     public static PsiClass resolveSmaliType(@NotNull Project project, @NotNull GlobalSearchScope scope,
    131                                             @NotNull String smaliType) {
    132         JavaPsiFacade facade = JavaPsiFacade.getInstance(project);
    133 
    134         String javaType = NameUtils.smaliToJavaType(smaliType);
    135 
    136         PsiClass psiClass = facade.findClass(javaType, scope);
    137         if (psiClass != null) {
    138             return psiClass;
    139         }
    140 
    141         int offset = javaType.lastIndexOf('.');
    142         if (offset < 0) {
    143             offset = 0;
    144         }
    145         // find the first $ after the last .
    146         offset = javaType.indexOf('$', offset+1);
    147         if (offset < 0) {
    148             return null;
    149         }
    150 
    151         while (offset > 0 && offset < javaType.length()-1) {
    152             String left = javaType.substring(0, offset);
    153             psiClass = facade.findClass(left, scope);
    154             if (psiClass != null) {
    155                 psiClass = findInnerClass(psiClass, javaType.substring(offset+1, javaType.length()), facade, scope);
    156                 if (psiClass != null) {
    157                     return psiClass;
    158                 }
    159             }
    160             offset = javaType.indexOf('$', offset+1);
    161         }
    162         return null;
    163     }
    164 
    165     @Nullable
    166     public static PsiClass resolveSmaliType(@NotNull PsiElement element, @NotNull String smaliType) {
    167         GlobalSearchScope scope = ResolveScopeManager.getElementResolveScope(element);
    168         return resolveSmaliType(element.getProject(), scope, smaliType);
    169     }
    170 
    171     @Nullable
    172     public static PsiClass findInnerClass(@NotNull PsiClass outerClass, String innerText, JavaPsiFacade facade,
    173                                           GlobalSearchScope scope) {
    174         int offset = innerText.indexOf('$');
    175         if (offset < 0) {
    176             offset = innerText.length();
    177         }
    178 
    179         while (offset > 0 && offset <= innerText.length()) {
    180             String left = innerText.substring(0, offset);
    181             String nextInner = outerClass.getQualifiedName() + "." + left;
    182             PsiClass psiClass = facade.findClass(nextInner, scope);
    183             if (psiClass != null) {
    184                 if (offset < innerText.length()) {
    185                     psiClass = findInnerClass(psiClass, innerText.substring(offset+1, innerText.length()), facade,
    186                             scope);
    187                     if (psiClass != null) {
    188                         return psiClass;
    189                     }
    190                 } else {
    191                     return psiClass;
    192                 }
    193             }
    194             if (offset >= innerText.length()) {
    195                 break;
    196             }
    197             offset = innerText.indexOf('$', offset+1);
    198             if (offset < 0) {
    199                 offset = innerText.length();
    200             }
    201         }
    202         return null;
    203     }
    204 
    205     private static String simpleJavaToSmaliType(@NotNull String simpleJavaType) {
    206         StringBuilder sb = new StringBuilder(simpleJavaType.length() + 2);
    207         convertSimpleJavaToSmaliType(simpleJavaType, sb);
    208         sb.trimToSize();
    209         return sb.toString();
    210     }
    211 
    212     @NotNull
    213     public static String smaliToJavaType(@NotNull String smaliType) {
    214         if (smaliType.charAt(0) == '[') {
    215             return convertSmaliArrayToJava(smaliType);
    216         } else {
    217             StringBuilder sb = new StringBuilder(smaliType.length());
    218             convertAndAppendNonArraySmaliTypeToJava(smaliType, sb);
    219             return sb.toString();
    220         }
    221     }
    222 
    223     @NotNull
    224     public static String resolveSmaliToJavaType(@NotNull Project project, @NotNull GlobalSearchScope scope,
    225                                                 @NotNull String smaliType) {
    226         // First, try to resolve the type and get its qualified name, so that we can make sure
    227         // to use the correct name for inner classes
    228         PsiClass resolvedType = resolveSmaliType(project, scope, smaliType);
    229         if (resolvedType != null) {
    230             String qualifiedName = resolvedType.getQualifiedName();
    231             if (qualifiedName != null) {
    232                 return qualifiedName;
    233             }
    234         }
    235 
    236         // if we can't find it, just do a textual conversion of the name
    237         return smaliToJavaType(smaliType);
    238     }
    239 
    240     @NotNull
    241     public static String resolveSmaliToJavaType(@NotNull PsiElement element, @NotNull String smaliType) {
    242         return resolveSmaliToJavaType(element.getProject(), element.getResolveScope(), smaliType);
    243     }
    244 
    245     @NotNull
    246     public static PsiType resolveSmaliToPsiType(@NotNull PsiElement element, @NotNull String smaliType) {
    247         PsiClass resolvedType = resolveSmaliType(element, smaliType);
    248         if (resolvedType != null) {
    249             PsiElementFactory factory = JavaPsiFacade.getInstance(element.getProject()).getElementFactory();
    250             return factory.createType(resolvedType);
    251         }
    252 
    253         String javaType = NameUtils.smaliToJavaType(smaliType);
    254         PsiElementFactory factory = JavaPsiFacade.getInstance(element.getProject()).getElementFactory();
    255         return factory.createTypeFromText(javaType, element);
    256     }
    257 
    258     @NotNull
    259     private static String convertSmaliArrayToJava(@NotNull String smaliType) {
    260         int dimensions=0;
    261         while (smaliType.charAt(dimensions) == '[') {
    262             dimensions++;
    263         }
    264 
    265         StringBuilder sb = new StringBuilder(smaliType.length() + dimensions);
    266         convertAndAppendNonArraySmaliTypeToJava(smaliType.substring(dimensions), sb);
    267         for (int i=0; i<dimensions; i++) {
    268             sb.append("[]");
    269         }
    270         return sb.toString();
    271     }
    272 
    273     private static void convertAndAppendNonArraySmaliTypeToJava(@NotNull String smaliType, @NotNull StringBuilder dest) {
    274         switch (smaliType.charAt(0)) {
    275             case 'Z':
    276                 dest.append("boolean");
    277                 return;
    278             case 'B':
    279                 dest.append("byte");
    280                 return;
    281             case 'C':
    282                 dest.append("char");
    283                 return;
    284             case 'S':
    285                 dest.append("short");
    286                 return;
    287             case 'I':
    288                 dest.append("int");
    289                 return;
    290             case 'J':
    291                 dest.append("long");
    292                 return;
    293             case 'F':
    294                 dest.append("float");
    295                 return;
    296             case 'D':
    297                 dest.append("double");
    298             case 'L':
    299                 for (int i=1; i<smaliType.length()-1; i++) {
    300                     char c = smaliType.charAt(i);
    301                     if (c == '/') {
    302                         dest.append('.');
    303                     } else {
    304                         dest.append(c);
    305                     }
    306                 }
    307                 return;
    308             case 'V':
    309                 dest.append("void");
    310                 return;
    311             case 'U':
    312                 if (smaliType.equals("Ujava/lang/Object;")) {
    313                     dest.append("java.lang.Object");
    314                     return;
    315                 }
    316                 // fall through
    317             default:
    318                 throw new RuntimeException("Invalid smali type: " + smaliType);
    319         }
    320     }
    321 
    322     @Nullable
    323     public static String shortNameFromQualifiedName(@Nullable String qualifiedName) {
    324         if (qualifiedName == null) {
    325             return null;
    326         }
    327 
    328         int index = qualifiedName.lastIndexOf('.');
    329         if (index == -1) {
    330             return qualifiedName;
    331         }
    332         return qualifiedName.substring(index+1);
    333     }
    334 }
    335