/*
 * Copyright (c) 2013, 2015, 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 8004698 8007073 8022343 8054304 8057804 8058595
 * @summary Unit test for type annotations
 */

import java.util.*;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.io.Serializable;

public class TypeAnnotationReflection {
    public static void main(String[] args) throws Exception {
        testSuper();
        testInterfaces();
        testReturnType();
        testNested();
        testArray();
        testRunException(TestClassException.class.getDeclaredMethod("foo", (Class<?>[])null));
        testRunException(Outer2.TestClassException2.class.getDeclaredConstructor(Outer2.class));
        testClassTypeVarBounds();
        testMethodTypeVarBounds();
        testFields();
        testClassTypeVar();
        testMethodTypeVar();
        testParameterizedType();
        testNestedParameterizedType();
        testWildcardType();
        testParameterTypes();
        testParameterType();
    }

    private static void check(boolean b) {
        if (!b)
            throw new RuntimeException();
    }

    private static void testSuper() throws Exception {
        check(Object.class.getAnnotatedSuperclass() == null);
        check(Class.class.getAnnotatedSuperclass().getAnnotations().length == 0);

        AnnotatedType a;
        a = TestClassArray.class.getAnnotatedSuperclass();
        Annotation[] annos = a.getAnnotations();
        check(annos.length == 2);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(annos[1].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno)annos[0]).value().equals("extends"));
        check(((TypeAnno2)annos[1]).value().equals("extends2"));
    }

    private static void testInterfaces() throws Exception {
        AnnotatedType[] as;
        as = TestClassArray.class.getAnnotatedInterfaces();
        check(as.length == 3);
        check(as[1].getAnnotations().length == 0);

        Annotation[] annos;
        annos = as[0].getAnnotations();
        check(annos.length == 2);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(annos[1].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno)annos[0]).value().equals("implements serializable"));
        check(((TypeAnno2)annos[1]).value().equals("implements2 serializable"));

        annos = as[2].getAnnotations();
        check(annos.length == 2);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(annos[1].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno)annos[0]).value().equals("implements cloneable"));
        check(((TypeAnno2)annos[1]).value().equals("implements2 cloneable"));
    }

    private static void testReturnType() throws Exception {
        Method m = TestClassArray.class.getDeclaredMethod("foo", (Class<?>[])null);
        Annotation[] annos = m.getAnnotatedReturnType().getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("return1"));
    }

    private static void testNested() throws Exception {
        Method m = TestClassNested.class.getDeclaredMethod("foo", (Class<?>[])null);
        Annotation[] annos = m.getAnnotatedReturnType().getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("array"));

        AnnotatedType t = m.getAnnotatedReturnType();
        t = ((AnnotatedArrayType)t).getAnnotatedGenericComponentType();
        annos = t.getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("Inner"));
    }

    private static void testArray() throws Exception {
        Method m = TestClassArray.class.getDeclaredMethod("foo", (Class<?>[])null);
        AnnotatedArrayType t = (AnnotatedArrayType) m.getAnnotatedReturnType();
        Annotation[] annos = t.getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("return1"));

        t = (AnnotatedArrayType)t.getAnnotatedGenericComponentType();
        annos = t.getAnnotations();
        check(annos.length == 0);

        t = (AnnotatedArrayType)t.getAnnotatedGenericComponentType();
        annos = t.getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("return3"));

        AnnotatedType tt = t.getAnnotatedGenericComponentType();
        check(!(tt instanceof AnnotatedArrayType));
        annos = tt.getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("return4"));
    }

    private static void testRunException(Executable e) throws Exception {
        AnnotatedType[] ts = e.getAnnotatedExceptionTypes();
        check(ts.length == 3);

        AnnotatedType t;
        Annotation[] annos;
        t = ts[0];
        annos = t.getAnnotations();
        check(annos.length == 2);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(annos[1].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno)annos[0]).value().equals("RE"));
        check(((TypeAnno2)annos[1]).value().equals("RE2"));

        t = ts[1];
        annos = t.getAnnotations();
        check(annos.length == 0);

        t = ts[2];
        annos = t.getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("AIOOBE"));
    }

    private static void testClassTypeVarBounds() throws Exception {
        Method m = TestClassTypeVarAndField.class.getDeclaredMethod("foo", (Class<?>[])null);
        AnnotatedType ret = m.getAnnotatedReturnType();
        Annotation[] annos = ret.getAnnotations();
        check(annos.length == 2);

        AnnotatedType[] annotatedBounds = ((AnnotatedTypeVariable)ret).getAnnotatedBounds();
        check(annotatedBounds.length == 2);

        annos = annotatedBounds[0].getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("Object1"));

        annos = annotatedBounds[1].getAnnotations();
        check(annos.length == 2);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(annos[1].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno)annos[0]).value().equals("Runnable1"));
        check(((TypeAnno2)annos[1]).value().equals("Runnable2"));
    }

    private static void testMethodTypeVarBounds() throws Exception {
        Method m2 = TestClassTypeVarAndField.class.getDeclaredMethod("foo2", (Class<?>[])null);
        AnnotatedType ret2 = m2.getAnnotatedReturnType();
        AnnotatedType[] annotatedBounds2 = ((AnnotatedTypeVariable)ret2).getAnnotatedBounds();
        check(annotatedBounds2.length == 1);

        Annotation[] annos = annotatedBounds2[0].getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("M Runnable"));

        // Check that AnnotatedTypeVariable.getAnnotatedBounds() returns jlO for a naked
        // type variable (i.e no bounds, no annotations)
        Method m4 = TestClassTypeVarAndField.class.getDeclaredMethod("foo4", (Class<?>[])null);
        AnnotatedType ret4 = m4.getAnnotatedReturnType();
        AnnotatedType[] annotatedBounds4 = ((AnnotatedTypeVariable)ret4).getAnnotatedBounds();
        check(annotatedBounds4.length == 1);

        annos = annotatedBounds4[0].getAnnotations();
        check(annos.length == 0);
        check(annotatedBounds4[0].getType().equals(Object.class));
    }

    private static void testFields() throws Exception {
        Field f1 = TestClassTypeVarAndField.class.getDeclaredField("field1");
        AnnotatedType at;
        Annotation[] annos;

        at = f1.getAnnotatedType();
        annos = at.getAnnotations();
        check(annos.length == 2);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(annos[1].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno)annos[0]).value().equals("T1 field"));
        check(((TypeAnno2)annos[1]).value().equals("T2 field"));

        Field f2 = TestClassTypeVarAndField.class.getDeclaredField("field2");
        at = f2.getAnnotatedType();
        annos = at.getAnnotations();
        check(annos.length == 0);

        Field f3 = TestClassTypeVarAndField.class.getDeclaredField("field3");
        at = f3.getAnnotatedType();
        annos = at.getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("Object field"));
    }

    private static void testClassTypeVar() throws Exception {
        TypeVariable[] typeVars = TestClassTypeVarAndField.class.getTypeParameters();
        Annotation[] annos;
        check(typeVars.length == 3);

        // First TypeVar
        AnnotatedType[] annotatedBounds = typeVars[0].getAnnotatedBounds();
        check(annotatedBounds.length == 2);

        annos = annotatedBounds[0].getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("Object1"));

        annos = annotatedBounds[1].getAnnotations();
        check(annos.length == 2);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(annos[1].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno)annos[0]).value().equals("Runnable1"));
        check(((TypeAnno2)annos[1]).value().equals("Runnable2"));

        // second TypeVar regular anno
        Annotation[] regularAnnos = typeVars[1].getAnnotations();
        check(regularAnnos.length == 1);
        check(typeVars[1].getAnnotation(TypeAnno.class).value().equals("EE"));

        // second TypeVar
        annotatedBounds = typeVars[1].getAnnotatedBounds();
        check(annotatedBounds.length == 1);

        annos = annotatedBounds[0].getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno2.class));
        check(((TypeAnno2)annos[0]).value().equals("EEBound"));

        // third Typevar V declared without explicit bounds should see jlO as its bound.
        annotatedBounds = typeVars[2].getAnnotatedBounds();
        check(annotatedBounds.length == 1);

        annos = annotatedBounds[0].getAnnotations();
        check(annos.length == 0);
        check(annotatedBounds[0].getType().equals(Object.class));
    }

    private static void testMethodTypeVar() throws Exception {
        Method m2 = TestClassTypeVarAndField.class.getDeclaredMethod("foo2", (Class<?>[])null);
        TypeVariable[] t = m2.getTypeParameters();
        check(t.length == 1);
        Annotation[] annos = t[0].getAnnotations();
        check(annos.length == 0);

        AnnotatedType[] annotatedBounds2 = t[0].getAnnotatedBounds();
        check(annotatedBounds2.length == 1);

        annos = annotatedBounds2[0].getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("M Runnable"));

        // Second method
        m2 = TestClassTypeVarAndField.class.getDeclaredMethod("foo3", (Class<?>[])null);
        t = m2.getTypeParameters();
        check(t.length == 2);
        annos = t[0].getAnnotations();
        check(annos.length == 1);
        check(annos[0].annotationType().equals(TypeAnno.class));
        check(((TypeAnno)annos[0]).value().equals("K"));

        annotatedBounds2 = t[0].getAnnotatedBounds();
        check(annotatedBounds2.length == 1);

        annos = annotatedBounds2[0].getAnnotations();
        check(annos.length == 0);

        // for the naked type variable L of foo3, we should see jlO as its bound.
        annotatedBounds2 = t[1].getAnnotatedBounds();
        check(annotatedBounds2.length == 1);
        check(annotatedBounds2[0].getType().equals(Object.class));

        annos = annotatedBounds2[0].getAnnotations();
        check(annos.length == 0);
    }

    private static void testParameterizedType() {
        // Base
        AnnotatedType[] as;
        as = TestParameterizedType.class.getAnnotatedInterfaces();
        check(as.length == 1);
        check(as[0].getAnnotations().length == 1);
        check(as[0].getAnnotation(TypeAnno.class).value().equals("M"));

        Annotation[] annos;
        as = ((AnnotatedParameterizedType)as[0]).getAnnotatedActualTypeArguments();
        check(as.length == 2);
        annos = as[0].getAnnotations();
        check(annos.length == 1);
        check(as[0].getAnnotation(TypeAnno.class).value().equals("S"));
        check(as[0].getAnnotation(TypeAnno2.class) == null);

        annos = as[1].getAnnotations();
        check(annos.length == 2);
        check(((TypeAnno)annos[0]).value().equals("I"));
        check(as[1].getAnnotation(TypeAnno2.class).value().equals("I2"));
    }

    private static void testNestedParameterizedType() throws Exception {
        Method m = TestParameterizedType.class.getDeclaredMethod("foo2", (Class<?>[])null);
        AnnotatedType ret = m.getAnnotatedReturnType();
        Annotation[] annos;
        annos = ret.getAnnotations();
        check(annos.length == 1);
        check(((TypeAnno)annos[0]).value().equals("I"));

        AnnotatedType[] args = ((AnnotatedParameterizedType)ret).getAnnotatedActualTypeArguments();
        check(args.length == 1);
        annos = args[0].getAnnotations();
        check(annos.length == 2);
        check(((TypeAnno)annos[0]).value().equals("I1"));
        check(args[0].getAnnotation(TypeAnno2.class).value().equals("I2"));

        // check type args
        Field f = TestParameterizedType.class.getDeclaredField("theField");
        AnnotatedParameterizedType fType = (AnnotatedParameterizedType)f.getAnnotatedType();
        args = fType.getAnnotatedActualTypeArguments();
        check(args.length == 1);
        annos = args[0].getAnnotations();
        check(annos.length == 1);
        check(((TypeAnno2)annos[0]).value().equals("Map Arg"));
        check(args[0].getAnnotation(TypeAnno2.class).value().equals("Map Arg"));

        // check outer type type args
        fType = (AnnotatedParameterizedType)fType.getAnnotatedOwnerType();
        args = fType.getAnnotatedActualTypeArguments();
        check(args.length == 1);
        annos = args[0].getAnnotations();
        check(annos.length == 1);
        check(((TypeAnno2)annos[0]).value().equals("String Arg"));
        check(args[0].getAnnotation(TypeAnno2.class).value().equals("String Arg"));

        // check outer type normal type annotations
        annos = fType.getAnnotations();
        check(annos.length == 1);
        check(((TypeAnno)annos[0]).value().equals("FieldOuter"));
        check(fType.getAnnotation(TypeAnno.class).value().equals("FieldOuter"));
    }

    private static void testWildcardType() throws Exception {
        Method m = TestWildcardType.class.getDeclaredMethod("foo", (Class<?>[])null);
        AnnotatedType ret = m.getAnnotatedReturnType();
        AnnotatedType[] t;
        t = ((AnnotatedParameterizedType)ret).getAnnotatedActualTypeArguments();
        check(t.length == 1);
        ret = t[0];

        Field f = TestWildcardType.class.getDeclaredField("f1");
        AnnotatedWildcardType w = (AnnotatedWildcardType)((AnnotatedParameterizedType)f
            .getAnnotatedType()).getAnnotatedActualTypeArguments()[0];
        t = w.getAnnotatedLowerBounds();
        check(t.length == 0);
        t = w.getAnnotatedUpperBounds();
        check(t.length == 1);
        Annotation[] annos;
        annos = t[0].getAnnotations();
        check(annos.length == 1);
        check(((TypeAnno)annos[0]).value().equals("2"));

        f = TestWildcardType.class.getDeclaredField("f2");
        w = (AnnotatedWildcardType)((AnnotatedParameterizedType)f
            .getAnnotatedType()).getAnnotatedActualTypeArguments()[0];
        t = w.getAnnotatedUpperBounds();
        check(t.length == 1);
        check(t[0].getType().equals(Object.class));
        annos = t[0].getAnnotations();
        check(annos.length == 0);
        t = w.getAnnotatedLowerBounds();
        check(t.length == 1);

        // for an unbounded wildcard, we should see jlO as its upperbound and null type as its lower bound.
        f = TestWildcardType.class.getDeclaredField("f3");
        w = (AnnotatedWildcardType)((AnnotatedParameterizedType)f
            .getAnnotatedType()).getAnnotatedActualTypeArguments()[0];
        t = w.getAnnotatedUpperBounds();
        check(t.length == 1);
        check(t[0].getType().equals(Object.class));
        annos = t[0].getAnnotations();
        check(annos.length == 0);
        t = w.getAnnotatedLowerBounds();
        check(t.length == 0);
    }

    private static void testParameterTypes() throws Exception {
        // NO PARAMS
        Method m = Params.class.getDeclaredMethod("noParams", (Class<?>[])null);
        AnnotatedType[] t = m.getAnnotatedParameterTypes();
        check(t.length == 0);

        // ONLY ANNOTATED PARAM TYPES
        Class[] argsArr = {String.class, String.class, String.class};
        m = Params.class.getDeclaredMethod("onlyAnnotated", (Class<?>[])argsArr);
        t = m.getAnnotatedParameterTypes();
        check(t.length == 3);

        check(t[0].getAnnotations().length == 1);
        check(t[0].getAnnotation(TypeAnno.class) != null);
        check(t[0].getAnnotationsByType(TypeAnno.class)[0].value().equals("1"));

        check(t[1].getAnnotations().length == 1);
        check(t[1].getAnnotation(TypeAnno.class) != null);
        check(t[1].getAnnotationsByType(TypeAnno.class)[0].value().equals("2"));

        check(t[2].getAnnotations().length == 2);
        check(t[2].getAnnotations()[0].annotationType().equals(TypeAnno.class));
        check(t[2].getAnnotation(TypeAnno.class) != null);
        check(t[2].getAnnotation(TypeAnno2.class) != null);
        check(t[2].getAnnotationsByType(TypeAnno.class)[0].value().equals("3a"));
        check(t[2].getAnnotationsByType(TypeAnno2.class)[0].value().equals("3b"));

        // MIXED ANNOTATED PARAM TYPES
        m = Params.class.getDeclaredMethod("mixed", (Class<?>[])argsArr);
        t = m.getAnnotatedParameterTypes();
        check(t.length == 3);

        check(t[0].getAnnotations().length == 1);
        check(t[0].getAnnotation(TypeAnno.class) != null);
        check(t[0].getAnnotationsByType(TypeAnno.class)[0].value().equals("1"));

        check(t[1].getAnnotations().length == 0);
        check(t[1].getAnnotation(TypeAnno.class) == null);
        check(t[1].getAnnotation(TypeAnno2.class) == null);

        check(t[2].getAnnotations().length == 2);
        check(t[2].getAnnotations()[0].annotationType().equals(TypeAnno.class));
        check(t[2].getAnnotation(TypeAnno.class) != null);
        check(t[2].getAnnotation(TypeAnno2.class) != null);
        check(t[2].getAnnotationsByType(TypeAnno.class)[0].value().equals("3a"));
        check(t[2].getAnnotationsByType(TypeAnno2.class)[0].value().equals("3b"));

        // NO ANNOTATED PARAM TYPES
        m = Params.class.getDeclaredMethod("unAnnotated", (Class<?>[])argsArr);
        t = m.getAnnotatedParameterTypes();
        check(t.length == 3);

        check(t[0].getAnnotations().length == 0);
        check(t[0].getAnnotation(TypeAnno.class) == null);
        check(t[0].getAnnotation(TypeAnno2.class) == null);

        check(t[1].getAnnotations().length == 0);
        check(t[1].getAnnotation(TypeAnno.class) == null);
        check(t[1].getAnnotation(TypeAnno2.class) == null);

        check(t[2].getAnnotations().length == 0);
        check(t[2].getAnnotation(TypeAnno.class) == null);
        check(t[2].getAnnotation(TypeAnno2.class) == null);
    }

    private static void testParameterType() throws Exception {
        // NO PARAMS
        Method m = Params.class.getDeclaredMethod("noParams", (Class<?>[])null);
        Parameter[] p = m.getParameters();
        check(p.length == 0);

        // ONLY ANNOTATED PARAM TYPES
        Class[] argsArr = {String.class, String.class, String.class};
        m = Params.class.getDeclaredMethod("onlyAnnotated", (Class<?>[])argsArr);
        p = m.getParameters();
        check(p.length == 3);
        AnnotatedType t0 = p[0].getAnnotatedType();
        AnnotatedType t1 = p[1].getAnnotatedType();
        AnnotatedType t2 = p[2].getAnnotatedType();

        check(t0.getAnnotations().length == 1);
        check(t0.getAnnotation(TypeAnno.class) != null);
        check(t0.getAnnotationsByType(TypeAnno.class)[0].value().equals("1"));

        check(t1.getAnnotations().length == 1);
        check(t1.getAnnotation(TypeAnno.class) != null);
        check(t1.getAnnotationsByType(TypeAnno.class)[0].value().equals("2"));

        check(t2.getAnnotations().length == 2);
        check(t2.getAnnotations()[0].annotationType().equals(TypeAnno.class));
        check(t2.getAnnotation(TypeAnno.class) != null);
        check(t2.getAnnotation(TypeAnno2.class) != null);
        check(t2.getAnnotationsByType(TypeAnno.class)[0].value().equals("3a"));
        check(t2.getAnnotationsByType(TypeAnno2.class)[0].value().equals("3b"));

        // MIXED ANNOTATED PARAM TYPES
        m = Params.class.getDeclaredMethod("mixed", (Class<?>[])argsArr);
        p = m.getParameters();
        check(p.length == 3);

        t0 = p[0].getAnnotatedType();
        t1 = p[1].getAnnotatedType();
        t2 = p[2].getAnnotatedType();

        check(t0.getAnnotations().length == 1);
        check(t0.getAnnotation(TypeAnno.class) != null);
        check(t0.getAnnotationsByType(TypeAnno.class)[0].value().equals("1"));

        check(t1.getAnnotations().length == 0);
        check(t1.getAnnotation(TypeAnno.class) == null);
        check(t1.getAnnotation(TypeAnno2.class) == null);

        check(t2.getAnnotations().length == 2);
        check(t2.getAnnotations()[0].annotationType().equals(TypeAnno.class));
        check(t2.getAnnotation(TypeAnno.class) != null);
        check(t2.getAnnotation(TypeAnno2.class) != null);
        check(t2.getAnnotationsByType(TypeAnno.class)[0].value().equals("3a"));
        check(t2.getAnnotationsByType(TypeAnno2.class)[0].value().equals("3b"));

        // NO ANNOTATED PARAM TYPES
        m = Params.class.getDeclaredMethod("unAnnotated", (Class<?>[])argsArr);
        p = m.getParameters();
        check(p.length == 3);

        t0 = p[0].getAnnotatedType();
        t1 = p[1].getAnnotatedType();
        t2 = p[2].getAnnotatedType();

        check(t0.getAnnotations().length == 0);
        check(t0.getAnnotation(TypeAnno.class) == null);
        check(t0.getAnnotation(TypeAnno2.class) == null);

        check(t1.getAnnotations().length == 0);
        check(t1.getAnnotation(TypeAnno.class) == null);
        check(t1.getAnnotation(TypeAnno2.class) == null);

        check(t2.getAnnotations().length == 0);
        check(t2.getAnnotation(TypeAnno.class) == null);
        check(t2.getAnnotation(TypeAnno2.class) == null);
    }
}

