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 {
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      */
typeToDescriptor(String type)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      */
simpleClassNameToBinary(String className)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      */
classNameOnly(String typeName)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      */
packageNameOnly(String typeName)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      */
ambiguousToBinaryName(String typeName, ApiList apiList)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      */
stripAngleBrackets(String str)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