1 /* 2 * Copyright (C) 2007 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.dx.rop.type; 18 19 import java.util.HashMap; 20 21 /** 22 * Representation of a method descriptor. Instances of this class are 23 * generally interned and may be usefully compared with each other 24 * using {@code ==}. 25 */ 26 public final class Prototype implements Comparable<Prototype> { 27 /** {@code non-null;} intern table mapping string descriptors to instances */ 28 private static final HashMap<String, Prototype> internTable = 29 new HashMap<String, Prototype>(500); 30 31 /** {@code non-null;} method descriptor */ 32 private final String descriptor; 33 34 /** {@code non-null;} return type */ 35 private final Type returnType; 36 37 /** {@code non-null;} list of parameter types */ 38 private final StdTypeList parameterTypes; 39 40 /** {@code null-ok;} list of parameter frame types, if calculated */ 41 private StdTypeList parameterFrameTypes; 42 43 /** 44 * Returns the unique instance corresponding to the 45 * given method descriptor. See vmspec-2 sec4.3.3 for details on the 46 * field descriptor syntax. 47 * 48 * @param descriptor {@code non-null;} the descriptor 49 * @return {@code non-null;} the corresponding instance 50 * @throws IllegalArgumentException thrown if the descriptor has 51 * invalid syntax 52 */ intern(String descriptor)53 public static Prototype intern(String descriptor) { 54 if (descriptor == null) { 55 throw new NullPointerException("descriptor == null"); 56 } 57 58 Prototype result; 59 synchronized (internTable) { 60 result = internTable.get(descriptor); 61 } 62 if (result != null) { 63 return result; 64 } 65 66 Type[] params = makeParameterArray(descriptor); 67 int paramCount = 0; 68 int at = 1; 69 70 for (;;) { 71 int startAt = at; 72 char c = descriptor.charAt(at); 73 if (c == ')') { 74 at++; 75 break; 76 } 77 78 // Skip array markers. 79 while (c == '[') { 80 at++; 81 c = descriptor.charAt(at); 82 } 83 84 if (c == 'L') { 85 // It looks like the start of a class name; find the end. 86 int endAt = descriptor.indexOf(';', at); 87 if (endAt == -1) { 88 throw new IllegalArgumentException("bad descriptor"); 89 } 90 at = endAt + 1; 91 } else { 92 at++; 93 } 94 95 params[paramCount] = 96 Type.intern(descriptor.substring(startAt, at)); 97 paramCount++; 98 } 99 100 Type returnType = Type.internReturnType(descriptor.substring(at)); 101 StdTypeList parameterTypes = new StdTypeList(paramCount); 102 103 for (int i = 0; i < paramCount; i++) { 104 parameterTypes.set(i, params[i]); 105 } 106 107 result = new Prototype(descriptor, returnType, parameterTypes); 108 return putIntern(result); 109 } 110 111 /** 112 * Helper for {@link #intern} which returns an empty array to 113 * populate with parsed parameter types, and which also ensures 114 * that there is a '(' at the start of the descriptor and a 115 * single ')' somewhere before the end. 116 * 117 * @param descriptor {@code non-null;} the descriptor string 118 * @return {@code non-null;} array large enough to hold all parsed parameter 119 * types, but which is likely actually larger than needed 120 */ makeParameterArray(String descriptor)121 private static Type[] makeParameterArray(String descriptor) { 122 int length = descriptor.length(); 123 124 if (descriptor.charAt(0) != '(') { 125 throw new IllegalArgumentException("bad descriptor"); 126 } 127 128 /* 129 * This is a cheesy way to establish an upper bound on the 130 * number of parameters: Just count capital letters. 131 */ 132 int closeAt = 0; 133 int maxParams = 0; 134 for (int i = 1; i < length; i++) { 135 char c = descriptor.charAt(i); 136 if (c == ')') { 137 closeAt = i; 138 break; 139 } 140 if ((c >= 'A') && (c <= 'Z')) { 141 maxParams++; 142 } 143 } 144 145 if ((closeAt == 0) || (closeAt == (length - 1))) { 146 throw new IllegalArgumentException("bad descriptor"); 147 } 148 149 if (descriptor.indexOf(')', closeAt + 1) != -1) { 150 throw new IllegalArgumentException("bad descriptor"); 151 } 152 153 return new Type[maxParams]; 154 } 155 156 /** 157 * Interns an instance, adding to the descriptor as necessary based 158 * on the given definer, name, and flags. For example, an init 159 * method has an uninitialized object of type {@code definer} 160 * as its first argument. 161 * 162 * @param descriptor {@code non-null;} the descriptor string 163 * @param definer {@code non-null;} class the method is defined on 164 * @param isStatic whether this is a static method 165 * @param isInit whether this is an init method 166 * @return {@code non-null;} the interned instance 167 */ intern(String descriptor, Type definer, boolean isStatic, boolean isInit)168 public static Prototype intern(String descriptor, Type definer, 169 boolean isStatic, boolean isInit) { 170 Prototype base = intern(descriptor); 171 172 if (isStatic) { 173 return base; 174 } 175 176 if (isInit) { 177 definer = definer.asUninitialized(Integer.MAX_VALUE); 178 } 179 180 return base.withFirstParameter(definer); 181 } 182 183 /** 184 * Interns an instance which consists of the given number of 185 * {@code int}s along with the given return type 186 * 187 * @param returnType {@code non-null;} the return type 188 * @param count {@code > 0;} the number of elements in the prototype 189 * @return {@code non-null;} the interned instance 190 */ internInts(Type returnType, int count)191 public static Prototype internInts(Type returnType, int count) { 192 // Make the descriptor... 193 194 StringBuffer sb = new StringBuffer(100); 195 196 sb.append('('); 197 198 for (int i = 0; i < count; i++) { 199 sb.append('I'); 200 } 201 202 sb.append(')'); 203 sb.append(returnType.getDescriptor()); 204 205 // ...and intern it. 206 return intern(sb.toString()); 207 } 208 209 /** 210 * Constructs an instance. This is a private constructor; use one 211 * of the public static methods to get instances. 212 * 213 * @param descriptor {@code non-null;} the descriptor string 214 */ Prototype(String descriptor, Type returnType, StdTypeList parameterTypes)215 private Prototype(String descriptor, Type returnType, 216 StdTypeList parameterTypes) { 217 if (descriptor == null) { 218 throw new NullPointerException("descriptor == null"); 219 } 220 221 if (returnType == null) { 222 throw new NullPointerException("returnType == null"); 223 } 224 225 if (parameterTypes == null) { 226 throw new NullPointerException("parameterTypes == null"); 227 } 228 229 this.descriptor = descriptor; 230 this.returnType = returnType; 231 this.parameterTypes = parameterTypes; 232 this.parameterFrameTypes = null; 233 } 234 235 /** {@inheritDoc} */ 236 @Override equals(Object other)237 public boolean equals(Object other) { 238 if (this == other) { 239 /* 240 * Since externally-visible instances are interned, this 241 * check helps weed out some easy cases. 242 */ 243 return true; 244 } 245 246 if (!(other instanceof Prototype)) { 247 return false; 248 } 249 250 return descriptor.equals(((Prototype) other).descriptor); 251 } 252 253 /** {@inheritDoc} */ 254 @Override hashCode()255 public int hashCode() { 256 return descriptor.hashCode(); 257 } 258 259 /** {@inheritDoc} */ compareTo(Prototype other)260 public int compareTo(Prototype other) { 261 if (this == other) { 262 return 0; 263 } 264 265 /* 266 * The return type is the major order, and then args in order, 267 * and then the shorter list comes first (similar to string 268 * sorting). 269 */ 270 271 int result = returnType.compareTo(other.returnType); 272 273 if (result != 0) { 274 return result; 275 } 276 277 int thisSize = parameterTypes.size(); 278 int otherSize = other.parameterTypes.size(); 279 int size = Math.min(thisSize, otherSize); 280 281 for (int i = 0; i < size; i++) { 282 Type thisType = parameterTypes.get(i); 283 Type otherType = other.parameterTypes.get(i); 284 285 result = thisType.compareTo(otherType); 286 287 if (result != 0) { 288 return result; 289 } 290 } 291 292 if (thisSize < otherSize) { 293 return -1; 294 } else if (thisSize > otherSize) { 295 return 1; 296 } else { 297 return 0; 298 } 299 } 300 301 /** {@inheritDoc} */ 302 @Override toString()303 public String toString() { 304 return descriptor; 305 } 306 307 /** 308 * Gets the descriptor string. 309 * 310 * @return {@code non-null;} the descriptor 311 */ getDescriptor()312 public String getDescriptor() { 313 return descriptor; 314 } 315 316 /** 317 * Gets the return type. 318 * 319 * @return {@code non-null;} the return type 320 */ getReturnType()321 public Type getReturnType() { 322 return returnType; 323 } 324 325 /** 326 * Gets the list of parameter types. 327 * 328 * @return {@code non-null;} the list of parameter types 329 */ getParameterTypes()330 public StdTypeList getParameterTypes() { 331 return parameterTypes; 332 } 333 334 /** 335 * Gets the list of frame types corresponding to the list of parameter 336 * types. The difference between the two lists (if any) is that all 337 * "intlike" types (see {@link Type#isIntlike}) are replaced by 338 * {@link Type#INT}. 339 * 340 * @return {@code non-null;} the list of parameter frame types 341 */ getParameterFrameTypes()342 public StdTypeList getParameterFrameTypes() { 343 if (parameterFrameTypes == null) { 344 int sz = parameterTypes.size(); 345 StdTypeList list = new StdTypeList(sz); 346 boolean any = false; 347 for (int i = 0; i < sz; i++) { 348 Type one = parameterTypes.get(i); 349 if (one.isIntlike()) { 350 any = true; 351 one = Type.INT; 352 } 353 list.set(i, one); 354 } 355 parameterFrameTypes = any ? list : parameterTypes; 356 } 357 358 return parameterFrameTypes; 359 } 360 361 /** 362 * Returns a new interned instance, which is the same as this instance, 363 * except that it has an additional parameter prepended to the original's 364 * argument list. 365 * 366 * @param param {@code non-null;} the new first parameter 367 * @return {@code non-null;} an appropriately-constructed instance 368 */ withFirstParameter(Type param)369 public Prototype withFirstParameter(Type param) { 370 String newDesc = "(" + param.getDescriptor() + descriptor.substring(1); 371 StdTypeList newParams = parameterTypes.withFirst(param); 372 373 newParams.setImmutable(); 374 375 Prototype result = 376 new Prototype(newDesc, returnType, newParams); 377 378 return putIntern(result); 379 } 380 381 /** 382 * Puts the given instance in the intern table if it's not already 383 * there. If a conflicting value is already in the table, then leave it. 384 * Return the interned value. 385 * 386 * @param desc {@code non-null;} instance to make interned 387 * @return {@code non-null;} the actual interned object 388 */ putIntern(Prototype desc)389 private static Prototype putIntern(Prototype desc) { 390 synchronized (internTable) { 391 String descriptor = desc.getDescriptor(); 392 Prototype already = internTable.get(descriptor); 393 if (already != null) { 394 return already; 395 } 396 internTable.put(descriptor, desc); 397 return desc; 398 } 399 } 400 } 401