class Params {
    public void noParams() {}
    public void onlyAnnotated(@TypeAnno("1") String s1, @TypeAnno("2") String s2, @TypeAnno("3a") @TypeAnno2("3b") String s3) {}
    public void mixed(@TypeAnno("1") String s1, String s2, @TypeAnno("3a") @TypeAnno2("3b") String s3) {}
    public void unAnnotated(String s1, String s2, String s3) {}
}

abstract class TestWildcardType {
    public <T> List<? super T> foo() { return null;}
    public Class<@TypeAnno("1") ? extends @TypeAnno("2") Annotation> f1;
    public Class<@TypeAnno("3") ? super @TypeAnno("4") Annotation> f2;
    public Class<@TypeAnno("5") ?> f3;
}

abstract class TestParameterizedType implements @TypeAnno("M") Map<@TypeAnno("S")String, @TypeAnno("I") @TypeAnno2("I2")Integer> {
    public ParameterizedOuter<String>.ParameterizedInner<Integer> foo() {return null;}
    public @TypeAnno("O") ParameterizedOuter<@TypeAnno("S1") @TypeAnno2("S2") String>.
            @TypeAnno("I") ParameterizedInner<@TypeAnno("I1") @TypeAnno2("I2")Integer> foo2() {
        return null;
    }

