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