/* * Copyright (c) 2008, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.dyn; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import sun.dyn.Access; import sun.dyn.Invokers; import sun.dyn.MethodTypeImpl; import sun.dyn.util.BytecodeDescriptor; import static sun.dyn.MemberName.newIllegalArgumentException; /** * A method type represents the arguments and return type accepted and * returned by a method handle, or the arguments and return type passed * and expected by a method handle caller. Method types must be properly * matched between a method handle and all its callers, * and the JVM's operations enforce this matching at, specifically * during calls to {@link MethodHandle#invokeExact} * and {@link MethodHandle#invokeGeneric}, and during execution * of {@code invokedynamic} instructions. *

* The structure is a return type accompanied by any number of parameter types. * The types (primitive, {@code void}, and reference) are represented by {@link Class} objects. * (For ease of exposition, we treat {@code void} as if it were a type. * In fact, it denotes the absence of a return type.) *

* All instances of {@code MethodType} are immutable. * Two instances are completely interchangeable if they compare equal. * Equality depends on pairwise correspondence of the return and parameter types and on nothing else. *

* This type can be created only by factory methods. * All factory methods may cache values, though caching is not guaranteed. * Some factory methods are static, while others are virtual methods which * modify precursor method types, e.g., by changing a selected parameter. *

* Factory methods which operate on groups of parameter types * are systematically presented in two versions, so that both Java arrays and * Java lists can be used to work with groups of parameter types. * The query methods {@code parameterArray} and {@code parameterList} * also provide a choice between arrays and lists. *

* {@code MethodType} objects are sometimes derived from bytecode instructions * such as {@code invokedynamic}, specifically from the type descriptor strings associated * with the instructions in a class file's constant pool. *

* Like classes and strings, method types can also be represented directly * in a class file's constant pool as constants. The may be loaded by an {@code ldc} * instruction which refers to a suitable {@code CONSTANT_MethodType} constant pool entry. * The entry refers to a {@code CONSTANT_Utf8} spelling for the descriptor string. * For more details, see the package summary. *

* When the JVM materializes a {@code MethodType} from a descriptor string, * all classes named in the descriptor must be accessible, and will be loaded. * (But the classes need not be initialized, as is the case with a {@code CONSTANT_Class}.) * This loading may occur at any time before the {@code MethodType} object is first derived. * @author John Rose, JSR 292 EG */ public final class MethodType { private final Class rtype; private final Class[] ptypes; private MethodTypeForm form; // erased form, plus cached data about primitives private MethodType wrapAlt; // alternative wrapped/unwrapped version private Invokers invokers; // cache of handy higher-order adapters private static final Access IMPL_TOKEN = Access.getToken(); // share a cache with a friend in this package Invokers getInvokers() { return invokers; } void setInvokers(Invokers inv) { invokers = inv; } static { // This hack allows the implementation package special access to // the internals of MethodType. In particular, the MTImpl has all sorts // of cached information useful to the implementation code. MethodTypeImpl.setMethodTypeFriend(IMPL_TOKEN, new MethodTypeImpl.MethodTypeFriend() { public Class[] ptypes(MethodType mt) { return mt.ptypes; } public MethodTypeImpl form(MethodType mt) { return mt.form; } public void setForm(MethodType mt, MethodTypeImpl form) { assert(mt.form == null); mt.form = (MethodTypeForm) form; } public MethodType makeImpl(Class rtype, Class[] ptypes, boolean trusted) { return MethodType.makeImpl(rtype, ptypes, trusted); } public MethodTypeImpl newMethodTypeForm(MethodType mt) { return new MethodTypeForm(mt); } public Invokers getInvokers(MethodType mt) { return mt.invokers; } public void setInvokers(MethodType mt, Invokers inv) { mt.invokers = inv; } }); } private MethodType(Class rtype, Class[] ptypes) { checkRtype(rtype); checkPtypes(ptypes); this.rtype = rtype; this.ptypes = ptypes; } private void checkRtype(Class rtype) { rtype.equals(rtype); // null check } private void checkPtypes(Class[] ptypes) { for (Class ptype : ptypes) { ptype.equals(ptype); // null check if (ptype == void.class) throw newIllegalArgumentException("parameter type cannot be void"); } } static final HashMap internTable = new HashMap(); static final Class[] NO_PTYPES = {}; /** Find or create an instance of the given method type. * @param rtype the return type * @param ptypes the parameter types * @return a method type with the given parts * @throws NullPointerException if rtype or any ptype is null * @throws IllegalArgumentException if any of the ptypes is void */ public static MethodType methodType(Class rtype, Class[] ptypes) { return makeImpl(rtype, ptypes, false); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. */ public static MethodType methodType(Class rtype, List> ptypes) { boolean notrust = false; // random List impl. could return evil ptypes array return makeImpl(rtype, ptypes.toArray(NO_PTYPES), notrust); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * The leading parameter type is prepended to the remaining array. */ public static MethodType methodType(Class rtype, Class ptype0, Class... ptypes) { Class[] ptypes1 = new Class[1+ptypes.length]; ptypes1[0] = ptype0; System.arraycopy(ptypes, 0, ptypes1, 1, ptypes.length); return makeImpl(rtype, ptypes1, true); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * The resulting method has no parameter types. */ public static MethodType methodType(Class rtype) { return makeImpl(rtype, NO_PTYPES, true); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * The resulting method has the single given parameter type. */ public static MethodType methodType(Class rtype, Class ptype0) { return makeImpl(rtype, new Class[]{ ptype0 }, true); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * The resulting method has the same parameter types as {@code ptypes}, * and the specified return type. */ public static MethodType methodType(Class rtype, MethodType ptypes) { return makeImpl(rtype, ptypes.ptypes, true); } /** * Sole factory method to find or create an interned method type. * @param rtype desired return type * @param ptypes desired parameter types * @param trusted whether the ptypes can be used without cloning * @return the unique method type of the desired structure */ private static MethodType makeImpl(Class rtype, Class[] ptypes, boolean trusted) { if (ptypes == null || ptypes.length == 0) { ptypes = NO_PTYPES; trusted = true; } MethodType mt1 = new MethodType(rtype, ptypes); MethodType mt0; synchronized (internTable) { mt0 = internTable.get(mt1); if (mt0 != null) return mt0; } if (!trusted) // defensively copy the array passed in by the user mt1 = new MethodType(rtype, ptypes.clone()); // promote the object to the Real Thing, and reprobe MethodTypeImpl.initForm(IMPL_TOKEN, mt1); synchronized (internTable) { mt0 = internTable.get(mt1); if (mt0 != null) return mt0; internTable.put(mt1, mt1); } return mt1; } // Entry point from JVM. TODO: Change the name & signature. private static MethodType makeImpl(Class rtype, Class[] ptypes, boolean ignore1, boolean ignore2) { return makeImpl(rtype, ptypes, true); } private static final MethodType[] objectOnlyTypes = new MethodType[20]; /** * Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * All parameters and the return type will be {@code Object}, * except the final varargs parameter if any, which will be {@code Object[]}. * @param objectArgCount number of parameters (excluding the varargs parameter if any) * @param varargs whether there will be a varargs parameter, of type {@code Object[]} * @return a totally generic method type, given only its count of parameters and varargs * @see #genericMethodType(int) */ public static MethodType genericMethodType(int objectArgCount, boolean varargs) { MethodType mt; int ivarargs = (!varargs ? 0 : 1); int ootIndex = objectArgCount*2 + ivarargs; if (ootIndex < objectOnlyTypes.length) { mt = objectOnlyTypes[ootIndex]; if (mt != null) return mt; } Class[] ptypes = new Class[objectArgCount + ivarargs]; Arrays.fill(ptypes, Object.class); if (ivarargs != 0) ptypes[objectArgCount] = Object[].class; mt = makeImpl(Object.class, ptypes, true); if (ootIndex < objectOnlyTypes.length) { objectOnlyTypes[ootIndex] = mt; // cache it here also! } return mt; } /** * All parameters and the return type will be Object. * @param objectArgCount number of parameters * @return a totally generic method type, given only its count of parameters * @see #genericMethodType(int, boolean) */ public static MethodType genericMethodType(int objectArgCount) { return genericMethodType(objectArgCount, false); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * @param num the index (zero-based) of the parameter type to change * @param nptype a new parameter type to replace the old one with * @return the same type, except with the selected parameter changed */ public MethodType changeParameterType(int num, Class nptype) { if (parameterType(num) == nptype) return this; Class[] nptypes = ptypes.clone(); nptypes[num] = nptype; return makeImpl(rtype, nptypes, true); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * @param num the position (zero-based) of the inserted parameter type(s) * @param ptypesToInsert zero or more a new parameter types to insert into the parameter list * @return the same type, except with the selected parameter(s) inserted */ public MethodType insertParameterTypes(int num, Class... ptypesToInsert) { int len = ptypes.length; if (num < 0 || num > len) throw newIllegalArgumentException("num="+num); //SPECME int ilen = ptypesToInsert.length; if (ilen == 0) return this; Class[] nptypes = Arrays.copyOfRange(ptypes, 0, len+ilen); System.arraycopy(nptypes, num, nptypes, num+ilen, len-num); System.arraycopy(ptypesToInsert, 0, nptypes, num, ilen); return makeImpl(rtype, nptypes, true); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * @param ptypesToInsert zero or more a new parameter types to insert after the end of the parameter list * @return the same type, except with the selected parameter(s) appended */ public MethodType appendParameterTypes(Class... ptypesToInsert) { return insertParameterTypes(parameterCount(), ptypesToInsert); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * @param ptypesToInsert zero or more a new parameter types to insert after the end of the parameter list * @return the same type, except with the selected parameter(s) appended */ public MethodType appendParameterTypes(List> ptypesToInsert) { return insertParameterTypes(parameterCount(), ptypesToInsert); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * @param num the position (zero-based) of the inserted parameter type(s) * @param ptypesToInsert zero or more a new parameter types to insert into the parameter list * @return the same type, except with the selected parameter(s) inserted */ public MethodType insertParameterTypes(int num, List> ptypesToInsert) { return insertParameterTypes(num, ptypesToInsert.toArray(NO_PTYPES)); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * @param start the index (zero-based) of the first parameter type to remove * @param end the index (greater than {@code start}) of the first parameter type after not to remove * @return the same type, except with the selected parameter(s) removed */ public MethodType dropParameterTypes(int start, int end) { int len = ptypes.length; if (!(0 <= start && start <= end && end <= len)) throw newIllegalArgumentException("start="+start+" end="+end); //SPECME if (start == end) return this; Class[] nptypes; if (start == 0) { if (end == len) { // drop all parameters nptypes = NO_PTYPES; } else { // drop initial parameter(s) nptypes = Arrays.copyOfRange(ptypes, end, len); } } else { if (end == len) { // drop trailing parameter(s) nptypes = Arrays.copyOfRange(ptypes, 0, start); } else { int tail = len - end; nptypes = Arrays.copyOfRange(ptypes, 0, start + tail); System.arraycopy(ptypes, end, nptypes, start, tail); } } return makeImpl(rtype, nptypes, true); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * @param nrtype a return parameter type to replace the old one with * @return the same type, except with the return type change */ public MethodType changeReturnType(Class nrtype) { if (returnType() == nrtype) return this; return makeImpl(nrtype, ptypes, true); } /** Convenience method. * Report if this type contains a primitive argument or return value. * The return type {@code void} counts as a primitive. * @return true if any of the types are primitives */ public boolean hasPrimitives() { return form.hasPrimitives(); } /** Convenience method. * Report if this type contains a wrapper argument or return value. * Wrappers are types which box primitive values, such as {@link Integer}. * The reference type {@code java.lang.Void} counts as a wrapper. * @return true if any of the types are wrappers */ public boolean hasWrappers() { return unwrap() != this; } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * Erase all reference types to {@code Object}. * All primitive types (including {@code void}) will remain unchanged. * @return a version of the original type with all reference types replaced */ public MethodType erase() { return form.erasedType(); } /** Convenience method for {@link #genericMethodType(int)}. * Convert all types, both reference and primitive, to {@code Object}. * The expression {@code type.wrap().erase()} produces the same value * as {@code type.generic()}. * @return a version of the original type with all types replaced */ public MethodType generic() { return genericMethodType(parameterCount()); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * Convert all primitive types to their corresponding wrapper types. * All reference types (including wrapper types) will remain unchanged. * A {@code void} return type is changed to the type {@code java.lang.Void}. * The expression {@code type.wrap().erase()} produces the same value * as {@code type.generic()}. * @return a version of the original type with all primitive types replaced */ public MethodType wrap() { return hasPrimitives() ? wrapWithPrims(this) : this; } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * Convert all wrapper types to their corresponding primitive types. * All primitive types (including {@code void}) will remain unchanged. * A return type of {@code java.lang.Void} is changed to {@code void}. * @return a version of the original type with all wrapper types replaced */ public MethodType unwrap() { MethodType noprims = !hasPrimitives() ? this : wrapWithPrims(this); return unwrapWithNoPrims(noprims); } private static MethodType wrapWithPrims(MethodType pt) { assert(pt.hasPrimitives()); MethodType wt = pt.wrapAlt; if (wt == null) { // fill in lazily wt = MethodTypeImpl.canonicalize(pt, MethodTypeImpl.WRAP, MethodTypeImpl.WRAP); assert(wt != null); pt.wrapAlt = wt; } return wt; } private static MethodType unwrapWithNoPrims(MethodType wt) { assert(!wt.hasPrimitives()); MethodType uwt = wt.wrapAlt; if (uwt == null) { // fill in lazily uwt = MethodTypeImpl.canonicalize(wt, MethodTypeImpl.UNWRAP, MethodTypeImpl.UNWRAP); if (uwt == null) uwt = wt; // type has no wrappers or prims at all wt.wrapAlt = uwt; } return uwt; } /** @param num the index (zero-based) of the desired parameter type * @return the selected parameter type */ public Class parameterType(int num) { return ptypes[num]; } /** @return the number of parameter types */ public int parameterCount() { return ptypes.length; } /** @return the return type */ public Class returnType() { return rtype; } /** * Convenience method to present the arguments as a list. * @return the parameter types (as an immutable list) */ public List> parameterList() { return Collections.unmodifiableList(Arrays.asList(ptypes)); } /** * Convenience method to present the arguments as an array. * Changes to the array will not result in changes to the type. * @return the parameter types (as a fresh copy if necessary) */ public Class[] parameterArray() { return ptypes.clone(); } /** * Compares the specified object with this type for equality. * That is, it returns true if and only if the specified object * is also a method type with exactly the same parameters and return type. * @param x object to compare * @see Object#equals(Object) */ @Override public boolean equals(Object x) { return this == x || x instanceof MethodType && equals((MethodType)x); } private boolean equals(MethodType that) { return this.rtype == that.rtype && Arrays.equals(this.ptypes, that.ptypes); } /** * Returns the hash code value for this method type. * It is defined to be the same as the hashcode of a List * whose elements are the return type followed by the * parameter types. * @return the hash code value for this method type * @see Object#hashCode() * @see #equals(Object) * @see List#hashCode() */ @Override public int hashCode() { int hashCode = 31 + rtype.hashCode(); for (Class ptype : ptypes) hashCode = 31*hashCode + ptype.hashCode(); return hashCode; } /** * The string representation of a method type is a * parenthesis enclosed, comma separated list of type names, * followed immediately by the return type. *

* Each type is represented by its * {@link java.lang.Class#getSimpleName simple name}. * If a type name name is array, it the base type followed * by [], rather than the Class.getName of the array type. */ @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("("); for (int i = 0; i < ptypes.length; i++) { if (i > 0) sb.append(","); sb.append(ptypes[i].getSimpleName()); } sb.append(")"); sb.append(rtype.getSimpleName()); return sb.toString(); } /// Queries which have to do with the bytecode architecture /** The number of JVM stack slots required to invoke a method * of this type. Note that (for historic reasons) the JVM requires * a second stack slot to pass long and double arguments. * So this method returns {@link #parameterCount()} plus the * number of long and double parameters (if any). *

* This method is included for the benfit of applications that must * generate bytecodes that process method handles and invokedynamic. * @return the number of JVM stack slots for this type's parameters */ public int parameterSlotCount() { return form.parameterSlotCount(); } /** Number of JVM stack slots which carry all parameters including and after * the given position, which must be in the range of 0 to * {@code parameterCount} inclusive. Successive parameters are * more shallowly stacked, and parameters are indexed in the bytecodes * according to their trailing edge. Thus, to obtain the depth * in the outgoing call stack of parameter {@code N}, obtain * the {@code parameterSlotDepth} of its trailing edge * at position {@code N+1}. *

* Parameters of type {@code long} and {@code double} occupy * two stack slots (for historical reasons) and all others occupy one. * Therefore, the number returned is the number of arguments * including and after the given parameter, * plus the number of long or double arguments * at or after after the argument for the given parameter. *

* This method is included for the benfit of applications that must * generate bytecodes that process method handles and invokedynamic. * @param num an index (zero-based, inclusive) within the parameter types * @return the index of the (shallowest) JVM stack slot transmitting the * given parameter */ public int parameterSlotDepth(int num) { if (num < 0 || num > ptypes.length) parameterType(num); // force a range check return form.parameterToArgSlot(num-1); } /** The number of JVM stack slots required to receive a return value * from a method of this type. * If the {@link #returnType() return type} is void, it will be zero, * else if the return type is long or double, it will be two, else one. *

* This method is included for the benfit of applications that must * generate bytecodes that process method handles and invokedynamic. * @return the number of JVM stack slots (0, 1, or 2) for this type's return value */ public int returnSlotCount() { return form.returnSlotCount(); } /** Convenience method for {@link #methodType(java.lang.Class, java.lang.Class[])}. * Find or create an instance of the given method type. * Any class or interface name embedded in the descriptor string * will be resolved by calling {@link ClassLoader#loadClass(java.lang.String)} * on the given loader (or if it is null, on the system class loader). *

* Note that it is possible to encounter method types which cannot be * constructed by this method, because their component types are * not all reachable from a common class loader. *

* This method is included for the benfit of applications that must * generate bytecodes that process method handles and invokedynamic. * @param descriptor a bytecode-level signature string "(T...)T" * @param loader the class loader in which to look up the types * @return a method type matching the bytecode-level signature * @throws IllegalArgumentException if the string is not well-formed * @throws TypeNotPresentException if a named type cannot be found */ public static MethodType fromMethodDescriptorString(String descriptor, ClassLoader loader) throws IllegalArgumentException, TypeNotPresentException { List> types = BytecodeDescriptor.parseMethod(descriptor, loader); Class rtype = types.remove(types.size() - 1); Class[] ptypes = types.toArray(NO_PTYPES); return makeImpl(rtype, ptypes, true); } /** * Create a bytecode descriptor representation of the method type. *

* Note that this is not a strict inverse of {@link #fromMethodDescriptorString}. * Two distinct classes which share a common name but have different class loaders * will appear identical when viewed within descriptor strings. *

* This method is included for the benfit of applications that must * generate bytecodes that process method handles and invokedynamic. * {@link #fromMethodDescriptorString(java.lang.String, java.lang.ClassLoader)}, * because the latter requires a suitable class loader argument. * @return the bytecode signature representation */ public String toMethodDescriptorString() { return BytecodeDescriptor.unparse(this); } }