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