/* * Copyright 2014, Google Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.jf.smalidea.util; import com.google.common.collect.ImmutableMap; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.impl.ResolveScopeManager; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; public class NameUtils { private static final Map<String, String> javaToSmaliPrimitiveTypes = ImmutableMap.<String, String>builder() .put("boolean", "Z") .put("byte", "B") .put("char", "C") .put("short", "S") .put("int", "I") .put("long", "J") .put("float", "F") .put("double", "D") .build(); @NotNull public static String javaToSmaliType(@NotNull PsiType psiType) { if (psiType instanceof PsiClassType) { PsiClass psiClass = ((PsiClassType)psiType).resolve(); if (psiClass != null) { return javaToSmaliType(psiClass); } } return javaToSmaliType(psiType.getCanonicalText()); } @NotNull public static String javaToSmaliType(@NotNull PsiClass psiClass) { String qualifiedName = psiClass.getQualifiedName(); if (qualifiedName == null) { throw new IllegalArgumentException("This method does not support anonymous classes"); } PsiClass parent = psiClass.getContainingClass(); if (parent != null) { int offset = qualifiedName.lastIndexOf('.'); String parentName = qualifiedName.substring(0, offset); assert parentName.equals(parent.getQualifiedName()); String className = qualifiedName.substring(offset+1, qualifiedName.length()); assert className.equals(psiClass.getName()); return javaToSmaliType(parentName + '$' + className); } else { return javaToSmaliType(psiClass.getQualifiedName()); } } @NotNull public static String javaToSmaliType(@NotNull String javaType) { if (javaType.charAt(javaType.length()-1) == ']') { int dimensions = 0; int firstArrayChar = -1; for (int i=0; i<javaType.length(); i++) { if (javaType.charAt(i) == '[') { if (firstArrayChar == -1) { firstArrayChar = i; } dimensions++; } } if (dimensions > 0) { StringBuilder sb = new StringBuilder(firstArrayChar + 2 + dimensions); for (int i=0; i<dimensions; i++) { sb.append('['); } convertSimpleJavaToSmaliType(javaType.substring(0, firstArrayChar), sb); return sb.toString(); } } return simpleJavaToSmaliType(javaType); } private static void convertSimpleJavaToSmaliType(@NotNull String javaType, @NotNull StringBuilder dest) { String smaliType = javaToSmaliPrimitiveTypes.get(javaType); if (smaliType != null) { dest.append(smaliType); } else { dest.append('L'); for (int i=0; i<javaType.length(); i++) { char c = javaType.charAt(i); if (c == '.') { dest.append('/'); } else { dest.append(c); } } dest.append(';'); } } public static PsiClass resolveSmaliType(@NotNull Project project, @NotNull GlobalSearchScope scope, @NotNull String smaliType) { if (DumbService.isDumb(project)) { return null; } JavaPsiFacade facade = JavaPsiFacade.getInstance(project); String javaType = NameUtils.smaliToJavaType(smaliType); PsiClass psiClass = facade.findClass(javaType, scope); if (psiClass != null) { return psiClass; } int offset = javaType.lastIndexOf('.'); if (offset < 0) { offset = 0; } // find the first $ after the last . offset = javaType.indexOf('$', offset+1); if (offset < 0) { return null; } while (offset > 0 && offset < javaType.length()-1) { String left = javaType.substring(0, offset); psiClass = facade.findClass(left, scope); if (psiClass != null) { psiClass = findInnerClass(psiClass, javaType.substring(offset+1, javaType.length()), facade, scope); if (psiClass != null) { return psiClass; } } offset = javaType.indexOf('$', offset+1); } return null; } @Nullable public static PsiClass resolveSmaliType(@NotNull PsiElement element, @NotNull String smaliType) { GlobalSearchScope scope = ResolveScopeManager.getElementResolveScope(element); return resolveSmaliType(element.getProject(), scope, smaliType); } @Nullable public static PsiClass findInnerClass(@NotNull PsiClass outerClass, String innerText, JavaPsiFacade facade, GlobalSearchScope scope) { int offset = innerText.indexOf('$'); if (offset < 0) { offset = innerText.length(); } while (offset > 0 && offset <= innerText.length()) { String left = innerText.substring(0, offset); String nextInner = outerClass.getQualifiedName() + "." + left; PsiClass psiClass = facade.findClass(nextInner, scope); if (psiClass != null) { if (offset < innerText.length()) { psiClass = findInnerClass(psiClass, innerText.substring(offset+1, innerText.length()), facade, scope); if (psiClass != null) { return psiClass; } } else { return psiClass; } } if (offset >= innerText.length()) { break; } offset = innerText.indexOf('$', offset+1); if (offset < 0) { offset = innerText.length(); } } return null; } private static String simpleJavaToSmaliType(@NotNull String simpleJavaType) { StringBuilder sb = new StringBuilder(simpleJavaType.length() + 2); convertSimpleJavaToSmaliType(simpleJavaType, sb); sb.trimToSize(); return sb.toString(); } @NotNull public static String smaliToJavaType(@NotNull String smaliType) { if (smaliType.charAt(0) == '[') { return convertSmaliArrayToJava(smaliType); } else { StringBuilder sb = new StringBuilder(smaliType.length()); convertAndAppendNonArraySmaliTypeToJava(smaliType, sb); return sb.toString(); } } @NotNull public static String resolveSmaliToJavaType(@NotNull Project project, @NotNull GlobalSearchScope scope, @NotNull String smaliType) { // First, try to resolve the type and get its qualified name, so that we can make sure // to use the correct name for inner classes PsiClass resolvedType = resolveSmaliType(project, scope, smaliType); if (resolvedType != null) { String qualifiedName = resolvedType.getQualifiedName(); if (qualifiedName != null) { return qualifiedName; } } // if we can't find it, just do a textual conversion of the name return smaliToJavaType(smaliType); } @NotNull public static String resolveSmaliToJavaType(@NotNull PsiElement element, @NotNull String smaliType) { return resolveSmaliToJavaType(element.getProject(), element.getResolveScope(), smaliType); } @NotNull public static PsiType resolveSmaliToPsiType(@NotNull PsiElement element, @NotNull String smaliType) { PsiClass resolvedType = resolveSmaliType(element, smaliType); if (resolvedType != null) { PsiElementFactory factory = JavaPsiFacade.getInstance(element.getProject()).getElementFactory(); return factory.createType(resolvedType); } String javaType = NameUtils.smaliToJavaType(smaliType); PsiElementFactory factory = JavaPsiFacade.getInstance(element.getProject()).getElementFactory(); return factory.createTypeFromText(javaType, element); } @NotNull private static String convertSmaliArrayToJava(@NotNull String smaliType) { int dimensions=0; while (smaliType.charAt(dimensions) == '[') { dimensions++; } StringBuilder sb = new StringBuilder(smaliType.length() + dimensions); convertAndAppendNonArraySmaliTypeToJava(smaliType.substring(dimensions), sb); for (int i=0; i<dimensions; i++) { sb.append("[]"); } return sb.toString(); } private static void convertAndAppendNonArraySmaliTypeToJava(@NotNull String smaliType, @NotNull StringBuilder dest) { switch (smaliType.charAt(0)) { case 'Z': dest.append("boolean"); return; case 'B': dest.append("byte"); return; case 'C': dest.append("char"); return; case 'S': dest.append("short"); return; case 'I': dest.append("int"); return; case 'J': dest.append("long"); return; case 'F': dest.append("float"); return; case 'D': dest.append("double"); case 'L': for (int i=1; i<smaliType.length()-1; i++) { char c = smaliType.charAt(i); if (c == '/') { dest.append('.'); } else { dest.append(c); } } return; case 'V': dest.append("void"); return; case 'U': if (smaliType.equals("Ujava/lang/Object;")) { dest.append("java.lang.Object"); return; } // fall through default: throw new RuntimeException("Invalid smali type: " + smaliType); } } @Nullable public static String shortNameFromQualifiedName(@Nullable String qualifiedName) { if (qualifiedName == null) { return null; } int index = qualifiedName.lastIndexOf('.'); if (index == -1) { return qualifiedName; } return qualifiedName.substring(index+1); } }