Review request for type annotation reflection test

Charlie Wang charlie.wang at oracle.com
Sun Jun 2 23:39:44 PDT 2013


Hi,
   Please review attached 2 files, which I've changed according to 
comments. If OK, I will update the rest of the tests and publish for review.

Regards,
Charlie
On 2013/5/22 9:55, Joseph Darcy wrote:
> Hello,
>
> On 5/21/2013 6:37 PM, Alex Buckley wrote:
>> I see you have extracted the validation logic and common annotation 
>> types into TestUtil, so that any individual test will primarily be a 
>> data provider + test cases. However:
>>
>> 1. Jon is our resident TestNG expert and he says that data providers 
>> are intended to enumerate input values for tests; other code 
>> determines which values should pass the tests and which values should 
>> fail. But your data provider encodes the types and element values of 
>> annotations which are expected to appear at various locations - this 
>> is output data, not input data.
>>
>> 2. There is basically no abstraction over the expected annotations in 
>> the data provider. It's just a bunch of Object[][] values. 
>> Consequently, TestUtil is very brittle. There is weird indexing of 
>> arrays - [i * 2 + 1] and [j / 2] all over the place - and excessive 
>> knowledge of specific test cases:
>>
>>   catch (NoSuchMethodException e) {
>>     //expected exception for TypeAnno3
>>   }
>>
>> 3. I'm suspicious of static methods in TestUtil. They are stateless 
>> at present, but if they ever share state then you'll be in trouble, 
>> especially with the instance methods in GetAnnotated*Test.
>>
>> Charlie, there are two techniques you should adopt for tests on the 
>> core reflection API:
>>
>> - First, encode expected values of type annotations more directly, by 
>> annotating the AnnotationTypeTestXX declarations themselves.
>
> Note that this technique is used in various other regression tests in 
> the JDK repository. For some recently-written small examples see
>
>     test/java/lang/reflect/Parameter/WithoutParameters.java
>     test/java/lang/reflect/Method/DefaultMethodModeling.java.html
>
>>
>> - Second, embrace combinatorial testing rather than declaring a bunch 
>> of ad-hoc AnnotationTypeTestXX types with type annotations in various 
>> locations.
>>
>> The core reflection tests for repeating annotations use both these 
>> techniques. Please look at item 2 of 
>> http://wiki.se.oracle.com/display/JPG/Repeating+Annotation+Test+Plan#RepeatingAnnotationTestPlan-1.Featurecases 
>> - apologies for non-Oracle readers. Please talk with Steve and 
>> Matherey to get background. To be clear, I will not sign off the type 
>> annotations reflection tests in their present form.
>
> I concur that using combinatorial tests is appropriate for this 
> domain. The basic approach is to programatically generate both the 
> expected result and the code to test and verify that the computed 
> answer is consistent with the expected one. The regression tests 
> currently in JDK 8 have code that allows source code to be generated 
> at runtime, loading in by a class loader, and then run.
>
> HTH,
>
> -Joe
>
>>
>> Alex
>>
>> On 5/21/2013 2:29 PM, Charlie Wang wrote:
>>> OK, thanks.
>>> I will create a TestUtil.java (as attached file). Extract the common
>>> part and put them in it. And Add comments on data provider.
>>> I'm on a very tight schedule, so this time I just send out two of the
>>> updated files (attached files) for review so I can get a quick 
>>> response.
>>> If it is OK, I will use it as a template on other tests.
>>> Please take a look and send me your comments as soon as possible.
>>>
>>>
>>> Thanks,
>>> Charlie
>>>
>>>
>>> On 2013/5/22 2:24, Alex Buckley wrote:
>>>> Please keep one source file per tested API method, because it is easy
>>>> to navigate. The problem is the body of the test() method in each
>>>> source file. It should call utility methods to inspect annotations,
>>>> rather that repeating more or less the same 'for' loops as every other
>>>> test() method.
>>>>
>>>> As for data providers, I believe they are a TestNG thing. Please add
>>>> comments to each source file to describe what the data provider
>>>> represents.
>>>>
>>>> Finally, when you make a "little update", you should give a short
>>>> description of what has changed. If you don't, someone who looked at
>>>> the previous version has no idea what changed and will have to look at
>>>> everything again.
>>>>
>>>> Alex
>>>>
>>>> On 5/21/2013 3:10 AM, Charlie Wang wrote:
>>>>> Yes, that make sense. What do you think if I combine the tests? It 
>>>>> would
>>>>> then have 1 unified data provider and test method, thus solve the 2
>>>>> problems.
>>>>>
>>>>> Regards,
>>>>> Charlie
>>>>> On 2013/5/21 14:56, Werner Dietl wrote:
>>>>>> I agree with Alex's first two comments. The code duplication for the
>>>>>> test logic and all the different annotation types seems hard to
>>>>>> maintain and update.
>>>>>>
>>>>>> cu, WMD.
>>>>>>
>>>>>> On Mon, May 20, 2013 at 7:56 PM, Charlie Wang
>>>>>> <charlie.wang at oracle.com> wrote:
>>>>>>> Hi,
>>>>>>>    Here's the latest one with a little update:
>>>>>>> http://cr.openjdk.java.net/~ssides/8013497/webrev.03/
>>>>>>>    Please provide comments as soon as possible now that due date is
>>>>>>> approaching.
>>>>>>>
>>>>>>>
>>>>>>> Regards,
>>>>>>> Charlie
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>> On 2013/5/19 14:10, Charlie Wang wrote:
>>>>>>>
>>>>>>> Hi,
>>>>>>>    Here's test for core reflection support of type-annotations
>>>>>>> JDK-8013497.
>>>>>>> Please take a look.
>>>>>>>
>>>>>>> http://cr.openjdk.java.net/~ssides/8013497/webrev.02/
>>>>>>>
>>>>>>>
>>>>>>> Regards,
>>>>>>> Charlie
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>>
>>>>>>
>>>>>>
>>>>>
>>>
>

