From 6e9bcd75ab79a7f6da1bc2cf706b5b5d05a5ff6b Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 24 Feb 2015 17:43:21 +0100 Subject: [PATCH 1/2] Turning on annotation processing. It will allow us to test the behavior of the annotation processor more deeply. To avoid own compilation error, we generate the META-INF/services registration dynamically using ServiceProvider annotation. The 'lookup' library which provides the annotation is used only during compilation (e.g. scope provided) then it is no longer needed. --- ObjectLayout/pom.xml | 8 ++++++-- .../main/java/org/ObjectLayout/IntrinsicProcessor.java | 3 +++ .../services/javax.annotation.processing.Processor | 1 - pom.xml | 8 ++++++++ 4 files changed, 17 insertions(+), 3 deletions(-) delete mode 100644 ObjectLayout/src/main/resources/META-INF/services/javax.annotation.processing.Processor diff --git a/ObjectLayout/pom.xml b/ObjectLayout/pom.xml index 2156d3b..86b0b30 100644 --- a/ObjectLayout/pom.xml +++ b/ObjectLayout/pom.xml @@ -25,6 +25,12 @@ 4.10 test + + org.netbeans.api + org-openide-util-lookup + RELEASE80 + provided + @@ -55,8 +61,6 @@ 1.7 1.7 - - -proc:none UTF-8 diff --git a/ObjectLayout/src/main/java/org/ObjectLayout/IntrinsicProcessor.java b/ObjectLayout/src/main/java/org/ObjectLayout/IntrinsicProcessor.java index b2ae9ad..00e51b6 100644 --- a/ObjectLayout/src/main/java/org/ObjectLayout/IntrinsicProcessor.java +++ b/ObjectLayout/src/main/java/org/ObjectLayout/IntrinsicProcessor.java @@ -5,6 +5,7 @@ import java.util.Set; import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.annotation.processing.SupportedSourceVersion; @@ -16,12 +17,14 @@ import javax.lang.model.element.Modifier; import javax.tools.Diagnostic; +import org.openide.util.lookup.ServiceProvider; /** * A javac annotation processor for the @Intrinsic annotation. */ @SupportedAnnotationTypes("org.ObjectLayout.Intrinsic") @SupportedSourceVersion(RELEASE_6) +@ServiceProvider(service = Processor.class) public class IntrinsicProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, diff --git a/ObjectLayout/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/ObjectLayout/src/main/resources/META-INF/services/javax.annotation.processing.Processor deleted file mode 100644 index 22377b1..0000000 --- a/ObjectLayout/src/main/resources/META-INF/services/javax.annotation.processing.Processor +++ /dev/null @@ -1 +0,0 @@ -org.ObjectLayout.IntrinsicProcessor \ No newline at end of file diff --git a/pom.xml b/pom.xml index e03d5ff..48dcbb7 100644 --- a/pom.xml +++ b/pom.xml @@ -14,6 +14,14 @@ UTF-8 + + + + netbeans + netbeans + http://bits.netbeans.org/maven2/ + + ObjectLayout From feb488f55e8ffd3ac10f22c78d79d205782ce65e Mon Sep 17 00:00:00 2001 From: Jaroslav Tulach Date: Tue, 24 Feb 2015 19:33:48 +0100 Subject: [PATCH 2/2] Testing proper error reporting by IntrinsicProcessor via individual Javac compilations --- .../test/java/org/ObjectLayout/Compile.java | 213 ++++++++++++++++++ .../ObjectLayout/IntrinsicProcessorTest.java | 48 ++++ .../ObjectLayout/IntrinsticObjectTest.java | 36 --- 3 files changed, 261 insertions(+), 36 deletions(-) create mode 100644 ObjectLayout/src/test/java/org/ObjectLayout/Compile.java create mode 100644 ObjectLayout/src/test/java/org/ObjectLayout/IntrinsicProcessorTest.java diff --git a/ObjectLayout/src/test/java/org/ObjectLayout/Compile.java b/ObjectLayout/src/test/java/org/ObjectLayout/Compile.java new file mode 100644 index 0000000..0711314 --- /dev/null +++ b/ObjectLayout/src/test/java/org/ObjectLayout/Compile.java @@ -0,0 +1,213 @@ +/* + * Written by Jaroslav Tulach, and released to the public domain, + * as explained at http://creativecommons.org/publicdomain/zero/1.0/ + */ + +package org.ObjectLayout; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import javax.tools.Diagnostic; +import javax.tools.DiagnosticListener; +import javax.tools.FileObject; +import javax.tools.ForwardingJavaFileManager; +import javax.tools.JavaFileManager; +import javax.tools.JavaFileObject; +import javax.tools.JavaFileObject.Kind; +import javax.tools.SimpleJavaFileObject; +import javax.tools.StandardJavaFileManager; +import javax.tools.ToolProvider; +import static org.junit.Assert.fail; + +final class Compile implements DiagnosticListener { + private final List> errors = new ArrayList<>(); + private final Map classes; + private final String pkg; + private final String cls; + + private Compile(String code) throws IOException { + this.pkg = findPkg(code); + this.cls = findCls(code); + classes = compile(code); + } + + /** Performs compilation of given Java code + */ + public static Compile create(String code) throws IOException { + return new Compile(code); + } + + /** Checks for given class among compiled resources */ + public byte[] get(String res) { + return classes.get(res); + } + + /** Obtains errors created during compilation. + */ + public List> getErrors() { + List> err = new ArrayList<>(); + for (Diagnostic diagnostic : errors) { + if (diagnostic.getKind() == Diagnostic.Kind.ERROR) { + err.add(diagnostic); + } + } + return err; + } + + private Map compile(final String code) throws IOException { + StandardJavaFileManager sjfm = ToolProvider.getSystemJavaCompiler().getStandardFileManager(this, null, null); + + final Map class2BAOS = new HashMap<>(); + + JavaFileObject file = new Mem(URI.create("mem://mem"), Kind.SOURCE, code); + + final URI scratch; + try { + scratch = new URI("mem://mem3"); + } catch (URISyntaxException ex) { + throw new IOException(ex); + } + + JavaFileManager jfm = new ForwardingJavaFileManagerImpl(sjfm, class2BAOS, scratch); + + ToolProvider.getSystemJavaCompiler().getTask(null, jfm, this, /*XXX:*/Arrays.asList("-source", "1.7", "-target", "1.7"), null, Arrays.asList(file)).call(); + + Map result = new HashMap<>(); + + for (Map.Entry e : class2BAOS.entrySet()) { + result.put(e.getKey(), e.getValue().toByteArray()); + } + + return result; + } + + + @Override + public void report(Diagnostic diagnostic) { + errors.add(diagnostic); + } + private static String findPkg(String java) throws IOException { + Pattern p = Pattern.compile("package\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}*;", Pattern.MULTILINE); + Matcher m = p.matcher(java); + if (!m.find()) { + throw new IOException("Can't find package declaration in the java file"); + } + String pkg = m.group(1); + return pkg; + } + private static String findCls(String java) throws IOException { + Pattern p = Pattern.compile("class\\p{javaWhitespace}*([\\p{Alnum}\\.]+)\\p{javaWhitespace}", Pattern.MULTILINE); + Matcher m = p.matcher(java); + if (!m.find()) { + throw new IOException("Can't find package declaration in the java file"); + } + String cls = m.group(1); + return cls; + } + + void assertError(String msg) { + if (getErrors().isEmpty()) { + fail(msg + " there should be no errors"); + } + StringBuilder sb = new StringBuilder(); + for (Diagnostic err : errors) { + final String txt = err.getMessage(Locale.US); + if (txt.contains(msg)) { + return; + } + sb.append("\n").append(txt); + } + fail(msg + sb); + } + + private class ForwardingJavaFileManagerImpl extends ForwardingJavaFileManager { + + private final Map class2BAOS; + private final URI scratch; + + public ForwardingJavaFileManagerImpl(JavaFileManager fileManager, Map class2BAOS, URI scratch) { + super(fileManager); + this.class2BAOS = class2BAOS; + this.scratch = scratch; + } + + @Override + public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling) throws IOException { + if (kind == Kind.CLASS) { + final ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + class2BAOS.put(className.replace('.', '/') + ".class", buffer); + return new Sibling(sibling.toUri(), kind, buffer); + } + + if (kind == Kind.SOURCE) { + return new Source(scratch/*sibling.toUri()*/, kind); + } + + throw new IllegalStateException(); + } + + @Override + public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException { + return null; + } + + private class Sibling extends SimpleJavaFileObject { + private final ByteArrayOutputStream buffer; + + public Sibling(URI uri, Kind kind, ByteArrayOutputStream buffer) { + super(uri, kind); + this.buffer = buffer; + } + + @Override + public OutputStream openOutputStream() throws IOException { + return buffer; + } + } + + private class Source extends SimpleJavaFileObject { + public Source(URI uri, Kind kind) { + super(uri, kind); + } + private final ByteArrayOutputStream data = new ByteArrayOutputStream(); + + @Override + public OutputStream openOutputStream() throws IOException { + return data; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + data.close(); + return new String(data.toByteArray()); + } + } + } + + private static class Mem extends SimpleJavaFileObject { + + private final String code; + + public Mem(URI uri, Kind kind, String code) { + super(uri, kind); + this.code = code; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return code; + } + } +} diff --git a/ObjectLayout/src/test/java/org/ObjectLayout/IntrinsicProcessorTest.java b/ObjectLayout/src/test/java/org/ObjectLayout/IntrinsicProcessorTest.java new file mode 100644 index 0000000..0736560 --- /dev/null +++ b/ObjectLayout/src/test/java/org/ObjectLayout/IntrinsicProcessorTest.java @@ -0,0 +1,48 @@ +/* + * Written by Jaroslav Tulach, and released to the public domain, + * as explained at http://creativecommons.org/publicdomain/zero/1.0/ + */ +package org.ObjectLayout; + +import org.junit.Test; + +public class IntrinsicProcessorTest { + + @Test public void nonPrivateIntrinsicObjectMember() throws Exception { + Compile c = Compile.create( +"package org.ObjectLayoutTest;\n" + +"import org.ObjectLayout.*;\n" + +"class BadContainerNonPrivate {\n" + +" @Intrinsic\n" + +" final Point intrinsicPoint = IntrinsicObjects.constructWithin(\"intrinsicPoint\", this);\n" + +"\n" + +" Point getPoint() {\n" + +" return intrinsicPoint;\n" + +" }\n" + +"}\n" + +"class Point {\n" + +"}\n" + ); + c.assertError("@Intrinsic object annotations can only be declared for private final fields"); + } + + @Test public void nonFinalIntrinsicObjectMember() throws Exception { + Compile c = Compile.create( +"package org.ObjectLayoutTest;\n" + +"import org.ObjectLayout.*;\n" + +"class BadContainerNonFinal {\n" + +" @Intrinsic\n" + +" private Point intrinsicPoint = IntrinsicObjects.constructWithin(\"intrinsicPoint\", this);\n" + +"\n" + +" Point getPoint() {\n" + +" return intrinsicPoint;\n" + +" }\n" + +"}\n" + +"class Point {\n" + +"}\n" + ); + c.assertError("@Intrinsic object annotations can only be declared for private final fields"); + } + + +} diff --git a/ObjectLayout/src/test/java/org/ObjectLayout/IntrinsticObjectTest.java b/ObjectLayout/src/test/java/org/ObjectLayout/IntrinsticObjectTest.java index e810f72..c8953b8 100644 --- a/ObjectLayout/src/test/java/org/ObjectLayout/IntrinsticObjectTest.java +++ b/ObjectLayout/src/test/java/org/ObjectLayout/IntrinsticObjectTest.java @@ -119,42 +119,6 @@ public void testBadContainerNonIntrinsic() throws NoSuchMethodException { bad.getPoint().getX(); } - /** - * BadContainerNonPrivate: non private intrinsic object member - */ - static class BadContainerNonPrivate { - @Intrinsic - final Point intrinsicPoint = IntrinsicObjects.constructWithin("intrinsicPoint", this); - - Point getPoint() { - return intrinsicPoint; - } - } - - @Test(expected = IllegalArgumentException.class) - public void testBadContainerNonPrivate() throws NoSuchMethodException { - BadContainerNonPrivate bad = new BadContainerNonPrivate(); - bad.getPoint().getX(); - } - - /** - * BadContainerNonFinal: non final intrinsic object member - */ - static class BadContainerNonFinal { - @Intrinsic - private Point intrinsicPoint = IntrinsicObjects.constructWithin("intrinsicPoint", this); - - Point getPoint() { - return intrinsicPoint; - } - } - - @Test(expected = IllegalArgumentException.class) - public void testBadContainerNonFinal() throws NoSuchMethodException { - BadContainerNonFinal bad = new BadContainerNonFinal(); - bad.getPoint().getX(); - } - /** * BadContainerNullMember: null intrinsic object member */