Home | History | Annotate | Download | only in apkcheck
      1 /*
      2  * Copyright (C) 2010 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.android.apkcheck;
     18 
     19 import java.util.HashMap;
     20 
     21 public class TypeUtils {
     22     private void TypeUtils() {}
     23 
     24     /*
     25      * Conversions for the primitive types, as well as a few things
     26      * that show up a lot so we can avoid the string manipulation.
     27      */
     28     private static final HashMap<String,String> sQuickConvert;
     29     static {
     30         sQuickConvert = new HashMap<String,String>();
     31 
     32         sQuickConvert.put("boolean", "Z");
     33         sQuickConvert.put("byte", "B");
     34         sQuickConvert.put("char", "C");
     35         sQuickConvert.put("short", "S");
     36         sQuickConvert.put("int", "I");
     37         sQuickConvert.put("float", "F");
     38         sQuickConvert.put("long", "J");
     39         sQuickConvert.put("double", "D");
     40         sQuickConvert.put("void", "V");
     41         sQuickConvert.put("java.lang.Object", "Ljava/lang/Object;");
     42         sQuickConvert.put("java.lang.String", "Ljava/lang/String;");
     43         sQuickConvert.put("java.util.ArrayList", "Ljava/util/ArrayList;");
     44         sQuickConvert.put("java.util.HashMap", "Ljava/util/HashMap;");
     45     };
     46 
     47     /*
     48      * Convert a human-centric type into something suitable for a method
     49      * signature.  Examples:
     50      *
     51      *   int --> I
     52      *   float[] --> [F
     53      *   java.lang.String --> Ljava/lang/String;
     54      */
     55     public static String typeToDescriptor(String type) {
     56         String quick = sQuickConvert.get(type);
     57         if (quick != null)
     58             return quick;
     59 
     60         int arrayDepth = 0;
     61         int firstPosn = -1;
     62         int posn = -1;
     63         while ((posn = type.indexOf('[', posn+1)) != -1) {
     64             if (firstPosn == -1)
     65                 firstPosn = posn;
     66             arrayDepth++;
     67         }
     68 
     69         /* if we found an array, strip the brackets off */
     70         if (firstPosn != -1)
     71             type = type.substring(0, firstPosn);
     72 
     73         StringBuilder builder = new StringBuilder();
     74         while (arrayDepth-- > 0)
     75             builder.append("[");
     76 
     77         /* retry quick convert */
     78         quick = sQuickConvert.get(type);
     79         if (quick != null) {
     80             builder.append(quick);
     81         } else {
     82             builder.append("L");
     83             builder.append(type.replace('.', '/'));
     84             builder.append(";");
     85         }
     86 
     87         return builder.toString();
     88     }
     89 
     90     /**
     91      * Converts a "simple" class name into a "binary" class name.  For
     92      * example:
     93      *
     94      *   SharedPreferences.Editor --&gt; SharedPreferences$Editor
     95      *
     96      * Do not use this on fully-qualified class names.
     97      */
     98     public static String simpleClassNameToBinary(String className) {
     99         return className.replace('.', '$');
    100     }
    101 
    102     /**
    103      * Returns the class name portion of a fully-qualified binary class name.
    104      */
    105     static String classNameOnly(String typeName) {
    106         int start = typeName.lastIndexOf(".");
    107         if (start < 0) {
    108             return typeName;
    109         } else {
    110             return typeName.substring(start+1);
    111         }
    112     }
    113 
    114     /**
    115      * Returns the package portion of a fully-qualified binary class name.
    116      */
    117     static String packageNameOnly(String typeName) {
    118         int end = typeName.lastIndexOf(".");
    119         if (end < 0) {
    120             /* lives in default package */
    121             return "";
    122         } else {
    123             return typeName.substring(0, end);
    124         }
    125     }
    126 
    127 
    128     /**
    129      * Normalizes a full class name to binary form.
    130      *
    131      * For example, "android.view.View.OnClickListener" could be in
    132      * the "android.view" package or the "android.view.View" package.
    133      * Checking capitalization is unreliable.  We do have a full list
    134      * of package names from the file though, so there's an excellent
    135      * chance that we can identify the package that way.  (Of course, we
    136      * can only do this after we have finished parsing the file.)
    137      *
    138      * If the name has two or more dots, we need to compare successively
    139      * shorter strings until we find a match in the package list.
    140      *
    141      * Do not call this on previously-returned output, as that may
    142      * confuse the code that deals with generic signatures.
    143      */
    144     public static String ambiguousToBinaryName(String typeName, ApiList apiList) {
    145         /*
    146          * In some cases this can be a generic signature:
    147          *   <parameter name="collection" type="java.util.Collection&lt;? extends E&gt;">
    148          *   <parameter name="interfaces" type="java.lang.Class&lt;?&gt;[]">
    149          *   <parameter name="object" type="E">
    150          *
    151          * If we see a '<', strip out everything from it to the '>'.  That
    152          * does pretty much the right thing, though we have to deal with
    153          * nested stuff like "<? extends Map<String>>".
    154          *
    155          * Handling the third item is ugly.  If the string is a single
    156          * character, change it to java.lang.Object.  This is generally
    157          * insufficient and also ambiguous with respect to classes in the
    158          * default package, but we don't have much choice here, and it gets
    159          * us through the standard collection classes.  Note this is risky
    160          * if somebody tries to normalize a string twice, since we could be
    161          * "promoting" a primitive type.
    162          */
    163         typeName = stripAngleBrackets(typeName);
    164         if (typeName.length() == 1) {
    165             //System.out.println("converting X to Object: " + typeName);
    166             typeName = "java.lang.Object";
    167         } else if (typeName.length() == 3 &&
    168                    typeName.substring(1, 3).equals("[]")) {
    169             //System.out.println("converting X[] to Object[]: " + typeName);
    170             typeName = "java.lang.Object[]";
    171         } else if (typeName.length() == 4 &&
    172                    typeName.substring(1, 4).equals("...")) {
    173             //System.out.println("converting X... to Object[]: " + typeName);
    174             typeName = "java.lang.Object[]";
    175         }
    176 
    177         /*
    178          * Catch-all for varargs, which come in different varieties:
    179          *  java.lang.Object...
    180          *  java.lang.Class...
    181          *  java.lang.CharSequence...
    182          *  int...
    183          *  Progress...
    184          *
    185          * The latter is a generic type that we didn't catch above because
    186          * it's not using a single-character descriptor.
    187          *
    188          * The method reference for "java.lang.Class..." will be looking
    189          * for java.lang.Class[], not java.lang.Object[], so we don't want
    190          * to do a blanket conversion.  Similarly, "int..." turns into int[].
    191          *
    192          * There's not much we can do with "Progress...", unless we want
    193          * to write off the default package and filter out primitive types.
    194          * Probably easier to fix it up elsewhere.
    195          */
    196         int ellipsisIndex = typeName.indexOf("...");
    197         if (ellipsisIndex >= 0) {
    198             String newTypeName = typeName.substring(0, ellipsisIndex) + "[]";
    199             //System.out.println("vararg " + typeName + " --> " + newTypeName);
    200             typeName = newTypeName;
    201         }
    202 
    203         /*
    204          * It's possible the code that generates API definition files
    205          * has been fixed.  If we see a '$', just return the original.
    206          */
    207         if (typeName.indexOf('$') >= 0)
    208             return typeName;
    209 
    210         int lastDot = typeName.lastIndexOf('.');
    211         if (lastDot < 0)
    212             return typeName;
    213 
    214         /*
    215          * What we have looks like some variation of these:
    216          *   package.Class
    217          *   Class.InnerClass
    218          *   long.package.name.Class
    219          *   long.package.name.Class.InnerClass
    220          *
    221          * We cut it off at the last '.' and test to see if it's a known
    222          * package name.  If not, keep moving left until we run out of dots.
    223          */
    224         int nextDot = lastDot;
    225         while (nextDot >= 0) {
    226             String testName = typeName.substring(0, nextDot);
    227             if (apiList.getPackage(testName) != null) {
    228                 break;
    229             }
    230 
    231             nextDot = typeName.lastIndexOf('.', nextDot-1);
    232         }
    233 
    234         if (nextDot < 0) {
    235             /* no package name found, convert all dots */
    236             System.out.println("+++ no pkg name found on " + typeName + typeName.length());
    237             typeName = typeName.replace('.', '$');
    238         } else if (nextDot == lastDot) {
    239             /* class name is last element; original string is fine */
    240         } else {
    241             /* in the middle; zap the dots in the inner class name */
    242             String oldClassName = typeName;
    243             typeName = typeName.substring(0, nextDot+1) +
    244                 typeName.substring(nextDot+1).replace('.', '$');
    245             //System.out.println("+++ " + oldClassName + " --> " + typeName);
    246         }
    247 
    248         return typeName;
    249     }
    250 
    251     /**
    252      * Strips out everything between '<' and '>'.  This will handle
    253      * nested brackets, but we're not expecting to see multiple instances
    254      * in series (i.e. "&lt;foo&lt;bar&gt;&gt;" is expected, but
    255      * "&lt;foo&gt;STUFF&lt;bar&gt; is not).
    256      *
    257      * @return the stripped string
    258      */
    259     private static String stripAngleBrackets(String str) {
    260         /*
    261          * Since we only expect to see one "run", we can just find the
    262          * first '<' and the last '>'.  Ideally we'd verify that they're
    263          * not mismatched, but we're assuming the input file is generally
    264          * correct.
    265          */
    266         int ltIndex = str.indexOf('<');
    267         if (ltIndex < 0)
    268             return str;
    269 
    270         int gtIndex = str.lastIndexOf('>');
    271         if (gtIndex < 0) {
    272             System.err.println("ERROR: found '<' without '>': " + str);
    273             return str;     // not much we can do
    274         }
    275 
    276         return str.substring(0, ltIndex) + str.substring(gtIndex+1);
    277     }
    278 }
    279 
    280