/*
* Copyright (c) 2010, 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 6920317
* @summary package-info.java file has to be specified on the javac cmdline, else it will not be avail
* @library ../lib
*/
import java.io.*;
import java.util.*;
import javax.annotation.processing.*;
import javax.lang.model.*;
import javax.lang.model.element.*;
import javax.lang.model.util.*;
import javax.tools.*;
/**
* The test exercises different ways of providing annotations for a package.
* Each way provides an annotation with a unique argument. For each test
* case, the test verifies that the annotation with the correct argument is
* found by the compiler.
*/
public class T6920317 {
public static void main(String... args) throws Exception {
new T6920317().run(args);
}
// Used to describe properties of files to be put on command line, source path, class path
enum Kind {
/** File is not used. */
NONE,
/** File is used. */
OLD,
/** Only applies to files on classpath/sourcepath, when there is another file on the
* other path of type OLD, in which case, this file must be newer than the other one. */
NEW,
/** Only applies to files on classpath/sourcepath, when there is no file in any other
* location, in which case, this file will be generated by the annotation processor. */
GEN
}
void run(String... args) throws Exception {
// if no args given, all test cases are run
// if args given, they indicate the test cases to be run
for (int i = 0; i < args.length; i++) {
tests.add(Integer.valueOf(args[i]));
}
setup();
// Run tests for all combinations of files on command line, source path and class path.
// Invalid combinations are skipped in the test method
for (Kind cmdLine: EnumSet.of(Kind.NONE, Kind.OLD)) {
for (Kind srcPath: Kind.values()) {
for (Kind clsPath: Kind.values()) {
try {
test(cmdLine, srcPath, clsPath);
} catch (Exception e) {
e.printStackTrace();
error("Exception " + e);
// uncomment to stop on first failed test case
// throw e;
}
}
}
}
if (errors > 0)
throw new Exception(errors + " errors occurred");
}
/** One time setup for files and directories to be used in the various test cases. */
void setup() throws Exception {
// Annotation used in test cases to annotate package. This file is
// given on the command line in test cases.
test_java = writeFile("Test.java", "package p; @interface Test { String value(); }");
// Compile the annotation for use later in setup
File tmpClasses = new File("tmp.classes");
compile(tmpClasses, new String[] { }, test_java);
// package-info file to use on the command line when requied
cl_pkgInfo_java = writeFile("cl/p/package-info.java", "@Test(\"CL\") package p;");
// source path containing package-info
sp_old = new File("src.old");
writeFile("src.old/p/package-info.java", "@Test(\"SP_OLD\") package p;");
// class path containing package-info
cp_old = new File("classes.old");
compile(cp_old, new String[] { "-classpath", tmpClasses.getPath() },
writeFile("tmp.old/p/package-info.java", "@Test(\"CP_OLD\") package p;"));
// source path containing package-info which is newer than the one in cp-old
sp_new = new File("src.new");
File old_class = new File(cp_old, "p/package-info.class");
writeFile("src.new/p/package-info.java", "@Test(\"SP_NEW\") package p;", old_class);
// class path containing package-info which is newer than the one in sp-old
cp_new = new File("classes.new");
File old_java = new File(sp_old, "p/package-info.java");
compile(cp_new, new String[] { "-classpath", tmpClasses.getPath() },
writeFile("tmp.new/p/package-info.java", "@Test(\"CP_NEW\") package p;", old_java));
// directory containing package-info.java to be "generated" later by annotation processor
sp_gen = new File("src.gen");
writeFile("src.gen/p/package-info.java", "@Test(\"SP_GEN\") package p;");
// directory containing package-info.class to be "generated" later by annotation processor
cp_gen = new File("classes.gen");
compile(cp_gen, new String[] { "-classpath", tmpClasses.getPath() },
writeFile("tmp.gen/p/package-info.java", "@Test(\"CP_GEN\") package p;"));
}
void test(Kind cl, Kind sp, Kind cp) throws Exception {
if (skip(cl, sp, cp))
return;
++count;
// if test cases specified, skip this test case if not selected
if (tests.size() > 0 && !tests.contains(count))
return;
System.err.println("Test " + count + " cl:" + cl + " sp:" + sp + " cp:" + cp);
// test specific tmp directory
File test_tmp = new File("tmp.test" + count);
test_tmp.mkdirs();
// build up list of options and files to be compiled
List<String> opts = new ArrayList<String>();
List<File> files = new ArrayList<File>();
// expected value for annotation
String expect = null;
opts.add("-processorpath");
opts.add(System.getProperty("test.classes"));
opts.add("-processor");
opts.add(Processor.class.getName());
opts.add("-proc:only");
opts.add("-d");
opts.add(test_tmp.getPath());
//opts.add("-verbose");
files.add(test_java);
/*
* Analyze each of cl, cp, sp, building up the options and files to
* be compiled, and determining the expected outcome fo the test case.
*/
// command line file: either omitted or given
if (cl == Kind.OLD) {
files.add(cl_pkgInfo_java);
// command line files always supercede files on paths
expect = "CL";
}
// source path:
switch (sp) {
case NONE:
break;
case OLD:
opts.add("-sourcepath");
opts.add(sp_old.getPath());
if (expect == null && cp == Kind.NONE) {
assert cl == Kind.NONE && cp == Kind.NONE;
expect = "SP_OLD";
}
break;
case NEW:
opts.add("-sourcepath");
opts.add(sp_new.getPath());
if (expect == null) {
assert cl == Kind.NONE && cp == Kind.OLD;
expect = "SP_NEW";
}
break;
case GEN:
opts.add("-Agen=" + new File(sp_gen, "p/package-info.java"));
assert cl == Kind.NONE && cp == Kind.NONE;
expect = "SP_GEN";
break;
}
// class path:
switch (cp) {
case NONE:
break;
case OLD:
opts.add("-classpath");
opts.add(cp_old.getPath());
if (expect == null && sp == Kind.NONE) {
assert cl == Kind.NONE && sp == Kind.NONE;
expect = "CP_OLD";
}
break;
case NEW:
opts.add("-classpath");
opts.add(cp_new.getPath());
if (expect == null) {
assert cl == Kind.NONE && sp == Kind.OLD;
expect = "CP_NEW";
}
break;
case GEN:
opts.add("-Agen=" + new File(cp_gen, "p/package-info.class"));
assert cl == Kind.NONE && sp == Kind.NONE;
expect = "CP_GEN";
break;
}
// pass expected value to annotation processor
assert expect != null;
opts.add("-Aexpect=" + expect);
// compile the files with the options that have been built up
compile(opts, files);
}
/**
* Return true if this combination of parameters does not identify a useful test case.
*/
boolean skip(Kind cl, Kind sp, Kind cp) {
// skip if no package files required
if (cl == Kind.NONE && sp == Kind.NONE && cp == Kind.NONE)
return true;
// skip if both sp and sp are OLD, since results may be indeterminate
if (sp == Kind.OLD && cp == Kind.OLD)
return true;
// skip if sp or cp is NEW but the other is not OLD
if ((sp == Kind.NEW && cp != Kind.OLD) || (cp == Kind.NEW && sp != Kind.OLD))
return true;
// only use GEN if no other package-info files present
if (sp == Kind.GEN && !(cl == Kind.NONE && cp == Kind.NONE) ||
cp == Kind.GEN && !(cl == Kind.NONE && sp == Kind.NONE)) {
return true;
}
// remaining combinations are valid
return false;
}
/** Write a file with a given body. */
File writeFile(String path, String body) throws Exception {
File f = new File(path);
if (f.getParentFile() != null)
f.getParentFile().mkdirs();
Writer out = new FileWriter(path);
try {
out.write(body);
} finally {
out.close();
}
return f;
}
/** Write a file with a given body, ensuring that the file is newer than a reference file. */
File writeFile(String path, String body, File ref) throws Exception {
for (int i = 0; i < 5; i++) {
File f = writeFile(path, body);
if (f.lastModified() > ref.lastModified())
return f;
Thread.sleep(2000);
}
throw new Exception("cannot create file " + path + " newer than " + ref);
}
/** Compile a file to a given directory, with options provided. */
void compile(File dir, String[] opts, File src) throws Exception {
dir.mkdirs();
List<String> opts2 = new ArrayList<String>();
opts2.addAll(Arrays.asList("-d", dir.getPath()));
opts2.addAll(Arrays.asList(opts));
compile(opts2, Collections.singletonList(src));
}
/** Compile files with options provided. */
void compile(List<String> opts, List<File> files) throws Exception {
System.err.println("javac: " + opts + " " + files);
List<String> args = new ArrayList<String>();
args.addAll(opts);
for (File f: files)
args.add(f.getPath());
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
int rc = com.sun.tools.javac.Main.compile(args.toArray(new String[args.size()]), pw);
pw.flush();
if (sw.getBuffer().length() > 0)
System.err.println(sw.toString());
if (rc != 0)
throw new Exception("compilation failed: rc=" + rc);
}
/** Report an error. */
void error(String msg) {
System.err.println("Error: " + msg);
errors++;
}
/** Test case counter. */
int count;
/** Number of errors found. */
int errors;
/** Optional set of test cases to be run; empty implies all test cases. */
Set<Integer> tests = new HashSet<Integer>();
/* Files created by setup. */
File test_java;
File sp_old;
File sp_new;
File sp_gen;
File cp_old;
File cp_new;
File cp_gen;
File cl_pkgInfo_java;
/** Annotation processor used to verify the expected value for the
package annotations found by javac. */
@SupportedOptions({ "gen", "expect" })
public static class Processor extends JavacTestingAbstractProcessor {
public boolean process(Set<? extends TypeElement> annots, RoundEnvironment renv) {
round++;
System.err.println("Round " + round + " annots:" + annots + " rootElems:" + renv.getRootElements());
// if this is the first round and the gen option is given, use the filer to create
// a copy of the file specified by the gen option.
String gen = getOption("gen");
if (round == 1 && gen != null) {
try {
Filer filer = processingEnv.getFiler();
JavaFileObject f;
if (gen.endsWith(".java"))
f = filer.createSourceFile("p.package-info");
else
f = filer.createClassFile("p.package-info");
System.err.println("copy " + gen + " to " + f.getName());
write(f, read(new File(gen)));
} catch (IOException e) {
error("Cannot create package-info file: " + e);
}
}
// if annotation processing is complete, verify the package annotation
// found by the compiler.
if (renv.processingOver()) {
System.err.println("final round");
Elements eu = processingEnv.getElementUtils();
TypeElement te = eu.getTypeElement("p.Test");
PackageElement pe = eu.getPackageOf(te);
System.err.println("final: te:" + te + " pe:" + pe);
List<? extends AnnotationMirror> annos = pe.getAnnotationMirrors();
System.err.println("final: annos:" + annos);
if (annos.size() == 1) {
String expect = "@" + te + "(\"" + getOption("expect") + "\")";
String actual = annos.get(0).toString();
checkEqual("package annotations", actual, expect);
} else {
error("Wrong number of annotations found: (" + annos.size() + ") " + annos);
}
}
return true;
}
/** Get an option given to the annotation processor. */
String getOption(String name) {
return processingEnv.getOptions().get(name);
}
/** Read a file. */
byte[] read(File file) {
byte[] bytes = new byte[(int) file.length()];
DataInputStream in = null;
try {
in = new DataInputStream(new FileInputStream(file));
in.readFully(bytes);
} catch (IOException e) {
error("Error reading file: " + e);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
error("Error closing file: " + e);
}
}
}
return bytes;
}
/** Write a file. */
void write(JavaFileObject file, byte[] bytes) {
OutputStream out = null;
try {
out = file.openOutputStream();
out.write(bytes, 0, bytes.length);
} catch (IOException e) {
error("Error writing file: " + e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
error("Error closing file: " + e);
}
}
}
}
/** Check two strings are equal, and report an error if they are not. */
private void checkEqual(String label, String actual, String expect) {
if (!actual.equals(expect)) {
error("Unexpected value for " + label + "; actual=" + actual + ", expected=" + expect);
}
}
/** Report an error to the annotation processing system. */
void error(String msg) {
Messager messager = processingEnv.getMessager();
messager.printMessage(Diagnostic.Kind.ERROR, msg);
}
int round;
}
}