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