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 --> 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<? extends E>"> 148 * <parameter name="interfaces" type="java.lang.Class<?>[]"> 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. "<foo<bar>>" is expected, but 255 * "<foo>STUFF<bar> 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