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