/*
 * Copyright (c) 2015, 2018, 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 8144903 8171981 8191802 8191842
 * @summary Tests for determining the type from the expression
 * @build KullaTesting TestingInputStream
 * @run testng TypeNameTest
 */

import org.testng.annotations.Test;

import static org.testng.Assert.assertEquals;

@Test
public class TypeNameTest extends KullaTesting {


    private void assertType(String expr, String type) {
        assertType(expr, type, type);
    }

    private void assertType(String expr, String type, String inferType) {
        assertEquals(varKey(assertEval(expr)).typeName(), type);
        assertInferredType(expr, inferType);
    }

    public void testTypeInference() {
        assertEval("import java.util.List;");
        assertEval("import java.util.ArrayList;");
        assertEval("import java.util.Arrays;");

        assertType("new Object().getClass().getSuperclass() ", "Class<?>");
        assertType("new ArrayList().getClass().getSuperclass()", "Class<?>");
        assertType("new ArrayList().getClass()", "Class<? extends ArrayList>");
        assertType("ArrayList.class", "Class<ArrayList>");
        assertType("ArrayList.class.getSuperclass()", "Class<? super ArrayList>");

        assertEval("class D<T extends CharSequence> { D<? super T> getS() { return null; } }");
        assertEval("D<?> d = new D<String>();");
        assertType("d.getS()", "D<?>");
        assertType("null", "Object");
        assertType("Class.forName( \"java.util.ArrayList\" )", "Class<?>");
        assertType("new ArrayList<Boolean>() {}", "<anonymous class extending ArrayList<Boolean>>", "ArrayList<Boolean>");
        assertType("new ArrayList<String>().stream()", "java.util.stream.Stream<String>");
        assertType("Arrays.asList( 1, 2, 3)", "List<Integer>");
        assertType("new ArrayList().getClass().getClass()", "Class<? extends Class>");

        assertEval("interface A {}");
        assertEval("interface I {}");
        assertEval("interface J extends A, I {}");
        assertEval("interface K extends A, I {}");
        assertEval("class P<T extends A & I> {}");
        assertType("(P<?>) null", "P<?>");
    }

    public void testConditionals() {
        assertEval("import java.util.List;");
        assertEval("import java.util.ArrayList;");
        assertEval("import java.util.Arrays;");

        assertEval("CharSequence cs = \"hi\";");
        assertEval("String st = \"low\";");
        assertEval("boolean b;");
        assertType("b? cs : st", "CharSequence");

        assertEval("List<String> l1 = Arrays.asList(\"hi\");");
        assertEval("List<? extends String> l2 = Arrays.asList(\"po\");");
        assertType("b? l1.get(0) : l2.get(0)", "String");

        assertEval("class X {}");
        assertEval("class B extends X {}");
        assertEval("class C extends X {}");
        assertType("b? new B() : new C()", "X");
    }