-------------- next part --------------
/*
 * Copyright (c) 2012, 2013, 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 8013497
 * @summary Utility class for constructing, compiling and execute code
 */
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject.Kind;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.StandardJavaFileManager;
import javax.tools.StandardLocation;
import javax.tools.ToolProvider;
import com.sun.source.util.JavacTask;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Method;

public class Helper {

    public enum SrcType {

        PKGINFO("[#ANNO] \npackage #NAME; \n") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length || 1 == annoStr.length) {
                    return PKGINFO.replaceMultipleAnnoStr(annoStr);
                } else {
                    throw new RuntimeException("illegal annotation or package info!");
                }
            }
        },
        PACKAGE("\npackage #NAME; \n") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length || 1 == annoStr.length) {
                    return PACKAGE.replaceMultipleAnnoStr(annoStr);
                } else {
                    throw new RuntimeException("illegal package name!");
                }
            }
        },
        IMPORT("\nimport java.io.*;\n"
        + "import java.util.*;\n"
        + "import java.lang.*;\n"
        + "import java.lang.reflect.*;\n"
        + "import java.lang.annotation.*;\n") {
            public String getSrc(String... annoStr) {
                if (null == annoStr) {
                    return template;
                }

                return "";
            }
        },
        CLASS("\n[#ANNO] \nclass #NAME") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length || 1 == annoStr.length) {
                    return CLASS.replaceMultipleAnnoStr(annoStr);
                } else {
                    throw new RuntimeException("illegal class name!");
                }
            }
        },
        INTERFACE("\n[#ANNO] \ninterface #NAME") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length || 1 == annoStr.length) {
                    return INTERFACE.replaceMultipleAnnoStr(annoStr);
                } else {
                    throw new RuntimeException("illegal interface name!");
                }
            }
        },
        EXTENDS(" extends [#ANNO] #NAME") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length || 1 == annoStr.length) {
                    return EXTENDS.replaceMultipleAnnoStr(annoStr);
                } else {
                    throw new RuntimeException("illegal class name!");
                }
            }
        },
        IMPLEMENTS(" implements [[#ANNO] #NAME, ]") {
            public String getSrc(String... annoStr) {
                return IMPLEMENTS.replaceMultipleAnnoStr(annoStr);
            }
        },
        GENERICS("<[[#ANNO1] #TYPE1 [#INHERITANCE [#ANNO2] #TYPE2], ]>") {
            // input should be like ["@realanno1 Type1 
            //extends @realanno2 Type2", "@realanno3 Type3 
            //super @realanno4 Type4"]
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                }

                String temp = template;
                for (String as : annoStr) {
                    temp = temp.replaceAll("\\[\\[#ANNO1\\] #TYPE1 "
                            + "\\[#INHERITANCE \\[#ANNO2\\] #TYPE2\\], \\]",
                            "[#ANNO1] #TYPE1 [#INHERITANCE [#ANNO2] #TYPE2], "
                            + "[[#TMP_ANNO1] #TMP_TYPE1 [#TMP_INHERITANCE "
                            + "[#TMP_ANNO2] #TMP_TYPE2], ]");

                    int j = 0; // index of )
                    String ss = as.trim();

                    // get @anno for [#ANNO1]
                    while (ss.startsWith("@")) {
                        j = ss.indexOf(")") + 1;
                        String anno = ss.substring(0, j);
                        temp = temp.replaceFirst("\\[#ANNO1\\]", anno
                                + " [#ANNO1]");
                        ss = ss.substring(j).trim();
                    }
                    temp = temp.replaceAll("\\[#ANNO1\\]", "");

                    // get #TYPE1
                    if (j == ss.length()) {
                        throw new RuntimeException("illegal generics.");
                    }
                    j = ss.indexOf(" ") + 1;
                    String type = j == 0 ? ss : ss.substring(0, j);
                    temp = temp.replaceFirst("#TYPE1", type);
                    ss = ss.substring(j).trim();

                    // get #INHERITANCE
                    if (ss.startsWith("extends") || ss.startsWith("super")) {
                        temp = temp.replaceAll("\\[#INHERITANCE "
                                + "\\[#ANNO2\\] #TYPE2\\]",
                                "#INHERITANCE \\[#ANNO2\\] #TYPE2");

                        j = ss.indexOf(" ") + 1;
                        String inheritance = j == 0 ? ss : ss.substring(0, j);
                        temp = temp.replaceFirst("#INHERITANCE", inheritance);
                        ss = ss.substring(j).trim();

                        // get @anno for [#ANNO2]
                        while (ss.startsWith("@")) {
                            j = ss.indexOf(")") + 1;
                            String anno = ss.substring(0, j);
                            temp = temp.replaceFirst("\\[#ANNO2\\]", anno
                                    + " [#ANNO2]");
                            ss = ss.substring(j).trim();
                        }
                        temp = temp.replaceAll("\\[#ANNO2\\]", "");

                        // get #TYPE2
                        if (j == ss.length()) {
                            throw new RuntimeException("********");
                        }
                        j = ss.indexOf(" ") + 1;
                        type = j == 0 ? ss : ss.substring(0, j);
                        temp = temp.replaceFirst("#TYPE2", type);
                        ss = ss.substring(j).trim();
                    } else {
                        temp = temp.replaceAll("\\[#INHERITANCE \\[#ANNO2\\] "
                                + "#TYPE2\\]", "");
                    }
                    temp = temp.replaceAll("TMP_", "");
                }

                return temp.replaceAll(", \\[\\[#ANNO1\\] #TYPE1 "
                        + "\\[#INHERITANCE \\[#ANNO2\\] #TYPE2\\], \\]", "");
            }
        },
        CLASSBODYSTART(" {\n") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }
        },
        FIELDTYPE("    public [#ANNO] #NAME") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length || 1 == annoStr.length) {
                    return FIELDTYPE.replaceMultipleAnnoStr(annoStr);
                } else {
                    throw new RuntimeException("illegal field type.");
                }
            }
        },
        FIELDARRAY(" [[#ANNO] [] ] ") {
            // input should be like ["@A() @B()", "@C() @D()"...]
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                }

                String temp = template;
                for (String as : annoStr) {
                    temp = temp.replaceAll("\\[\\[#ANNO\\] \\[\\] \\]",
                            "[#ANNO] [] [[#TMP_ANNO] [] ]");
                    // replace "[#ANNO]..." with "@realannotation [#ANNO]..."
                    int i = 0; // index of @
                    int j = 0; // index of )
                    for (; -1 != as.indexOf("@", j);) {
                        i = as.indexOf("@", j);
                        j = as.indexOf(")", i) + 1;
                        String anno = as.substring(i, j);
                        temp = temp.replaceFirst("\\[#ANNO\\]",
                                anno + " [#ANNO]");
                    }
                    temp = temp.replaceAll("\\[#ANNO\\]", "").
                            replaceAll("TMP_", "");
                }
                return temp.replaceAll("\\[\\[#ANNO\\] \\[\\] \\]", "");
            }
        },
        FIELDNAME(" #NAME;\n") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length || 1 == annoStr.length) {
                    return FIELDNAME.replaceMultipleAnnoStr(annoStr);
                } else {
                    throw new RuntimeException("illegal field name");
                }
            }
        },
        METHODHEAD("\n    public [#ANNO] #TYPE #NAME") {
            // input should be like "@realanno Type method"
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                }

                String temp = template;
                for (String as : annoStr) {
                    int j = 0; // index of ")"
                    String ss = as.trim();

                    // get @anno for [#ANNO1]
                    while (ss.startsWith("@")) {
                        j = ss.indexOf(")") + 1;
                        String anno = ss.substring(0, j);
                        temp = temp.replaceFirst("\\[#ANNO\\]",
                                anno + " [#ANNO]");
                        ss = ss.substring(j).trim();
                    }
                    temp = temp.replaceAll("\\[#ANNO\\]", "");

                    // get #TYPE
                    if (j == ss.length()) {
                        throw new RuntimeException("illegal method head.");
                    }
                    j = ss.indexOf(" ") + 1;
                    if (0 == j) {
                        throw new RuntimeException("illegal method head.");
                    }
                    String type = ss.substring(0, j);
                    temp = temp.replaceFirst("#TYPE", type);
                    ss = ss.substring(j).trim();

                    // get #NAME
                    if (0 <= ss.indexOf(" ") || 0 == ss.length()) {
                        throw new RuntimeException("illegal method head.");
                    }
                    temp = temp.replaceFirst("#NAME", ss);
                }

                return temp;
            }
        },
        METHODPARAM("([[#ANNO] #TYPE #NAME, ])") {
            // input should be like "@realanno Type arg"
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                }

                String temp = template;
                for (String as : annoStr) {
                    temp = temp.replaceFirst("\\[\\[#ANNO\\] #TYPE #NAME, \\]",
                            "[#ANNO] #TYPE #NAME, "
                            + "[[#TMP_ANNO] #TMP_TYPE #TMP_NAME, ]");

                    int j = 0; // index of ")"
                    String ss = as.trim();

                    // get @anno for [#ANNO1]
                    while (ss.startsWith("@")) {
                        j = ss.indexOf(")") + 1;
                        String anno = ss.substring(0, j);
                        temp = temp.replaceFirst("\\[#ANNO\\]", anno
                                + " [#ANNO]");
                        ss = ss.substring(j).trim();
                    }
                    temp = temp.replaceAll("\\[#ANNO\\]", "");

                    // get #TYPE
                    if (j == ss.length()) {
                        throw new RuntimeException("illegal method parameter.");
                    }
                    j = ss.indexOf(" ") + 1;
                    if (0 == j) {
                        throw new RuntimeException("illegal method parameter.");
                    }
                    String type = ss.substring(0, j);
                    temp = temp.replaceFirst("#TYPE", type);
                    ss = ss.substring(j).trim();

                    // get #NAME
                    if (0 <= ss.indexOf(" ") || 0 == ss.length()) {
                        throw new RuntimeException("illegal method parameter.");
                    }
                    temp = temp.replaceFirst("#NAME", ss);

                    temp = temp.replaceAll("TMP_", "");
                }

                return temp.replaceAll(", \\[\\[#ANNO\\] #TYPE #NAME, \\]", "");
            }
        },
        METHODEXCEPTION(" throws [[#ANNO] #NAME, ]") {
            public String getSrc(String... annoStr) {
                return METHODEXCEPTION.replaceMultipleAnnoStr(annoStr);
            }
        },
        METHODBODY(" {return null;}\n"),
        CLASSBODYEND("\n}\n") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }
        },
        TYPEANNO1("\n at Target(ElementType.TYPE_USE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\n at Repeatable(TypeAnno1Container.class)"
        + "\n at interface TypeAnno1 {"
        + "\n    String value();"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }

            public String toString() {
                return "@TypeAnno1(\"TypeAnno1\")";
            }
        },
        TYPEANNO2("\n at Target(ElementType.TYPE_USE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\n at Repeatable(TypeAnno2Container.class)"
        + "\n at interface TypeAnno2 {"
        + "\n    String value();"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }

            public String toString() {
                return "@TypeAnno2(\"TypeAnno2\")";
            }
        },
        TYPEANNO3("\n at Target(ElementType.TYPE_USE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\n at Repeatable(TypeAnno3Container.class)"
        + "\n at interface TypeAnno3 {"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }

            public String toString() {
                return "@TypeAnno3";
            }
        },
        TYPEANNOCONTAINER1("\n at Target(ElementType.TYPE_USE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\n at interface TypeAnno1Container {"
        + "\n    TypeAnno1[] value();"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }
        },
        TYPEANNOCONTAINER2("\n at Target(ElementType.TYPE_USE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\n at interface TypeAnno2Container {"
        + "\n    TypeAnno2[] value();"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }
        },
        TYPEANNOCONTAINER3("\n at Target(ElementType.TYPE_USE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\n at interface TypeAnno3Container {"
        + "\n    TypeAnno3[] value();"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }
        },
        PKGANNOTATION1("\n at Target(ElementType.PACKAGE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\npublic @interface PkgAnno1 {"
        + "\n    String value();"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }

            public String toString() {
                return "@PkgAnno1(\"PkgAnno1\")";
            }
        },
        PKGANNOTATION2("\n at Target(ElementType.PACKAGE)"
        + "\n at Retention(RetentionPolicy.RUNTIME)"
        + "\npublic @interface PkgAnno2 {"
        + "\n    String value();"
        + "\n}") {
            public String getSrc(String... annoStr) {
                if (0 == annoStr.length) {
                    return "";
                } else {
                    return template;
                }
            }

            public String toString() {
                return "@PkgAnno2(\"PkgAnno2\")";
            }
        };
        String template = "";

        private SrcType(String template) {
            this.template = template;
        }

        // change [#ANNO] #NAME into @realannotation  name
        private String replaceMultipleAnnoStr(String... annoStr) {
            if (0 == annoStr.length) {
                return "";
            }

            String temp = template;
            for (String as : annoStr) {
                temp = temp.replaceAll("\\[\\[#ANNO\\] #NAME, \\]",
                        "[#ANNO] #NAME, [[#TMP_ANNO] #TMP_NAME, ]");
                // replace "[#ANNO]..." with "@realannotation [#ANNO]..."
                int i = 0; // index of @
                int j = 0; // index of " "
                for (; -1 != as.indexOf("@", j);) {
                    i = as.indexOf("@", j);
                    j = as.indexOf(" ", i);
                    // annotation check - should be like @A or @A() or @A("...")
                    if (2 > j - i) {
                        throw new RuntimeException("illegal annotation.");
                    }
                    String anno = as.substring(i, j);
                    temp = temp.replaceFirst("\\[#ANNO\\]", anno + " [#ANNO]");

                }
                String name = as.substring(j, as.length()).trim();
                temp = temp.replaceAll("#NAME", name).replaceAll(
                        "\\[#ANNO\\]", "");
                temp = temp.replaceAll("\\[\\[#TMP_ANNO\\] #TMP_NAME, \\]",
                        "[[#ANNO] #NAME, ]");
            }
            return temp.replaceAll(", \\[\\[#ANNO\\] #NAME, \\]", "");
        }

        public String getTemplate() {
            return template;
        }

        public String getSrc(String... annoStr) {
            if (0 == annoStr.length) {
                return "";
            } else {

                return "";
            }
        }
    }

    // generate test class code with annotations, which is used for test
    public synchronized static String genTestCode(LinkedHashMap input) {
        StringBuffer result = new StringBuffer();
        for (Helper.SrcType st : Helper.SrcType.values()) {
            if (input.containsKey(st)) {
                Object temp = input.get(st);
                if (temp instanceof String) {
                    result.append(st.getSrc((String) temp));
                } else {
                    result.append(st.getSrc((String[]) temp));
                }

                // auto correction, in case input misses class brackets
                if (SrcType.CLASS == st || SrcType.INTERFACE == st) {
                    input.put(SrcType.CLASSBODYSTART, "");
                    input.put(SrcType.CLASSBODYEND, "");
                }

                // auto correction, in case input misses method brackets
                if (SrcType.METHODHEAD == st) {
                    if (!input.containsKey(SrcType.METHODPARAM)) {
                        input.put(SrcType.METHODPARAM, "");
                    }
                    input.put(SrcType.METHODBODY, "");
                }
            } else {
                result.append(st.getSrc());
            }
        }

        return result.toString();
    }

    // Create and compile FileObject using values for className and contents
    public static boolean compileCode(String className, String contents,
            DiagnosticCollector<JavaFileObject> diagnostics) {
        boolean ok = false;
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            throw new RuntimeException("can't get javax.tools.JavaCompiler!");
        }

        JavaFileObject file = getFile(className, contents);
        Iterable<? extends JavaFileObject> compilationUnit = Arrays.asList(file);

        CompilationTask task = compiler.getTask(null, null, diagnostics, null,
                null, compilationUnit);
        ok = task.call();
        return ok;
    }
    // Compile a list of FileObjects
    // Used when packages are needed and classes need to be loaded at runtime
    static File destDir = new File(System.getProperty("user.dir"));

    public static boolean compileCode(
            DiagnosticCollector<JavaFileObject> diagnostics,
            Iterable<? extends JavaFileObject> files) {
        boolean ok = false;
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        if (compiler == null) {
            throw new RuntimeException("can't get javax.tools.JavaCompiler!");
        }

        StandardJavaFileManager fm = compiler.getStandardFileManager(
                null, null, null);

        // Assuming filesCount can maximum be 2 and if true, one file is package-info.java
        if (isPkgInfoPresent(files)) {
            JavacTask task = (JavacTask) compiler.getTask(null, fm, diagnostics,
                    null, null, files);
            try {
                fm.setLocation(StandardLocation.CLASS_OUTPUT,
                        Arrays.asList(destDir));
                task.generate();
            } catch (IOException ioe) {
                throw new RuntimeException(
                        "Compilation failed for package level tests", ioe);
            }
            int err = 0;
            for (Diagnostic<? extends JavaFileObject> d
                    : diagnostics.getDiagnostics()) {
                if (d.getKind() == Diagnostic.Kind.ERROR) {
                    err++;
                }
            }
            ok = (err == 0);
        } else {
            CompilationTask task = compiler.getTask(null, null, diagnostics,
                    null, null, files);
            ok = task.call();
        }
        return ok;
    }

    static private boolean isPkgInfoPresent(
            Iterable<? extends JavaFileObject> files) {
        Iterator<? extends JavaFileObject> itr = files.iterator();
        while (itr.hasNext()) {
            String name = itr.next().getName();
            if (name.contains("package-info")) {
                return true;
            }
        }
        return false;
    }

    static JavaFileObject getFile(String name, String code) {
        JavaFileObject o = null;
        try {
            o = new JavaStringFileObject(name, code);
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        return o;
    }

    static class JavaStringFileObject extends SimpleJavaFileObject {

        final String theCode;

        public JavaStringFileObject(String fileName, String theCode)
                throws URISyntaxException {
            super(new URI("string:///" + fileName.replace('.', '/') + ".java"), Kind.SOURCE);
            this.theCode = theCode;
        }

        @Override
        public CharSequence getCharContent(boolean ignoreEncodingErrors) {
            return theCode;
        }
    }

    public static Class<?> loadClass(String className,
            ClassLoader parentClassLoader, File... destDirs) {
        try {
            List<URL> list = new ArrayList<>();
            for (File f : destDirs) {
                list.add(new URL("file:" + f.toString().replace("\\", "/") + "/"));
            }
            return Class.forName(className, true, new URLClassLoader(
                    list.toArray(new URL[list.size()]), parentClassLoader));
        } catch (ClassNotFoundException | MalformedURLException e) {
            throw new RuntimeException("Error loading class " + className, e);
        }
    }

    // generate class from test code
    public static void genClass(String testCode, ClassLoader parentClassLoader,
            String clsName) {

        JavaFileObject jfo = Helper.getFile(clsName, testCode);
        Iterable<? extends JavaFileObject> files = Arrays.asList(jfo);
        DiagnosticCollector<JavaFileObject> diagnostics =
                new DiagnosticCollector<>();

        // Compile the list of JavaFileObjects
        try {
            Helper.compileCode(diagnostics, files);
        } catch (Exception ex) {
            throw new RuntimeException(
                    "Exception when compiling class ");
        }
    }

    // get annotation
    public static String getAnno(Annotation[] annotations) throws Exception {
        StringBuffer result = new StringBuffer("");
        for (int i = 0; i < annotations.length; i++) {
            if (null != annotations) {
                String annoName = "";
                Object value = null;
                try {
                    value = annotations[i].annotationType().getMethod("value",
                            new Class[]{}).invoke(annotations[i], new Object[]{});
                } catch (NoSuchMethodException e) {
                    //expected exception for annotations without value
                }

                if (null == value) {
                    annoName = annotations[i].annotationType().getSimpleName();
                    result.append("@" + annoName);
                    result.append(" ");
                } else if (value instanceof String) {
                    annoName = annotations[i].annotationType().getSimpleName();
                    result.append("@" + annoName);
                    result.append("(\"" + value + "\") ");
                } else {
                    //for repeated anno
                    Annotation[] av = (Annotation[]) value;
                    for (int j = 0; j < av.length; j++) {
                        annoName = av[j].annotationType().getSimpleName();
                        value = " ";
                        try {
                            value = av[j].annotationType().getMethod("value",
                                    new Class[]{}).invoke(av[j], new Object[]{});
                            value = "(\"" + value + "\") ";
                        } catch (NoSuchMethodException e) {
                            //expected exception for annotations without value
                        }
                        result.append("@" + annoName);
                        result.append(value);
                    }
                }
            }
        }
        return result.toString();
    }

    // get AnnotatedType type and value 
    public static String getAT(AnnotatedType at) throws Exception {
        Annotation[] annotations = at.getAnnotations();
        return getAnno(annotations);
    }

    // get multiple AnnotatedType type and value 
    public static List getMultipleAT(AnnotatedType[] as) throws Exception {
        List result = new ArrayList();
        for (int i = 0; i < as.length; i++) {
            result.add(getAT(as[i]));
        }
        return result;
    }
}
-------------- next part --------------

