1
0
mirror of https://gitee.com/dcren/initializr.git synced 2025-04-05 17:38:06 +08:00

Introduce CodeBlock

This commit introduces a way to build more complex statement, including
multi statements and nested method invocations. CodeBlock is heavily
inspired from Square's JavaPoet project.

A CodeBlock is an immutable piece of code that provide the imports that
are needed and can emit code to an IndentingWriter.

This commit also migrates the use of expressions in Java, Kotlin, and
Groovy to the newly introduced CodeBlock. Those are deprecated in
favor of CodeBlock.

Closes gh-1043
This commit is contained in:
Stephane Nicoll 2023-06-02 18:32:08 +02:00
parent be0d541d4d
commit 8acbad503a
31 changed files with 889 additions and 129 deletions

View File

@ -23,11 +23,9 @@ import io.spring.initializr.generator.buildsystem.maven.MavenBuildSystem;
import io.spring.initializr.generator.condition.ConditionalOnBuildSystem;
import io.spring.initializr.generator.condition.ConditionalOnPackaging;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.groovy.GroovyExpressionStatement;
import io.spring.initializr.generator.language.groovy.GroovyMethodDeclaration;
import io.spring.initializr.generator.language.groovy.GroovyMethodInvocation;
import io.spring.initializr.generator.language.groovy.GroovyReturnStatement;
import io.spring.initializr.generator.language.groovy.GroovyTypeDeclaration;
import io.spring.initializr.generator.packaging.war.WarPackaging;
import io.spring.initializr.generator.project.ProjectDescription;
@ -58,14 +56,16 @@ class GroovyProjectGenerationDefaultContributorsConfiguration {
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
.returning("void")
.parameters(new Parameter("java.lang.String[]", "args"))
.body(new GroovyExpressionStatement(new GroovyMethodInvocation("org.springframework.boot.SpringApplication",
"run", typeDeclaration.getName(), "args"))));
.body(CodeBlock.ofStatement("$T.run($L, args)", "org.springframework.boot.SpringApplication",
typeDeclaration.getName())));
}
@Bean
TestApplicationTypeCustomizer<GroovyTypeDeclaration> junitJupiterTestMethodContributor() {
return (typeDeclaration) -> {
GroovyMethodDeclaration method = GroovyMethodDeclaration.method("contextLoads").returning("void").body();
GroovyMethodDeclaration method = GroovyMethodDeclaration.method("contextLoads")
.returning("void")
.body(CodeBlock.of(""));
method.annotate(Annotation.name("org.junit.jupiter.api.Test"));
typeDeclaration.addMethodDeclaration(method);
};
@ -92,8 +92,7 @@ class GroovyProjectGenerationDefaultContributorsConfiguration {
.returning("org.springframework.boot.builder.SpringApplicationBuilder")
.parameters(
new Parameter("org.springframework.boot.builder.SpringApplicationBuilder", "application"))
.body(new GroovyReturnStatement(
new GroovyMethodInvocation("application", "sources", description.getApplicationName())));
.body(CodeBlock.ofStatement("application.sources($L)", description.getApplicationName()));
configure.annotate(Annotation.name("java.lang.Override"));
typeDeclaration.addMethodDeclaration(configure);
};

View File

@ -20,11 +20,9 @@ import java.lang.reflect.Modifier;
import io.spring.initializr.generator.condition.ConditionalOnPackaging;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.java.JavaExpressionStatement;
import io.spring.initializr.generator.language.java.JavaMethodDeclaration;
import io.spring.initializr.generator.language.java.JavaMethodInvocation;
import io.spring.initializr.generator.language.java.JavaReturnStatement;
import io.spring.initializr.generator.language.java.JavaTypeDeclaration;
import io.spring.initializr.generator.packaging.war.WarPackaging;
import io.spring.initializr.generator.project.ProjectDescription;
@ -52,15 +50,17 @@ class JavaProjectGenerationDefaultContributorsConfiguration {
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
.returning("void")
.parameters(new Parameter("java.lang.String[]", "args"))
.body(new JavaExpressionStatement(new JavaMethodInvocation("org.springframework.boot.SpringApplication",
"run", typeDeclaration.getName() + ".class", "args"))));
.body(CodeBlock.ofStatement("$T.run($L.class, args)", "org.springframework.boot.SpringApplication",
typeDeclaration.getName())));
};
}
@Bean
TestApplicationTypeCustomizer<JavaTypeDeclaration> junitJupiterTestMethodContributor() {
return (typeDeclaration) -> {
JavaMethodDeclaration method = JavaMethodDeclaration.method("contextLoads").returning("void").body();
JavaMethodDeclaration method = JavaMethodDeclaration.method("contextLoads")
.returning("void")
.body(CodeBlock.of(""));
method.annotate(Annotation.name("org.junit.jupiter.api.Test"));
typeDeclaration.addMethodDeclaration(method);
};
@ -83,8 +83,8 @@ class JavaProjectGenerationDefaultContributorsConfiguration {
.returning("org.springframework.boot.builder.SpringApplicationBuilder")
.parameters(
new Parameter("org.springframework.boot.builder.SpringApplicationBuilder", "application"))
.body(new JavaReturnStatement(new JavaMethodInvocation("application", "sources",
description.getApplicationName() + ".class")));
.body(CodeBlock.ofStatement("return application.sources($L.class)",
description.getApplicationName()));
configure.annotate(Annotation.name("java.lang.Override"));
typeDeclaration.addMethodDeclaration(configure);
};

View File

@ -23,14 +23,11 @@ import io.spring.initializr.generator.condition.ConditionalOnBuildSystem;
import io.spring.initializr.generator.condition.ConditionalOnPackaging;
import io.spring.initializr.generator.condition.ConditionalOnPlatformVersion;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.kotlin.KotlinCompilationUnit;
import io.spring.initializr.generator.language.kotlin.KotlinExpressionStatement;
import io.spring.initializr.generator.language.kotlin.KotlinFunctionDeclaration;
import io.spring.initializr.generator.language.kotlin.KotlinFunctionInvocation;
import io.spring.initializr.generator.language.kotlin.KotlinModifier;
import io.spring.initializr.generator.language.kotlin.KotlinReifiedFunctionInvocation;
import io.spring.initializr.generator.language.kotlin.KotlinReturnStatement;
import io.spring.initializr.generator.language.kotlin.KotlinTypeDeclaration;
import io.spring.initializr.generator.packaging.war.WarPackaging;
import io.spring.initializr.generator.project.ProjectDescription;
@ -56,7 +53,8 @@ class KotlinProjectGenerationDefaultContributorsConfiguration {
@Bean
TestApplicationTypeCustomizer<KotlinTypeDeclaration> junitJupiterTestMethodContributor() {
return (typeDeclaration) -> {
KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("contextLoads").body();
KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("contextLoads")
.body(CodeBlock.of(""));
function.annotate(Annotation.name("org.junit.jupiter.api.Test"));
typeDeclaration.addFunctionDeclaration(function);
};
@ -96,12 +94,10 @@ class KotlinProjectGenerationDefaultContributorsConfiguration {
@Bean
MainCompilationUnitCustomizer<KotlinTypeDeclaration, KotlinCompilationUnit> mainFunctionContributor(
ProjectDescription description) {
return (compilationUnit) -> compilationUnit.addTopLevelFunction(
KotlinFunctionDeclaration.function("main")
.parameters(new Parameter("Array<String>", "args"))
.body(new KotlinExpressionStatement(
new KotlinReifiedFunctionInvocation("org.springframework.boot.runApplication",
description.getApplicationName(), "*args"))));
return (compilationUnit) -> compilationUnit.addTopLevelFunction(KotlinFunctionDeclaration.function("main")
.parameters(new Parameter("Array<String>", "args"))
.body(CodeBlock.ofStatement("$T<$L>(*args)", "org.springframework.boot.runApplication",
description.getApplicationName())));
}
}
@ -122,8 +118,8 @@ class KotlinProjectGenerationDefaultContributorsConfiguration {
.returning("org.springframework.boot.builder.SpringApplicationBuilder")
.parameters(
new Parameter("org.springframework.boot.builder.SpringApplicationBuilder", "application"))
.body(new KotlinReturnStatement(new KotlinFunctionInvocation("application", "sources",
description.getApplicationName() + "::class.java")));
.body(CodeBlock.ofStatement("return application.sources($L::class.java)",
description.getApplicationName()));
typeDeclaration.addFunctionDeclaration(configure);
};
}

View File

@ -0,0 +1,310 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* 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
*
* https://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.spring.initializr.generator.language;
import java.util.ArrayList;
import java.util.List;
import io.spring.initializr.generator.io.IndentingWriter;
import org.springframework.util.ClassUtils;
/**
* A fragment of code, potentially containing declarations, or statements. CodeBlocks are
* not validated.
* <p>
* Code blocks support placeholders identified by {@code $}. The following placeholders
* are supported:
* <ul>
* <li>{@code $L} emits a literal value. Arguments for literals may be plain String,
* primitives, or any type where the {@code toString()} representation can be used.
* <li>{@code $S} escapes the value as a string, wraps it with double quotes, and emits
* that. Emit {@code "null"} if the value is {@code null}. Does not handle multi-line
* strings.
* <li>{@code $T} emits a type reference. Arguments for types may be plain
* {@linkplain Class classes}, fully qualified class names, and fully qualified
* functions.</li>
* <li>{@code $$} emits a dollar sign.
* <li>{@code $]} ends a statement and emits the configured
* {@linkplain FormattingOptions#statementSeparator() statement separator}.
* </ul>
* <p>
* Code blocks can be {@linkplain #write(IndentingWriter, FormattingOptions) rendered}
* using an {@link IndentingWriter} and {@link FormattingOptions}.
* <p>
* This class is heavily inspired by JavaPoet.
*
* @author Stephane Nicoll
*/
public final class CodeBlock {
/**
* Standard {@link FormattingOptions} for Java.
*/
public static final FormattingOptions JAVA_FORMATTING_OPTIONS = new JavaFormattingOptions();
private final List<String> parts;
private final List<Object> args;
private final List<String> imports;
private CodeBlock(Builder builder) {
this.parts = List.copyOf(builder.parts);
this.args = List.copyOf(builder.args);
this.imports = List.copyOf(builder.imports);
}
/**
* Return the imports this instance contributes.
* @return the imports.
*/
public List<String> getImports() {
return this.imports;
}
/**
* Write this instance using the specified writer.
* @param writer the writer to use
* @param options the formatting options to use
*/
public void write(IndentingWriter writer, FormattingOptions options) {
int argIndex = 0;
for (String part : this.parts) {
switch (part) {
case "$L" -> writer.print(String.valueOf(this.args.get(argIndex++)));
case "$S" -> {
String value = (String) this.args.get(argIndex++);
String valueToEmit = (value != null) ? quote(value) : "null";
writer.print(valueToEmit);
}
case "$T" -> {
String className = (String) this.args.get(argIndex++);
writer.print(className);
}
case "$]" -> writer.println(options.statementSeparator());
case "$$" -> writer.print("$");
default -> writer.print(part);
}
}
}
private static String quote(String value) {
StringBuilder result = new StringBuilder(value.length() + 2);
result.append('"');
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == '\'') {
result.append("'");
continue;
}
if (c == '\"') {
result.append("\\\"");
continue;
}
result.append(c);
}
result.append('"');
return result.toString();
}
/**
* Create a code block using the specified code and optional arguments. To create a
* single statement, consider {@link #ofStatement(String, Object...)} instead.
* @param format the code
* @param args the arguments, if any
* @return a new instance
* @see #builder()
*/
public static CodeBlock of(String format, Object... args) {
return new Builder().add(format, args).build();
}
/**
* Create a code block with a single statement using the specified code and optional
* arguments.
* @param format the statement
* @param args the arguments, if any
* @return a new instance
* @see #builder()
*/
public static CodeBlock ofStatement(String format, Object... args) {
return new Builder().addStatement(format, args).build();
}
/**
* Initialize a new builder.
* @return a code block builder
*/
public static Builder builder() {
return new Builder();
}
public static final class Builder {
private final List<String> parts = new ArrayList<>();
private final List<Object> args = new ArrayList<>();
private final List<String> imports = new ArrayList<>();
private Builder() {
}
/**
* Add the specified {@link CodeBlock}, without any extra line or statement
* separator.
* @param codeBlock the code to add
* @return this for method chaining
*/
public Builder add(CodeBlock codeBlock) {
this.parts.addAll(codeBlock.parts);
this.args.addAll(codeBlock.args);
this.imports.addAll(codeBlock.imports);
return this;
}
/**
* Add more code using the specified code and optional arguments.
* @param format the code
* @param args the arguments, if any
* @return this for method chaining
*/
public Builder add(String format, Object... args) {
int relativeParameterCount = 0;
for (int p = 0; p < format.length();) {
if (format.charAt(p) != '$') {
int nextP = format.indexOf('$', p + 1);
nextP = (nextP != -1) ? nextP : format.length();
this.parts.add(format.substring(p, nextP));
p = nextP;
continue;
}
p++; // placeholder
if (p >= format.length()) {
throw new IllegalArgumentException("Should not end with '$': '%s'".formatted(format));
}
char placeHolderType = format.charAt(p++);
if (!isNoArgPlaceholder(placeHolderType)) {
int index = relativeParameterCount;
relativeParameterCount++;
if (index >= args.length) {
throw new IllegalArgumentException("Argument mismatch for '%s', expected at least %s %s, got %s"
.formatted(format, relativeParameterCount,
(relativeParameterCount > 1) ? "arguments" : "argument", args.length));
}
addArgument(format, placeHolderType, args[index]);
}
this.parts.add("$" + placeHolderType);
}
if (relativeParameterCount != args.length) {
throw new IllegalArgumentException(
"Argument mismatch for '%s', expected %s %s, got %s".formatted(format, relativeParameterCount,
(relativeParameterCount > 1) ? "arguments" : "argument", args.length));
}
return this;
}
private boolean isNoArgPlaceholder(char c) {
return c == '$' || c == ']';
}
private void addArgument(String format, char c, Object arg) {
switch (c) {
case 'L' -> this.args.add(arg);
case 'S' -> this.args.add(argToString(arg));
case 'T' -> this.args.add(argToType(arg));
default -> throw new IllegalArgumentException(
String.format("Unsupported placeholder '$%s' for '%s'", c, format));
}
}
private String argToString(Object o) {
return (o != null) ? String.valueOf(o) : null;
}
private String argToType(Object arg) {
if (arg instanceof Class type) {
this.imports.add(type.getName());
return type.getSimpleName();
}
if (arg instanceof String className) {
this.imports.add(className);
return ClassUtils.getShortName(className);
}
throw new IllegalArgumentException("Failed to extract type from '%s'".formatted(arg));
}
/**
* Add the specified {@link CodeBlock} as a statement.
* @param codeBlock the code to add
* @return this for method chaining
*/
public Builder addStatement(CodeBlock codeBlock) {
add(codeBlock);
this.parts.add("$]");
return this;
}
/**
* Add more code using the specified code and optional arguments and indicate that
* this statement is finished.
* @param format the code
* @param args the arguments, if any
* @return this for method chaining
*/
public Builder addStatement(String format, Object... args) {
add(format, args);
this.parts.add("$]");
return this;
}
/**
* Build a {@link CodeBlock} with the current state of this builder.
* @return a {@link CodeBlock}
*/
public CodeBlock build() {
return new CodeBlock(this);
}
}
/**
* Strategy interface to customize formatting of generated code block.
*/
public interface FormattingOptions {
/**
* Return the separator to use to end a statement.
* @return the statement separator
*/
String statementSeparator();
}
private static class JavaFormattingOptions implements FormattingOptions {
@Override
public String statementSeparator() {
return ";";
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A Groovy expression.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class GroovyExpression {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A statement that contains a single expression.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class GroovyExpressionStatement extends GroovyStatement {
private final GroovyExpression expression;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import java.util.List;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Parameter;
/**
@ -43,15 +44,26 @@ public final class GroovyMethodDeclaration implements Annotatable {
private final List<Parameter> parameters;
private final CodeBlock code;
private final List<GroovyStatement> statements;
private GroovyMethodDeclaration(String name, String returnType, int modifiers, List<Parameter> parameters,
List<GroovyStatement> statements) {
this.name = name;
this.returnType = returnType;
this.modifiers = modifiers;
this.parameters = parameters;
this.statements = statements;
private GroovyMethodDeclaration(Builder builder, CodeBlock code) {
this.name = builder.name;
this.returnType = builder.returnType;
this.modifiers = builder.modifiers;
this.parameters = List.copyOf(builder.parameters);
this.code = code;
this.statements = Collections.emptyList();
}
private GroovyMethodDeclaration(Builder builder, List<GroovyStatement> statements) {
this.name = builder.name;
this.returnType = builder.returnType;
this.modifiers = builder.modifiers;
this.parameters = List.copyOf(builder.parameters);
this.code = CodeBlock.of("");
this.statements = List.copyOf(statements);
}
public static Builder method(String name) {
@ -74,6 +86,11 @@ public final class GroovyMethodDeclaration implements Annotatable {
return this.modifiers;
}
CodeBlock getCode() {
return this.code;
}
@Deprecated(since = "0.20.0", forRemoval = true)
public List<GroovyStatement> getStatements() {
return this.statements;
}
@ -120,9 +137,13 @@ public final class GroovyMethodDeclaration implements Annotatable {
return this;
}
public GroovyMethodDeclaration body(CodeBlock code) {
return new GroovyMethodDeclaration(this, code);
}
@Deprecated(since = "0.20.0", forRemoval = true)
public GroovyMethodDeclaration body(GroovyStatement... statements) {
return new GroovyMethodDeclaration(this.name, this.returnType, this.modifiers, this.parameters,
Arrays.asList(statements));
return new GroovyMethodDeclaration(this, Arrays.asList(statements));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,11 +19,15 @@ package io.spring.initializr.generator.language.groovy;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.language.CodeBlock;
/**
* An invocation of a method.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class GroovyMethodInvocation extends GroovyExpression {
private final String target;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A return statement.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class GroovyReturnStatement extends GroovyStatement {
private final GroovyExpression expression;

View File

@ -38,6 +38,7 @@ import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
@ -51,6 +52,8 @@ import io.spring.initializr.generator.language.SourceStructure;
*/
public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode> {
private static final FormattingOptions FORMATTING_OPTIONS = new GroovyFormattingOptions();
private static final Map<Predicate<Integer>, String> TYPE_MODIFIERS;
private static final Map<Predicate<Integer>, String> FIELD_MODIFIERS;
@ -208,21 +211,27 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
}
writer.println(") {");
writer.indented(() -> {
List<GroovyStatement> statements = methodDeclaration.getStatements();
for (GroovyStatement statement : statements) {
if (statement instanceof GroovyExpressionStatement) {
writeExpression(writer, ((GroovyExpressionStatement) statement).getExpression());
}
else if (statement instanceof GroovyReturnStatement) {
writeExpression(writer, ((GroovyReturnStatement) statement).getExpression());
}
writer.println();
}
methodDeclaration.getCode().write(writer, FORMATTING_OPTIONS);
writeStatements(writer, methodDeclaration);
});
writer.println("}");
writer.println();
}
@SuppressWarnings("removal")
private void writeStatements(IndentingWriter writer, GroovyMethodDeclaration methodDeclaration) {
List<GroovyStatement> statements = methodDeclaration.getStatements();
for (GroovyStatement statement : statements) {
if (statement instanceof GroovyExpressionStatement) {
writeExpression(writer, ((GroovyExpressionStatement) statement).getExpression());
}
else if (statement instanceof GroovyReturnStatement) {
writeExpression(writer, ((GroovyReturnStatement) statement).getExpression());
}
writer.println();
}
}
private void writeModifiers(IndentingWriter writer, Map<Predicate<Integer>, String> availableModifiers,
int declaredModifiers) {
String modifiers = availableModifiers.entrySet()
@ -267,21 +276,27 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
imports.addAll(getRequiredImports(methodDeclaration.getAnnotations(), this::determineImports));
imports.addAll(getRequiredImports(methodDeclaration.getParameters(),
(parameter) -> Collections.singletonList(parameter.getType())));
imports.addAll(getRequiredImports(
methodDeclaration.getStatements()
.stream()
.filter(GroovyExpressionStatement.class::isInstance)
.map(GroovyExpressionStatement.class::cast)
.map(GroovyExpressionStatement::getExpression)
.filter(GroovyMethodInvocation.class::isInstance)
.map(GroovyMethodInvocation.class::cast),
(methodInvocation) -> Collections.singleton(methodInvocation.getTarget())));
imports.addAll(methodDeclaration.getCode().getImports());
determineImportsFromStatements(imports, methodDeclaration);
}
}
Collections.sort(imports);
return new LinkedHashSet<>(imports);
}
@SuppressWarnings("removal")
private void determineImportsFromStatements(List<String> imports, GroovyMethodDeclaration methodDeclaration) {
imports.addAll(getRequiredImports(
methodDeclaration.getStatements()
.stream()
.filter(GroovyExpressionStatement.class::isInstance)
.map(GroovyExpressionStatement.class::cast)
.map(GroovyExpressionStatement::getExpression)
.filter(GroovyMethodInvocation.class::isInstance)
.map(GroovyMethodInvocation.class::cast),
(methodInvocation) -> Collections.singleton(methodInvocation.getTarget())));
}
private Collection<String> determineImports(Annotation annotation) {
List<String> imports = new ArrayList<>();
imports.add(annotation.getName());
@ -325,4 +340,13 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
return !"java.lang".equals(packageName);
}
static class GroovyFormattingOptions implements FormattingOptions {
@Override
public String statementSeparator() {
return "";
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A statement in Groovy.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class GroovyStatement {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.java;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A Java expression.
*
* @author Andy Wilkinson
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class JavaExpression {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.java;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A statement that contains a single expression.
*
* @author Andy Wilkinson
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class JavaExpressionStatement extends JavaStatement {
private final JavaExpression expression;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ import java.util.List;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Parameter;
/**
@ -42,14 +43,26 @@ public final class JavaMethodDeclaration implements Annotatable {
private final List<Parameter> parameters;
private final CodeBlock code;
private final List<JavaStatement> statements;
private JavaMethodDeclaration(Builder builder, CodeBlock code) {
this.name = builder.name;
this.returnType = builder.returnType;
this.modifiers = builder.modifiers;
this.parameters = List.copyOf(builder.parameters);
this.code = code;
this.statements = Collections.emptyList();
}
private JavaMethodDeclaration(String name, String returnType, int modifiers, List<Parameter> parameters,
List<JavaStatement> statements) {
this.name = name;
this.returnType = returnType;
this.modifiers = modifiers;
this.parameters = parameters;
this.code = CodeBlock.of((""));
this.statements = statements;
}
@ -73,6 +86,11 @@ public final class JavaMethodDeclaration implements Annotatable {
return this.modifiers;
}
CodeBlock getCode() {
return this.code;
}
@Deprecated(since = "0.20.0", forRemoval = true)
public List<JavaStatement> getStatements() {
return this.statements;
}
@ -119,6 +137,11 @@ public final class JavaMethodDeclaration implements Annotatable {
return this;
}
public JavaMethodDeclaration body(CodeBlock code) {
return new JavaMethodDeclaration(this, code);
}
@Deprecated(since = "0.20.0", forRemoval = true)
public JavaMethodDeclaration body(JavaStatement... statements) {
return new JavaMethodDeclaration(this.name, this.returnType, this.modifiers, this.parameters,
Arrays.asList(statements));

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,11 +19,15 @@ package io.spring.initializr.generator.language.java;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.language.CodeBlock;
/**
* An invocation of a method.
*
* @author Andy Wilkinson
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class JavaMethodInvocation extends JavaExpression {
private final String target;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.java;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A return statement.
*
* @author Andy Wilkinson
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class JavaReturnStatement extends JavaStatement {
private final JavaExpression expression;

View File

@ -38,6 +38,7 @@ import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
@ -210,22 +211,28 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
}
writer.println(") {");
writer.indented(() -> {
List<JavaStatement> statements = methodDeclaration.getStatements();
for (JavaStatement statement : statements) {
if (statement instanceof JavaExpressionStatement) {
writeExpression(writer, ((JavaExpressionStatement) statement).getExpression());
}
else if (statement instanceof JavaReturnStatement) {
writer.print("return ");
writeExpression(writer, ((JavaReturnStatement) statement).getExpression());
}
writer.println(";");
}
methodDeclaration.getCode().write(writer, CodeBlock.JAVA_FORMATTING_OPTIONS);
writeJavaStatements(writer, methodDeclaration);
});
writer.println("}");
writer.println();
}
@SuppressWarnings("removal")
private void writeJavaStatements(IndentingWriter writer, JavaMethodDeclaration methodDeclaration) {
List<JavaStatement> statements = methodDeclaration.getStatements();
for (JavaStatement statement : statements) {
if (statement instanceof JavaExpressionStatement) {
writeExpression(writer, ((JavaExpressionStatement) statement).getExpression());
}
else if (statement instanceof JavaReturnStatement) {
writer.print("return ");
writeExpression(writer, ((JavaReturnStatement) statement).getExpression());
}
writer.println(";");
}
}
private void writeModifiers(IndentingWriter writer, Map<Predicate<Integer>, String> availableModifiers,
int declaredModifiers) {
String modifiers = availableModifiers.entrySet()
@ -270,21 +277,27 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
imports.addAll(getRequiredImports(methodDeclaration.getAnnotations(), this::determineImports));
imports.addAll(getRequiredImports(methodDeclaration.getParameters(),
(parameter) -> Collections.singletonList(parameter.getType())));
imports.addAll(getRequiredImports(
methodDeclaration.getStatements()
.stream()
.filter(JavaExpressionStatement.class::isInstance)
.map(JavaExpressionStatement.class::cast)
.map(JavaExpressionStatement::getExpression)
.filter(JavaMethodInvocation.class::isInstance)
.map(JavaMethodInvocation.class::cast),
(methodInvocation) -> Collections.singleton(methodInvocation.getTarget())));
determineImportsFromStatements(imports, methodDeclaration);
imports.addAll(methodDeclaration.getCode().getImports());
}
}
Collections.sort(imports);
return new LinkedHashSet<>(imports);
}
@SuppressWarnings("removal")
private void determineImportsFromStatements(List<String> imports, JavaMethodDeclaration methodDeclaration) {
imports.addAll(getRequiredImports(
methodDeclaration.getStatements()
.stream()
.filter(JavaExpressionStatement.class::isInstance)
.map(JavaExpressionStatement.class::cast)
.map(JavaExpressionStatement::getExpression)
.filter(JavaMethodInvocation.class::isInstance)
.map(JavaMethodInvocation.class::cast),
(methodInvocation) -> Collections.singleton(methodInvocation.getTarget())));
}
private Collection<String> determineImports(Annotation annotation) {
List<String> imports = new ArrayList<>();
imports.add(annotation.getName());

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.java;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A statement in Java.
*
* @author Andy Wilkinson
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class JavaStatement {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.kotlin;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A Kotlin expression.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class KotlinExpression {
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.kotlin;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A statement that contains a single expression.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class KotlinExpressionStatement extends KotlinStatement {
private final KotlinExpression expression;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -23,6 +23,7 @@ import java.util.List;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Parameter;
/**
@ -42,15 +43,26 @@ public final class KotlinFunctionDeclaration implements Annotatable {
private final List<Parameter> parameters;
private final CodeBlock code;
private final List<KotlinStatement> statements;
private KotlinFunctionDeclaration(String name, String returnType, List<KotlinModifier> modifiers,
List<Parameter> parameters, List<KotlinStatement> statements) {
this.name = name;
this.returnType = returnType;
this.modifiers = modifiers;
this.parameters = parameters;
this.statements = statements;
private KotlinFunctionDeclaration(Builder builder, CodeBlock code) {
this.name = builder.name;
this.returnType = builder.returnType;
this.modifiers = builder.modifiers;
this.parameters = List.copyOf(builder.parameters);
this.code = code;
this.statements = Collections.emptyList();
}
private KotlinFunctionDeclaration(Builder builder, List<KotlinStatement> statements) {
this.name = builder.name;
this.returnType = builder.returnType;
this.modifiers = builder.modifiers;
this.parameters = List.copyOf(builder.parameters);
this.code = CodeBlock.of("");
this.statements = List.copyOf(statements);
}
public static Builder function(String name) {
@ -73,6 +85,11 @@ public final class KotlinFunctionDeclaration implements Annotatable {
return this.modifiers;
}
CodeBlock getCode() {
return this.code;
}
@Deprecated(since = "0.20.0", forRemoval = true)
public List<KotlinStatement> getStatements() {
return this.statements;
}
@ -119,9 +136,13 @@ public final class KotlinFunctionDeclaration implements Annotatable {
return this;
}
public KotlinFunctionDeclaration body(CodeBlock code) {
return new KotlinFunctionDeclaration(this, code);
}
@Deprecated(since = "0.20.0", forRemoval = true)
public KotlinFunctionDeclaration body(KotlinStatement... statements) {
return new KotlinFunctionDeclaration(this.name, this.returnType, this.modifiers, this.parameters,
Arrays.asList(statements));
return new KotlinFunctionDeclaration(this, Arrays.asList(statements));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,11 +19,15 @@ package io.spring.initializr.generator.language.kotlin;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.language.CodeBlock;
/**
* An invocation of a function.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class KotlinFunctionInvocation extends KotlinExpression {
private final String target;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2020 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -24,6 +24,7 @@ import java.util.function.Consumer;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
/**
* Declaration of a property written in Kotlin.
@ -197,6 +198,8 @@ public final class KotlinPropertyDeclaration implements Annotatable {
private final List<Annotation> annotations = new ArrayList<>();
private CodeBlock code;
private KotlinExpressionStatement body;
private final T parent;
@ -213,11 +216,17 @@ public final class KotlinPropertyDeclaration implements Annotatable {
return this;
}
@Deprecated(since = "0.20.0", forRemoval = true)
public AccessorBuilder<?> withBody(KotlinExpressionStatement expressionStatement) {
this.body = expressionStatement;
return this;
}
public AccessorBuilder<?> withBody(CodeBlock code) {
this.code = code;
return this;
}
public T buildAccessor() {
this.accessorFunction.accept(new Accessor(this));
return this.parent;
@ -229,17 +238,25 @@ public final class KotlinPropertyDeclaration implements Annotatable {
private final List<Annotation> annotations = new ArrayList<>();
private final CodeBlock code;
private final KotlinExpressionStatement body;
Accessor(AccessorBuilder<?> builder) {
this.annotations.addAll(builder.annotations);
this.code = builder.code;
this.body = builder.body;
}
boolean isEmptyBody() {
return this.body == null;
return (this.body == null && this.code == null);
}
CodeBlock getCode() {
return this.code;
}
@Deprecated(since = "0.20.0", forRemoval = true)
KotlinExpressionStatement getBody() {
return this.body;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -19,11 +19,15 @@ package io.spring.initializr.generator.language.kotlin;
import java.util.Arrays;
import java.util.List;
import io.spring.initializr.generator.language.CodeBlock;
/**
* Invocation of a function with a reified type parameter.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class KotlinReifiedFunctionInvocation extends KotlinExpression {
private final String name;

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.kotlin;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A return statement.
*
* @author Andy Wilkinson
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class KotlinReturnStatement extends KotlinStatement {
private final KotlinExpression expression;

View File

@ -34,6 +34,7 @@ import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
@ -47,6 +48,8 @@ import io.spring.initializr.generator.language.SourceStructure;
*/
public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode> {
private static final FormattingOptions FORMATTING_OPTIONS = new KotlinFormattingOptions();
private final IndentingWriterFactory indentingWriterFactory;
public KotlinSourceCodeWriter(IndentingWriterFactory indentingWriterFactory) {
@ -157,7 +160,12 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
writer.print(accessorName);
if (!accessor.isEmptyBody()) {
writer.print("() = ");
writeExpression(writer, accessor.getBody().getExpression());
if (accessor.getCode() != null) {
accessor.getCode().write(writer, FORMATTING_OPTIONS);
}
else if (accessor.getBody() != null) {
writeExpression(writer, accessor.getBody().getExpression());
}
}
}
@ -178,22 +186,28 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
writer.print(": " + getUnqualifiedName(functionDeclaration.getReturnType()));
}
writer.println(" {");
List<KotlinStatement> statements = functionDeclaration.getStatements();
writer.indented(() -> {
for (KotlinStatement statement : statements) {
if (statement instanceof KotlinExpressionStatement) {
writeExpression(writer, ((KotlinExpressionStatement) statement).getExpression());
}
else if (statement instanceof KotlinReturnStatement) {
writer.print("return ");
writeExpression(writer, ((KotlinReturnStatement) statement).getExpression());
}
writer.println("");
}
functionDeclaration.getCode().write(writer, FORMATTING_OPTIONS);
writeStatements(writer, functionDeclaration);
});
writer.println("}");
}
@SuppressWarnings("removal")
private void writeStatements(IndentingWriter writer, KotlinFunctionDeclaration functionDeclaration) {
List<KotlinStatement> statements = functionDeclaration.getStatements();
for (KotlinStatement statement : statements) {
if (statement instanceof KotlinExpressionStatement) {
writeExpression(writer, ((KotlinExpressionStatement) statement).getExpression());
}
else if (statement instanceof KotlinReturnStatement) {
writer.print("return ");
writeExpression(writer, ((KotlinReturnStatement) statement).getExpression());
}
writer.println("");
}
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
for (Annotation annotation : annotatable.getAnnotations()) {
writeAnnotation(writer, annotation, true);
@ -316,6 +330,7 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
imports.addAll(getRequiredImports(functionDeclaration.getAnnotations(), this::determineImports));
imports.addAll(getRequiredImports(functionDeclaration.getParameters(),
(parameter) -> Collections.singleton(parameter.getType())));
imports.addAll(functionDeclaration.getCode().getImports());
imports.addAll(getRequiredImports(
getKotlinExpressions(functionDeclaration).filter(KotlinFunctionInvocation.class::isInstance)
.map(KotlinFunctionInvocation.class::cast),
@ -344,6 +359,7 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
return imports;
}
@SuppressWarnings("removal")
private Stream<KotlinExpression> getKotlinExpressions(KotlinFunctionDeclaration functionDeclaration) {
return functionDeclaration.getStatements()
.stream()
@ -378,4 +394,13 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
return !"java.lang".equals(packageName);
}
static class KotlinFormattingOptions implements FormattingOptions {
@Override
public String statementSeparator() {
return "";
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2012-2019 the original author or authors.
* Copyright 2012-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,11 +16,15 @@
package io.spring.initializr.generator.language.kotlin;
import io.spring.initializr.generator.language.CodeBlock;
/**
* A statement in Kotlin.
*
* @author Stephane Nicoll
* @deprecated since 0.20.0 in favor of {@link CodeBlock}
*/
@Deprecated(since = "0.20.0", forRemoval = true)
public class KotlinStatement {
}

View File

@ -0,0 +1,173 @@
/*
* Copyright 2012-2023 the original author or authors.
*
* 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
*
* https://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.spring.initializr.generator.language;
import java.io.StringWriter;
import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.io.SimpleIndentStrategy;
import io.spring.initializr.generator.language.CodeBlock.FormattingOptions;
import org.junit.jupiter.api.Test;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link CodeBlock}.
*
* @author Stephane Nicoll
*/
class CodeBlockTests {
@Test
void codeBlockWithPlaceholderAndTooFewArguments() {
assertThatIllegalArgumentException().isThrownBy(() -> CodeBlock.of("$T.doStuff()"))
.withMessage("Argument mismatch for '$T.doStuff()', expected at least 1 argument, got 0");
}
@Test
void codeBlockWithPlaceholderAndTooManyArguments() {
assertThatIllegalArgumentException().isThrownBy(() -> CodeBlock.of("$T.doStuff()", String.class, Integer.class))
.withMessage("Argument mismatch for '$T.doStuff()', expected 1 argument, got 2");
}
@Test
void codeBlockWithInvalidPlaceholder() {
assertThatIllegalArgumentException().isThrownBy(() -> CodeBlock.of("$X.doStuff()", 123))
.withMessage("Unsupported placeholder '$X' for '$X.doStuff()'");
}
@Test
void codeBlockWithTrailingDollarSign() {
assertThatIllegalArgumentException().isThrownBy(() -> CodeBlock.of("doStuff()$"))
.withMessage("Should not end with '$': 'doStuff()$'");
}
@Test
void codeBlockWithStringPlaceholder() {
CodeBlock code = CodeBlock.of("return myUtil.truncate($S)", "value");
assertThat(writeJava(code)).isEqualTo("return myUtil.truncate(\"value\")");
}
@Test
void codeBlockWithStringPlaceholderAndDoubleQuote() {
CodeBlock code = CodeBlock.of("return myUtil.truncate($S)", "va\"lue");
assertThat(writeJava(code)).isEqualTo("return myUtil.truncate(\"va\\\"lue\")");
}
@Test
void codeBlockWithStringPlaceholderAndEscapedSingleQuote() {
CodeBlock code = CodeBlock.of("return myUtil.truncate($S)", "va\'lue");
assertThat(writeJava(code)).isEqualTo("return myUtil.truncate(\"va'lue\")");
}
@Test
void codeBlockWithLiteralPlaceholder() {
CodeBlock code = CodeBlock.of("return $L.truncate(myString)", "myUtil");
assertThat(writeJava(code)).isEqualTo("return myUtil.truncate(myString)");
}
@Test
void codeBlockWithDollarSignPlaceholder() {
CodeBlock code = CodeBlock.of("// $$ allowed, that's $$$L.", 25);
assertThat(writeJava(code)).isEqualTo("// $ allowed, that's $25.");
}
@Test
void codeBlockWithEndOfStatementPlaceholderInvokeConfiguredFormattingOptions() {
FormattingOptions options = mock(FormattingOptions.class);
given(options.statementSeparator()).willReturn(";");
CodeBlock code = CodeBlock.of("invoke(param)$]");
assertThat(write(code, options)).isEqualToNormalizingNewlines("""
invoke(param);
""");
verify(options).statementSeparator();
}
@Test
void codeBlockWithTypePlaceholderAndClassAddsImport() {
CodeBlock code = CodeBlock.of("return $T.truncate(myString)", StringUtils.class);
assertThat(writeJava(code)).isEqualTo("return StringUtils.truncate(myString)");
assertThat(code.getImports()).containsExactly(StringUtils.class.getName());
}
@Test
void codeBlockWithTypePlaceholderAndClassNameAddsImport() {
CodeBlock code = CodeBlock.of("return $T.truncate(myString)", "com.example.StringUtils");
assertThat(writeJava(code)).isEqualTo("return StringUtils.truncate(myString)");
assertThat(code.getImports()).containsExactly("com.example.StringUtils");
}
@Test
void codeBlockWithTypePlaceholderAndNonResolvableType() {
assertThatIllegalArgumentException().isThrownBy(() -> CodeBlock.of("return $T.truncate(myString)", true))
.withMessageContaining("Failed to extract type from 'true'");
}
@Test
void codeBlockDoesNotAddNewLine() {
CodeBlock code = CodeBlock.of("(123, 456)");
assertThat(writeJava(code)).isEqualTo("(123, 456)");
assertThat(code.getImports()).isEmpty();
}
@Test
void codeBlocksCanBeAdded() {
CodeBlock code = CodeBlock.builder().add("(123").add(CodeBlock.of(", 456)")).build();
assertThat(writeJava(code)).isEqualTo("(123, 456)");
assertThat(code.getImports()).isEmpty();
}
@Test
void codeBlockWithSingleStatement() {
CodeBlock code = CodeBlock.ofStatement("myInstance.sayHello(123)");
assertThat(writeJava(code)).isEqualToNormalizingNewlines("""
myInstance.sayHello(123);
""");
assertThat(code.getImports()).isEmpty();
}
@Test
void codeBlockWithMultipleStatements() {
CodeBlock code = CodeBlock.builder()
.addStatement("myInstance.sayHello(123)")
.addStatement(CodeBlock.of("myInstance.sayHello(456)"))
.build();
assertThat(writeJava(code)).isEqualToNormalizingNewlines("""
myInstance.sayHello(123);
myInstance.sayHello(456);
""");
assertThat(code.getImports()).isEmpty();
}
private String writeJava(CodeBlock code) {
return write(code, CodeBlock.JAVA_FORMATTING_OPTIONS);
}
private String write(CodeBlock code, FormattingOptions options) {
StringWriter out = new StringWriter();
IndentingWriter writer = new IndentingWriter(out, new SimpleIndentStrategy("\t"));
code.write(writer, options);
return out.toString();
}
}

View File

@ -29,6 +29,7 @@ import java.util.UUID;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Language;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceStructure;
@ -108,6 +109,22 @@ class GroovySourceCodeWriterTests {
@Test
void method() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addMethodDeclaration(GroovyMethodDeclaration.method("trim")
.returning("java.lang.String")
.parameters(new Parameter("java.lang.String", "value"))
.body(CodeBlock.ofStatement("value.trim()")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" String trim(String value) {", " value.trim()", " }", "", "}");
}
@Test
@Deprecated
@SuppressWarnings("removal")
void methodWithStatements() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
@ -130,8 +147,7 @@ class GroovySourceCodeWriterTests {
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
.returning("void")
.parameters(new Parameter("java.lang.String[]", "args"))
.body(new GroovyExpressionStatement(
new GroovyMethodInvocation("org.springframework.boot.SpringApplication", "run", "Test", "args"))));
.body(CodeBlock.ofStatement("$T.run($L, args)", "org.springframework.boot.SpringApplication", "Test")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "",
"import org.springframework.boot.SpringApplication",
@ -278,7 +294,7 @@ class GroovySourceCodeWriterTests {
GroovyMethodDeclaration method = GroovyMethodDeclaration.method("something")
.returning("void")
.parameters()
.body();
.body(CodeBlock.of(""));
method.annotate(Annotation.name("com.example.test.TestAnnotation"));
test.addMethodDeclaration(method);
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");

View File

@ -29,6 +29,7 @@ import java.util.UUID;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Language;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceStructure;
@ -107,6 +108,23 @@ class JavaSourceCodeWriterTests {
@Test
void method() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addMethodDeclaration(JavaMethodDeclaration.method("trim")
.returning("java.lang.String")
.modifiers(Modifier.PUBLIC)
.parameters(new Parameter("java.lang.String", "value"))
.body(CodeBlock.ofStatement("return value.trim()")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "", "class Test {", "",
" public String trim(String value) {", " return value.trim();", " }", "", "}");
}
@Test
@Deprecated
@SuppressWarnings("removal")
void methodWithStatements() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
@ -205,8 +223,8 @@ class JavaSourceCodeWriterTests {
.modifiers(Modifier.PUBLIC | Modifier.STATIC)
.returning("void")
.parameters(new Parameter("java.lang.String[]", "args"))
.body(new JavaExpressionStatement(new JavaMethodInvocation("org.springframework.boot.SpringApplication",
"run", "Test.class", "args"))));
.body(CodeBlock.ofStatement("$T.run($L.class, args)", "org.springframework.boot.SpringApplication",
"Test")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "",
"import org.springframework.boot.SpringApplication;",
@ -283,7 +301,10 @@ class JavaSourceCodeWriterTests {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
JavaMethodDeclaration method = JavaMethodDeclaration.method("something").returning("void").parameters().body();
JavaMethodDeclaration method = JavaMethodDeclaration.method("something")
.returning("void")
.parameters()
.body(CodeBlock.of(""));
method.annotate(Annotation.name("com.example.test.TestAnnotation"));
test.addMethodDeclaration(method);
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");

View File

@ -28,6 +28,7 @@ import java.util.UUID;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.CodeBlock;
import io.spring.initializr.generator.language.Language;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceStructure;
@ -107,6 +108,22 @@ class KotlinSourceCodeWriterTests {
@Test
void function() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFunctionDeclaration(KotlinFunctionDeclaration.function("reverse")
.returning("java.lang.String")
.parameters(new Parameter("java.lang.String", "echo"))
.body(CodeBlock.ofStatement("return echo.reversed()")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" fun reverse(echo: String): String {", " return echo.reversed()", " }", "", "}");
}
@Test
@Deprecated
@SuppressWarnings("removal")
void functionWithStatements() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
@ -127,7 +144,7 @@ class KotlinSourceCodeWriterTests {
test.addFunctionDeclaration(KotlinFunctionDeclaration.function("toString")
.modifiers(KotlinModifier.OVERRIDE, KotlinModifier.PUBLIC, KotlinModifier.OPEN)
.returning("java.lang.String")
.body(new KotlinReturnStatement(new KotlinFunctionInvocation("super", "toString"))));
.body(CodeBlock.ofStatement("return super.toString()")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" open override fun toString(): String {", " return super.toString()", " }", "", "}");
@ -159,6 +176,27 @@ class KotlinSourceCodeWriterTests {
@Test
void valGetterProperty() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(
KotlinPropertyDeclaration.val("testProp").returning("java.lang.String").value("\"This is a TEST\""));
test.addPropertyDeclaration(KotlinPropertyDeclaration.val("withGetter")
.returning("java.lang.String")
.getter()
.withBody(CodeBlock.of("testProp.toLowerCase()"))
.buildAccessor()
.emptyValue());
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" val testProp: String = \"This is a TEST\"", "", " val withGetter: String",
" get() = testProp.toLowerCase()", "", "}");
}
@Test
@Deprecated
@SuppressWarnings("removal")
void valGetterPropertyWithStatement() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
@ -265,8 +303,7 @@ class KotlinSourceCodeWriterTests {
test.annotate(Annotation.name("org.springframework.boot.autoconfigure.SpringBootApplication"));
compilationUnit.addTopLevelFunction(KotlinFunctionDeclaration.function("main")
.parameters(new Parameter("Array<String>", "args"))
.body(new KotlinExpressionStatement(
new KotlinReifiedFunctionInvocation("org.springframework.boot.runApplication", "Test", "*args"))));
.body(CodeBlock.ofStatement("$T<$L>(*args)", "org.springframework.boot.runApplication", "Test")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "",
"import org.springframework.boot.autoconfigure.SpringBootApplication",
@ -339,7 +376,7 @@ class KotlinSourceCodeWriterTests {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("something").body();
KotlinFunctionDeclaration function = KotlinFunctionDeclaration.function("something").body(CodeBlock.of(""));
function.annotate(Annotation.name("com.example.test.TestAnnotation"));
test.addFunctionDeclaration(function);
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");