pro.projo.generation.interfaces.InterfaceTemplateProcessor Maven / Gradle / Ivy
The newest version!
// //
// Copyright 2019 - 2022 Mirko Raner //
// //
// 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 //
// //
// http://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 pro.projo.generation.interfaces;
import java.io.IOError;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.lang.annotation.AnnotationFormatError;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Method;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.stream.Stream;
import javax.annotation.Generated;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementScanner8;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.FileObject;
import org.xml.sax.SAXException;
import pro.projo.generation.ProjoProcessor;
import pro.projo.generation.ProjoTemplateFactoryGenerator;
import pro.projo.generation.dtd.DtdElementCollector;
import pro.projo.generation.dtd.DtdInputSource;
import pro.projo.generation.utilities.DefaultNameComparator;
import pro.projo.generation.utilities.MergeOptions;
import pro.projo.generation.utilities.MethodFilter;
import pro.projo.generation.utilities.PackageShortener;
import pro.projo.generation.utilities.Reduction;
import pro.projo.generation.utilities.Source;
import pro.projo.generation.utilities.Source.EnumSource;
import pro.projo.generation.utilities.Source.InterfaceSource;
import pro.projo.generation.utilities.TypeConverter;
import pro.projo.generation.utilities.TypeConverter.Type;
import pro.projo.interfaces.annotation.Dtd;
import pro.projo.interfaces.annotation.Enum;
import pro.projo.interfaces.annotation.Enums;
import pro.projo.interfaces.annotation.Interface;
import pro.projo.interfaces.annotation.Options;
import pro.projo.template.annotation.Configuration;
import static java.util.Collections.emptyList;
import static java.util.Collections.singleton;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Stream.iterate;
import static javax.lang.model.SourceVersion.RELEASE_8;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.type.TypeKind.NONE;
import static javax.tools.Diagnostic.Kind.ERROR;
import static javax.tools.Diagnostic.Kind.NOTE;
import static javax.tools.StandardLocation.CLASS_PATH;
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Dtd;
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Dtds;
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Enum;
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Enums;
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Interface;
import static pro.projo.generation.interfaces.InterfaceTemplateProcessor.Interfaces;
import static pro.projo.generation.utilities.TypeConverter.primitives;
import static pro.projo.interfaces.annotation.Ternary.FALSE;
import static pro.projo.interfaces.annotation.Ternary.TRUE;
/**
* The {@link InterfaceTemplateProcessor} is an annotation processor that, at compile time, detects
* source files that have an {@link Interface @Interface}, {@link Enum @Enum} or {@link Dtd @Dtd}
* annotation and will use these sources for generating synthetic interfaces and enums.
*
* @author Mirko Raner
**/
@SupportedSourceVersion(RELEASE_8)
@SupportedAnnotationTypes({Dtd, Dtds, Enum, Enums, Interface, Interfaces})
public class InterfaceTemplateProcessor extends ProjoProcessor
{
// Shortcuts for supported annotation types
//
// NOTE: annotations are apparently still processed even if they are not among the
// @SupportedAnnotationTypes, but Projo's annotations are listed nonetheless,
// if only to keep up with best practices...
//
final static String Dtd = "pro.projo.interfaces.annotation.Dtd";
final static String Dtds = "pro.projo.interfaces.annotation.Dtds";
final static String Enum = "pro.projo.interfaces.annotation.Enum";
final static String Enums = "pro.projo.interfaces.annotation.Enums";
final static String Interface = "pro.projo.interfaces.annotation.Interface";
final static String Interfaces = "pro.projo.interfaces.annotation.Interfaces";
final static Predicate notPrimitive = not(primitives::contains);
private Filer filer;
private Types types;
private Elements elements;
private Messager messager;
@Override
public Elements elements()
{
return elements;
}
@Override
public synchronized void init(ProcessingEnvironment environment)
{
super.init(environment);
filer = environment.getFiler();
types = environment.getTypeUtils();
elements = environment.getElementUtils();
messager = environment.getMessager();
}
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment round)
{
messager.printMessage(NOTE, "Processing interfaces...");
process(round, this::getInterfaceConfiguration, Interface.class, $package.$InterfaceTemplate.class);
messager.printMessage(NOTE, "Processing enums...");
process(round, this::getEnumConfiguration, Enum.class, $package.$EnumTemplate.class);
messager.printMessage(NOTE, "Processing DTDs...");
process(round, this::getDtdConfiguration, Dtd.class, $package.$InterfaceTemplate.class);
messager.printMessage(NOTE, "Done processing...");
return true;
}
private <_Annotation_ extends Annotation> void process(RoundEnvironment round,
BiFunction, Collection extends Configuration>> configurationFactory,
Class<_Annotation_> singleAnnotation,
Class> templateClass)
{
ProjoTemplateFactoryGenerator generator = new ProjoTemplateFactoryGenerator();
Class extends Annotation> multiAnnotation = singleAnnotation.getAnnotation(Repeatable.class).value();
Set annotatedElements = new HashSet<>();
annotatedElements.addAll(round.getElementsAnnotatedWith(singleAnnotation));
annotatedElements.addAll(round.getElementsAnnotatedWith(multiAnnotation));
for (Element element: annotatedElements)
{
PackageElement packageElement = (PackageElement)element;
List<_Annotation_> annotations = getAnnotations(packageElement, singleAnnotation, multiAnnotation);
Collection extends Configuration> configurations = configurationFactory.apply(packageElement, annotations);
messager.printMessage(NOTE, "Generating " + configurations.size() + " additional sources...");
for (Configuration configuration: configurations)
{
String className = configuration.fullyQualifiedClassName();
TypeElement typeElement = elements.getTypeElement(className);
try
{
String templateClassName = templateClass.getName();
FileObject sourceFile = createFile(filer, configuration, className, typeElement);
String resourceName = "/" + templateClassName.replace('.', '/') + ".java";
try (PrintWriter writer = new PrintWriter(sourceFile.openWriter(), true))
{
try (Reader reader = new InputStreamReader(getClass().getResourceAsStream(resourceName)))
{
generator.generate(reader, resourceName, configuration.parameters(), configuration.postProcessor().apply(writer));
}
}
messager.printMessage(NOTE, "Generated " + className);
}
catch (IOException ioException)
{
// Carry on for now; throwing an exception here would bring the compiler to a halt...
//
ioException.printStackTrace();
messager.printMessage(ERROR, ioException.getClass().getName() + ": " + ioException.getMessage());
}
}
}
}
private FileObject createFile(Filer filer, Configuration configuration, String className, Element typeElement)
throws IOException
{
if (configuration.isDefault(Options::fileExtension))
{
return filer.createSourceFile(className, typeElement);
}
Options options = configuration.options();
String fileExtension = options.fileExtension();
String sourceFileName = className.substring(className.lastIndexOf('.')+1) + fileExtension;
String packageName = className.substring(0, className.lastIndexOf('.'));
return filer.createResource(options.outputLocation(), packageName, sourceFileName, typeElement);
}
private <_Annotation_ extends Annotation> List<_Annotation_> getAnnotations(Element packageElement,
Class<_Annotation_> single, Class extends Annotation> repeated)
{
List<_Annotation_> annotations = new ArrayList<>();
Annotation multiples = packageElement.getAnnotation(repeated);
if (multiples != null)
{
try
{
Method value = multiples.getClass().getMethod("value");
@SuppressWarnings("unchecked")
_Annotation_[] repeatedAnnotations = (_Annotation_[])value.invoke(multiples);
annotations = Arrays.asList(repeatedAnnotations);
}
catch (Exception exception)
{
messager.printMessage(ERROR, exception.getClass().getName() + ": " + exception.getMessage());
}
}
Optional.ofNullable(packageElement.getAnnotation(single)).ifPresent(annotations::add);
return annotations;
}
/**
* Generates code generation configurations for declared {@link Interface @Interface} annotations.
*
* @param element the package in which interfaces will be generated
* @param interfaces the set of {@link Interface @Interface} annotations containing the configuration data
* @return a collection of code generation configurations, one for each generated interface
**/
private Collection extends Configuration> getInterfaceConfiguration(PackageElement element, List interfaces)
{
String object = Object.class.getName();
Name packageName = element.getQualifiedName();
Predicate notSamePackage = name -> !name.substring(0, name.lastIndexOf('.')).equals(String.valueOf(packageName));
Function getConfiguration = annotation ->
{
Set imports = new HashSet<>();
if (new MergeOptions(element.getAnnotation(Options.class), annotation.options()).options().addAnnotations() != FALSE)
{
imports.add(Generated.class.getName());
}
TypeMirror originalClass = getTypeMirror(annotation::from);
MethodFilter methodFilter = new MethodFilter(annotation);
TypeElement type = elements.getTypeElement(originalClass.toString());
List methods = new ArrayList<>();
List extends TypeParameterElement> typeParameters;
if (type != null) // type does not represent a primitive
{
typeParameters = staticMethodsOnly(annotation)? emptyList():type.getTypeParameters();
ElementScanner8> scanner = new ElementScanner8>()
{
@Override
public Void visitExecutable(ExecutableElement method, List executables)
{
if (type.equals(method.getEnclosingElement())
&& (methodFilter.matches(method)))
{
executables.add(method);
}
return super.visitExecutable(method, executables);
}
};
type.accept(scanner, methods);
}
else
{
typeParameters = emptyList();
}
PackageShortener shortener = new PackageShortener();
// Include both interfaces and enums in the TypeConverter, so that references to enums from
// within interfaces are handled properly:
//
InterfaceSource primary = new InterfaceSource(annotation, element.getAnnotation(Options.class));
Stream