/*
 * Copyright (c) 2013, 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 8013497
 * @author   Charlie
 * @summary test for API: Class.getAnnotatedInterfaces()
 * @build    Helper
 * @run main ClsGetAnnotatedInterTest
 */

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Set;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaFileObject;

public class ClsGetAnnotatedInterTest {

    // combinations of annotations to test
    String[] annoComb = new String[]{"", Helper.SrcType.TYPEANNO1 + " ",
        Helper.SrcType.TYPEANNO2 + " " + Helper.SrcType.TYPEANNO2 + " "
        + Helper.SrcType.TYPEANNO3 + " ",
        Helper.SrcType.TYPEANNO2 + " " + Helper.SrcType.TYPEANNO2 + " "
        + Helper.SrcType.TYPEANNO2 + " ",
        Helper.SrcType.TYPEANNO3 + " " + Helper.SrcType.TYPEANNO3 + " "
        + Helper.SrcType.TYPEANNO3 + " ",
        Helper.SrcType.TYPEANNO3 + " "};
    // holds class name -- annotation correspondence
    static LinkedHashMap<String, String> testInput =
            new LinkedHashMap<String, String>();
    // Set it to true to get more debug information
    static final boolean DEBUG = false;
    String typeAnnoClsName = "TypeAnnoCls";
    String typeAnnoIntName = "TypeAnnoInt";
    String testBaseClsName = typeAnnoClsName + 0;
    static LinkedHashMap lhm = new LinkedHashMap();

