| // Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| package com.android.tools.r8.utils; | |
| import static com.android.tools.r8.utils.FileUtils.CLASS_EXTENSION; | |
| import com.android.tools.r8.errors.CompilationError; | |
| import com.android.tools.r8.errors.Unreachable; | |
| import com.android.tools.r8.naming.ClassNameMapper; | |
| import com.google.common.collect.ImmutableMap; | |
| import java.io.File; | |
| import java.nio.file.Path; | |
| import java.util.Map; | |
| public class DescriptorUtils { | |
| public static final char DESCRIPTOR_PACKAGE_SEPARATOR = '/'; | |
| public static final char JAVA_PACKAGE_SEPARATOR = '.'; | |
| private static final Map<String, String> typeNameToLetterMap = | |
| ImmutableMap.<String, String>builder() | |
| .put("void", "V") | |
| .put("boolean", "Z") | |
| .put("byte", "B") | |
| .put("short", "S") | |
| .put("char", "C") | |
| .put("int", "I") | |
| .put("long", "J") | |
| .put("float", "F") | |
| .put("double", "D") | |
| .build(); | |
| private static String internalToDescriptor( | |
| String typeName, boolean shorty, boolean ignorePrimitives) { | |
| String descriptor = null; | |
| if (!ignorePrimitives) { | |
| descriptor = typeNameToLetterMap.get(typeName); | |
| } | |
| if (descriptor != null) { | |
| return descriptor; | |
| } | |
| // Must be some array or object type. | |
| if (shorty) { | |
| return "L"; | |
| } | |
| if (typeName.endsWith("[]")) { | |
| return "[" + internalToDescriptor( | |
| typeName.substring(0, typeName.length() - 2), shorty, ignorePrimitives); | |
| } | |
| // Must be an object type. | |
| return "L" + typeName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR) + ";"; | |
| } | |
| /** | |
| * Convert a Java type name to a descriptor string. | |
| * | |
| * @param typeName the java type name | |
| * @return the descriptor string | |
| */ | |
| public static String javaTypeToDescriptor(String typeName) { | |
| assert typeName.indexOf(DESCRIPTOR_PACKAGE_SEPARATOR) == -1; | |
| return internalToDescriptor(typeName, false, false); | |
| } | |
| /** | |
| * Convert a Java type name to a descriptor string ignoring primitive types. | |
| * | |
| * Ignoring primitives mean that type named like int and long are considered class names, will | |
| * return Lint; and Llong; respectively instead of I and J. These are not legal Java class names, | |
| * but valid on the JVM and minification/obfuscation can generate them. | |
| * | |
| * @param typeName the java type name | |
| * @return the descriptor string | |
| */ | |
| public static String javaTypeToDescriptorIgnorePrimitives(String typeName) { | |
| assert typeName.indexOf(DESCRIPTOR_PACKAGE_SEPARATOR) == -1; | |
| return internalToDescriptor(typeName, false, true); | |
| } | |
| /** | |
| * Convert a Java type name to a descriptor string only if the given {@param typeName} is valid. | |
| * | |
| * @param typeName the java type name | |
| * @return the descriptor string if {@param typeName} is not valid or null otherwise | |
| */ | |
| public static String javaTypeToDescriptorIfValidJavaType(String typeName) { | |
| if (isValidJavaType(typeName)) { | |
| return javaTypeToDescriptor(typeName); | |
| } | |
| return null; | |
| } | |
| /** | |
| * Determine the given {@param typeName} is a valid jvms binary name or not (jvms 4.2.1). | |
| * | |
| * @param typeName the jvms binary name | |
| * @return true if and only if the given type name is valid jvms binary name | |
| */ | |
| public static boolean isValidJavaType(String typeName) { | |
| if (typeName.length() == 0) { | |
| return false; | |
| } | |
| char last = 0; | |
| for (int i = 0; i < typeName.length(); i++) { | |
| char c = typeName.charAt(i); | |
| if (c == ';' || | |
| c == '[' || | |
| c == '/') { | |
| return false; | |
| } | |
| if (c == '.' && (i == 0 || last == '.')) { | |
| return false; | |
| } | |
| last = c; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Convert a Java type name to a shorty descriptor string. | |
| * | |
| * @param typeName the java type name | |
| * @return the shorty descriptor string | |
| */ | |
| public static String javaTypeToShorty(String typeName) { | |
| return internalToDescriptor(typeName, true, false); | |
| } | |
| /** | |
| * Convert a type descriptor to a Java type name. | |
| * | |
| * @param descriptor type descriptor | |
| * @return Java type name | |
| */ | |
| public static String descriptorToJavaType(String descriptor) { | |
| return descriptorToJavaType(descriptor, null); | |
| } | |
| /** | |
| * Convert a class type descriptor to an ASM internal name. | |
| * | |
| * @param descriptor type descriptor | |
| * @return Java type name | |
| */ | |
| public static String descriptorToInternalName(String descriptor) { | |
| switch (descriptor.charAt(0)) { | |
| case '[': | |
| return descriptor; | |
| case 'L': | |
| return descriptor.substring(1, descriptor.length() - 1); | |
| default: | |
| throw new Unreachable("Not array or class type"); | |
| } | |
| } | |
| /** | |
| * Convert a type descriptor to a Java type name. Will also deobfuscate class names if a | |
| * class mapper is provided. | |
| * | |
| * @param descriptor type descriptor | |
| * @param classNameMapper class name mapper for mapping obfuscated class names | |
| * @return Java type name | |
| */ | |
| public static String descriptorToJavaType(String descriptor, ClassNameMapper classNameMapper) { | |
| switch (descriptor.charAt(0)) { | |
| case 'L': | |
| assert descriptor.charAt(descriptor.length() - 1) == ';'; | |
| String clazz = descriptor.substring(1, descriptor.length() - 1) | |
| .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| String originalName = | |
| classNameMapper == null ? clazz : classNameMapper.deobfuscateClassName(clazz); | |
| return originalName; | |
| case '[': | |
| return descriptorToJavaType(descriptor.substring(1, descriptor.length()), classNameMapper) | |
| + "[]"; | |
| case 'V': | |
| return "void"; | |
| case 'Z': | |
| return "boolean"; | |
| case 'B': | |
| return "byte"; | |
| case 'S': | |
| return "short"; | |
| case 'C': | |
| return "char"; | |
| case 'I': | |
| return "int"; | |
| case 'J': | |
| return "long"; | |
| case 'F': | |
| return "float"; | |
| case 'D': | |
| return "double"; | |
| default: | |
| throw new Unreachable("Unknown type " + descriptor); | |
| } | |
| } | |
| /** | |
| * Get simple class name from its descriptor. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return class name i.e. "Object" | |
| */ | |
| public static String getSimpleClassNameFromDescriptor(String classDescriptor) { | |
| return getSimpleClassNameFromBinaryName(getClassBinaryNameFromDescriptor(classDescriptor)); | |
| } | |
| /** | |
| * Get class name from its descriptor. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return full class name i.e. "java.lang.Object" | |
| */ | |
| public static String getClassNameFromDescriptor(String classDescriptor) { | |
| return getClassBinaryNameFromDescriptor(classDescriptor) | |
| .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| } | |
| /** | |
| * Get package java name from a class descriptor. | |
| * | |
| * @param descriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return java package name i.e. "java.lang" | |
| */ | |
| public static String getPackageNameFromDescriptor(String descriptor) { | |
| return getPackageNameFromBinaryName(getClassBinaryNameFromDescriptor(descriptor)); | |
| } | |
| /** | |
| * Convert class descriptor to a binary name. | |
| * | |
| * @param classDescriptor a class descriptor i.e. "Ljava/lang/Object;" | |
| * @return class binary name i.e. "java/lang/Object" | |
| */ | |
| public static String getClassBinaryNameFromDescriptor(String classDescriptor) { | |
| assert isClassDescriptor(classDescriptor) : "Invalid class descriptor " | |
| + classDescriptor; | |
| return classDescriptor.substring(1, classDescriptor.length() - 1); | |
| } | |
| /** | |
| * Convert package name to a binary name. | |
| * | |
| * @param packageName a package name i.e., "java.lang" | |
| * @return java package name in a binary name format, i.e., java/lang | |
| */ | |
| public static String getPackageBinaryNameFromJavaType(String packageName) { | |
| return packageName.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR); | |
| } | |
| /** | |
| * Convert class name to a binary name. | |
| * | |
| * @param className a package name i.e., "java.lang.Object" | |
| * @return java class name in a binary name format, i.e., java/lang/Object | |
| */ | |
| public static String getBinaryNameFromJavaType(String className) { | |
| return className.replace(JAVA_PACKAGE_SEPARATOR, DESCRIPTOR_PACKAGE_SEPARATOR); | |
| } | |
| /** | |
| * Convert a class binary name to a descriptor. | |
| * | |
| * @param typeBinaryName class binary name i.e. "java/lang/Object" | |
| * @return a class descriptor i.e. "Ljava/lang/Object;" | |
| */ | |
| public static String getDescriptorFromClassBinaryName(String typeBinaryName) { | |
| return ('L' + typeBinaryName + ';'); | |
| } | |
| /** | |
| * Get class name from its binary name. | |
| * | |
| * @param classBinaryName a class binary name i.e. "java/lang/Object" | |
| * @return class name i.e. "Object" | |
| */ | |
| public static String getSimpleClassNameFromBinaryName(String classBinaryName) { | |
| int simpleNameIndex = classBinaryName.lastIndexOf(DESCRIPTOR_PACKAGE_SEPARATOR); | |
| return (simpleNameIndex < 0) ? classBinaryName : classBinaryName.substring(simpleNameIndex + 1); | |
| } | |
| public static boolean isClassDescriptor(String descriptor) { | |
| char[] buffer = descriptor.toCharArray(); | |
| int length = buffer.length; | |
| if (length < 3 || buffer[0] != 'L') { | |
| return false; | |
| } | |
| int pos = 1; | |
| char ch; | |
| do { | |
| // First letter of an Ident (an Ident can't be empty) | |
| if (pos >= length) { | |
| return false; | |
| } | |
| ch = buffer[pos++]; | |
| if (isInvalidChar(ch) || ch == DESCRIPTOR_PACKAGE_SEPARATOR || ch == ';') { | |
| return false; | |
| } | |
| // Next letters of an Ident | |
| do { | |
| if (pos >= length) { | |
| return false; | |
| } | |
| ch = buffer[pos++]; | |
| if (isInvalidChar(ch)) { | |
| return false; | |
| } | |
| } while (ch != DESCRIPTOR_PACKAGE_SEPARATOR && ch != ';'); | |
| } while (ch != ';'); | |
| return pos == length; | |
| } | |
| /** | |
| * Get package java name from a class binary name | |
| * | |
| * @param classBinaryName a class binary name i.e. "java/lang/Object" | |
| * @return java package name i.e. "java.lang" | |
| */ | |
| public static String getPackageNameFromBinaryName(String classBinaryName) { | |
| int nameIndex = classBinaryName.lastIndexOf(DESCRIPTOR_PACKAGE_SEPARATOR); | |
| return (nameIndex < 0) ? "" : classBinaryName.substring(0, nameIndex) | |
| .replace(DESCRIPTOR_PACKAGE_SEPARATOR, JAVA_PACKAGE_SEPARATOR); | |
| } | |
| private static boolean isInvalidChar(char ch) { | |
| switch (ch) { | |
| case JAVA_PACKAGE_SEPARATOR: | |
| case '[': | |
| return true; | |
| default: | |
| return false; | |
| } | |
| } | |
| /** | |
| * Guess class descriptor from location of the class file on the file system | |
| * | |
| * @param name Path of the file to convert to the corresponding descriptor | |
| * @return java class descriptor | |
| */ | |
| public static String guessTypeDescriptor(Path name) { | |
| String fileName = name.toString(); | |
| if (File.separatorChar != '/') { | |
| fileName = fileName.replace(File.separatorChar, '/'); | |
| } | |
| return guessTypeDescriptor(fileName); | |
| } | |
| /** | |
| * Guess class descriptor from location of the class file. This method assumes that the | |
| * name uses '/' as the separator. Therefore, this should not be the name of a file | |
| * on a file system. | |
| * | |
| * @param name the location of the class file to convert to descriptor | |
| * @return java class descriptor | |
| */ | |
| public static String guessTypeDescriptor(String name) { | |
| assert name != null; | |
| assert name.endsWith(CLASS_EXTENSION) : | |
| "Name " + name + " must have " + CLASS_EXTENSION + " suffix"; | |
| String descriptor = name.substring(0, name.length() - CLASS_EXTENSION.length()); | |
| if (descriptor.contains(".")) { | |
| throw new CompilationError("Unexpected class file name: " + name); | |
| } | |
| return 'L' + descriptor + ';'; | |
| } | |
| public static String getPathFromDescriptor(String descriptor) { | |
| // We are quite loose on names here to support testing illegal names, too. | |
| assert descriptor.startsWith("L"); | |
| assert descriptor.endsWith(";"); | |
| return descriptor.substring(1, descriptor.length() - 1) + ".class"; | |
| } | |
| public static String getPathFromJavaType(String typeName) { | |
| assert isValidJavaType(typeName); | |
| return typeName.replace(".", "/") + ".class"; | |
| } | |
| } |