/*
 * Copyright (c) 2013, 2016, 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.
 *
 * 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.
 */

/*
 * @test
 * @bug 8027232
 * @library /test/lib/
 * @modules java.base/jdk.internal.org.objectweb.asm
 *          jdk.jdeps/com.sun.tools.classfile
 *          jdk.zipfs
 * @compile LambdaAsm.java
 * @run main/othervm LambdaAsm
 * @summary ensures that j.l.i.InvokerByteCodeGenerator and ASM visitMethodInsn
 * generate  bytecodes with correct constant pool references
 */
import com.sun.tools.classfile.Attribute;
import com.sun.tools.classfile.ClassFile;
import com.sun.tools.classfile.Code_attribute;
import com.sun.tools.classfile.ConstantPool;
import com.sun.tools.classfile.ConstantPool.CPInfo;
import com.sun.tools.classfile.Instruction;
import com.sun.tools.classfile.Method;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.nio.file.DirectoryStream;
import java.nio.file.Path;
import jdk.internal.org.objectweb.asm.ClassWriter;
import jdk.internal.org.objectweb.asm.MethodVisitor;
import jdk.test.lib.compiler.CompilerUtils;
import jdk.test.lib.process.OutputAnalyzer;

import static java.nio.file.Files.*;
import static jdk.internal.org.objectweb.asm.Opcodes.*;
import static jdk.test.lib.process.ProcessTools.*;

public class LambdaAsm {
    static final Path SRC = Path.of("src");
    static final Path CLASSES = Path.of("classes");

    static void init() throws Exception {
        emitCode();
        CompilerUtils.compile(SRC, CLASSES);
        OutputAnalyzer outputAnalyzer = executeProcess(createTestJavaProcessBuilder(
                "-Djdk.internal.lambda.dumpProxyClasses=.",
                "-cp", CLASSES.toString(), "A"));
        outputAnalyzer.shouldHaveExitValue(0);
    }

    static void emitCode() throws Exception {
        ArrayList<String> scratch = new ArrayList<>();
        scratch.add("import java.util.function.*;");
        scratch.add("class A {");
        scratch.add("   interface I {");
        scratch.add("       default Supplier<Integer> a() { return () -> 1; }");
        scratch.add("       default Supplier<Integer> b(int i) { return () -> i; }");
        scratch.add("       default Supplier<Integer> c(int i) { return () -> m(i); }");
        scratch.add("       int m(int i);");
        scratch.add("       static Integer d() { return 0; }");
        scratch.add("   }");
        scratch.add("   static class C implements I {");
        scratch.add("       public int m(int i) { return i;}");
        scratch.add("   }");
        scratch.add("   public static void main(String[] args) {");
        scratch.add("       I i = new C();");
        scratch.add("       i.a();");
        scratch.add("       i.b(1);");
        scratch.add("       i.c(1);");
        scratch.add("       I.d();");
        scratch.add("   }");
        scratch.add("}");

        Path testFile = SRC.resolve("A.java");
        Files.createDirectories(SRC);
        Files.write(testFile, scratch, Charset.defaultCharset());
    }

    static void checkMethod(String cname, String mname, ConstantPool cp,
            Code_attribute code) throws ConstantPool.InvalidIndex {
        for (Instruction i : code.getInstructions()) {
            String iname = i.getMnemonic();
            if ("invokespecial".equals(iname)
                    || "invokestatic".equals(iname)) {
                int idx = i.getByte(2);
                System.out.println("Verifying " + cname + ":" + mname +
                        " instruction:" + iname + " index @" + idx);
                CPInfo cpinfo = cp.get(idx);
                if (cpinfo instanceof ConstantPool.CONSTANT_Methodref_info) {
                    throw new RuntimeException("unexpected CP type expected "
                            + "InterfaceMethodRef, got MethodRef, " + cname
                            + ", " + mname);
                }
            }
        }
    }

    static int checkMethod(ClassFile cf, String mthd) throws Exception {
        if (cf.major_version < 52) {
            throw new RuntimeException("unexpected class file version, in "
                    + cf.getName() + "expected 52, got " + cf.major_version);
        }
        int count = 0;
        for (Method m : cf.methods) {
            String mname = m.getName(cf.constant_pool);
            if (mname.equals(mthd)) {
                for (Attribute a : m.attributes) {
                    if ("Code".equals(a.getName(cf.constant_pool))) {
                        count++;
                        checkMethod(cf.getName(), mname, cf.constant_pool,
                                (Code_attribute) a);
                    }
                }
            }
        }
        return count;
    }

    static void verifyInvokerBytecodeGenerator() throws Exception {
        int count = 0;
        int mcount = 0;
        try (DirectoryStream<Path> ds = newDirectoryStream(new File(".").toPath(),
                // filter in lambda proxy classes
                "A$I$$Lambda$*.class")) {
            for (Path p : ds) {
                System.out.println(p.toFile());
                ClassFile cf = ClassFile.read(p.toFile());
                // Check those methods implementing Supplier.get
                mcount += checkMethod(cf, "get");
                count++;
            }
        }
        if (count < 3) {
            throw new RuntimeException("unexpected number of files, "
                    + "expected atleast 3 files, but got only " + count);
        }
        if (mcount < 3) {
            throw new RuntimeException("unexpected number of methods, "
                    + "expected atleast 3 methods, but got only " + mcount);
        }
    }

    static void verifyASM() throws Exception {
        ClassWriter cw = new ClassWriter(0);
        cw.visit(V1_8, ACC_PUBLIC, "X", null, "java/lang/Object", null);
        MethodVisitor mv = cw.visitMethod(ACC_STATIC, "foo",
                "()V", null, null);
        mv.visitMaxs(2, 1);
        mv.visitMethodInsn(INVOKESTATIC,
                "java/util/function/Function.class",
                "identity", "()Ljava/util/function/Function;", true);
        mv.visitInsn(RETURN);
        cw.visitEnd();
        byte[] carray = cw.toByteArray();
        // for debugging
        // write((new File("X.class")).toPath(), carray, CREATE, TRUNCATE_EXISTING);

        // verify using javap/classfile reader
        ClassFile cf = ClassFile.read(new ByteArrayInputStream(carray));
        int mcount = checkMethod(cf, "foo");
        if (mcount < 1) {
            throw new RuntimeException("unexpected method count, expected 1" +
                    "but got " + mcount);
        }
    }

    public static void main(String... args) throws Exception {
        init();
        verifyInvokerBytecodeGenerator();
        verifyASM();
    }
}