    enum TestCase {
        // base class
        TESTBASECLS() {
            // generate test base, input should be like [cls name]
            public String genTestCase(String... str) {
                /* generate class like below
                 import java.io.*;
                 import java.util.*;
                 import java.lang.*;
                 import java.lang.reflect.*;
                 import java.lang.annotation.*;
                 class testBaseClsName {
                 }
                 */
                if (1 != str.length) {
                    throw new RuntimeException("bad test base.");
                }

                String testCode = "";
                lhm.clear();
                lhm.put(Helper.SrcType.IMPORT, null);
                lhm.put(Helper.SrcType.CLASS, str[0]);
                testCode += Helper.genTestCode(lhm);
                lhm.clear();

                lhm.put(Helper.SrcType.TYPEANNO1, "");
                lhm.put(Helper.SrcType.TYPEANNO2, "");
                lhm.put(Helper.SrcType.TYPEANNO3, "");
                lhm.put(Helper.SrcType.TYPEANNOCONTAINER1, "");
                lhm.put(Helper.SrcType.TYPEANNOCONTAINER2, "");
                lhm.put(Helper.SrcType.TYPEANNOCONTAINER3, "");
                testCode += Helper.genTestCode(lhm);
                lhm.clear();

                return testCode;
            }
        },
        // single interface
        SINGLEINT() {
            // input should be like [cls name, "@anno"]
            public String genTestCase(String... str) {
                // append annotation on ###: interface TypeAnnoInt01 { }
                if (2 != str.length) {
                    throw new RuntimeException("bad test case.");
                }

                String testCode = "";
                lhm.put(Helper.SrcType.INTERFACE, str[0]);
                testCode += Helper.genTestCode(lhm);
                lhm.clear();
                testInput.put(str[0], null);

                return testCode;
            }
        },
        // interface extends interface
        INTEXTINT() {
            // input should be like [cls name, "@anno"]
            public String genTestCase(String... str) {
                // append annotation on ###: interface TypeAnnoInt02 extends ###
                // Serializable { }
                if (2 != str.length) {
                    throw new RuntimeException("bad test case.");
                }

                String testCode = "";
                lhm.put(Helper.SrcType.INTERFACE, str[0]);
                lhm.put(Helper.SrcType.EXTENDS, str[1] + " Serializable");
                testCode += Helper.genTestCode(lhm);
                lhm.clear();
                testInput.put(str[0], str[1]);

                return testCode;
            }
        },
        // single class
        SINGLECLS() {
            // input should be like [cls name, "@anno"]
            public String genTestCase(String... str) {
                // append annotation on ### class TypeAnnoCls01 { }
                if (2 != str.length) {
                    throw new RuntimeException("bad test case.");
                }

                String testCode = "";
                lhm.put(Helper.SrcType.CLASS, str[0]);
                testCode += Helper.genTestCode(lhm);
                lhm.clear();
                testInput.put(str[0], null);

                return testCode;
            }
        },
        // cls implements interface
        CLSIMPINTS() {
            // input should be like [cls name, "@anno"]
            public String genTestCase(String... str) {
                // append annotation on ### class TypeAnnoCls02 implements ###
                // Serializable, ### Cloneable{ }
                if (2 != str.length) {
                    throw new RuntimeException("bad test case.");
                }

                String testCode = "";
                lhm.put(Helper.SrcType.CLASS, str[0]);
                lhm.put(Helper.SrcType.IMPLEMENTS, new String[]{str[1]
                    + " Serializable", str[1] + " Cloneable"});
                testCode += Helper.genTestCode(lhm);
                lhm.clear();
                testInput.put(str[0], str[1]);

                return testCode;
            }
        };

