diff --git a/README.md b/README.md index 24ccd3dc..7d2a6c16 100644 --- a/README.md +++ b/README.md @@ -173,6 +173,8 @@ Version History ([Issue #31](https://github.com/orfjackal/retrolambda/issues/31)) - *Experimental! Can be enabled with the enviroment variable DEFAULT_METHODS=2* +- Backports lambda expressions in an interface's constant initializer + ([Issue #42](https://github.com/orfjackal/retrolambda/issues/42)) ### Retrolambda 1.8.0 (2014-11-16) diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java index 8330b82a..205c5375 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java @@ -1,4 +1,4 @@ -// Copyright © 2013-2014 Esko Luontola +// Copyright © 2013-2015 Esko Luontola // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 @@ -27,6 +27,9 @@ public byte[] backportLambdaClass(ClassReader reader) { } else if (FeatureToggles.DEFAULT_METHODS == 2) { next = new UpdateRelocatedMethodInvocations(next, methodRelocations); next = new AddMethodDefaultImplementations(next, methodRelocations); + } else { + // needed for lambdas in an interface's constant initializer + next = new UpdateRelocatedMethodInvocations(next, methodRelocations); } next = new BackportLambdaClass(next); return next; @@ -56,6 +59,9 @@ public byte[] backportInterface(ClassReader reader) { next = new RemoveStaticMethods(next); next = new RemoveDefaultMethodBodies(next); next = new UpdateRelocatedMethodInvocations(next, methodRelocations); + } else { + // needed for lambdas in an interface's constant initializer + next = new RemoveStaticMethods(next); } next = new BackportLambdaInvocations(next); return next; @@ -63,17 +69,15 @@ public byte[] backportInterface(ClassReader reader) { } public byte[] extractInterfaceCompanion(ClassReader reader) { - String companion; - if (FeatureToggles.DEFAULT_METHODS == 2 - && (companion = methodRelocations.getCompanionClass(reader.getClassName())) != null) { - return transform(reader, (next) -> { - next = new UpdateRelocatedMethodInvocations(next, methodRelocations); - next = new ExtractInterfaceCompanionClass(next, companion); - return next; - }); - } else { + String companion = methodRelocations.getCompanionClass(reader.getClassName()); + if (companion == null) { return null; } + return transform(reader, (next) -> { + next = new UpdateRelocatedMethodInvocations(next, methodRelocations); + next = new ExtractInterfaceCompanionClass(next, companion); + return next; + }); } private byte[] transform(ClassReader reader, ClassVisitorChain chain) { diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/ExtractInterfaceCompanionClass.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/ExtractInterfaceCompanionClass.java index 13351445..b75d3393 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/ExtractInterfaceCompanionClass.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/ExtractInterfaceCompanionClass.java @@ -1,4 +1,4 @@ -// Copyright © 2013-2014 Esko Luontola +// Copyright © 2013-2015 Esko Luontola // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 @@ -34,6 +34,19 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si // do not copy abstract methods to the companion class return null; } + if (Flags.isClassInitializer(name, desc, access)) { + // we won't copy constant fields from the interface, so a class initializer won't be needed + return null; + } + if (Flags.hasFlag(access, ACC_STATIC) + && Flags.hasFlag(access, ACC_PRIVATE)) { + // XXX: Possibly a lambda impl method, which is private. It is the easiest for us to make it visible, + // which should be quite safe because static methods are not inherited (and anyways nothing inherits + // the companion class). The clean solution would be to generate an access method for it, but due to + // the location in code which generates those access methods, it would require complex code changes to + // pass around the information from one transformation to another. + access &= ~ACC_PRIVATE; + } if (!Flags.hasFlag(access, ACC_STATIC)) { // default method; make static and take 'this' as the first argument access |= ACC_STATIC; @@ -42,4 +55,10 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si } return super.visitMethod(access, name, desc, signature, exceptions); } + + @Override + public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { + // an interface can only contain constant fields; they don't need to be copied + return null; + } } diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/RemoveStaticMethods.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/RemoveStaticMethods.java index 99c6e898..77a7d1f1 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/RemoveStaticMethods.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/RemoveStaticMethods.java @@ -1,4 +1,4 @@ -// Copyright © 2013-2014 Esko Luontola +// Copyright © 2013-2015 Esko Luontola // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 @@ -17,7 +17,7 @@ public RemoveStaticMethods(ClassVisitor next) { @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { - if (isStaticMethod(access)) { + if (isStaticMethod(access) && !Flags.isClassInitializer(name, desc, access)) { return null; } else { return super.visitMethod(access, name, desc, signature, exceptions); diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/BackportLambdaInvocations.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/BackportLambdaInvocations.java index 649e48c6..e8334254 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/BackportLambdaInvocations.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/lambdas/BackportLambdaInvocations.java @@ -1,4 +1,4 @@ -// Copyright © 2013-2014 Esko Luontola +// Copyright © 2013-2015 Esko Luontola // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 @@ -57,7 +57,7 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si } if (FeatureToggles.DEFAULT_METHODS == 0 && isNonAbstractMethodOnInterface(access) - && !isClassInitializerMethod(name, desc, access)) { + && !Flags.isClassInitializer(name, desc, access)) { // In case we have missed a case of Java 8 producing non-abstract methods // on interfaces, we have this warning here to get a bug report sooner. // Not allowed by Java 7: @@ -85,16 +85,14 @@ private boolean isNonAbstractMethodOnInterface(int methodAccess) { !Flags.hasFlag(methodAccess, ACC_ABSTRACT); } - private static boolean isClassInitializerMethod(String name, String desc, int methodAccess) { - return name.equals("") && - desc.equals("()V") && - Flags.hasFlag(methodAccess, ACC_STATIC); - } - Handle getLambdaAccessMethod(Handle implMethod) { if (!implMethod.getOwner().equals(className)) { return implMethod; } + if (Flags.hasFlag(classAccess, ACC_INTERFACE)) { + // the method will be relocated to a companion class + return implMethod; + } // TODO: do not generate an access method if the impl method is not private (probably not implementable with a single pass) String name = "access$lambda$" + lambdaAccessToImplMethods.size(); String desc = implMethod.getTag() == H_INVOKESTATIC diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/util/Flags.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/util/Flags.java index 43ab1354..e9e399bc 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/util/Flags.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/util/Flags.java @@ -1,12 +1,20 @@ -// Copyright © 2013-2014 Esko Luontola +// Copyright © 2013-2015 Esko Luontola // This software is released under the Apache License 2.0. // The license text is at http://www.apache.org/licenses/LICENSE-2.0 package net.orfjackal.retrolambda.util; +import static org.objectweb.asm.Opcodes.ACC_STATIC; + public class Flags { public static boolean hasFlag(int subject, int flag) { return (subject & flag) == flag; } + + public static boolean isClassInitializer(String name, String desc, int methodAccess) { + return name.equals("") && + desc.equals("()V") && + hasFlag(methodAccess, ACC_STATIC); + } }