diff --git a/core/processor/pom.xml b/core/processor/pom.xml index 8427a552eb841..aee2ce4090171 100644 --- a/core/processor/pom.xml +++ b/core/processor/pom.xml @@ -59,6 +59,17 @@ jboss-logmanager test + + com.karuslabs + elementary + 2.0.1 + test + + + io.quarkus + quarkus-builder + test + diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java index c660290a11c37..3d978555bbecc 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessor.java @@ -183,7 +183,7 @@ void doFinish() { Properties javaDocProperties = new Properties(); try { - Files.walkFileTree(path, new FileVisitor() { + Files.walkFileTree(path, new FileVisitor<>() { public FileVisitResult preVisitDirectory(final Path dir, final BasicFileAttributes attrs) { return FileVisitResult.CONTINUE; } @@ -282,8 +282,6 @@ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) } catch (IOException e) { processingEnv.getMessager() .printMessage(Diagnostic.Kind.ERROR, "Failed to generate extension doc: " + e); - return; - } } @@ -368,8 +366,7 @@ private void processBuildStep(RoundEnvironment roundEnv, TypeElement annotation) StandardLocation.SOURCE_OUTPUT, pkg.getQualifiedName() .toString(), - rbn.toString() + ".bsc", - clazz); + rbn + ".bsc", clazz); writeResourceFile(binaryName, itemResource); } catch (IOException e1) { processingEnv.getMessager() @@ -618,7 +615,7 @@ private void processMethodConfigMapping(ExecutableElement method, Properties jav if (method.getSimpleName() .contentEquals("toString") && method.getParameters() - .size() == 0) { + .isEmpty()) { return; } @@ -650,7 +647,7 @@ private TypeElement unwrapConfigGroup(TypeMirror typeMirror) { String name = declaredType.asElement() .toString(); List typeArguments = declaredType.getTypeArguments(); - if (typeArguments.size() == 0) { + if (typeArguments.isEmpty()) { if (!name.startsWith("java.")) { return (TypeElement) declaredType.asElement(); } diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessorTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessorTest.java new file mode 100644 index 0000000000000..d9d85a19ab53a --- /dev/null +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/ExtensionAnnotationProcessorTest.java @@ -0,0 +1,76 @@ +package io.quarkus.annotation.processor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import javax.tools.JavaFileObject; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import com.karuslabs.elementary.Results; +import com.karuslabs.elementary.junit.JavacExtension; +import com.karuslabs.elementary.junit.annotations.Classpath; +import com.karuslabs.elementary.junit.annotations.Processors; + +import io.quarkus.annotation.processor.fs.CustomMemoryFileSystemProvider; + +@ExtendWith(JavacExtension.class) +@Processors({ ExtensionAnnotationProcessor.class }) +class ExtensionAnnotationProcessorTest { + + @BeforeEach + void beforeEach() { + // This is of limited use, since the filesystem doesn't seem to directly generate files, in the current usage + CustomMemoryFileSystemProvider.reset(); + } + + @Test + @Classpath("org.acme.examples.ClassWithBuildStep") + void shouldProcessClassWithBuildStepWithoutErrors(Results results) throws IOException { + assertNoErrrors(results); + } + + @Test + @Classpath("org.acme.examples.ClassWithBuildStep") + void shouldGenerateABscFile(Results results) throws IOException { + assertNoErrrors(results); + List sources = results.sources; + JavaFileObject bscFile = sources.stream() + .filter(source -> source.getName() + .endsWith(".bsc")) + .findAny() + .orElse(null); + assertNotNull(bscFile); + + String contents = removeLineBreaks(new String(bscFile + .openInputStream() + .readAllBytes(), StandardCharsets.UTF_8)); + assertEquals("org.acme.examples.ClassWithBuildStep", contents); + } + + private String removeLineBreaks(String s) { + return s.replace(System.getProperty("line.separator"), "") + .replace("\n", ""); + } + + @Test + @Classpath("org.acme.examples.ClassWithoutBuildStep") + void shouldProcessEmptyClassWithoutErrors(Results results) { + assertNoErrrors(results); + } + + private static void assertNoErrrors(Results results) { + assertEquals(0, results.find() + .errors() + .count(), + "Errors were: " + results.find() + .errors() + .diagnostics()); + } +} diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/fs/CustomMemoryFileSystem.java b/core/processor/src/test/java/io/quarkus/annotation/processor/fs/CustomMemoryFileSystem.java new file mode 100644 index 0000000000000..432bd86b334e9 --- /dev/null +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/fs/CustomMemoryFileSystem.java @@ -0,0 +1,158 @@ +package io.quarkus.annotation.processor.fs; + +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.Paths; +import java.nio.file.WatchService; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class CustomMemoryFileSystem extends FileSystem { + + private final Map fileContents = new HashMap<>(); + private final CustomMemoryFileSystemProvider provider; + + public CustomMemoryFileSystem(CustomMemoryFileSystemProvider provider) { + this.provider = provider; + } + + @Override + public FileSystemProvider provider() { + return provider; + } + + @Override + public void close() throws IOException { + // No resources to close + } + + @Override + public boolean isOpen() { + return true; // Always open + } + + @Override + public boolean isReadOnly() { + return false; // This filesystem is writable + } + + @Override + public String getSeparator() { + return "/"; // Unix-style separator + } + + @Override + public Iterable getRootDirectories() { + return Collections.singleton(Paths.get("/")); // Single root directory + } + + @Override + public Iterable getFileStores() { + return Collections.emptyList(); // No file stores + } + + @Override + public Set supportedFileAttributeViews() { + return Collections.emptySet(); // No supported file attribute views + } + + @Override + public Path getPath(String first, String... more) { + String path = first; + for (String segment : more) { + path += "/" + segment; + } + return Paths.get(path); + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + return null; + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + return null; + } + + @Override + public WatchService newWatchService() throws IOException { + return null; + } + + public void addFile(URI uri, byte[] content) { + fileContents.put(uri, ByteBuffer.wrap(content)); + } + + static class CustomMemorySeekableByteChannel implements SeekableByteChannel { + + private final ByteBuffer buffer; + + CustomMemorySeekableByteChannel(ByteBuffer buffer) { + this.buffer = buffer; + } + + @Override + public int read(ByteBuffer dst) throws IOException { + int remaining = buffer.remaining(); + int count = Math.min(remaining, dst.remaining()); + if (count > 0) { + ByteBuffer slice = buffer.slice(); + slice.limit(count); + dst.put(slice); + buffer.position(buffer.position() + count); + } + return count; + } + + @Override + public int write(ByteBuffer src) throws IOException { + int count = src.remaining(); + buffer.put(src); + return count; + } + + @Override + public long position() throws IOException { + return buffer.position(); + } + + @Override + public SeekableByteChannel position(long newPosition) throws IOException { + buffer.position((int) newPosition); + return this; + } + + @Override + public long size() throws IOException { + return buffer.limit(); + } + + @Override + public SeekableByteChannel truncate(long size) throws IOException { + buffer.limit((int) size); + return this; + } + + @Override + public boolean isOpen() { + return true; // Always open + } + + @Override + public void close() throws IOException { + // No resources to close + } + } + +} diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/fs/CustomMemoryFileSystemProvider.java b/core/processor/src/test/java/io/quarkus/annotation/processor/fs/CustomMemoryFileSystemProvider.java new file mode 100644 index 0000000000000..8d28a7ae672a6 --- /dev/null +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/fs/CustomMemoryFileSystemProvider.java @@ -0,0 +1,152 @@ +package io.quarkus.annotation.processor.fs; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryStream; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.spi.FileSystemProvider; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +public class CustomMemoryFileSystemProvider extends FileSystemProvider { + + private static final String MEM = "mem"; + + private static Map fileContents = new HashMap(); + + public static void reset() { + fileContents = new HashMap(); + } + + public static Set getCreatedFiles() { + return fileContents.keySet(); + } + + @Override + public String getScheme() { + return MEM; + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) throws IOException { + // There's a bit of a disconnect here between the Elementary JavaFileManager and the memory filesystem, + // even though both are in-memory filesystems + return new CustomMemoryFileSystem(this); + } + + @Override + public FileSystem getFileSystem(URI uri) { + throw new UnsupportedOperationException(); + } + + @Override + public Path getPath(URI uri) { + + if (uri.getScheme() == null || !uri.getScheme() + .equalsIgnoreCase(MEM)) { + throw new IllegalArgumentException("For URI " + uri + ", URI scheme is not '" + MEM + "'"); + + } + + // TODO what should we do here? Can we use the java file manager used by Elementary? + try { + return Path.of(File.createTempFile("mem-fs", "adhoc") + .toURI()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) + throws IOException { + if (fileContents.containsKey(path.toUri())) { + ByteBuffer buffer = fileContents.get(path.toUri()); + return new CustomMemoryFileSystem.CustomMemorySeekableByteChannel(buffer); + } else { + throw new NoSuchFileException(path.toString()); + } + } + + @Override + public DirectoryStream newDirectoryStream(Path dir, DirectoryStream.Filter filter) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void delete(Path path) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void copy(Path source, Path target, CopyOption... options) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isSameFile(Path path1, Path path2) throws IOException { + return path1.equals(path2); + } + + @Override + public boolean isHidden(Path path) throws IOException { + return false; + } + + @Override + public FileStore getFileStore(Path path) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void checkAccess(Path path, AccessMode... modes) throws IOException { + if (!fileContents.containsKey(path.toUri())) { + throw new NoSuchFileException(path.toString()); + } + } + + @Override + public V getFileAttributeView(Path path, Class type, + LinkOption... options) { + throw new UnsupportedOperationException(); + } + + @Override + public A readAttributes(Path path, Class type, LinkOption... options) + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { + throw new UnsupportedOperationException(); + } +} diff --git a/core/processor/src/test/resources/META-INF/services/java.nio.file.spi.FileSystemProvider b/core/processor/src/test/resources/META-INF/services/java.nio.file.spi.FileSystemProvider new file mode 100644 index 0000000000000..9582882517a77 --- /dev/null +++ b/core/processor/src/test/resources/META-INF/services/java.nio.file.spi.FileSystemProvider @@ -0,0 +1 @@ +io.quarkus.annotation.processor.fs.CustomMemoryFileSystemProvider \ No newline at end of file diff --git a/core/processor/src/test/resources/io/quarkus/deployment/annotations/BuildStep.java b/core/processor/src/test/resources/io/quarkus/deployment/annotations/BuildStep.java new file mode 100644 index 0000000000000..944813a9d720a --- /dev/null +++ b/core/processor/src/test/resources/io/quarkus/deployment/annotations/BuildStep.java @@ -0,0 +1,13 @@ +package io.quarkus.deployment.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface BuildStep { + // FAKE! FAKE! This is only here so we can test without introducing a circular dependency + +} \ No newline at end of file diff --git a/core/processor/src/test/resources/org/acme/examples/ArbitraryBuildItem.java b/core/processor/src/test/resources/org/acme/examples/ArbitraryBuildItem.java new file mode 100644 index 0000000000000..ecb7fb4ee6ee6 --- /dev/null +++ b/core/processor/src/test/resources/org/acme/examples/ArbitraryBuildItem.java @@ -0,0 +1,6 @@ +package org.acme.examples; + +import io.quarkus.builder.item.MultiBuildItem; + +public final class ArbitraryBuildItem extends MultiBuildItem { +} \ No newline at end of file diff --git a/core/processor/src/test/resources/org/acme/examples/ClassWithBuildStep.java b/core/processor/src/test/resources/org/acme/examples/ClassWithBuildStep.java new file mode 100644 index 0000000000000..8dbecc4bff2bc --- /dev/null +++ b/core/processor/src/test/resources/org/acme/examples/ClassWithBuildStep.java @@ -0,0 +1,10 @@ +package org.acme.examples; + +import io.quarkus.deployment.annotations.BuildStep; + +public class ClassWithBuildStep { + @BuildStep + ArbitraryBuildItem feature() { + return new ArbitraryBuildItem(); + } +} diff --git a/core/processor/src/test/resources/org/acme/examples/ClassWithoutBuildStep.java b/core/processor/src/test/resources/org/acme/examples/ClassWithoutBuildStep.java new file mode 100644 index 0000000000000..b40b6d25d2059 --- /dev/null +++ b/core/processor/src/test/resources/org/acme/examples/ClassWithoutBuildStep.java @@ -0,0 +1,6 @@ +package org.acme.examples; + +public class ClassWithoutBuildStep { + + +}