io.micronaut.annotation.processing.AnnotationProcessingOutputVisitor Maven / Gradle / Ivy
The newest version!
/*
* Copyright 2017-2020 original authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.micronaut.annotation.processing;
import io.micronaut.annotation.processing.visitor.JavaNativeElement;
import io.micronaut.core.annotation.Nullable;
import io.micronaut.core.util.ArrayUtils;
import io.micronaut.inject.writer.AbstractClassWriterOutputVisitor;
import io.micronaut.inject.writer.ClassGenerationException;
import io.micronaut.inject.writer.GeneratedFile;
import javax.annotation.processing.Filer;
import javax.lang.model.element.Element;
import javax.tools.FileObject;
import javax.tools.JavaFileObject;
import javax.tools.StandardLocation;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
/**
* An implementation of {@link io.micronaut.inject.writer.ClassWriterOutputVisitor} for annotation processing.
*
* @author Graeme Rocher
* @since 1.0
*/
public class AnnotationProcessingOutputVisitor extends AbstractClassWriterOutputVisitor {
private static final Element[] ELEMENTS_EMPTY_ARRAY = new Element[0];
private final Filer filer;
private final Map> metaInfFiles = new LinkedHashMap<>();
private final Map openedFiles = new LinkedHashMap<>();
private final Map> generatedFiles = new LinkedHashMap<>();
private final boolean isGradleFiler;
/**
* @param filer The {@link Filer} for creating new files
*/
public AnnotationProcessingOutputVisitor(Filer filer) {
super(isEclipseFiler(filer));
this.filer = filer;
if (filer != null) {
final String filerName = filer.getClass().getName();
this.isGradleFiler = filerName.startsWith("org.gradle.api") || filerName.startsWith("org.jetbrains.kotlin.kapt3");
} else {
this.isGradleFiler = false;
}
}
private static boolean isEclipseFiler(Filer filer) {
return filer != null && filer.getClass().getTypeName().startsWith("org.eclipse.jdt");
}
@Override
public OutputStream visitClass(String classname, @Nullable io.micronaut.inject.ast.Element originatingElement) throws IOException {
return visitClass(classname, new io.micronaut.inject.ast.Element[] {originatingElement});
}
@Override
public OutputStream visitClass(String classname, io.micronaut.inject.ast.Element... originatingElements) throws IOException {
JavaFileObject javaFileObject;
Element[] nativeOriginatingElements;
if (ArrayUtils.isNotEmpty(originatingElements)) {
if (isGradleFiler) {
// gradle filer only support single originating element for isolating processors
final io.micronaut.inject.ast.Element e = originatingElements[0];
final Object nativeType = e.getNativeType();
if (nativeType instanceof JavaNativeElement javaNativeElement) {
nativeOriginatingElements = new Element[] {javaNativeElement.element()};
} else {
nativeOriginatingElements = ELEMENTS_EMPTY_ARRAY;
}
} else {
// other compilers like the IntelliJ compiler support multiple
var list = new ArrayList(originatingElements.length);
for (io.micronaut.inject.ast.Element originatingElement : originatingElements) {
Object nativeType = originatingElement.getNativeType();
if (nativeType instanceof JavaNativeElement javaNativeElement) {
list.add(javaNativeElement.element());
}
}
nativeOriginatingElements = list.toArray(ELEMENTS_EMPTY_ARRAY);
}
} else {
nativeOriginatingElements = ELEMENTS_EMPTY_ARRAY;
}
javaFileObject = filer.createClassFile(classname, nativeOriginatingElements);
return javaFileObject.openOutputStream();
}
@Override
@SuppressWarnings("java:S1075")
public void visitServiceDescriptor(String type, String classname, io.micronaut.inject.ast.Element originatingElement) {
final String path = "META-INF/micronaut/" + type + "/" + classname;
try {
final FileObject fileObject = filer.createResource(
StandardLocation.CLASS_OUTPUT,
"",
path,
((JavaNativeElement) originatingElement.getNativeType()).element()
);
try (Writer w = fileObject.openWriter()) {
w.write("");
}
} catch (IOException e) {
throw new ClassGenerationException("Unable to generate Bean entry at path: " + path, e);
}
}
@Override
public Optional visitMetaInfFile(String path, io.micronaut.inject.ast.Element... originatingElements) {
return metaInfFiles.computeIfAbsent(path, s -> {
String finalPath = "META-INF/" + path;
Element[] nativeOriginatingElements = toNativeOriginatingElements(originatingElements);
return Optional.of(new GeneratedFileObject(finalPath, nativeOriginatingElements));
});
}
private static Element[] toNativeOriginatingElements(io.micronaut.inject.ast.Element[] originatingElements) {
return Arrays.stream(originatingElements)
.map(e -> ((JavaNativeElement) e.getNativeType()).element()).toArray(Element[]::new);
}
@Override
public Optional visitGeneratedFile(String path) {
return generatedFiles.computeIfAbsent(path, s -> Optional.of(new GeneratedFileObject(path, StandardLocation.SOURCE_OUTPUT)));
}
@Override
public Optional visitGeneratedFile(String path, io.micronaut.inject.ast.Element... originatingElements) {
Element[] nativeOriginatingElements = toNativeOriginatingElements(originatingElements);
return generatedFiles.computeIfAbsent(path, s -> Optional.of(new GeneratedFileObject(path, StandardLocation.SOURCE_OUTPUT, nativeOriginatingElements)));
}
@Override
public Optional visitGeneratedSourceFile(String packageName, String fileNameWithoutExtension, io.micronaut.inject.ast.Element... originatingElements) {
Element[] nativeOriginatingElements = toNativeOriginatingElements(originatingElements);
var path = packageName.replace('.', File.separatorChar) + File.separator + fileNameWithoutExtension + ".java";
return generatedFiles.computeIfAbsent(path, s -> Optional.of(new GeneratedFileObject(packageName, fileNameWithoutExtension, nativeOriginatingElements)));
}
/**
* Class to handle generated files by the annotation processor.
*/
class GeneratedFileObject implements GeneratedFile {
private final String packageName;
private final String fileName;
private final String path;
private final StandardLocation classOutput;
private final Element[] originatingElements;
private FileObject inputObject;
private FileObject outputObject;
/**
* @param packageName The package of the generated source file
* @param fileName The name of the source file, without extension
* @param originatingElements the originating elements
*/
GeneratedFileObject(String packageName, String fileName, Element... originatingElements) {
this.path = packageName.replace('.', File.separatorChar);
this.packageName = packageName;
this.fileName = fileName;
this.classOutput = StandardLocation.SOURCE_OUTPUT;
this.originatingElements = originatingElements;
}
/**
* @param path The path for the generated file
* @param originatingElements the originating elements
*/
GeneratedFileObject(String path, Element... originatingElements) {
this.path = path;
this.classOutput = StandardLocation.CLASS_OUTPUT;
this.originatingElements = originatingElements;
this.packageName = null;
this.fileName = null;
}
/**
* @param path The path for the generated file
* @param location The location
* @param originatingElements The originating elements
*/
GeneratedFileObject(String path, StandardLocation location, Element... originatingElements) {
this.path = path;
this.classOutput = location;
this.originatingElements = originatingElements;
this.packageName = null;
this.fileName = null;
}
@Override
public URI toURI() {
try {
return getOutputObject().toUri();
} catch (IOException e) {
throw new ClassGenerationException("Unable to return URI for file object: " + path);
}
}
@Override
public String getName() {
return path;
}
@Override
public Writer openWriter() throws IOException {
return getOutputObject().openWriter();
}
@Override
public OutputStream openOutputStream() throws IOException {
return getOutputObject().openOutputStream();
}
@Override
public InputStream openInputStream() throws IOException {
if (inputObject == null) {
inputObject = openFileForReading(path);
}
return inputObject.openInputStream();
}
@Override
public Reader openReader() throws IOException {
if (inputObject == null) {
inputObject = openFileForReading(path);
}
return inputObject.openReader(true);
}
@Override
public CharSequence getTextContent() throws IOException {
try {
if (inputObject == null) {
inputObject = openFileForReading(path);
}
return inputObject.getCharContent(true);
} catch (FileNotFoundException e) {
return null;
}
}
private FileObject openFileForReading(String path) {
return openedFiles.computeIfAbsent(path, s -> {
try {
return filer.getResource(StandardLocation.CLASS_OUTPUT, "", path);
} catch (IOException e) {
throw new ClassGenerationException("Unable to open file for path: " + path, e);
}
});
}
private FileObject getOutputObject() throws IOException {
if (outputObject == null) {
if (packageName != null && fileName != null) {
outputObject = filer.createSourceFile(
packageName + "." + fileName,
originatingElements
);
} else {
outputObject = filer.createResource(classOutput, "", path, originatingElements);
}
}
return outputObject;
}
}
}