io.vavr.match.PatternsProcessor Maven / Gradle / Ivy
Show all versions of vavr-match-processor Show documentation
/* __ __ __ __ __ ___
* \ \ / / \ \ / / __/
* \ \/ / /\ \ \/ / /
* \____/__/ \__\____/__/
*
* Copyright 2014-2024 Vavr, http://vavr.io
*
* 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 io.vavr.match;
import io.vavr.match.annotation.Patterns;
import io.vavr.match.annotation.Unapply;
import io.vavr.match.generator.Generator;
import io.vavr.match.model.ClassModel;
import io.vavr.match.model.MethodModel;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import java.io.IOException;
import java.io.Writer;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import static java.util.stream.Collectors.toList;
/**
* A code generator for Vavr structural pattern matching patterns.
*
* Note:
*
* If javac complains {@code [WARNING] No processor claimed any of these annotations: ...}
* we need to provide the compiler arg {@code -Xlint:-processing}.
*
* See JDK-6999068 bug.
*
* @author Daniel Dietrich
*/
// See Difference between Element, Type and Mirror: http://stackoverflow.com/a/2127320/1110815
public class PatternsProcessor extends AbstractProcessor {
@Override
public Set getSupportedAnnotationTypes() {
// we do not use @SupportedAnnotationTypes in order to be type-safe
return Collections.singleton(Patterns.class.getName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
// intended to be used with Java 8+
return SourceVersion.latestSupported();
}
/**
* Gathers annotated elements, transforms elements to a generator model and generates the model to code.
*
* @param annotations the annotation types requested to be processed
* @param roundEnv environment for information about the current and prior round
* @return whether or not the set of annotation types are claimed by this processor
*/
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (!annotations.isEmpty()) {
final Set typeElements = roundEnv.getElementsAnnotatedWith(Patterns.class).stream()
.filter(element -> element instanceof TypeElement)
.map(element -> (TypeElement) element)
.collect(Collectors.toSet());
if (!typeElements.isEmpty()) {
final Set classModels = transform(typeElements);
if (!classModels.isEmpty()) {
generate(classModels);
}
}
}
return true;
}
// Verify correct usage of annotations @Patterns and @Unapply
private Set transform(Set typeElements) {
final Set classModels = new HashSet<>();
final javax.lang.model.util.Elements elementUtils = processingEnv.getElementUtils();
final Messager messager = processingEnv.getMessager();
for (TypeElement typeElement : typeElements) {
final ClassModel classModel = ClassModel.of(elementUtils, typeElement);
final List methodModels = classModel.getMethods().stream()
.filter(method -> method.isAnnotatedWith(Unapply.class))
.collect(toList());
if (methodModels.isEmpty()) {
messager.printMessage(Diagnostic.Kind.WARNING, "No @Unapply methods found.", classModel.typeElement());
} else {
final boolean methodsValid = methodModels.stream().reduce(true, (bool, method) -> bool && UnapplyChecker.isValid(method.getExecutableElement(), messager), (b1, b2) -> b1 && b2);
if (methodsValid) {
classModels.add(classModel);
}
}
}
return classModels;
}
// Expands all @Patterns classes
private void generate(Set classModels) {
final Filer filer = processingEnv.getFiler();
for (ClassModel classModel : classModels) {
final String derivedClassName = deriveClassName(classModel);
final String code = Generator.generate(derivedClassName, classModel);
final String fqn = (classModel.hasDefaultPackage() ? "" : classModel.getPackageName() + ".") + derivedClassName;
try (final Writer writer = filer.createSourceFile(fqn, classModel.typeElement()).openWriter()) {
writer.write(code);
} catch (IOException x) {
throw new Error("Error writing " + fqn, x);
}
}
}
private String deriveClassName(ClassModel classModel) {
final String name = classModel.getClassName();
return ("$".equals(name) ? "" : name.endsWith(".$") ? name.substring(0, name.length() - 2) : name)
.replaceAll("\\.", "_")
+ Patterns.class.getSimpleName();
}
}