        // generate test class of a specific case
        public String genTestCase(String... str) {
            return "";
        }
    }

    // generate source code for test according to TestCase
    public String GenTestCode() {
        int i = 0;
        String testCode = "";
        for (String anno : annoComb) {
            for (TestCase tc : TestCase.values()) {
                if (tc == TestCase.TESTBASECLS) {
                    if (0 == i) {
                        testCode += tc.genTestCase(testBaseClsName);
                        i++;
                    }
                } else {
                    // append annotation on automatically generated 
                    // class/interface with given name
                    if (tc == TestCase.SINGLEINT || tc == TestCase.INTEXTINT) {
                        testCode += tc.genTestCase(typeAnnoIntName + i, anno);
                    } else {
                        testCode += tc.genTestCase(typeAnnoClsName + i, anno);
                    }
                    i++;
                }
            }
        }

        return testCode;
    }

    // compare input with result
    public boolean checkResult() throws Exception {
        boolean ret = true;
        String testCode = GenTestCode();
        debugPrint(testCode);
        ClassLoader parentClassLoader = getClass().getClassLoader();
        // Get Class object for the compiled class
        Helper.genClass(testCode, parentClassLoader, testBaseClsName);
        for (String clsName : testInput.keySet()) {
            // Get Class object for the compiled class
            Class c1 = Helper.loadClass(clsName, parentClassLoader, Helper.destDir);
            AnnotatedType[] as = c1.getAnnotatedInterfaces();
            List<String> result = Helper.getMultipleAT(as);
            if (0 == result.size()) {
                if (null != testInput.get(clsName)) {
                    // should be empty output
                    ret = false;
                    debugPrint(clsName);
                }
            } else {
                for (int i = 0; i < result.size(); i++) {
                    if (!result.get(i).equals(testInput.get(clsName))) {
                        // annotations not match
                        ret = false;
                        debugPrint(clsName);
                    }
                }
            }
        }
        return ret;
    }

    public void test() throws Exception {
        if (!checkResult()) {
            throw new RuntimeException("test fail.");
        } else {
            System.out.println("Pass.");
        }
    }

    public static void main(String[] args) throws Exception {
        new ClsGetAnnotatedInterTest().test();
    }

    private static void debugPrint(String string) {
        if (DEBUG) {
            System.out.println(string);
        }
    }
}


More information about the type-annotations-dev mailing list