| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.dx.rop.type; |
| |
| import java.util.HashMap; |
| |
| /** |
| * Representation of a method descriptor. Instances of this class are |
| * generally interned and may be usefully compared with each other |
| * using {@code ==}. |
| */ |
| public final class Prototype implements Comparable<Prototype> { |
| /** {@code non-null;} intern table mapping string descriptors to instances */ |
| private static final HashMap<String, Prototype> internTable = |
| new HashMap<String, Prototype>(500); |
| |
| /** {@code non-null;} method descriptor */ |
| private final String descriptor; |
| |
| /** {@code non-null;} return type */ |
| private final Type returnType; |
| |
| /** {@code non-null;} list of parameter types */ |
| private final StdTypeList parameterTypes; |
| |
| /** {@code null-ok;} list of parameter frame types, if calculated */ |
| private StdTypeList parameterFrameTypes; |
| |
| /** |
| * Returns the unique instance corresponding to the |
| * given method descriptor. See vmspec-2 sec4.3.3 for details on the |
| * field descriptor syntax. |
| * |
| * @param descriptor {@code non-null;} the descriptor |
| * @return {@code non-null;} the corresponding instance |
| * @throws IllegalArgumentException thrown if the descriptor has |
| * invalid syntax |
| */ |
| public static Prototype intern(String descriptor) { |
| if (descriptor == null) { |
| throw new NullPointerException("descriptor == null"); |
| } |
| |
| Prototype result; |
| synchronized (internTable) { |
| result = internTable.get(descriptor); |
| } |
| if (result != null) { |
| return result; |
| } |
| |
| Type[] params = makeParameterArray(descriptor); |
| int paramCount = 0; |
| int at = 1; |
| |
| for (;;) { |
| int startAt = at; |
| char c = descriptor.charAt(at); |
| if (c == ')') { |
| at++; |
| break; |
| } |
| |
| // Skip array markers. |
| while (c == '[') { |
| at++; |
| c = descriptor.charAt(at); |
| } |
| |
| if (c == 'L') { |
| // It looks like the start of a class name; find the end. |
| int endAt = descriptor.indexOf(';', at); |
| if (endAt == -1) { |
| throw new IllegalArgumentException("bad descriptor"); |
| } |
| at = endAt + 1; |
| } else { |
| at++; |
| } |
| |
| params[paramCount] = |
| Type.intern(descriptor.substring(startAt, at)); |
| paramCount++; |
| } |
| |
| Type returnType = Type.internReturnType(descriptor.substring(at)); |
| StdTypeList parameterTypes = new StdTypeList(paramCount); |
| |
| for (int i = 0; i < paramCount; i++) { |
| parameterTypes.set(i, params[i]); |
| } |
| |
| result = new Prototype(descriptor, returnType, parameterTypes); |
| return putIntern(result); |
| } |
| |
| /** |
| * Helper for {@link #intern} which returns an empty array to |
| * populate with parsed parameter types, and which also ensures |
| * that there is a '(' at the start of the descriptor and a |
| * single ')' somewhere before the end. |
| * |
| * @param descriptor {@code non-null;} the descriptor string |
| * @return {@code non-null;} array large enough to hold all parsed parameter |
| * types, but which is likely actually larger than needed |
| */ |
| private static Type[] makeParameterArray(String descriptor) { |
| int length = descriptor.length(); |
| |
| if (descriptor.charAt(0) != '(') { |
| throw new IllegalArgumentException("bad descriptor"); |
| } |
| |
| /* |
| * This is a cheesy way to establish an upper bound on the |
| * number of parameters: Just count capital letters. |
| */ |
| int closeAt = 0; |
| int maxParams = 0; |
| for (int i = 1; i < length; i++) { |
| char c = descriptor.charAt(i); |
| if (c == ')') { |
| closeAt = i; |
| break; |
| } |
| if ((c >= 'A') && (c <= 'Z')) { |
| maxParams++; |
| } |
| } |
| |
| if ((closeAt == 0) || (closeAt == (length - 1))) { |
| throw new IllegalArgumentException("bad descriptor"); |
| } |
| |
| if (descriptor.indexOf(')', closeAt + 1) != -1) { |
| throw new IllegalArgumentException("bad descriptor"); |
| } |
| |
| return new Type[maxParams]; |
| } |
| |
| /** |
| * Interns an instance, adding to the descriptor as necessary based |
| * on the given definer, name, and flags. For example, an init |
| * method has an uninitialized object of type {@code definer} |
| * as its first argument. |
| * |
| * @param descriptor {@code non-null;} the descriptor string |
| * @param definer {@code non-null;} class the method is defined on |
| * @param isStatic whether this is a static method |
| * @param isInit whether this is an init method |
| * @return {@code non-null;} the interned instance |
| */ |
| public static Prototype intern(String descriptor, Type definer, |
| boolean isStatic, boolean isInit) { |
| Prototype base = intern(descriptor); |
| |
| if (isStatic) { |
| return base; |
| } |
| |
| if (isInit) { |
| definer = definer.asUninitialized(Integer.MAX_VALUE); |
| } |
| |
| return base.withFirstParameter(definer); |
| } |
| |
| /** |
| * Interns an instance which consists of the given number of |
| * {@code int}s along with the given return type |
| * |
| * @param returnType {@code non-null;} the return type |
| * @param count {@code > 0;} the number of elements in the prototype |
| * @return {@code non-null;} the interned instance |
| */ |
| public static Prototype internInts(Type returnType, int count) { |
| // Make the descriptor... |
| |
| StringBuffer sb = new StringBuffer(100); |
| |
| sb.append('('); |
| |
| for (int i = 0; i < count; i++) { |
| sb.append('I'); |
| } |
| |
| sb.append(')'); |
| sb.append(returnType.getDescriptor()); |
| |
| // ...and intern it. |
| return intern(sb.toString()); |
| } |
| |
| /** |
| * Constructs an instance. This is a private constructor; use one |
| * of the public static methods to get instances. |
| * |
| * @param descriptor {@code non-null;} the descriptor string |
| */ |
| private Prototype(String descriptor, Type returnType, |
| StdTypeList parameterTypes) { |
| if (descriptor == null) { |
| throw new NullPointerException("descriptor == null"); |
| } |
| |
| if (returnType == null) { |
| throw new NullPointerException("returnType == null"); |
| } |
| |
| if (parameterTypes == null) { |
| throw new NullPointerException("parameterTypes == null"); |
| } |
| |
| this.descriptor = descriptor; |
| this.returnType = returnType; |
| this.parameterTypes = parameterTypes; |
| this.parameterFrameTypes = null; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean equals(Object other) { |
| if (this == other) { |
| /* |
| * Since externally-visible instances are interned, this |
| * check helps weed out some easy cases. |
| */ |
| return true; |
| } |
| |
| if (!(other instanceof Prototype)) { |
| return false; |
| } |
| |
| return descriptor.equals(((Prototype) other).descriptor); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int hashCode() { |
| return descriptor.hashCode(); |
| } |
| |
| /** {@inheritDoc} */ |
| public int compareTo(Prototype other) { |
| if (this == other) { |
| return 0; |
| } |
| |
| /* |
| * The return type is the major order, and then args in order, |
| * and then the shorter list comes first (similar to string |
| * sorting). |
| */ |
| |
| int result = returnType.compareTo(other.returnType); |
| |
| if (result != 0) { |
| return result; |
| } |
| |
| int thisSize = parameterTypes.size(); |
| int otherSize = other.parameterTypes.size(); |
| int size = Math.min(thisSize, otherSize); |
| |
| for (int i = 0; i < size; i++) { |
| Type thisType = parameterTypes.get(i); |
| Type otherType = other.parameterTypes.get(i); |
| |
| result = thisType.compareTo(otherType); |
| |
| if (result != 0) { |
| return result; |
| } |
| } |
| |
| if (thisSize < otherSize) { |
| return -1; |
| } else if (thisSize > otherSize) { |
| return 1; |
| } else { |
| return 0; |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| return descriptor; |
| } |
| |
| /** |
| * Gets the descriptor string. |
| * |
| * @return {@code non-null;} the descriptor |
| */ |
| public String getDescriptor() { |
| return descriptor; |
| } |
| |
| /** |
| * Gets the return type. |
| * |
| * @return {@code non-null;} the return type |
| */ |
| public Type getReturnType() { |
| return returnType; |
| } |
| |
| /** |
| * Gets the list of parameter types. |
| * |
| * @return {@code non-null;} the list of parameter types |
| */ |
| public StdTypeList getParameterTypes() { |
| return parameterTypes; |
| } |
| |
| /** |
| * Gets the list of frame types corresponding to the list of parameter |
| * types. The difference between the two lists (if any) is that all |
| * "intlike" types (see {@link Type#isIntlike}) are replaced by |
| * {@link Type#INT}. |
| * |
| * @return {@code non-null;} the list of parameter frame types |
| */ |
| public StdTypeList getParameterFrameTypes() { |
| if (parameterFrameTypes == null) { |
| int sz = parameterTypes.size(); |
| StdTypeList list = new StdTypeList(sz); |
| boolean any = false; |
| for (int i = 0; i < sz; i++) { |
| Type one = parameterTypes.get(i); |
| if (one.isIntlike()) { |
| any = true; |
| one = Type.INT; |
| } |
| list.set(i, one); |
| } |
| parameterFrameTypes = any ? list : parameterTypes; |
| } |
| |
| return parameterFrameTypes; |
| } |
| |
| /** |
| * Returns a new interned instance, which is the same as this instance, |
| * except that it has an additional parameter prepended to the original's |
| * argument list. |
| * |
| * @param param {@code non-null;} the new first parameter |
| * @return {@code non-null;} an appropriately-constructed instance |
| */ |
| public Prototype withFirstParameter(Type param) { |
| String newDesc = "(" + param.getDescriptor() + descriptor.substring(1); |
| StdTypeList newParams = parameterTypes.withFirst(param); |
| |
| newParams.setImmutable(); |
| |
| Prototype result = |
| new Prototype(newDesc, returnType, newParams); |
| |
| return putIntern(result); |
| } |
| |
| /** |
| * Puts the given instance in the intern table if it's not already |
| * there. If a conflicting value is already in the table, then leave it. |
| * Return the interned value. |
| * |
| * @param desc {@code non-null;} instance to make interned |
| * @return {@code non-null;} the actual interned object |
| */ |
| private static Prototype putIntern(Prototype desc) { |
| synchronized (internTable) { |
| String descriptor = desc.getDescriptor(); |
| Prototype already = internTable.get(descriptor); |
| if (already != null) { |
| return already; |
| } |
| internTable.put(descriptor, desc); |
| return desc; |
| } |
| } |
| } |