org.scijava.ops.indexer.OpImplNoteParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of scijava-ops-indexer Show documentation
Show all versions of scijava-ops-indexer Show documentation
An annotation processor for indexing Ops with javadoc.
The newest version!
/*-
* #%L
* An annotation processor for indexing Ops with javadoc.
* %%
* Copyright (C) 2021 - 2024 SciJava developers.
* %%
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
* #L%
*/
// This class was adapted from the
// com.github.therapi.runtimejavadoc.scribe.JavadocAnnotationProcessor class
// of Therapi Runtime Javadoc 0.13.0, which is distributed
// under the Apache 2 license.
package org.scijava.ops.indexer;
import org.yaml.snakeyaml.Yaml;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static java.nio.charset.StandardCharsets.UTF_8;
import static javax.lang.model.element.ElementKind.*;
/**
* {@link javax.annotation.processing.Processor} used to find code blocks
* annotated as Ops, using the implNote syntax.
*
* @author Gabriel Selzer
*/
public class OpImplNoteParser extends AbstractProcessor {
public static final String OP_VERSION = "scijava.ops.opVersion";
private static final String PARSE_OPS = "scijava.ops.parse";
private final Yaml yaml = new Yaml();
@Override
public boolean process(Set extends TypeElement> annotations,
RoundEnvironment roundEnvironment)
{
processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE,
"Processing Ops written using the implNote syntax...");
final Map options = processingEnv.getOptions();
if ("true".equals(options.get(PARSE_OPS))) {
final List data = new ArrayList<>();
// Make sure each element only gets processed once.
final Set alreadyProcessed = new HashSet<>();
// If retaining Javadoc for all packages, the @RetainJavadoc annotation is
// redundant. Otherwise, make sure annotated classes have their Javadoc
// retained regardless of package.
for (TypeElement annotation : annotations) {
for (Element e : roundEnvironment.getElementsAnnotatedWith(
annotation))
{
generateJavadoc(e, data, alreadyProcessed);
}
}
for (Element e : roundEnvironment.getRootElements()) {
generateJavadoc(e, data, alreadyProcessed);
}
if (!roundEnvironment.getRootElements().isEmpty() && !data.isEmpty()) {
try {
outputYamlDoc(data);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
// This annotation processor only looks at Javadoc - it doesn't look at, and
// thus doesn't claim any annotations
return false;
}
// TODO: Consider adding record
private static final EnumSet elementKindsToInspect = EnumSet.of(
ElementKind.CLASS, ElementKind.INTERFACE, ElementKind.ENUM);
private void generateJavadoc(Element element, List data,
Set alreadyProcessed)
{
// Ignore elements that have been parsed already
if (!alreadyProcessed.add(element)) {
return;
}
// Ignore elements that we don't care to parse
if (!elementKindsToInspect.contains(element.getKind())) {
return;
}
// Start by checking the element itself
TypeElement classElement = (TypeElement) element;
Optional clsData = elementToImplData(classElement);
clsData.ifPresent(data::add);
// Then check contained elements
for (Element e : classElement.getEnclosedElements()) {
elementToImplData(e).ifPresent(data::add);
}
}
private void outputYamlDoc(List collectedData)
throws IOException
{
var data = collectedData.stream().map(OpImplData::dumpData).collect(
Collectors.toList());
String doc = yaml.dump(data);
FileObject resource = processingEnv.getFiler().createResource( //
StandardLocation.CLASS_OUTPUT, //
"", //
"ops.yaml" //
);
try (OutputStream os = resource.openOutputStream()) {
os.write(doc.getBytes(UTF_8));
}
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public Set getSupportedAnnotationTypes() {
return Collections.singleton("*");
}
@Override
public Set getSupportedOptions() {
Set supportedOptions = new HashSet<>();
supportedOptions.add(PARSE_OPS);
supportedOptions.add(OP_VERSION);
return supportedOptions;
}
private Optional elementToImplData(final Element element) {
String javadoc = processingEnv.getElementUtils().getDocComment(element);
if (javadoc != null && javadoc.contains("implNote op")) {
try {
if (element.getKind() == CLASS) {
TypeElement typeElement = (TypeElement) element;
var fMethod = ProcessingUtils.findFunctionalMethod(processingEnv,
typeElement);
var fMethodDoc = processingEnv.getElementUtils().getDocComment(
fMethod);
return Optional.of(new OpClassImplData(typeElement, fMethod, javadoc,
fMethodDoc, processingEnv));
}
else if (element.getKind() == METHOD) {
return Optional.of(new OpMethodImplData((ExecutableElement) element,
javadoc, processingEnv));
}
else if (element.getKind() == FIELD) {
return Optional.of(new OpFieldImplData(element, javadoc,
processingEnv));
}
}
catch (Exception e) {
ProcessingUtils.printProcessingException(element, e, processingEnv);
}
}
return Optional.empty();
}
}