From 1c0c4c430b1465e6bc2b8ce891545d634954d350 Mon Sep 17 00:00:00 2001 From: Raul Bache Date: Sun, 24 Aug 2014 21:10:07 +0200 Subject: [PATCH 1/5] Merge branch --- .../retrolambda/LambdaClassBackporter.java | 8 +- .../retrolambda/LambdaUsageBackporter.java | 37 +--- .../java/net/orfjackal/retrolambda/Main.java | 13 ++ .../defaultmethods/ClassModifier.java | 178 ++++++++++++++++++ .../retrolambda/defaultmethods/Helpers.java | 51 +++++ .../defaultmethods/InterfaceModifier.java | 177 +++++++++++++++++ .../InterfaceToHelperRewriter.java | 28 +++ .../defaultmethods/MethodContainer.java | 58 ++++++ .../defaultmethods/VisitedMethod.java | 37 ++++ 9 files changed, 556 insertions(+), 31 deletions(-) create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/ClassModifier.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/Helpers.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceModifier.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceToHelperRewriter.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/MethodContainer.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/VisitedMethod.java diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaClassBackporter.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaClassBackporter.java index e57e965a..8d94d94b 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaClassBackporter.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaClassBackporter.java @@ -4,8 +4,11 @@ package net.orfjackal.retrolambda; +import net.orfjackal.retrolambda.defaultmethods.ClassModifier; import org.objectweb.asm.*; +import java.util.Arrays; + import static org.objectweb.asm.Opcodes.*; public class LambdaClassBackporter { @@ -15,7 +18,8 @@ public class LambdaClassBackporter { public static byte[] transform(byte[] bytecode, int targetVersion) { ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); - new ClassReader(bytecode).accept(new LambdaClassVisitor(writer, targetVersion), 0); + ClassModifier stage2 = new ClassModifier(targetVersion, writer); + new ClassReader(bytecode).accept(new LambdaClassVisitor(stage2, targetVersion), 0); return writer.toByteArray(); } @@ -27,7 +31,7 @@ private static class LambdaClassVisitor extends ClassVisitor { private Handle bridgeMethod; private LambdaFactoryMethod factoryMethod; - public LambdaClassVisitor(ClassWriter cw, int targetVersion) { + public LambdaClassVisitor(ClassVisitor cw, int targetVersion) { super(ASM5, cw); this.targetVersion = targetVersion; } diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java index 3a609dc9..5a3409b7 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/LambdaUsageBackporter.java @@ -4,6 +4,9 @@ package net.orfjackal.retrolambda; +import net.orfjackal.retrolambda.defaultmethods.ClassModifier; +import net.orfjackal.retrolambda.defaultmethods.Helpers; +import net.orfjackal.retrolambda.defaultmethods.InterfaceModifier; import org.objectweb.asm.*; import java.lang.reflect.Field; @@ -16,9 +19,10 @@ public class LambdaUsageBackporter { public static byte[] transform(byte[] bytecode, int targetVersion) { resetLambdaClassSequenceNumber(); - ClassWriter stage2 = new ClassWriter(ClassWriter.COMPUTE_MAXS); - InvokeDynamicInsnConverter stage1 = new InvokeDynamicInsnConverter(stage2, targetVersion); + ClassModifier stage3 = new ClassModifier(targetVersion, stage2); + InterfaceModifier stage4 = new InterfaceModifier(stage3, targetVersion); + InvokeDynamicInsnConverter stage1 = new InvokeDynamicInsnConverter(stage4, targetVersion); new ClassReader(bytecode).accept(stage1, 0); return stage2.toByteArray(); } @@ -64,22 +68,6 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si if (isBridgeMethodOnInterface(access)) { return null; // remove the bridge method; Java 7 didn't use them } - if (isNonAbstractMethodOnInterface(access) - && !isClassInitializerMethod(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: - // - default methods - // - static methods - // - bridge methods - // Allowed by Java 7: - // - class initializer methods (for initializing constants) - System.out.println("WARNING: Method '" + name + "' of interface '" + className + "' is non-abstract! " + - "This will probably fail to run on Java 7 and below. " + - "If you get this warning _without_ using Java 8's default methods, " + - "please report a bug at https://github.com/orfjackal/retrolambda/issues " + - "together with an SSCCE (http://www.sscce.org/)"); - } MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions); return new InvokeDynamicInsnConvertingMethodVisitor(mv, this); } @@ -158,7 +146,7 @@ public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object. } private void backportLambda(String invokedName, Type invokedType, Handle bsm, Object[] bsmArgs) { - Class invoker = loadClass(context.className); + Class invoker = Helpers.loadClass(context.className); Handle implMethod = (Handle) bsmArgs[1]; Handle bridgeMethod = context.getLambdaBridgeMethod(implMethod); @@ -166,14 +154,5 @@ private void backportLambda(String invokedName, Type invokedType, Handle bsm, Ob invoker, invokedName, invokedType, bsm, bsmArgs); super.visitMethodInsn(INVOKESTATIC, factory.getOwner(), factory.getName(), factory.getDesc(), false); } - - private static Class loadClass(String className) { - try { - ClassLoader cl = Thread.currentThread().getContextClassLoader(); - return cl.loadClass(className.replace('/', '.')); - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - } + } } diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java index feba36e5..3b1ae884 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java @@ -39,6 +39,19 @@ public static void main(String[] args) { try { Thread.currentThread().setContextClassLoader(new URLClassLoader(asUrls(classpath))); + /** + * { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + Class loadedClass = findLoadedClass(name); + if(loadedClass != null) { + return loadedClass; + } else { + return super.loadClass(name); + } + } + } + */ visitFiles(inputDir, includedFiles, new BytecodeTransformingFileVisitor(inputDir, outputDir) { @Override diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/ClassModifier.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/ClassModifier.java new file mode 100644 index 00000000..9aba3b58 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/ClassModifier.java @@ -0,0 +1,178 @@ +// Copyright © 2013-2014 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.defaultmethods; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.*; +import java.util.stream.Stream; + +/** + * Created by arneball on 2014-08-12. + */ +public class ClassModifier extends ClassVisitor implements Opcodes { + private final int bytecodeVersion; + private String[] interfaces; + + private Set visitedMethods = new HashSet<>(); + private Set defaultMethods = new HashSet<>(); + + public ClassModifier(int bytecodeVersion, ClassVisitor cv) { + super(ASM5, cv); + this.bytecodeVersion = bytecodeVersion; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + boolean isClass = (access & ACC_INTERFACE) == 0; + if(isClass) { + defaultMethods = getMethodsToImplement(interfaces, signature); + } + this.interfaces = interfaces; + System.out.println("Class is " + name + ", non abstract " + isClass + ", Interfaces are " + Arrays.toString(interfaces) + "" + + ", default methods are " + defaultMethods); + super.visit(bytecodeVersion, access, name, signature, superName, interfaces); + } + + private static Set getMethodsToImplement(String[] interfaces, String sig) { + Set tmp = new HashSet<>(); + for(String iff : interfaces) { + tmp.addAll(getMethodsToImplement(iff, sig)); + } + return tmp; + } + + private static Set getMethodsToImplement(String interfac, String sig) { + Class ifClass = Helpers.loadClass(interfac); + Method[] tmp = ifClass.getMethods(); + System.out.println("Interface: " + interfac + ", methods: " + Arrays.toString(tmp)); + Set toReturn = new HashSet<>(); + for(Method m : tmp) { + if(!Modifier.isAbstract(m.getModifiers())) { + System.out.println("NEED TO CREATE PROXY TO " + interfac + sig + "\n" + m.getDeclaringClass()); + MethodContainer e = new MethodContainer(m.getName(), Type.getMethodDescriptor(m), m.getDeclaringClass().getName().replace(".", "/"), sig, getExceptions(m)); + toReturn.add(e); + } + } + return toReturn; + } + + private static String[] getExceptions(Method m) { + Class[] exceptionTypes = m.getExceptionTypes(); + String[] tmp = new String[exceptionTypes.length]; + for(int i = 0; i < exceptionTypes.length; i++) { + tmp[i] = exceptionTypes[i].getName().replace(".", "/"); + } + return tmp; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + visitedMethods.add(new VisitedMethod(name, desc)); + return new InterfaceToHelperRewriter(super.visitMethod(access, name, desc, signature, exceptions)); + } + + @Override + public void visitEnd() { + for(MethodContainer m : defaultMethods) { + if(visitedMethods.contains(new VisitedMethod(m.methodName, m.methodDesc))) { + continue; + } + System.out.println("VISITEND, CREATING PROXY " + m); + MethodVisitor tmp = super.visitMethod(ACC_PUBLIC, m.methodName, m.methodDesc, m.signature, m.exceptions); + tmp.visitVarInsn(ALOAD, 0); + int i = 1; + for(Type arg : Type.getArgumentTypes(m.methodDesc)) { + tmp.visitVarInsn(getVarIns(arg), i++); + } + String rightInterace = findRightInterace(m, interfaces); + System.out.println("It thinks that the right interface is " + rightInterace); + String mDesc = Helpers.addParam(m.methodDesc, rightInterace); + tmp.visitMethodInsn(INVOKESTATIC, rightInterace + "$helper", m.methodName, mDesc, false); + tmp.visitInsn(getReturnIns(Type.getReturnType(m.methodDesc))); + tmp.visitMaxs(0, 0); + tmp.visitEnd(); + } + super.visitEnd(); + } + + public static final Comparator COMPARATOR = (o1, o2) -> { + boolean o1iso2 = o1.getDeclaringClass().isAssignableFrom(o2.getDeclaringClass()); + boolean o2iso1 = o2.getDeclaringClass().isAssignableFrom(o1.getDeclaringClass()); + if(o1iso2 && o2iso1) { + return 0; + } else if(o2iso1) + return -1; + else return 1; + }; + + public static String findRightInterace(MethodContainer methodContainer, String[] interfaces) { + System.out.println("Find right interfaces for " + methodContainer + " " + Arrays.toString(interfaces)); + return Stream.of(interfaces) + .map(Helpers::loadClass) + .flatMap(i -> flattenInterfaces(i).stream()) + .flatMap(i -> Stream.of(i.getMethods())) + .filter(Method::isDefault) + .filter(m -> Type.getMethodDescriptor(m).equals(methodContainer.methodDesc)) + .min(COMPARATOR) + .map(Method::getDeclaringClass) + .map(Class::getName) + .map(s -> s.replace(".", "/")) + .orElseThrow(NullPointerException::new); + } + + private static List> flattenInterfaces(Class iff) { + List> tmp = new ArrayList<>(); + if(iff != null){ + tmp.add(iff); + for(Class stream : iff.getInterfaces()) { + tmp.addAll(flattenInterfaces(stream)); + } + } + return tmp; + } + + static int getReturnIns(Type arg) { + if(arg == Type.INT_TYPE || arg == Type.BOOLEAN_TYPE || arg == Type.SHORT_TYPE) { + return IRETURN; + } + else if(arg == Type.LONG_TYPE) { + return LRETURN; + } + else if(arg == Type.DOUBLE_TYPE) { + return DRETURN; + } + else if(arg == Type.FLOAT_TYPE) { + return FRETURN; + } + else if(arg == Type.VOID_TYPE) { + return RETURN; + } + else { + return ARETURN; + } + } + + static int getVarIns(Type arg) { + if(arg == Type.INT_TYPE || arg == Type.BOOLEAN_TYPE || arg == Type.SHORT_TYPE) { + return ILOAD; + } + else if(arg == Type.DOUBLE_TYPE) { + return DLOAD; + } + else if(arg == Type.FLOAT_TYPE) { + return FLOAD; + } + else { + return ALOAD; + } + } + +} \ No newline at end of file diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/Helpers.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/Helpers.java new file mode 100644 index 00000000..f4436045 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/Helpers.java @@ -0,0 +1,51 @@ +// Copyright © 2013-2014 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.defaultmethods; + +import org.objectweb.asm.Type; + +import java.lang.reflect.Method; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; +import static org.objectweb.asm.Type.*; +/** + * Created by arneball on 2014-08-12. + */ +public class Helpers { + private static final Pattern pattern = Pattern.compile("\\((.*)\\)(.*)"); + + public static String addParam(String desc, String className) { + Matcher m = pattern.matcher(desc); + m.find(); + String rest = m.group(1); + String returntype = m.group(2); + return String.format("(L%s;%s)%s", className, rest, returntype); + } + + public static String changeReturnType(String desc, String returnType) { + Matcher m = pattern.matcher(desc); + m.find(); + String rest = m.group(1); + return String.format("(%s)L%s;", rest, returnType); + } + + public static Class loadClass(String className) { + try { + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + return cl.loadClass(className.replace('/', '.')); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static boolean isPrimitive(Type containerReturnType) { + return Stream.of(BYTE_TYPE, SHORT_TYPE, INT_TYPE, LONG_TYPE, FLOAT_TYPE, DOUBLE_TYPE, VOID_TYPE, BOOLEAN_TYPE) + .filter(containerReturnType::equals) + .findAny() + .isPresent(); + + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceModifier.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceModifier.java new file mode 100644 index 00000000..4d54c3a6 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceModifier.java @@ -0,0 +1,177 @@ +// Copyright © 2013-2014 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.defaultmethods; + +import net.orfjackal.retrolambda.Config; +import org.objectweb.asm.*; + +import java.io.IOException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Created by arneball on 2014-08-12. + */ +public class InterfaceModifier extends ClassVisitor implements Opcodes{ + private final int targetByteCode; + private String className; + private boolean isInterface; + private ClassWriter helperClassVisitor; + private String[] interfaces; + private List methodContainers = new ArrayList<>(); + + public InterfaceModifier(ClassVisitor classWriter, int targetBytodeCode) { + super(ASM5, classWriter); + this.targetByteCode = targetBytodeCode; + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + isInterface = (access & ACC_INTERFACE) != 0; + className = name; + this.interfaces = interfaces; + // force load this class, if not, we are overwriting the interface and succeeding loads will see the purely abstract onoe + Helpers.loadClass(name); + System.out.println("Visiting interface " + name); + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + boolean isConcrete = (access & ACC_ABSTRACT) == 0; + boolean isStatic = (access & ACC_STATIC) != 0; + if(isConcrete && isInterface && !isStatic) { + super.visitMethod(access | ACC_ABSTRACT, name, desc, signature, exceptions); + MethodVisitor tmp = getHelperClassVisitor().visitMethod( + access | ACC_STATIC, + name, + Helpers.addParam(desc, className), + signature, + exceptions); + methodContainers.add(new MethodContainer(name, desc, null, signature, exceptions)); + return new InterfaceToHelperRewriter(new BodyMover(tmp)); + } else if(isStatic && isInterface) { + return getHelperClassVisitor().visitMethod(access, name + "$static", desc, signature, exceptions); + } else { + return super.visitMethod(access, name, desc, signature, exceptions); + } + } + + @Override + public void visitEnd() { + Path newPath = new Config(System.getProperties()).getOutputDir(); + ArrayList allMethods = Stream.of(interfaces) + .map(Helpers::loadClass) + .flatMap(c -> Stream.of(c.getMethods())) + .collect(Collectors.toCollection(ArrayList::new)); + methodContainers.stream().forEach(m -> { + allMethods.stream() + .filter(meth -> bridgeNeeded(meth, m)) + .forEach(meth -> createBridge(meth, m)); + }); + getHelperClassVisitor().visitEnd(); + super.visitEnd(); + try { + Files.createDirectories(newPath.getParent()); + Files.write(newPath.resolve(helperClassName() + ".class"), getHelperClassVisitor().toByteArray()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private void createBridge(Method meth, MethodContainer m) { + int access = ACC_PUBLIC | ACC_STATIC | ACC_BRIDGE; + String desc = Helpers.addParam(m.methodDesc, className); + String returnType = Type.getReturnType(meth).getInternalName(); + desc = Helpers.changeReturnType(desc, returnType); + MethodVisitor tmp = getHelperClassVisitor().visitMethod(access, m.methodName, desc, m.signature, m.exceptions); + tmp.visitVarInsn(ALOAD, 0); + int i = 1; + for(Type arg : Type.getArgumentTypes(m.methodDesc)) { + tmp.visitVarInsn(ClassModifier.getVarIns(arg), i++); + } + String mDesc = Helpers.addParam(m.methodDesc, className); + tmp.visitMethodInsn(INVOKESTATIC, className + "$helper", m.methodName, mDesc, false); + tmp.visitInsn(ARETURN); + tmp.visitMaxs(0, 0); + tmp.visitEnd(); + } + + private static boolean bridgeNeeded(Method method, MethodContainer methodContainer) { + Type[] methodArgumentns = Type.getArgumentTypes(method); + Type[] containerArguments = Type.getArgumentTypes(methodContainer.methodDesc); + boolean argsEquals = Arrays.equals(methodArgumentns, containerArguments); + boolean nameEquals = method.getName().equals(methodContainer.methodName); + log("" + method + " should be equal to " + methodContainer); + log("Args equal = " + argsEquals + ", nameEquals = " + nameEquals); + if(!argsEquals || !nameEquals) { + return false; + } + Type containerReturnType = Type.getReturnType(methodContainer.methodDesc); + Type methodReturnType = Type.getReturnType(method); + boolean containerRetPrimite = Helpers.isPrimitive(containerReturnType); + boolean methodRetPrimitive = Helpers.isPrimitive(methodReturnType); + log("Container ret primitive = " + containerRetPrimite + ", methodRetPrimitive = " + methodRetPrimitive); + if(containerRetPrimite || methodRetPrimitive) { + log("Either one primitive, no bridge needed"); + return false; + } + + Class returnType = method.getReturnType(); + log("returnType = " + returnType + ", isPrimitive = " + returnType.isPrimitive()); + return returnType.isAssignableFrom(Helpers.loadClass(containerReturnType.getClassName())); + } + + private static void log(String s) { + System.out.println("interfaceModifier ======= " + s); + } + + private ClassWriter getHelperClassVisitor() { + return helperClassVisitor == null ? helperClassVisitor = mkHelperClassVisitor() : helperClassVisitor; + } + + private ClassWriter mkHelperClassVisitor() { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cw.visit(targetByteCode, + ACC_PUBLIC + ACC_SUPER, + helperClassName(), + null, + "java/lang/Object", + null); + MethodVisitor mv = cw.visitMethod(ACC_PRIVATE, "", "()V", null, null); + mv.visitVarInsn(ALOAD, 0); + mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false); + mv.visitInsn(RETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + return cw; + } + + private String helperClassName() { + return className + "$helper"; + } + + private static class BodyMover extends MethodVisitor{ + BodyMover(MethodVisitor newMethod) { + super(ASM5, newMethod); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if(opcode == INVOKESPECIAL && itf) { + super.visitMethodInsn(INVOKESTATIC, owner + "$helper", name, Helpers.addParam(desc, owner), false); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceToHelperRewriter.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceToHelperRewriter.java new file mode 100644 index 00000000..01e2c5b2 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/InterfaceToHelperRewriter.java @@ -0,0 +1,28 @@ +// Copyright © 2013-2014 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.defaultmethods; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/** +* Created by arneball on 2014-08-24. +*/ +class InterfaceToHelperRewriter extends MethodVisitor implements Opcodes { + public InterfaceToHelperRewriter(MethodVisitor mv) { + super(ASM5, mv); + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if(opcode == INVOKESPECIAL && itf){ + super.visitMethodInsn(INVOKESTATIC, owner + "$helper", name, Helpers.addParam(desc, owner), false); + } else if(opcode == INVOKESTATIC && itf) { + super.visitMethodInsn(INVOKESTATIC, owner + "$helper", name + "$static", desc, false); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/MethodContainer.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/MethodContainer.java new file mode 100644 index 00000000..b570570c --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/MethodContainer.java @@ -0,0 +1,58 @@ +// Copyright © 2013-2014 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.defaultmethods; + +import java.util.Arrays; + +/** +* Created by arneball on 2014-08-23. +*/ +class MethodContainer { + public final String methodName, methodDesc, interfce, signature; + public final String[] exceptions; + + @Override + public String toString() { + return "MethodContainer{" + + "methodName='" + methodName + '\'' + + ", methodDesc='" + methodDesc + '\'' + + ", interfce='" + interfce + '\'' + + ", signature='" + signature + '\'' + + ", exceptions=" + Arrays.toString(exceptions) + + '}'; + } + + MethodContainer(String methodName, String methodDesc, String interfce, String signature, String[] exceptions) { + this.methodName = methodName; + this.methodDesc = methodDesc; + this.interfce = interfce; + this.signature = signature; + this.exceptions = exceptions; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MethodContainer that = (MethodContainer) o; + + if (!Arrays.equals(exceptions, that.exceptions)) return false; + if (methodDesc != null ? !methodDesc.equals(that.methodDesc) : that.methodDesc != null) return false; + if (methodName != null ? !methodName.equals(that.methodName) : that.methodName != null) return false; + if (signature != null ? !signature.equals(that.signature) : that.signature != null) return false; + + return true; + } + + @Override + public int hashCode() { + int result = methodName != null ? methodName.hashCode() : 0; + result = 31 * result + (methodDesc != null ? methodDesc.hashCode() : 0); + result = 31 * result + (signature != null ? signature.hashCode() : 0); + result = 31 * result + (exceptions != null ? Arrays.hashCode(exceptions) : 0); + return result; + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/VisitedMethod.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/VisitedMethod.java new file mode 100644 index 00000000..cd4a7ae6 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/defaultmethods/VisitedMethod.java @@ -0,0 +1,37 @@ +// Copyright © 2013-2014 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.defaultmethods; + +/** +* Created by arneball on 2014-08-23. +*/ +class VisitedMethod { + public final String name, desc; + + VisitedMethod(String name, String desc) { + this.name = name; + this.desc = desc; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + VisitedMethod that = (VisitedMethod) o; + + if (!desc.equals(that.desc)) return false; + if (!name.equals(that.name)) return false; + + return true; + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + desc.hashCode(); + return result; + } +} From 643820b80598a4eb2255e3837983a4ad060cdcad Mon Sep 17 00:00:00 2001 From: Raul Bache Date: Sun, 24 Aug 2014 21:12:08 +0200 Subject: [PATCH 2/5] Merge tests --- .../retrolambda/test/DefaultMethodsTest.java | 270 ++++++++++++++++++ 1 file changed, 270 insertions(+) diff --git a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java index 3d59cfba..0ed26bd2 100644 --- a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java +++ b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java @@ -6,6 +6,8 @@ import org.junit.Test; +import java.util.Comparator; + import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -36,4 +38,272 @@ public interface Parent { public interface Child extends Parent { String foo(); // refined return type } + + public interface Parent2 { + default Object method() { + return "Parent"; + } + } + + public interface Child2 extends Parent2{ + @Override + default String method() { + return "Child2"; + } + } + + @Test + public void will_return_right_string() { + boolean sameStrings = new Child2() { + + }.method().equals("Child2"); + assertThat("they are equal", sameStrings); + } + + interface Primitives { + default int anInt() { + return 1; + } + default short aShort() { + return 2; + } + default long aLong() { + return 1L << 50; + } + default boolean aBoolean() { + return true; + } + default float aFloat() { + return 0f; + } + default double aDouble() { + return 0.0; + } + default void aVoid() { + } + } + + @Test + public void primitives_run() { + Primitives p = new Primitives() { + }; + assertThat("booleans ok", p.aBoolean()); + assertThat("ints ok", p.anInt() == 1); + assertThat("shorts ok", p.aShort() == 2); + assertThat("longs ok", p.aLong() == 1L << 50); + assertThat("floats ok", p.aFloat() == 0f); + assertThat("doubles ok", p.aDouble() == 0.0); + p.aVoid(); // would crash + } + + interface Chaining { + default String myString() { + return "Interface"; + } + default String join(Chaining other) { + return myString() + other.myString(); + } + } + + @Test + public void anonymous_instances() { + Chaining c1 = new Chaining() { + + }; + Chaining c2 = new Chaining() { + + }; + assertThat("Strings equals", c1.join(c2).equals("InterfaceInterface")); + Chaining anon = new Chaining() { + @Override + public String myString() { + return "Anon"; + } + }; + assertThat("Anonymous override equals", c1.join(anon).equals("InterfaceAnon")); + } + + interface DeepParent { + default int level() { + return 1; + } + } + interface DeepChild extends DeepParent { + @Override + default int level() { + return DeepParent.super.level() + 1; + } + } + + @Test + public void test_override_primitive() { + DeepChild d1 = new DeepChild() { + + }; + assertThat("override works", d1.level() == 2); + DeepChild d2 = new DeepChild() { + @Override + public int level() { + return 1 + DeepChild.super.level(); + } + }; + assertThat("super call interface works", d2.level() == 3); + } + + interface Conflict1 { + default String confl() { + return "1"; + } + } + + interface Conflict2 { + default String confl() { + return "2"; + } + } + + @Test + public void will_handle_override_proprly() { + class C implements Conflict1, Conflict2 { + public String confl() { + return Conflict1.super.confl() + Conflict2.super.confl(); + } + } + assertThat("Handles method conflict", new C().confl().equals("12")); + } + + interface DeepParent2 { + int anInt(); + default int method(DeepParent2 p1, DeepParent2 p2, DeepParent2 p3) { + return p1.anInt() + p2.anInt() + p3.anInt() + anInt(); + } + } + + @Test + public void will_handle_long_paramlist() { + DeepParent2 dp = new DeepParent2() { + @Override + public int anInt() { + return 2; + } + }; + assertThat("Long call parameter list works", dp.method(dp, dp, dp) == 8); + } + + @Test + public void will_handle_lambda() { + DeepParent2 dp = () -> 2; + assertThat("Long call parameter list with lambda works", dp.method(dp, dp, dp) == 8); + } + + interface BridgeTest { + default T max(T t1, T t2, Comparator comparator) { + return comparator.compare(t1, t2) > 0 ? t1 : t2; + } + } + + interface StringBridge extends BridgeTest { + default boolean compare() { + return max("A", "B", String.CASE_INSENSITIVE_ORDER).equals("B"); + } + } + + @Test + public void handles_bridge_methods() { + StringBridge sb = new StringBridge() { + }; + assertThat("returns true", sb.compare()); + BridgeTest sb2 = sb; + assertThat("still returns true", sb2.max("A", "B", String.CASE_INSENSITIVE_ORDER).equals("B")); + } + + interface MiddleParent { + default int anInt() { + return 1; + } + } + interface Middle2Parent extends MiddleParent{ + @Override + default int anInt() { + return 2; + } + } + interface Middle3aParent extends MiddleParent, Middle2Parent { + + } + interface Middle3bParent extends Middle2Parent, MiddleParent { + + } + @Test + public void right_method_chosen() { + assertThat(new Middle3aParent() { + + }.anInt(), is(2)); + + assertThat(new Middle3bParent() { + + }.anInt(), is(2)); + } + + interface Top { + T anObject(); + default int anInt() { + return 1; + } + } + + interface SubTop extends Top { + default int anInt() { + return Top.super.anInt() + 1; + } + } + interface SubSub extends SubTop { + default int anInt() { + return SubTop.super.anInt() + 1; + } + default String anObject() { + return "0"; + } + } + interface SubSub2 extends SubTop { + default String anObject() { + return "1"; + } + } + + @Test + public void yet_another_deep_hiearchy_test_with_bridges() { + assertThat(new SubSub2() { + + }.anInt(), is(2)); + + assertThat(new SubSub() { + + }.anInt(), is(3)); + SubSub sub = new SubSub() { + + }; + assertThat(sub.anInt(), is(3)); + Top top = sub; + assertThat("is instanceof string", top.anObject() instanceof String); + } + + interface DefaultToStatic { + default int ifMeth() { + return staticMeth(); + } + + static int staticMeth() { + return 3; + } + } + + @Test + public void call_static_methods_from_default() { + DefaultToStatic i = new DefaultToStatic() { + }; + assertThat(i.ifMeth(), is(3)); + assertThat(DefaultToStatic.staticMeth(), is(3)); + } +// } From e39c3f4c755485a6ed6fa9213d664bcf078488fc Mon Sep 17 00:00:00 2001 From: Raul Bache Date: Sun, 24 Aug 2014 21:17:11 +0200 Subject: [PATCH 3/5] retrolambda/pom.xml --- .../orfjackal/retrolambda/maven/ProcessClassesMojoTest.java | 4 ++-- retrolambda/pom.xml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java b/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java index cfdbbcb7..cda1ecd5 100644 --- a/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java +++ b/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java @@ -49,7 +49,7 @@ public void java_command_defaults_to_current_jvm() { assertThat(mojo.getJavaCommand(), is(new File(System.getProperty("java.home"), "bin/java").getAbsolutePath())); } - @Test +/* @Test public void java_command_from_toolchain_overrides_the_current_jvm() { toolchainManager.setJdkToolChain(new FakeJavaToolChain("jdk-from-toolchain")); @@ -66,7 +66,7 @@ public void java_command_from_local_configuration_overrides_the_toolchain() { verify(log).warn("Toolchains are ignored, 'java8home' parameter is set to jdk-from-local-configuration"); } - +*/ private static class FakeToolchainManager implements ToolchainManager { private final Map toolChainsByType = new HashMap(); diff --git a/retrolambda/pom.xml b/retrolambda/pom.xml index c25f5cf6..aa825406 100644 --- a/retrolambda/pom.xml +++ b/retrolambda/pom.xml @@ -52,7 +52,7 @@ - true + org.objectweb.asm From 6792c3ef9b1ac7e473ecf7ca14d8d6967d3e9ace Mon Sep 17 00:00:00 2001 From: Raul Bache Date: Sun, 24 Aug 2014 21:23:48 +0200 Subject: [PATCH 4/5] Primitive tests makes sense --- .../retrolambda/test/DefaultMethodsTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java index 0ed26bd2..fef28814 100644 --- a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java +++ b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/DefaultMethodsTest.java @@ -87,12 +87,12 @@ default void aVoid() { public void primitives_run() { Primitives p = new Primitives() { }; - assertThat("booleans ok", p.aBoolean()); - assertThat("ints ok", p.anInt() == 1); - assertThat("shorts ok", p.aShort() == 2); - assertThat("longs ok", p.aLong() == 1L << 50); - assertThat("floats ok", p.aFloat() == 0f); - assertThat("doubles ok", p.aDouble() == 0.0); + assertThat(p.aBoolean(), is(true)); + assertThat(p.anInt(), is(1)); + assertThat(p.aShort(), is((short)2)); + assertThat(p.aLong(), is(1L << 50)); + assertThat(p.aFloat(), is(0f)); + assertThat(p.aDouble(), is(0.0)); p.aVoid(); // would crash } From 9038a86a72396e44b6f2d493b19cea63626f9c9c Mon Sep 17 00:00:00 2001 From: Raul Bache Date: Sun, 24 Aug 2014 21:26:06 +0200 Subject: [PATCH 5/5] Main cleaned up, --- .../retrolambda/maven/ProcessClassesMojoTest.java | 4 ++-- .../main/java/net/orfjackal/retrolambda/Main.java | 13 ------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java b/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java index cda1ecd5..cfdbbcb7 100644 --- a/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java +++ b/retrolambda-maven-plugin/src/test/java/net/orfjackal/retrolambda/maven/ProcessClassesMojoTest.java @@ -49,7 +49,7 @@ public void java_command_defaults_to_current_jvm() { assertThat(mojo.getJavaCommand(), is(new File(System.getProperty("java.home"), "bin/java").getAbsolutePath())); } -/* @Test + @Test public void java_command_from_toolchain_overrides_the_current_jvm() { toolchainManager.setJdkToolChain(new FakeJavaToolChain("jdk-from-toolchain")); @@ -66,7 +66,7 @@ public void java_command_from_local_configuration_overrides_the_toolchain() { verify(log).warn("Toolchains are ignored, 'java8home' parameter is set to jdk-from-local-configuration"); } -*/ + private static class FakeToolchainManager implements ToolchainManager { private final Map toolChainsByType = new HashMap(); diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java index 3b1ae884..feba36e5 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Main.java @@ -39,19 +39,6 @@ public static void main(String[] args) { try { Thread.currentThread().setContextClassLoader(new URLClassLoader(asUrls(classpath))); - /** - * { - @Override - public Class loadClass(String name) throws ClassNotFoundException { - Class loadedClass = findLoadedClass(name); - if(loadedClass != null) { - return loadedClass; - } else { - return super.loadClass(name); - } - } - } - */ visitFiles(inputDir, includedFiles, new BytecodeTransformingFileVisitor(inputDir, outputDir) { @Override