    public void testJEP286NonDenotable() {
        assertEval("import java.util.List;");
        assertEval("import java.util.Arrays;");
        assertEval("import java.util.Iterator;");

        assertEval("List<? extends String> extString() { return Arrays.asList( \"hi\", \"low\" ); }");
        assertEval("List<? super String> supString() { return Arrays.asList( \"hi\", \"low\" ); }");
        assertEval("List<?> unbString() { return Arrays.asList( \"hi\", \"low\" ); }");
        assertEval("List<? extends String>[] extStringArr() {" +
                " @SuppressWarnings(\"unchecked\") " +
                "List<? extends String>[] a = new List[1]; a[0] = Arrays.asList(\"hi\"); return a; }");
        assertEval("List<? super String>[] supStringArr() {" +
                " @SuppressWarnings(\"unchecked\") " +
                "List<? super String>[] a = new List[1]; a[0] = Arrays.asList(\"hi\"); return a; }");
        assertEval("List<?>[] unbStringArr() {" +
                " @SuppressWarnings(\"unchecked\") " +
                "List<?>[] a = new List[1]; a[0] = Arrays.asList(\"hi\"); return a; }");
        assertEval("Iterable<? extends List<? extends String>> extStringIter() {" +
                "return Arrays.asList( Arrays.asList( \"hi\" ) ); }");
        assertEval("Iterable<? extends List<? super String>> supStringIter() {" +
                "return Arrays.asList( Arrays.asList( \"hi\" ) ); }");
        assertEval("Iterable<? extends List<?>> unbStringIter() {" +
                "return Arrays.asList( Arrays.asList( \"hi\" ) ); }");
        assertType("extString()", "List<? extends String>");
        assertType("extString().get(0)", "String");
        assertType("supString()", "List<? super String>");
        assertType("supString().get(0)", "Object");
        assertType("unbString()", "List<?>");
        assertType("unbString().get(0)", "Object");
        assertType("supStringArr()", "List<? super String>[]");
        assertType("supStringArr()[0]", "List<? super String>");
        assertType("supStringArr()[0].get(0)", "Object");
        assertType("unbStringArr()", "List<?>[]");
        assertType("unbStringArr()[0]", "List<?>");
        assertType("unbStringArr()[0].get(0)", "Object");
        assertType("extStringIter()", "Iterable<? extends List<? extends String>>");
        assertType("extStringIter().iterator()", "Iterator<? extends List<? extends String>>");
        assertType("extStringIter().iterator().next()", "List<? extends String>");
        assertType("extStringIter().iterator().next().get(0)", "String");
        assertType("supStringIter()", "Iterable<? extends List<? super String>>");
        assertType("supStringIter().iterator()", "Iterator<? extends List<? super String>>");
        assertType("supStringIter().iterator().next()", "List<? super String>");
        assertType("supStringIter().iterator().next().get(0)", "Object");
        assertType("unbStringIter()", "Iterable<? extends List<?>>");
        assertType("unbStringIter().iterator()", "Iterator<? extends List<?>>");
        assertType("unbStringIter().iterator().next()", "List<?>");
        assertType("unbStringIter().iterator().next().get(0)", "Object");
    }

    public void testJEP286NonDenotable2() {
        assertEval("import java.util.List;");
        assertEval("import java.util.Arrays;");
        assertEval("import java.lang.reflect.Array;");

        assertEval("<Z extends Comparable<Z>> List<? extends Z> extFbound() {" +
                "return Arrays.asList( (Z)null ); }");
        assertEval("<Z extends Comparable<Z>> List<? super Z> supFbound() {" +
                "return Arrays.asList( (Z)null ); }");
        assertEval("<Z extends Comparable<Z>> List<? extends Z>[] extFboundArr() {" +
                "@SuppressWarnings(\"unchecked\")" +
                "List<? extends Z>[] a = new List[1]; a[0] = Arrays.asList( (Z)null ); return a; }");
        assertEval("<Z extends Comparable<Z>> List<? super Z>[] supFboundArr() {" +
                "@SuppressWarnings(\"unchecked\")" +
                "List<? super Z>[] a = new List[1]; a[0] = Arrays.asList( (Z)null ); return a; }");
        assertEval("<Z extends Comparable<Z>> Iterable<? extends List<? extends Z>> extFboundIter() {" +
                "return Arrays.asList( Arrays.asList( (Z)null ) ); }");
        assertEval("<Z extends Comparable<Z>> Iterable<? extends List<? super Z>> supFboundIter() {" +
                "return Arrays.asList( Arrays.asList( (Z)null ) ); }");
        assertEval("<Z> List<Z> listOf(Z z) { return Arrays.asList( z ); }");
        assertEval("<Z> Z[] arrayOf(Z z) {" +
                "@SuppressWarnings(\"unchecked\")" +
                "final Z[] a = (Z[]) Array.newInstance(z.getClass(), 1); a[0] = z; return a; }");
        assertType("extFbound()", "List<? extends Comparable<?>>");
        assertType("extFbound().get(0)", "Comparable<?>");
        assertType("supFbound()", "List<?>");
        assertType("supFbound().get(0)", "Object");
        assertType("extFboundArr()", "List<? extends Comparable<?>>[]");
        assertType("extFboundArr()[0]", "List<? extends Comparable<?>>");
        assertType("extFboundArr()[0].get(0)", "Comparable<?>");
        assertType("supFboundArr()", "List<?>[]");
        assertType("supFboundArr()[0]", "List<?>");
        assertType("supFboundArr()[0].get(0)", "Object");
        assertType("extFboundIter()", "Iterable<? extends List<? extends Comparable<?>>>");
        assertType("extFboundIter().iterator()", "java.util.Iterator<? extends List<? extends Comparable<?>>>");
        assertType("extFboundIter().iterator().next()", "List<? extends Comparable<?>>");
        assertType("extFboundIter().iterator().next().get(0)", "Comparable<?>");
        assertType("supFboundIter()", "Iterable<? extends List<?>>");
        assertType("supFboundIter().iterator()", "java.util.Iterator<? extends List<?>>");
        assertType("supFboundIter().iterator().next()", "List<?>");
        assertType("supFboundIter().iterator().next().get(0)", "Object");
        assertType("listOf(23)", "List<Integer>");
        assertType("listOf(true)", "List<Boolean>");
        assertType("listOf(true).get(0)", "Boolean");
        assertType("arrayOf(99)", "Integer[]");
        assertType("arrayOf(99)[0]", "Integer");

        assertEval("<Z> Z choose(Z z1, Z z2) { return z1; }");
        assertType("choose(1, 1L);", "Number&Comparable<? extends Number&Comparable<?>>", "Object");
    }

