javaslang.match.generator.Generator Maven / Gradle / Ivy
/* / \____ _ _ ____ ______ / \ ____ __ _______
* / / \/ \ / \/ \ / /\__\/ // \/ \ // /\__\ JΛVΛSLΛNG
* _/ / /\ \ \/ / /\ \\__\\ \ // /\ \ /\\/ \ /__\ \ Copyright 2014-2016 Javaslang, http://javaslang.io
* /___/\_/ \_/\____/\_/ \_/\__\/__/\__\_/ \_// \__/\_____/ Licensed under the Apache License, Version 2.0
*/
package javaslang.match.generator;
import javaslang.match.annotation.Unapply;
import javaslang.match.model.ClassModel;
import javaslang.match.model.MethodModel;
import javaslang.match.model.TypeParameterModel;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.IntStream;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
/**
* Code generator for structural pattern matching patterns.
*
* @author Daniel Dietrich
* @since 2.0.0
*/
public class Generator {
private Generator() {
}
// ENTRY POINT: Expands one @Patterns class
public static String generate(String derivedClassName, ClassModel classModel) {
final List methodModels = classModel.getMethods().stream()
.filter(method -> method.isAnnotatedWith(Unapply.class))
.collect(toList());
final String _package = classModel.getPackageName();
final ImportManager im = ImportManager.forClass(classModel, "javaslang.API.Match");
final String methods = generate(im, classModel, methodModels);
return (_package.isEmpty() ? "" : "package " + _package + ";\n\n") +
im.getImports() +
"\n\n// GENERATED BY JAVASLANG <<>> derived from " + classModel.getFullQualifiedName() + "\n\n" +
"public final class " + derivedClassName + " {\n\n" +
" private " + derivedClassName + "() {\n" +
" }\n\n" +
methods +
"}\n";
}
// Expands the @Unapply methods of a @Patterns class
private static String generate(ImportManager im, ClassModel classModel, List methodModels) {
final StringBuilder builder = new StringBuilder();
for (MethodModel methodModel : methodModels) {
generate(im, classModel, methodModel, builder);
builder.append("\n");
}
return builder.toString();
}
// Expands one @Unapply method
private static void generate(ImportManager im, ClassModel classModel, MethodModel methodModel, StringBuilder builder) {
final String paramTypeName = im.getType(methodModel.getParameter(0).getType());
final String name = methodModel.getName();
final int arity = Integer.parseInt(methodModel.getReturnType().getClassName().substring("Tuple".length()));
final String body;
if (arity == 0) {
body = pattern(im, 0) + ".of(" + paramTypeName + ".class)";
} else {
final String args = IntStream.rangeClosed(1, arity).mapToObj(i -> "p" + i).collect(joining(", "));
final String unapplyRef = classModel.getFullQualifiedName() + "::" + name;
body = String.format("%s.of(%s, %s, %s)", pattern(im, arity), paramTypeName + ".class", args, unapplyRef);
}
final List typeArgs = methodModel.getTypeParameters().stream()
.map(typeParameterModel -> mapToName(im, typeParameterModel))
.collect(toList());
final List upperBoundArgs = deriveUpperBounds(typeArgs, methodModel.getReturnType().getTypeParameters().size());
final String returnType = genReturnType(im, methodModel, upperBoundArgs, arity);
final String method;
if (arity == 0 && methodModel.getTypeParameters().size() == 0) {
method = String.format("final %s %s = %s;", returnType, name, body);
} else {
final String generics = genGenerics(im, methodModel, typeArgs, upperBoundArgs);
final String params = genParams(im, upperBoundArgs, arity);
method = String.format("%s %s %s(%s) {\n return %s;\n }", generics, returnType, name, params, body);
}
builder.append(" public static ").append(method).append("\n");
}
// Introduces new upper generic type bounds for decomposed object parts
private static List deriveUpperBounds(List typeArgs, int count) {
final List result = new ArrayList<>();
final Set knownTypeArgs = new HashSet<>(typeArgs);
for (int i = 0; i < count; i++) {
String typeArg = "_" + (i + 1);
while (knownTypeArgs.contains(typeArg)) {
typeArg = "_" + typeArg;
}
result.add(typeArg);
knownTypeArgs.add(typeArg);
}
return result;
}
// Expands the generics part of a method declaration
private static String genGenerics(ImportManager im, MethodModel methodModel, List typeParameters, List upperBoundArgs) {
final List returnTypeArgs = methodModel.getReturnType().getTypeParameters();
if (typeParameters.size() + returnTypeArgs.size() == 0) {
return "";
} else {
final List result = new ArrayList<>(typeParameters);
for (int i = 0; i < returnTypeArgs.size(); i++) {
final String returnTypeArg = mapToName(im, returnTypeArgs.get(i));
result.add(upperBoundArgs.get(i) + " extends " + returnTypeArg);
}
return result.stream().collect(joining(", ", "<", ">"));
}
}
// Expands the return type of a method declaration
private static String genReturnType(ImportManager im, MethodModel methodModel, List upperBoundArgs, int arity) {
final List resultTypes = new ArrayList<>();
final String type = mapToName(im, methodModel.getParameter(0).getType());
resultTypes.add(type);
resultTypes.addAll(upperBoundArgs);
return pattern(im, arity) + resultTypes.stream().collect(joining(", ", "<", ">"));
}
// Expands the parameters of a method declaration
private static String genParams(ImportManager im, List upperBoundArgs, int arity) {
final String patternType = im.getType("javaslang", "API.Match.Pattern");
return IntStream.range(0, arity)
.mapToObj(i -> patternType + "<" + upperBoundArgs.get(i) + ", ?> p" + (i + 1))
.collect(joining(", "));
}
// Recursively maps generic type parameters to names according to their kind
private static String mapToName(ImportManager im, TypeParameterModel typeParameterModel) {
if (typeParameterModel.isType()) {
return mapToName(im, typeParameterModel.asType());
} else if (typeParameterModel.isTypeVar()) {
return typeParameterModel.asTypeVar();
} else {
throw new IllegalStateException("Unhandled type parameter: " + typeParameterModel.toString());
}
}
// Recursively maps class generics to names
private static String mapToName(ImportManager im, ClassModel classModel) {
final List typeParameters = classModel.getTypeParameters();
final String simpleName = im.getType(classModel);
if (typeParameters.size() == 0) {
return simpleName;
} else {
return simpleName + classModel.getTypeParameters().stream()
.map(typeParam -> mapToName(im, typeParam))
.collect(joining(", ", "<", ">"));
}
}
private static String pattern(ImportManager im, int arity) {
return im.getType("javaslang", "API.Match.Pattern" + arity);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy