OpenJDK / lambda / lambda / jdk
changeset 6008:5e95936891d2
Update DefaultMethodAdd/RemoveTests, add shapes, and simplify (Sue Wei w/ RF). combo-test output now to build dir. Metafactory: make bridge et. al. detection and logging available to all MFs. Comment out outdated serialization code. MHI output like javap. More doc.
author | Robert Field <Robert.Field@oracle.com> |
---|---|
date | Thu, 13 Sep 2012 19:27:22 -0700 |
parents | cd4e5bc1ccb1 |
children | 1ebaa3f08ed1 |
files | combo-tests/build.xml combo-tests/tests/tools/javac/lambda/DefaultMethodAddTest.java combo-tests/tests/tools/javac/lambda/DefaultMethodRemoveTest.java src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java src/share/classes/java/lang/invoke/LambdaMetafactory.java src/share/classes/java/lang/invoke/MethodHandleInfo.java src/share/classes/java/lang/invoke/MethodHandleProxyLambdaMetafactory.java src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java |
diffstat | 9 files changed, 232 insertions(+), 155 deletions(-) [+] |
line wrap: on
line diff
--- a/combo-tests/build.xml Thu Sep 13 19:20:08 2012 -0400 +++ b/combo-tests/build.xml Thu Sep 13 19:27:22 2012 -0700 @@ -1,8 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> <project name="jdk" default="test"> - <property name="build.dir" value="build" /> - <property name="gen.dir" value="gen" /> + <property name="build.dir" value="../../build/combo-tests" /> + <property name="gen.dir" value="${build.dir}/gen" /> <property name="test.classes.dir" value="${build.dir}/test-classes"/> <property name="test.reports.dir" value="${build.dir}/test-reports"/> <property name="test.src.dir" value="tests"/>
--- a/combo-tests/tests/tools/javac/lambda/DefaultMethodAddTest.java Thu Sep 13 19:20:08 2012 -0400 +++ b/combo-tests/tests/tools/javac/lambda/DefaultMethodAddTest.java Thu Sep 13 19:27:22 2012 -0700 @@ -33,19 +33,16 @@ @DimensionVar("ADD") AddType addType; @SourceFile(value="B.java", group="A") - String interfaceBModified = "interface B #{SHAPE.EXTEND} { #{ADD} }"; + String interfaceBModified = "interface B #{SHAPE.B_DECL} { #{ADD} }"; @SourceFile(value="B.java", group="B") - String interfaceB = "interface B #{SHAPE.EXTEND} {}"; + String interfaceB = "interface B #{SHAPE.B_DECL} {}"; @SourceFile(value="A.java", group="B") - String interfaceA = "interface A { String m() default { return \"A\"; } }"; - - @SourceFile(value="AB.java", group="B") - String interfaceAB = "#{SHAPE.COMMENT}interface AB extends A, B {}"; + String interfaceA = "interface A { String m() default { return \"A\"; } }"; @SourceFile(value="C.java", group="B") - String classC = "class C implements #{SHAPE.IMPLEMENT} {}"; + String classC = "#{SHAPE.C}"; @SourceFile(value="Main.java", group="B") String classMain = "public class Main {\n" + @@ -67,42 +64,59 @@ } catch (InvocationTargetException ex) { output = ex.getCause().getMessage(); } - if(shapeType == CShapes.C_B_A){ + if(shapeType == CShapes.C_B_A) { if(addType == AddType.ADD) assertEquals(result, "B"); else //redeclare - assertEquals(result, "A"); - } else { - if(addType == AddType.ADD) - assertTrue(output.matches("Conflicting default methods: .+[.]m .+[.]m")); - else //redeclare - assertEquals(result, "A"); + assertEquals(output, "Method B.m()Ljava/lang/String; is abstract"); } + else if(shapeType == CShapes.C_I_AB2) + assertEquals(result, "A"); + else if(shapeType == CShapes.C_CI) + assertEquals(result, "D"); + else if(shapeType == CShapes.C_I_AB3) + assertEquals(result, "AB"); + else + assertTrue(output.matches("Conflicting default methods: .+[.]m .+[.]m")); } enum CShapes implements Template { //shapes of class hirarchy //class C implements interface A, B - C_AB("", "A, B", "//"), + C_AB("", + "class C implements A, B {}"), //class C implments interface B, B extends interface A - C_B_A("extends A", "B", "//"), + C_B_A("extends A", + "class C implements B {}"), + //class C implments interface AB, AB extends interface A, B - C_I_AB("", "AB", ""); + C_I_AB("", + "interface AB extends A, B { }\n" + + "class C implements AB {}"), + //class C implments interface AB, AB extends interface A, B and explicitly inherits the default method in A + C_I_AB2("", + "interface AB extends A, B { String m() default { return A.super.m(); } }\n" + + "class C implements AB {}"), + //class C implments interface AB, AB extends interface A, B and overrides the default method inherited + C_I_AB3("", + "interface AB extends A, B { String m() default { return \"AB\"; } }\n" + + "class C implements AB {}"), + //class C extends Class D implements Interface B + C_CI("", + "class D { public String m() { return \"D\"; } }\n" + + "class C extends D implements B {}"); - private final String extend; - private final String implement; - private final String commentSign; + private final String sB_DECL; + private final String sC; - CShapes(String extend, String implement, String commentSign) { - this.extend = extend; - this.implement = implement; - this.commentSign = commentSign; + CShapes(String sB_DECL, String sC) { + this.sB_DECL = sB_DECL; + this.sC = sC; } public String expand(String selector) { switch(selector) { - case "EXTEND": return extend; - case "IMPLEMENT": return implement; - case "COMMENT": return commentSign; + case "B_DECL": return sB_DECL; + case "C": return sC; default: return toString(); } }
--- a/combo-tests/tests/tools/javac/lambda/DefaultMethodRemoveTest.java Thu Sep 13 19:20:08 2012 -0400 +++ b/combo-tests/tests/tools/javac/lambda/DefaultMethodRemoveTest.java Thu Sep 13 19:27:22 2012 -0700 @@ -32,19 +32,16 @@ @DimensionVar("REMOVE") RemoveType removeType; @SourceFile(value="B.java", group="A") - String interfaceBModified = "interface B #{SHAPE.EXTENDS} { #{REMOVE} }"; + String interfaceBModified = "interface B #{SHAPE.B_DECL} { #{REMOVE} }"; @SourceFile(value="A.java", group="B") String interfaceA = "interface A { String m() default { return \"A\"; } }"; @SourceFile(value="B.java", group="B") - String interfaceB = "interface B #{SHAPE.EXTENDS} { String m() default { return \"B\"; } }"; - - @SourceFile(value="D.java", group="B") - String interfaceD = "#{SHAPE.COMMENT}interface D { String m() default { return \"D\"; } }"; - + String interfaceB = "interface B #{SHAPE.B_DECL} { String m() default { return \"B\"; } }"; + @SourceFile(value="C.java", group="B") - String classC = "class C implements B {}"; + String classC = "#{SHAPE.C}"; @SourceFile(value="Main.java", group="B") String classMain = "public class Main {\n" + @@ -72,40 +69,56 @@ } catch (InvocationTargetException ex) { output = ex.getCause().getMessage(); } - if(shapeType == CShapes.C_B_A) - assertEquals(result, "A"); - else if(shapeType == CShapes.C_B) - assertEquals(output, "C.m()Ljava/lang/String;"); - else if (shapeType == CShapes.C_B_AD) - assertTrue(output.matches("Conflicting default methods: .+[.]m .+[.]m")); + if(shapeType == CShapes.C_B_A) { + if(removeType == RemoveType.REMOVE) + assertEquals(result, "A"); + else + assertEquals(output, "Method B.m()Ljava/lang/String; is abstract"); + } + else if(shapeType == CShapes.C_CI) + assertEquals(result, "D"); + else { + if(shapeType == CShapes.C_B_AD && removeType == RemoveType.REDECLARE) + assertEquals(output, "Method B.m()Ljava/lang/String; is abstract"); + else + assertEquals(output, "C.m()Ljava/lang/String;"); + } } enum CShapes implements Template { //shapes of class hirarchy //class C implements interface B - C_B("", "//"), + C_B("", + "class C implements B {}"), //class C implments interface B, B extends interface A - C_B_A("extends A", "//"), + C_B_A("extends A", + "class C implements B {}"), //class C implments interface B, B extends interface A, D - C_B_AD("extends A, D", ""); + C_B_AD("extends A, D", + "interface D { String m() default { return \"D\"; } }\n" + + "class C implements B {}"), + //class C extends Class D implements Interface B + C_CI("", + "class D { public String m() { return \"D\"; } }\n" + + "class C extends D implements B {}"); - private final String extend; - private final String commentSign; + private final String sB_DECL; + private final String sC; - CShapes(String extend, String commentSign) { - this.extend = extend; - this.commentSign = commentSign; + CShapes(String sB_DECL, String sC) { + this.sB_DECL = sB_DECL; + this.sC = sC; } public String expand(String selector) { switch(selector) { - case "EXTENDS": return extend; - case "COMMENT": return commentSign; + case "B_DECL": return sB_DECL; + case "C": return sC; default: return toString(); } } } - enum RemoveType implements Template { // remove by removing default method code or redeclaring the interface method + enum RemoveType implements Template { //remove by removing default method code or redeclaring the interface method REMOVE(""), REDECLARE("String m();");
--- a/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java Thu Sep 13 19:20:08 2012 -0400 +++ b/src/share/classes/java/lang/invoke/AbstractValidatingLambdaMetafactory.java Thu Sep 13 19:27:22 2012 -0700 @@ -25,11 +25,17 @@ package java.lang.invoke; import java.io.Serializable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; /** * Abstract implementation of a meta-factory which provides parameter unrolling and input validation. @@ -63,6 +69,9 @@ final MethodType implMethodType; // Type of the implementation method "(int)String" final MethodType instantiatedMethodType; // Instantiated erased functional interface method type "(Integer)Object" + static final Executor logPool = Executors.newSingleThreadExecutor(); // @@@ For debugging only + + /** * Meta-factory constructor. * @@ -304,4 +313,101 @@ || !fromType.equals(void.class) && isAdaptableTo(fromType, toType, false); } + // @@@ Logging support -- for debugging only -- delete before shipping + protected static void log(final String s) { + MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { + @Override + public void run() { + System.out.println(s); + } + }); + } + + protected static void log(final String s, final Throwable e) { + MethodHandleProxyLambdaMetafactory.logPool.execute(new Runnable() { + @Override + public void run() { + System.out.println(s); + e.printStackTrace(System.out); + } + }); + } + + /** + * Find the SAM method and corresponding methods which should be bridged. SAM method and those to be bridged + * will have the same name and number of parameters. Check for matching default methods (non-abstract), they + * should not be bridged-over and indicate a complex bridging situation. + */ + class MethodAnalyzer { + private final Method[] methods = samBase.getMethods(); + private final List<Method> methodsFound = new ArrayList<>(methods.length); + + private Method samMethod = null; + private final List<Method> methodsToBridge = new ArrayList<>(methods.length); + private boolean defaultMethodFound = false; + + MethodAnalyzer() { + String samMethodName = samInfo.getName(); + Class<?>[] samParamTypes = samMethodType.parameterArray(); + int samParamLength = samParamTypes.length; + Class<?> samReturnType = samMethodType.returnType(); + Class<?> objectClass = Object.class; + + for (Method m : methods) { + if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) { + Class<?>[] mParamTypes = m.getParameterTypes(); + if (mParamTypes.length == samParamLength) { + if (Modifier.isAbstract(m.getModifiers())) { + // Exclude methods with duplicate signatures + if (methodUnique(m)) { + if (m.getReturnType().equals(samReturnType) && Arrays.equals(mParamTypes, samParamTypes)) { + // Exact match, this is the SAM method signature + samMethod = m; + } else { + methodsToBridge.add(m); + } + } + } else { + // This is a default method, flag for special processing + defaultMethodFound = true; + // Ignore future matching abstracts. + // Note, due to reabstraction, this is really a punt, hence pass-off to VM + methodUnique(m); + } + } + } + } + } + + Method getSamMethod() { + return samMethod; + } + + List<Method> getMethodsToBridge() { + return methodsToBridge; + } + + boolean wasDefaultMethodFound() { + return defaultMethodFound; + } + + /** + * Search the list of previously found methods to determine if there is a method with the same signature + * (return and parameter types) as the specified method. If it wasn't found before, add to the found list. + * + * @param m The method to match + * @return False if the method was found, True otherwise + */ + private boolean methodUnique(Method m) { + Class<?>[] ptypes = m.getParameterTypes(); + Class<?> rtype = m.getReturnType(); + for (Method md : methodsFound) { + if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) { + return false; + } + } + methodsFound.add(m); + return true; + } + } }
--- a/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Thu Sep 13 19:20:08 2012 -0400 +++ b/src/share/classes/java/lang/invoke/InnerClassLambdaMetafactory.java Thu Sep 13 19:27:22 2012 -0700 @@ -147,7 +147,6 @@ private <T> Class<? extends T> spinInnerClass() { String samMethodName = samInfo.getName(); - Method[] methods = samBase.getMethods(); String samName = samBase.getName().replace('.', '/'); Type samType = Type.getType(samBase); @@ -162,65 +161,28 @@ generateConstructor(); - // Generate methods - Class<?>[] samParamTypes = samMethodType.parameterArray(); - int samParamLength = samParamTypes.length; - Class<?> samReturnType = samMethodType.returnType(); - boolean defaultMethodFound = false; - Method samMethod = null; - List<Method> methodsFound = new ArrayList<>(methods.length); - List<Method> methodsToBridge = new ArrayList<>(methods.length); - Class<?> objectClass = Object.class; - - // Find the SAM method and corresponding methods which should be bridged. - // SAM method and those to be bridged will have the same name and number of parameters. - // Check for default methods (non-abstract), they should not be bridged-over and indicate a complex bridging situation. - for (Method m : methods) { - if (m.getName().equals(samMethodName) && m.getDeclaringClass() != objectClass) { - Class<?>[] mParamTypes = m.getParameterTypes(); - if (mParamTypes.length == samParamLength) { - if (Modifier.isAbstract(m.getModifiers())) { - // Exclude methods with duplicate signatures - if (!matchesAnyMethod(m, methodsFound)) { - methodsFound.add(m); - if (m.getReturnType().equals(samReturnType) && Arrays.equals(mParamTypes, samParamTypes)) { - // Exact match, this is the SAM method signature - samMethod = m; - } else { - methodsToBridge.add(m); - } - } - } else { - // This is a default method, flag for special processing - defaultMethodFound = true; - // Ignore future matching abstracts. - // Note, due to reabstraction, this is really a punt, hence pass-off to VM - if (!matchesAnyMethod(m, methodsFound)) { - methodsFound.add(m); - } - } - } - } - } + MethodAnalyzer ma = new MethodAnalyzer(); // Forward the SAM method - if (samMethod == null) { + if (ma.getSamMethod() == null) { throw new LambdaConversionException(String.format("SAM method not found: %s", samMethodType)); } else { - generateForwardingMethod(samMethod, false); + generateForwardingMethod(ma.getSamMethod(), false); } // Forward the bridges // @@@ Once the VM can do fail-over, uncomment the default method test - if (!methodsToBridge.isEmpty() /* && !defaultMethodFound*/) { - for (Method m : methodsToBridge) { + if (!ma.getMethodsToBridge().isEmpty() /* && !ma.wasDefaultMethodFound() */) { + for (Method m : ma.getMethodsToBridge()) { generateForwardingMethod(m, true); } } + /***** Serialization not yet supported if (isSerializable) { generateSerializationMethod(samType, samMethodName); } + ******/ cw.visitEnd(); @@ -243,23 +205,6 @@ } /** - * Search the specified list of methods to determine if there is a method with the same signature (return and parameter types) as the specified method. - * @param m The method to match - * @param methods The list of methods to search - * @return True if a method was found in 'methods' which the same signature as 'm', False otherwise - */ - private static boolean matchesAnyMethod(Method m, List<Method> methods) { - Class<?>[] ptypes = m.getParameterTypes(); - Class<?> rtype = m.getReturnType(); - for (Method md : methods) { - if (md.getReturnType().equals(rtype) && Arrays.equals(ptypes, md.getParameterTypes())) { - return true; - } - } - return false; - } - - /** * Generate the constructor for the class */ private void generateConstructor() { @@ -283,6 +228,7 @@ /** * Generate the serialization method (if needed) */ + /****** This code is out of date -- known to be wrong -- and not currently used ****** private void generateSerializationMethod(Type samType, String samMethodName) { String samMethodDesc = samMethodType.toMethodDescriptorString(); TypeConvertingMethodAdapter mv = new TypeConvertingMethodAdapter(cw.visitMethod(ACC_PRIVATE + ACC_FINAL, NAME_METHOD_WRITE_REPLACE, DESCR_METHOD_WRITE_REPLACE, null, null)); @@ -313,6 +259,7 @@ mv.visitMaxs(-1, -1); // Maxs computed by ClassWriter.COMPUTE_MAXS, these arguments ignored mv.visitEnd(); } + ********/ /** * Generate a method which calls the lambda implementation method,
--- a/src/share/classes/java/lang/invoke/LambdaMetafactory.java Thu Sep 13 19:20:08 2012 -0400 +++ b/src/share/classes/java/lang/invoke/LambdaMetafactory.java Thu Sep 13 19:27:22 2012 -0700 @@ -69,8 +69,9 @@ * as well as a method signature describing the number and static types (but not the values) of the dynamic * arguments, and the static return type of the invokedynamic site. * - * <p>The implementation method can, in theory, be anything representable with a method handle. Currently supported - * are method handles representing invocation of virtual, interface, constructor and static methods. + * <p>The implementation method is described with a method handle. In theory, any method handle could be used. + * Currently supported are method handles representing invocation of virtual, interface, constructor and static + * methods. * * <p>Assume: * <ul> @@ -86,7 +87,8 @@ * <ul> * <li>Rd is a subtype of F</li> * <li>For i=1..N, Ti is a subtype of Ui</li> - * <li>Rt is a subtype of Ru</li> + * <li>Either Rt and Ru are primitive and are the same type, or both are reference types and + * Rt is a subtype of Ru</li> * <li>If the implementation method is a static method: * <ul> * <li>K + N = M</li> @@ -106,8 +108,8 @@ * <p>Note that the potentially parameterized implementation return type provides the value for the SAM. Whereas * the completely known instantiated return type is adapted to the implementation arguments. Because the * instantiated type of the implementation method is not available, the adaptability of return types cannot be - * checked as precisely at link-time as arguments. Thus a loose version of link-time checking is done on return - * type, while a strict version is applied to arguments. + * checked as precisely at link-time as the arguments can be checked. Thus a loose version of link-time checking is + * done on return type, while a strict version is applied to arguments. * * <p>A type Q is considered adaptable to S as follows: * <table>
--- a/src/share/classes/java/lang/invoke/MethodHandleInfo.java Thu Sep 13 19:20:08 2012 -0400 +++ b/src/share/classes/java/lang/invoke/MethodHandleInfo.java Thu Sep 13 19:27:22 2012 -0700 @@ -79,21 +79,21 @@ private String getReferenceKindString() { switch (referenceKind) { case REF_NONE: return "REF_NONE"; - case REF_getField: return "getField"; - case REF_getStatic: return "getStatic"; - case REF_putField: return "putField"; - case REF_putStatic: return "putStatic"; - case REF_invokeVirtual: return "invokeVirtual"; - case REF_invokeStatic: return "invokeStatic"; - case REF_invokeSpecial: return "invokeSpecial"; - case REF_newInvokeSpecial: return "newInvokeSpecial"; - case REF_invokeInterface: return "invokeInterface"; + case REF_getField: return "getfield"; + case REF_getStatic: return "getstatic"; + case REF_putField: return "putfield"; + case REF_putStatic: return "putstatic"; + case REF_invokeVirtual: return "invokevirtual"; + case REF_invokeStatic: return "invokestatic"; + case REF_invokeSpecial: return "invokespecial"; + case REF_newInvokeSpecial: return "newinvokespecial"; + case REF_invokeInterface: return "invokeinterface"; default: return "UNKNOWN_REFENCE_KIND"; } } @Override public String toString() { - return String.format("MethodHandleInfo[%s %s.%s%s]", getReferenceKindString(), declaringClass.getName(), name, methodType); + return String.format("%s %s.%s:%s", getReferenceKindString(), declaringClass.getName(), name, methodType); } }
--- a/src/share/classes/java/lang/invoke/MethodHandleProxyLambdaMetafactory.java Thu Sep 13 19:20:08 2012 -0400 +++ b/src/share/classes/java/lang/invoke/MethodHandleProxyLambdaMetafactory.java Thu Sep 13 19:27:22 2012 -0700 @@ -25,8 +25,6 @@ package java.lang.invoke; import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; /** * MethodHandleProxyLambdaMetafactory @@ -37,27 +35,6 @@ private final MethodHandle implMethod; - // @@@ For debugging only -- delete before shipping - static Executor logPool = Executors.newSingleThreadExecutor(); - private static void log(final String s) { - logPool.execute(new Runnable() { - @Override - public void run() { - System.out.println(s); - } - }); - } - private static void log(final String s, final Throwable e) { - logPool.execute(new Runnable() { - @Override - public void run() { - System.out.println(s); - e.printStackTrace(System.out); - } - }); - } - // -- dev-only -- - /** * Meta-factory constructor. *
--- a/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java Thu Sep 13 19:20:08 2012 -0400 +++ b/src/share/classes/java/lang/invoke/TypeConvertingMethodAdapter.java Thu Sep 13 19:27:22 2012 -0700 @@ -180,8 +180,13 @@ boxingDescriptor(sort)); } - void unbox(Type sType, Type tType) { - int sSort = sType.getSort(); + /** + * Convert types by unboxing. The source type is known to be a primitive wrapper. + * @param spType A primitive type corresponding to wrapped reference source type + * @param tType A primitive type being converted to + */ + void unbox(Type spType, Type tType) { + int sSort = spType.getSort(); int tSort = tType.getSort(); visitMethodInsn(INVOKEVIRTUAL, NAME_PRIMITIVE_WRAPPER[sSort], @@ -189,6 +194,12 @@ unboxingDescriptor(tSort)); } + /** + * Convert types by unboxing. Use the base wrapper (for example, Number for int, short, etc) to go directly + * to the desired type. + * @param sType A reference type being converted from + * @param tType A primitive type being converted to + */ void looseUnbox(Type sType, Type tType) { int tSort = tType.getSort(); String baseWrapper = NAME_PRIMITIVE_BASE_WRAPPER[tSort]; @@ -230,6 +241,13 @@ return NAME_PRIMITIVE_WRAPPER[type.getSort()]; } + /** + * Convert an argument of type 'argType' to be passed to 'targetType' assuring that it is 'functionalType'. + * Insert the needed conversion instructions in the method code. + * @param argType + * @param targetType + * @param functionalType + */ void convertType(Type argType, Type targetType, Type functionalType) { if (argType.equals(targetType) || argType.equals(VOID_TYPE)) { return;