    public void testVariableTypeName() {
        assertType("\"x\"", "String");

        assertType("java.util.regex.Pattern.compile(\"x\")", "java.util.regex.Pattern");
        assertEval("import java.util.regex.*;");
        assertType("java.util.regex.Pattern.compile(\"x\")", "Pattern");

        assertType("new java.util.ArrayList()", "java.util.ArrayList");
        assertEval("import java.util.ArrayList;");
        assertType("new java.util.ArrayList()", "ArrayList");

        assertType("java.util.Locale.Category.FORMAT", "java.util.Locale.Category");
        assertEval("import static java.util.Locale.Category;");
        assertType("java.util.Locale.Category.FORMAT", "Category");
    }

    public void testReplNestedClassName() {
        assertEval("class D { static class E {} }");
        assertType("new D.E();", "D.E");
    }

    public void testAnonymousClassName() {
        assertEval("class C {}");
        assertType("new C();", "C");
        assertType("new C() { int x; };", "<anonymous class extending C>", "C");
    }

    public void testCapturedTypeName() {
        assertType("\"\".getClass();", "Class<? extends String>");
        assertType("\"\".getClass().getEnumConstants();", "String[]");
    }

    public void testJavaLang() {
        assertType("\"\";", "String");
    }

    public void testNotOverEagerPackageEating() {
        assertType("\"\".getClass().getDeclaredMethod(\"hashCode\");", "java.lang.reflect.Method");
    }

    public void testBounds() {
        assertEval("java.util.List<? extends String> list1 = java.util.Arrays.asList(\"\");");
        assertType("list1.iterator().next()", "String");
        assertEval("java.util.List<? super String> list2 = java.util.Arrays.asList(\"\");");
        assertType("list2.iterator().next()", "Object");
        assertEval("java.util.List<?> list3 = java.util.Arrays.asList(\"\");");
        assertType("list3.iterator().next()", "Object");
        assertEval("class Test1<X extends CharSequence> { public X get() { return null; } }");
        assertEval("Test1<?> test1 = new Test1<>();");
        assertType("test1.get()", "CharSequence");
        assertEval("class Test2<X extends Number & CharSequence> { public X get() { return null; } }");
        assertEval("Test2<?> test2 = new Test2<>();");
        assertType("test2.get()", "Number&CharSequence", "Object");
        assertEval("class Test3<T> { T[][] get() { return null; } }");
        assertEval("Test3<? extends String> test3 = new Test3<>();");
        assertType("test3.get()", "String[][]");
    }
}
