/* * Copyright (C) 2010 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.apkcheck; import java.util.HashMap; public class TypeUtils { private void TypeUtils() {} /* * Conversions for the primitive types, as well as a few things * that show up a lot so we can avoid the string manipulation. */ private static final HashMap<String,String> sQuickConvert; static { sQuickConvert = new HashMap<String,String>(); sQuickConvert.put("boolean", "Z"); sQuickConvert.put("byte", "B"); sQuickConvert.put("char", "C"); sQuickConvert.put("short", "S"); sQuickConvert.put("int", "I"); sQuickConvert.put("float", "F"); sQuickConvert.put("long", "J"); sQuickConvert.put("double", "D"); sQuickConvert.put("void", "V"); sQuickConvert.put("java.lang.Object", "Ljava/lang/Object;"); sQuickConvert.put("java.lang.String", "Ljava/lang/String;"); sQuickConvert.put("java.util.ArrayList", "Ljava/util/ArrayList;"); sQuickConvert.put("java.util.HashMap", "Ljava/util/HashMap;"); }; /* * Convert a human-centric type into something suitable for a method * signature. Examples: * * int --> I * float[] --> [F * java.lang.String --> Ljava/lang/String; */ public static String typeToDescriptor(String type) { String quick = sQuickConvert.get(type); if (quick != null) return quick; int arrayDepth = 0; int firstPosn = -1; int posn = -1; while ((posn = type.indexOf('[', posn+1)) != -1) { if (firstPosn == -1) firstPosn = posn; arrayDepth++; } /* if we found an array, strip the brackets off */ if (firstPosn != -1) type = type.substring(0, firstPosn); StringBuilder builder = new StringBuilder(); while (arrayDepth-- > 0) builder.append("["); /* retry quick convert */ quick = sQuickConvert.get(type); if (quick != null) { builder.append(quick); } else { builder.append("L"); builder.append(type.replace('.', '/')); builder.append(";"); } return builder.toString(); } /** * Converts a "simple" class name into a "binary" class name. For * example: * * SharedPreferences.Editor --> SharedPreferences$Editor * * Do not use this on fully-qualified class names. */ public static String simpleClassNameToBinary(String className) { return className.replace('.', '$'); } /** * Returns the class name portion of a fully-qualified binary class name. */ static String classNameOnly(String typeName) { int start = typeName.lastIndexOf("."); if (start < 0) { return typeName; } else { return typeName.substring(start+1); } } /** * Returns the package portion of a fully-qualified binary class name. */ static String packageNameOnly(String typeName) { int end = typeName.lastIndexOf("."); if (end < 0) { /* lives in default package */ return ""; } else { return typeName.substring(0, end); } } /** * Normalizes a full class name to binary form. * * For example, "android.view.View.OnClickListener" could be in * the "android.view" package or the "android.view.View" package. * Checking capitalization is unreliable. We do have a full list * of package names from the file though, so there's an excellent * chance that we can identify the package that way. (Of course, we * can only do this after we have finished parsing the file.) * * If the name has two or more dots, we need to compare successively * shorter strings until we find a match in the package list. * * Do not call this on previously-returned output, as that may * confuse the code that deals with generic signatures. */ public static String ambiguousToBinaryName(String typeName, ApiList apiList) { /* * In some cases this can be a generic signature: * <parameter name="collection" type="java.util.Collection<? extends E>"> * <parameter name="interfaces" type="java.lang.Class<?>[]"> * <parameter name="object" type="E"> * * If we see a '<', strip out everything from it to the '>'. That * does pretty much the right thing, though we have to deal with * nested stuff like "<? extends Map<String>>". * * Handling the third item is ugly. If the string is a single * character, change it to java.lang.Object. This is generally * insufficient and also ambiguous with respect to classes in the * default package, but we don't have much choice here, and it gets * us through the standard collection classes. Note this is risky * if somebody tries to normalize a string twice, since we could be * "promoting" a primitive type. */ typeName = stripAngleBrackets(typeName); if (typeName.length() == 1) { //System.out.println("converting X to Object: " + typeName); typeName = "java.lang.Object"; } else if (typeName.length() == 3 && typeName.substring(1, 3).equals("[]")) { //System.out.println("converting X[] to Object[]: " + typeName); typeName = "java.lang.Object[]"; } else if (typeName.length() == 4 && typeName.substring(1, 4).equals("...")) { //System.out.println("converting X... to Object[]: " + typeName); typeName = "java.lang.Object[]"; } /* * Catch-all for varargs, which come in different varieties: * java.lang.Object... * java.lang.Class... * java.lang.CharSequence... * int... * Progress... * * The latter is a generic type that we didn't catch above because * it's not using a single-character descriptor. * * The method reference for "java.lang.Class..." will be looking * for java.lang.Class[], not java.lang.Object[], so we don't want * to do a blanket conversion. Similarly, "int..." turns into int[]. * * There's not much we can do with "Progress...", unless we want * to write off the default package and filter out primitive types. * Probably easier to fix it up elsewhere. */ int ellipsisIndex = typeName.indexOf("..."); if (ellipsisIndex >= 0) { String newTypeName = typeName.substring(0, ellipsisIndex) + "[]"; //System.out.println("vararg " + typeName + " --> " + newTypeName); typeName = newTypeName; } /* * It's possible the code that generates API definition files * has been fixed. If we see a '$', just return the original. */ if (typeName.indexOf('$') >= 0) return typeName; int lastDot = typeName.lastIndexOf('.'); if (lastDot < 0) return typeName; /* * What we have looks like some variation of these: * package.Class * Class.InnerClass * long.package.name.Class * long.package.name.Class.InnerClass * * We cut it off at the last '.' and test to see if it's a known * package name. If not, keep moving left until we run out of dots. */ int nextDot = lastDot; while (nextDot >= 0) { String testName = typeName.substring(0, nextDot); if (apiList.getPackage(testName) != null) { break; } nextDot = typeName.lastIndexOf('.', nextDot-1); } if (nextDot < 0) { /* no package name found, convert all dots */ System.out.println("+++ no pkg name found on " + typeName + typeName.length()); typeName = typeName.replace('.', '$'); } else if (nextDot == lastDot) { /* class name is last element; original string is fine */ } else { /* in the middle; zap the dots in the inner class name */ String oldClassName = typeName; typeName = typeName.substring(0, nextDot+1) + typeName.substring(nextDot+1).replace('.', '$'); //System.out.println("+++ " + oldClassName + " --> " + typeName); } return typeName; } /** * Strips out everything between '<' and '>'. This will handle * nested brackets, but we're not expecting to see multiple instances * in series (i.e. "<foo<bar>>" is expected, but * "<foo>STUFF<bar> is not). * * @return the stripped string */ private static String stripAngleBrackets(String str) { /* * Since we only expect to see one "run", we can just find the * first '<' and the last '>'. Ideally we'd verify that they're * not mismatched, but we're assuming the input file is generally * correct. */ int ltIndex = str.indexOf('<'); if (ltIndex < 0) return str; int gtIndex = str.lastIndexOf('>'); if (gtIndex < 0) { System.err.println("ERROR: found '<' without '>': " + str); return str; // not much we can do } return str.substring(0, ltIndex) + str.substring(gtIndex+1); } }