    public @TypeAnno("FieldOuter") ParameterizedOuter<@TypeAnno2("String Arg") String>.
            @TypeAnno("FieldInner")ParameterizedInner<@TypeAnno2("Map Arg")Map> theField;
}

class ParameterizedOuter <T> {
    class ParameterizedInner <U> {}
}

abstract class TestClassArray extends @TypeAnno("extends") @TypeAnno2("extends2") Object
    implements @TypeAnno("implements serializable") @TypeAnno2("implements2 serializable") Serializable,
    Readable,
    @TypeAnno("implements cloneable") @TypeAnno2("implements2 cloneable") Cloneable {
    public @TypeAnno("return4") Object @TypeAnno("return1") [][] @TypeAnno("return3")[] foo() { return null; }
}

abstract class TestClassNested {
    public @TypeAnno("Outer") Outer.@TypeAnno("Inner")Inner @TypeAnno("array")[] foo() { return null; }
}

class Outer {
    class Inner {
    }
}

abstract class TestClassException {
    public Object foo() throws @TypeAnno("RE") @TypeAnno2("RE2") RuntimeException,
                                                                 NullPointerException,
                                             @TypeAnno("AIOOBE") ArrayIndexOutOfBoundsException {
        return null;
    }
}

class Outer2 {
    abstract class TestClassException2 {
        public TestClassException2() throws
                @TypeAnno("RE") @TypeAnno2("RE2") RuntimeException,
                NullPointerException,
                @TypeAnno("AIOOBE") ArrayIndexOutOfBoundsException {}
    }
}

abstract class TestClassTypeVarAndField <T extends @TypeAnno("Object1") Object
                                          & @TypeAnno("Runnable1") @TypeAnno2("Runnable2") Runnable,
                                        @TypeAnno("EE")EE extends @TypeAnno2("EEBound") Runnable, V > {
    @TypeAnno("T1 field") @TypeAnno2("T2 field") T field1;
    T field2;
    @TypeAnno("Object field") Object field3;

    public @TypeAnno("t1") @TypeAnno2("t2") T foo(){ return null; }
    public <M extends @TypeAnno("M Runnable") Runnable> M foo2() {return null;}
    public <@TypeAnno("K") K extends Cloneable, L> K foo3() {return null;}
    public <L> L foo4() {return null;}
}

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeAnno {
    String value();
}

@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
@interface TypeAnno2 {
    String value();
}
