package io.micronaut.annotation.processing;
import edu.umd.cs.findbugs.annotations.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.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.*;
public class AnnotationProcessingOutputVisitor extends AbstractClassWriterOutputVisitor {
private final Filer filer;
private final Map<String, Optional<GeneratedFile>> metaInfFiles = new HashMap<>();
private final Map<String, FileObject> openedFiles = new HashMap<>();
private final Map<String, Optional<GeneratedFile>> generatedFiles = new HashMap<>();
public AnnotationProcessingOutputVisitor(Filer filer) {
super(isEclipseFiler(filer));
this.filer = filer;
}
private static boolean isEclipseFiler(Filer filer) {
return 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)) {
List<Element> list = new ArrayList<>(originatingElements.length);
for (io.micronaut.inject.ast.Element originatingElement : originatingElements) {
Object nativeType = originatingElement.getNativeType();
if (nativeType instanceof Element) {
list.add((Element) nativeType);
}
}
nativeOriginatingElements = list.toArray(new Element[0]);
} else {
nativeOriginatingElements = new Element[0];
}
javaFileObject = filer.createClassFile(classname, nativeOriginatingElements);
return javaFileObject.openOutputStream();
}
@Override
public Optional<GeneratedFile> visitMetaInfFile(String path) {
return metaInfFiles.computeIfAbsent(path, s -> {
String finalPath = "META-INF/" + path;
return Optional.of(new GeneratedFileObject(finalPath));
});
}
@Override
public Optional<GeneratedFile> visitGeneratedFile(String path) {
return generatedFiles.computeIfAbsent(path, s -> Optional.of(new GeneratedFileObject(path, StandardLocation.SOURCE_OUTPUT)));
}
class GeneratedFileObject implements GeneratedFile {
private final String path;
private final StandardLocation classOutput;
private FileObject inputObject;
private FileObject outputObject;
GeneratedFileObject(String path) {
this.path = path;
classOutput = StandardLocation.CLASS_OUTPUT;
}
GeneratedFileObject(String path, StandardLocation location) {
this.path = path;
this.classOutput = location;
}
@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) {
outputObject = filer.createResource(classOutput, "", path);
}
return outputObject;
}
}
}