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