Adding field declaration for code generation

See gh-881
This commit is contained in:
Matt Berteaux
2019-03-31 18:52:39 -06:00
committed by Stephane Nicoll
parent d8a5be76f5
commit e7d5f8ffaf
40 changed files with 2372 additions and 239 deletions

View File

@@ -27,7 +27,7 @@ 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.KotlinMethodInvocation;
import io.spring.initializr.generator.language.kotlin.KotlinModifier;
import io.spring.initializr.generator.language.kotlin.KotlinReifiedFunctionInvocation;
import io.spring.initializr.generator.language.kotlin.KotlinReturnStatement;
@@ -96,7 +96,7 @@ class KotlinProjectGenerationDefaultContributorsConfiguration {
return (compilationUnit) -> compilationUnit.addTopLevelFunction(
KotlinFunctionDeclaration.function("main").parameters(new Parameter("Array<String>", "args"))
.body(new KotlinExpressionStatement(
new KotlinFunctionInvocation("org.springframework.boot.SpringApplication", "run",
new KotlinMethodInvocation("org.springframework.boot.SpringApplication", "run",
projectDescription.getApplicationName() + "::class.java", "*args"))));
}
@@ -143,7 +143,7 @@ class KotlinProjectGenerationDefaultContributorsConfiguration {
.returning("org.springframework.boot.builder.SpringApplicationBuilder")
.parameters(new Parameter("org.springframework.boot.builder.SpringApplicationBuilder",
"application"))
.body(new KotlinReturnStatement(new KotlinFunctionInvocation("application", "sources",
.body(new KotlinReturnStatement(new KotlinMethodInvocation("application", "sources",
projectDescription.getApplicationName() + "::class.java")));
typeDeclaration.addFunctionDeclaration(configure);
};

View File

@@ -20,7 +20,8 @@ package io.spring.initializr.generator.language.groovy;
* A Groovy expression.
*
* @author Stephane Nicoll
* @author Matt Berteaux
*/
public class GroovyExpression {
public interface GroovyExpression {
}

View File

@@ -21,7 +21,7 @@ package io.spring.initializr.generator.language.groovy;
*
* @author Stephane Nicoll
*/
public class GroovyExpressionStatement extends GroovyStatement {
public class GroovyExpressionStatement implements GroovyStatement {
private final GroovyExpression expression;

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2012-2019 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.groovy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
/**
* Declaration, and potential initialization, of a field in Groovy.
*
* @author Matt Berteaux
*/
public final class GroovyFieldDeclaration implements Annotatable {
private final List<Annotation> annotations;
private final int modifiers;
private final String name;
private final String returnType;
private final Object value;
private GroovyFieldDeclaration(Builder builder) {
this.modifiers = builder.modifiers;
this.name = builder.name;
this.returnType = builder.returnType;
this.value = builder.value;
this.annotations = builder.annotations;
}
public static Builder field(String name) {
return new Builder(name);
}
@Override
public void annotate(Annotation annotation) {
this.annotations.add(annotation);
}
@Override
public List<Annotation> getAnnotations() {
return Collections.unmodifiableList(this.annotations);
}
public int getModifiers() {
return this.modifiers;
}
public String getName() {
return this.name;
}
public String getReturnType() {
return this.returnType;
}
public Object getValue() {
return this.value;
}
public static final class Builder {
private final List<Annotation> annotations = new ArrayList<>();
private final String name;
private String returnType;
private int modifiers;
private Object value = InitializedStatus.NOT_INITIALIZED;
private Builder(String name) {
this.name = name;
}
public Builder modifiers(int modifiers) {
this.modifiers = modifiers;
return this;
}
public Builder value(GroovyExpression value) {
this.value = value;
return this;
}
public Builder withAnnotation(Annotation annotation) {
this.annotations.add(annotation);
return this;
}
public GroovyFieldDeclaration returning(String returnType) {
this.returnType = returnType;
return new GroovyFieldDeclaration(this);
}
}
/**
* Track if the value has been set or not. Using this because initializing a field to
* null should be possible.
*/
enum InitializedStatus {
NOT_INITIALIZED;
}
}

View File

@@ -23,8 +23,9 @@ import java.util.List;
* An invocation of a method.
*
* @author Stephane Nicoll
* @author Matt Berteaux
*/
public class GroovyMethodInvocation extends GroovyExpression {
public class GroovyMethodInvocation implements GroovyExpression, GroovyStatement {
private final String target;

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2012-2019 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.groovy;
/**
* A primitive in the Groovy language.
*
* @author Matt Berteaux
*/
public interface GroovyPrimitive extends GroovyExpression {
}

View File

@@ -0,0 +1,270 @@
/*
* Copyright 2012-2019 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.groovy;
import java.util.function.Supplier;
import io.spring.initializr.generator.language.java.JavaPrimitives;
/**
* Primitives for the Groovy language. Are just wrappers around the
* {@link JavaPrimitives}.
*
* @author Matt Berteaux
*/
public final class GroovyPrimitives {
private GroovyPrimitives() {
// hide public constructor
}
public static GroovyPrimitive byteValue(Byte value) {
return new GroovyByte(value);
}
public static GroovyPrimitive shortValue(Short value) {
return new GroovyShort(value);
}
public static GroovyPrimitive integerValue(Integer value) {
return new GroovyInteger(value);
}
public static GroovyPrimitive doubleValue(Double value) {
return new GroovyDouble(value);
}
public static GroovyPrimitive longValue(Long value) {
return new GroovyLong(value);
}
public static GroovyPrimitive charValue(String charString) {
return new GroovyChar(charString);
}
public static GroovyPrimitive booleanValue(Boolean value) {
return new GroovyBoolean(value);
}
private static String valueOrNull(Object value, Supplier<String> nonNullSupplier) {
if (value == null) {
return "null";
}
return nonNullSupplier.get();
}
public static final class GroovyByte implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Byte";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "byte";
protected final Byte value;
public GroovyByte(Byte value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Byte.toString(this.value));
}
}
public static final class GroovyShort implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Short";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "short";
protected final Short value;
private GroovyShort(Short value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Short.toString(this.value));
}
}
public static final class GroovyInteger implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Integer";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "int";
protected final Integer value;
private GroovyInteger(Integer value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Integer.toString(this.value));
}
}
public static final class GroovyDouble implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Double";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "double";
protected final Double value;
private GroovyDouble(Double value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Double.toString(this.value));
}
}
public static final class GroovyFloat implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Float";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "float";
protected final Float value;
private GroovyFloat(Float value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value + "f");
}
}
public static final class GroovyLong implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Long";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "long";
protected final Long value;
private GroovyLong(Long value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value + "L");
}
}
public static final class GroovyChar implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Character";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "char";
protected final String value;
private GroovyChar(String charString) {
this.value = charString;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value);
}
}
public static final class GroovyBoolean implements GroovyPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Boolean";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "boolean";
protected final Boolean value;
private GroovyBoolean(Boolean value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Boolean.toString(this.value));
}
}
}

View File

@@ -20,17 +20,12 @@ package io.spring.initializr.generator.language.groovy;
* A return statement.
*
* @author Stephane Nicoll
* @author Matt Berteaux
*/
public class GroovyReturnStatement extends GroovyStatement {
private final GroovyExpression expression;
public class GroovyReturnStatement extends GroovyExpressionStatement {
public GroovyReturnStatement(GroovyExpression expression) {
this.expression = expression;
}
public GroovyExpression getExpression() {
return this.expression;
super(expression);
}
}

View File

@@ -28,6 +28,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -46,6 +47,7 @@ import io.spring.initializr.generator.language.SourceCodeWriter;
* A {@link SourceCodeWriter} that writes {@link SourceCode} in Groovy.
*
* @author Stephane Nicoll
* @author Matt Berteaux
*/
public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode> {
@@ -53,6 +55,8 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
private static final Map<Predicate<Integer>, String> METHOD_MODIFIERS;
private static final Map<Predicate<Integer>, String> FIELD_MODIFIERS;
static {
Map<Predicate<Integer>, String> typeModifiers = new LinkedHashMap<>();
typeModifiers.put(Modifier::isProtected, "protected");
@@ -66,6 +70,16 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
methodModifiers.put(Modifier::isSynchronized, "synchronized");
methodModifiers.put(Modifier::isNative, "native");
METHOD_MODIFIERS = methodModifiers;
Map<Predicate<Integer>, String> fieldModifiers = new LinkedHashMap<>();
fieldModifiers.put(Modifier::isPublic, "public");
fieldModifiers.put(Modifier::isProtected, "protected");
fieldModifiers.put(Modifier::isPrivate, "private");
fieldModifiers.put(Modifier::isStatic, "static");
fieldModifiers.put(Modifier::isFinal, "final");
fieldModifiers.put(Modifier::isTransient, "transient");
fieldModifiers.put(Modifier::isVolatile, "volatile");
FIELD_MODIFIERS = fieldModifiers;
}
private final IndentingWriterFactory indentingWriterFactory;
@@ -76,7 +90,7 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
@Override
public void writeTo(Path directory, GroovySourceCode sourceCode) throws IOException {
if (!Files.exists(directory)) {
if (!directory.toFile().exists()) {
Files.createDirectories(directory);
}
for (GroovyCompilationUnit compilationUnit : sourceCode.getCompilationUnits()) {
@@ -107,6 +121,14 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
}
writer.println(" {");
writer.println();
List<GroovyFieldDeclaration> fieldDeclarations = type.getFieldDeclarations();
if (!fieldDeclarations.isEmpty()) {
writer.indented(() -> {
for (GroovyFieldDeclaration fieldDeclaration : fieldDeclarations) {
writeFieldDeclaration(writer, fieldDeclaration);
}
});
}
List<GroovyMethodDeclaration> methodDeclarations = type.getMethodDeclarations();
if (!methodDeclarations.isEmpty()) {
writer.indented(() -> {
@@ -120,49 +142,35 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
}
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
annotatable.getAnnotations().forEach((annotation) -> writeAnnotation(writer, annotation));
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
writer.print("@" + getUnqualifiedName(annotation.getName()));
List<Annotation.Attribute> attributes = annotation.getAttributes();
if (!attributes.isEmpty()) {
writer.print("(");
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
writer.print(formatAnnotationAttribute(attributes.get(0)));
}
else {
writer.print(attributes.stream()
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
.collect(Collectors.joining(", ")));
}
writer.print(")");
private void writeFieldDeclaration(IndentingWriter writer, GroovyFieldDeclaration fieldDeclaration) {
if (!fieldDeclaration.getAnnotations().isEmpty()) {
writeAnnotations(writer, fieldDeclaration);
}
writeFieldModifiers(writer, fieldDeclaration);
writer.print(getUnqualifiedName(fieldDeclaration.getReturnType()));
writer.print(" ");
writer.print(fieldDeclaration.getName());
if (!Objects.equals(GroovyFieldDeclaration.InitializedStatus.NOT_INITIALIZED, fieldDeclaration.getValue())) {
writer.print(" = ");
writeFieldValue(writer, fieldDeclaration.getValue(), fieldDeclaration.getReturnType());
}
writer.println();
writer.println();
}
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
List<String> values = attribute.getValues();
if (attribute.getType().equals(Class.class)) {
return formatValues(values, this::getUnqualifiedName);
}
if (Enum.class.isAssignableFrom(attribute.getType())) {
return formatValues(values, (value) -> {
String enumValue = value.substring(value.lastIndexOf(".") + 1);
String enumClass = value.substring(0, value.lastIndexOf("."));
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
});
}
if (attribute.getType().equals(String.class)) {
return formatValues(values, (value) -> String.format("\"%s\"", value));
}
return formatValues(values, (value) -> String.format("%s", value));
private void writeFieldValue(IndentingWriter writer, Object value, String returnType) {
quote(writer, value, returnType);
writer.print(String.valueOf(value));
quote(writer, value, returnType);
}
private String formatValues(List<String> values, Function<String, String> formatter) {
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
return (values.size() > 1) ? "{ " + result + " }" : result;
private void quote(IndentingWriter writer, Object value, String returnType) {
if (value instanceof Character || "char".equals(returnType) || "java.lang.Character".equals(returnType)) {
writer.print("\'");
}
else if (value instanceof CharSequence) {
writer.print("\"");
}
}
private void writeMethodDeclaration(IndentingWriter writer, GroovyMethodDeclaration methodDeclaration) {
@@ -192,6 +200,10 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
writer.println();
}
private void writeFieldModifiers(IndentingWriter writer, GroovyFieldDeclaration fieldDeclaration) {
writeModifiers(writer, FIELD_MODIFIERS, fieldDeclaration.getModifiers());
}
private void writeModifiers(IndentingWriter writer, Map<Predicate<Integer>, String> availableModifiers,
int declaredModifiers) {
String modifiers = availableModifiers.entrySet().stream()
@@ -203,6 +215,10 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
}
}
private void writeMethodModifiers(IndentingWriter writer, GroovyMethodDeclaration methodDeclaration) {
writeModifiers(writer, METHOD_MODIFIERS, methodDeclaration.getModifiers());
}
private void writeExpression(IndentingWriter writer, GroovyExpression expression) {
if (expression instanceof GroovyMethodInvocation) {
writeMethodInvocation(writer, (GroovyMethodInvocation) expression);
@@ -248,6 +264,85 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
return new LinkedHashSet<>(imports);
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
writeAnnotations(writer, annotatable, true);
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable, boolean newLine) {
for (Annotation annotation : annotatable.getAnnotations()) {
writeAnnotation(writer, annotation, newLine);
}
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation, boolean newLine) {
writer.print("@" + getUnqualifiedName(annotation.getName()));
List<Annotation.Attribute> attributes = annotation.getAttributes();
if (!attributes.isEmpty()) {
writer.print("(");
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
writer.print(formatAnnotationAttribute(attributes.get(0)));
}
else {
writer.print(attributes.stream()
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
.collect(Collectors.joining(", ")));
}
writer.print(")");
}
if (newLine) {
writer.println();
}
else {
writer.print(" ");
}
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
writeAnnotation(writer, annotation, true);
}
protected String formatAnnotationAttribute(Annotation.Attribute attribute) {
List<String> values = attribute.getValues();
if (attribute.getType().equals(Class.class)) {
return formatValues(values, (value) -> String.format(annotationFormatString(), getUnqualifiedName(value)));
}
if (Enum.class.isAssignableFrom(attribute.getType())) {
return formatValues(values, (value) -> {
String enumValue = value.substring(value.lastIndexOf(".") + 1);
String enumClass = value.substring(0, value.lastIndexOf("."));
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
});
}
if (attribute.getType().equals(String.class)) {
return formatValues(values, (value) -> String.format("\"%s\"", value));
}
return formatValues(values, (value) -> String.format("%s", value));
}
private String formatValues(List<String> values, Function<String, String> formatter) {
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
return (values.size() > 1) ? formatAnnotationArray(result) : result;
}
private String formatAnnotationArray(String values) {
return "{ " + values + " }";
}
private String getUnqualifiedName(String name) {
if (!name.contains(".")) {
return name;
}
return name.substring(name.lastIndexOf(".") + 1);
}
private boolean requiresImport(String name) {
if (name == null || !name.contains(".")) {
return false;
}
String packageName = name.substring(0, name.lastIndexOf('.'));
return !"java.lang".equals(packageName);
}
private Collection<String> determineImports(Annotation annotation) {
List<String> imports = new ArrayList<>();
imports.add(annotation.getName());
@@ -272,19 +367,8 @@ public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode
.collect(Collectors.toList());
}
private String getUnqualifiedName(String name) {
if (!name.contains(".")) {
return name;
}
return name.substring(name.lastIndexOf(".") + 1);
}
private boolean requiresImport(String name) {
if (name == null || !name.contains(".")) {
return false;
}
String packageName = name.substring(0, name.lastIndexOf('.'));
return !"java.lang".equals(packageName);
private String annotationFormatString() {
return "%s";
}
}

View File

@@ -20,7 +20,8 @@ package io.spring.initializr.generator.language.groovy;
* A statement in Groovy.
*
* @author Stephane Nicoll
* @author Matt Berteaux
*/
public class GroovyStatement {
public interface GroovyStatement {
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2019 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.groovy;
/**
* A String type in the Groovy language.
*
* @author Matt Berteaux
*/
public final class GroovyString implements GroovyExpression {
/**
* The class name of this type.
*/
public static final String CLASS_NAME = "java.lang.String";
private final String value;
private GroovyString(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
@Override
public String toString() {
return "\"" + value + "\"";
}
public static GroovyString stringValue(String value) {
return new GroovyString(value);
}
}

View File

@@ -32,6 +32,8 @@ public class GroovyTypeDeclaration extends TypeDeclaration {
private final List<GroovyMethodDeclaration> methodDeclarations = new ArrayList<>();
private final List<GroovyFieldDeclaration> fieldDeclarations = new ArrayList<>();
GroovyTypeDeclaration(String name) {
super(name);
}
@@ -52,4 +54,12 @@ public class GroovyTypeDeclaration extends TypeDeclaration {
return this.methodDeclarations;
}
public void addFieldDeclaration(GroovyFieldDeclaration fieldDeclaration) {
this.fieldDeclarations.add(fieldDeclaration);
}
public List<GroovyFieldDeclaration> getFieldDeclarations() {
return this.fieldDeclarations;
}
}

View File

@@ -20,7 +20,8 @@ package io.spring.initializr.generator.language.java;
* A Java expression.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
public class JavaExpression {
public interface JavaExpression {
}

View File

@@ -20,8 +20,9 @@ package io.spring.initializr.generator.language.java;
* A statement that contains a single expression.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
public class JavaExpressionStatement extends JavaStatement {
public class JavaExpressionStatement implements JavaStatement {
private final JavaExpression expression;

View File

@@ -0,0 +1,127 @@
/*
* Copyright 2012-2019 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.java;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
/**
* Declaration, and potential initialization, of a field in Java.
*
* @author Matt Berteaux
*/
public final class JavaFieldDeclaration implements Annotatable {
private final List<Annotation> annotations = new ArrayList<>();
private final int modifiers;
private final String name;
private final String returnType;
private final Object value;
private JavaFieldDeclaration(int modifiers, String name, String returnType, Object value) {
this.modifiers = modifiers;
this.name = name;
this.returnType = returnType;
this.value = value;
}
public static Builder field(String name) {
return new Builder(name);
}
String getName() {
return this.name;
}
String getReturnType() {
return this.returnType;
}
int getModifiers() {
return this.modifiers;
}
Object getValue() {
return this.value;
}
@Override
public void annotate(Annotation annotation) {
this.annotations.add(annotation);
}
@Override
public List<Annotation> getAnnotations() {
return Collections.unmodifiableList(this.annotations);
}
/**
* Builder for creating a {@link JavaFieldDeclaration}.
*/
public static final class Builder {
private final String name;
private int modifiers = Modifier.PUBLIC;
private Object value = InitializedStatus.NOT_INITIALIZED;
private Builder(String name) {
this.name = name;
}
public Builder packagePrivate() {
this.modifiers = 0;
return this;
}
public Builder modifiers(int modifiers) {
this.modifiers = modifiers;
return this;
}
public Builder value(JavaExpression value) {
this.value = value;
return this;
}
public JavaFieldDeclaration returning(String returnType) {
return new JavaFieldDeclaration(this.modifiers, this.name, returnType, this.value);
}
}
/**
* Track if the value has been set or not. Using this because initializing a field to
* null should be possible.
*/
enum InitializedStatus {
NOT_INITIALIZED;
}
}

View File

@@ -24,7 +24,7 @@ import java.util.List;
*
* @author Andy Wilkinson
*/
public class JavaMethodInvocation extends JavaExpression {
public class JavaMethodInvocation implements JavaExpression, JavaStatement {
private final String target;

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2012-2019 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.java;
/**
* A primitive for the Java language.
*
* @author Matt Berteaux
*/
public interface JavaPrimitive extends JavaExpression {
}

View File

@@ -0,0 +1,271 @@
/*
* Copyright 2012-2019 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.java;
import java.util.function.Supplier;
/**
* Primitives for the java language.
*
* @author Matt Berteaux
*/
public final class JavaPrimitives {
private JavaPrimitives() {
// hide public constructor
}
public static JavaPrimitive byteValue(Byte value) {
return new JavaByte(value);
}
public static JavaPrimitive shortValue(Short value) {
return new JavaShort(value);
}
public static JavaPrimitive integerValue(Integer value) {
return new JavaInteger(value);
}
public static JavaPrimitive longValue(Long value) {
return new JavaLong(value);
}
public static JavaPrimitive doubleValue(Double value) {
return new JavaDouble(value);
}
public static JavaPrimitive floatValue(Float value) {
return new JavaFloat(value);
}
public static JavaPrimitive charValue(String charValue) {
return new JavaChar(charValue);
}
public static JavaPrimitive booleanValue(Boolean value) {
return new JavaBoolean(value);
}
private static String valueOrNull(Object value, Supplier<String> nonNullSupplier) {
if (value == null) {
return "null";
}
return nonNullSupplier.get();
}
public static final class JavaByte implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Byte";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "byte";
protected final Byte value;
private JavaByte(Byte value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Byte.toString(this.value));
}
}
public static final class JavaShort implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Short";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "short";
protected final Short value;
private JavaShort(Short value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Short.toString(this.value));
}
}
public static final class JavaInteger implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Integer";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "int";
protected final Integer value;
private JavaInteger(Integer value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Integer.toString(this.value));
}
}
public static final class JavaLong implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Long";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "long";
protected final Long value;
private JavaLong(Long value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value + "L");
}
}
public static final class JavaDouble implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Double";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "double";
protected final Double value;
private JavaDouble(Double value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Double.toString(this.value));
}
}
public static final class JavaFloat implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Float";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "float";
protected final Float value;
private JavaFloat(Float value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value + "f");
}
}
public static final class JavaChar implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Character";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "char";
protected final String value;
private JavaChar(String charString) {
this.value = charString;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value);
}
}
public static final class JavaBoolean implements JavaPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "java.lang.Boolean";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "boolean";
protected final Boolean value;
private JavaBoolean(Boolean value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Boolean.toString(this.value));
}
}
}

View File

@@ -20,17 +20,12 @@ package io.spring.initializr.generator.language.java;
* A return statement.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
public class JavaReturnStatement extends JavaStatement {
private final JavaExpression expression;
public class JavaReturnStatement extends JavaExpressionStatement {
public JavaReturnStatement(JavaExpression expression) {
this.expression = expression;
}
public JavaExpression getExpression() {
return this.expression;
super(expression);
}
}

View File

@@ -28,6 +28,7 @@ import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -46,6 +47,7 @@ import io.spring.initializr.generator.language.SourceCodeWriter;
* A {@link SourceCodeWriter} that writes {@link SourceCode} in Java.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
@@ -53,6 +55,8 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
private static final Map<Predicate<Integer>, String> METHOD_MODIFIERS;
private static final Map<Predicate<Integer>, String> FIELD_MODIFIERS;
static {
Map<Predicate<Integer>, String> typeModifiers = new LinkedHashMap<>();
typeModifiers.put(Modifier::isPublic, "public");
@@ -67,6 +71,16 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
methodModifiers.put(Modifier::isSynchronized, "synchronized");
methodModifiers.put(Modifier::isNative, "native");
METHOD_MODIFIERS = methodModifiers;
Map<Predicate<Integer>, String> fieldModifiers = new LinkedHashMap<>();
fieldModifiers.put(Modifier::isPublic, "public");
fieldModifiers.put(Modifier::isProtected, "protected");
fieldModifiers.put(Modifier::isPrivate, "private");
fieldModifiers.put(Modifier::isStatic, "static");
fieldModifiers.put(Modifier::isFinal, "final");
fieldModifiers.put(Modifier::isTransient, "transient");
fieldModifiers.put(Modifier::isVolatile, "volatile");
FIELD_MODIFIERS = fieldModifiers;
}
private final IndentingWriterFactory indentingWriterFactory;
@@ -77,7 +91,7 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
@Override
public void writeTo(Path directory, JavaSourceCode sourceCode) throws IOException {
if (!Files.exists(directory)) {
if (!directory.toFile().exists()) {
Files.createDirectories(directory);
}
for (JavaCompilationUnit compilationUnit : sourceCode.getCompilationUnits()) {
@@ -108,6 +122,14 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
}
writer.println(" {");
writer.println();
List<JavaFieldDeclaration> fieldDeclarations = type.getFieldDeclarations();
if (!fieldDeclarations.isEmpty()) {
writer.indented(() -> {
for (JavaFieldDeclaration fieldDeclaration : fieldDeclarations) {
writeFieldDeclaration(writer, fieldDeclaration);
}
});
}
List<JavaMethodDeclaration> methodDeclarations = type.getMethodDeclarations();
if (!methodDeclarations.isEmpty()) {
writer.indented(() -> {
@@ -121,49 +143,37 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
}
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
annotatable.getAnnotations().forEach((annotation) -> writeAnnotation(writer, annotation));
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
writer.print("@" + getUnqualifiedName(annotation.getName()));
List<Annotation.Attribute> attributes = annotation.getAttributes();
if (!attributes.isEmpty()) {
writer.print("(");
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
writer.print(formatAnnotationAttribute(attributes.get(0)));
}
else {
writer.print(attributes.stream()
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
.collect(Collectors.joining(", ")));
}
writer.print(")");
private void writeFieldDeclaration(IndentingWriter writer, JavaFieldDeclaration fieldDeclaration) {
writeAnnotations(writer, fieldDeclaration);
writeFieldModifiers(writer, fieldDeclaration);
writer.print(getUnqualifiedName(fieldDeclaration.getReturnType()));
writer.print(" ");
writer.print(fieldDeclaration.getName());
if (!Objects.equals(JavaFieldDeclaration.InitializedStatus.NOT_INITIALIZED, fieldDeclaration.getValue())) {
writer.print(" = ");
writeFieldValue(writer, fieldDeclaration.getValue(), fieldDeclaration.getReturnType());
}
writer.println(";");
writer.println();
}
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
List<String> values = attribute.getValues();
if (attribute.getType().equals(Class.class)) {
return formatValues(values, (value) -> String.format("%s.class", getUnqualifiedName(value)));
}
if (Enum.class.isAssignableFrom(attribute.getType())) {
return formatValues(values, (value) -> {
String enumValue = value.substring(value.lastIndexOf(".") + 1);
String enumClass = value.substring(0, value.lastIndexOf("."));
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
});
}
if (attribute.getType().equals(String.class)) {
return formatValues(values, (value) -> String.format("\"%s\"", value));
}
return formatValues(values, (value) -> String.format("%s", value));
private void writeFieldValue(IndentingWriter writer, Object value, String returnType) {
quote(writer, value, returnType);
writer.print(String.valueOf(value));
quote(writer, value, returnType);
}
private String formatValues(List<String> values, Function<String, String> formatter) {
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
return (values.size() > 1) ? "{ " + result + " }" : result;
private void quote(IndentingWriter writer, Object value, String returnType) {
if (value instanceof Character || "char".equals(returnType) || "java.lang.Character".equals(returnType)) {
writer.print("\'");
}
else if (value instanceof CharSequence) {
writer.print("\"");
}
}
private void writeFieldModifiers(IndentingWriter writer, JavaFieldDeclaration fieldDeclaration) {
writeModifiers(writer, FIELD_MODIFIERS, fieldDeclaration.getModifiers());
}
private void writeMethodDeclaration(IndentingWriter writer, JavaMethodDeclaration methodDeclaration) {
@@ -180,13 +190,13 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
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) {
if (statement instanceof JavaReturnStatement) {
writer.print("return ");
writeExpression(writer, ((JavaReturnStatement) statement).getExpression());
}
else if (statement instanceof JavaExpressionStatement) {
writeExpression(writer, ((JavaExpressionStatement) statement).getExpression());
}
writer.println(";");
}
});
@@ -194,6 +204,10 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
writer.println();
}
private void writeMethodModifiers(IndentingWriter writer, JavaMethodDeclaration methodDeclaration) {
writeModifiers(writer, METHOD_MODIFIERS, methodDeclaration.getModifiers());
}
private void writeModifiers(IndentingWriter writer, Map<Predicate<Integer>, String> availableModifiers,
int declaredModifiers) {
String modifiers = availableModifiers.entrySet().stream()
@@ -232,6 +246,12 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
imports.add(typeDeclaration.getExtends());
}
imports.addAll(getRequiredImports(typeDeclaration.getAnnotations(), this::determineImports));
for (JavaFieldDeclaration fieldDeclaration : typeDeclaration.getFieldDeclarations()) {
if (requiresImport(fieldDeclaration.getReturnType())) {
imports.add(fieldDeclaration.getReturnType());
}
imports.addAll(getRequiredImports(fieldDeclaration.getAnnotations(), this::determineImports));
}
for (JavaMethodDeclaration methodDeclaration : typeDeclaration.getMethodDeclarations()) {
if (requiresImport(methodDeclaration.getReturnType())) {
imports.add(methodDeclaration.getReturnType());
@@ -274,13 +294,6 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
.collect(Collectors.toList());
}
private String getUnqualifiedName(String name) {
if (!name.contains(".")) {
return name;
}
return name.substring(name.lastIndexOf(".") + 1);
}
private boolean requiresImport(String name) {
if (name == null || !name.contains(".")) {
return false;
@@ -289,4 +302,79 @@ public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
return !"java.lang".equals(packageName);
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
writeAnnotations(writer, annotatable, true);
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable, boolean newLine) {
for (Annotation annotation : annotatable.getAnnotations()) {
writeAnnotation(writer, annotation, newLine);
}
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation, boolean newLine) {
writer.print("@" + getUnqualifiedName(annotation.getName()));
List<Annotation.Attribute> attributes = annotation.getAttributes();
if (!attributes.isEmpty()) {
writer.print("(");
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
writer.print(formatAnnotationAttribute(attributes.get(0)));
}
else {
writer.print(attributes.stream()
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
.collect(Collectors.joining(", ")));
}
writer.print(")");
}
if (newLine) {
writer.println();
}
else {
writer.print(" ");
}
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
writeAnnotation(writer, annotation, true);
}
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
List<String> values = attribute.getValues();
if (attribute.getType().equals(Class.class)) {
return formatValues(values, (value) -> String.format(annotationFormatString(), getUnqualifiedName(value)));
}
if (Enum.class.isAssignableFrom(attribute.getType())) {
return formatValues(values, (value) -> {
String enumValue = value.substring(value.lastIndexOf(".") + 1);
String enumClass = value.substring(0, value.lastIndexOf("."));
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
});
}
if (attribute.getType().equals(String.class)) {
return formatValues(values, (value) -> String.format("\"%s\"", value));
}
return formatValues(values, (value) -> String.format("%s", value));
}
private String formatValues(List<String> values, Function<String, String> formatter) {
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
return (values.size() > 1) ? formatAnnotationArray(result) : result;
}
private String formatAnnotationArray(String values) {
return "{ " + values + " }";
}
private String getUnqualifiedName(String name) {
if (!name.contains(".")) {
return name;
}
return name.substring(name.lastIndexOf(".") + 1);
}
private String annotationFormatString() {
return "%s.class";
}
}

View File

@@ -20,7 +20,8 @@ package io.spring.initializr.generator.language.java;
* A statement in Java.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
public class JavaStatement {
public interface JavaStatement {
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2019 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.java;
/**
* A String type in the Java language.
*
* @author Matt Berteaux
*/
public final class JavaString implements JavaExpression {
/**
* The class name of this type.
*/
public static final String CLASS_NAME = "java.lang.String";
private final String value;
private JavaString(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
@Override
public String toString() {
return "\"" + this.value + "\"";
}
public static JavaString stringValue(String value) {
return new JavaString(value);
}
}

View File

@@ -25,6 +25,7 @@ import io.spring.initializr.generator.language.TypeDeclaration;
* A {@link TypeDeclaration declaration } of a type written in Java.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
public class JavaTypeDeclaration extends TypeDeclaration {
@@ -32,6 +33,8 @@ public class JavaTypeDeclaration extends TypeDeclaration {
private final List<JavaMethodDeclaration> methodDeclarations = new ArrayList<>();
private final List<JavaFieldDeclaration> fieldDeclarations = new ArrayList<>();
JavaTypeDeclaration(String name) {
super(name);
}
@@ -52,4 +55,12 @@ public class JavaTypeDeclaration extends TypeDeclaration {
return this.methodDeclarations;
}
public void addFieldDeclaration(JavaFieldDeclaration fieldDeclaration) {
this.fieldDeclarations.add(fieldDeclaration);
}
public List<JavaFieldDeclaration> getFieldDeclarations() {
return this.fieldDeclarations;
}
}

View File

@@ -21,6 +21,6 @@ package io.spring.initializr.generator.language.kotlin;
*
* @author Stephane Nicoll
*/
public class KotlinExpression {
public interface KotlinExpression {
}

View File

@@ -21,7 +21,7 @@ package io.spring.initializr.generator.language.kotlin;
*
* @author Stephane Nicoll
*/
public class KotlinExpressionStatement extends KotlinStatement {
public class KotlinExpressionStatement implements KotlinStatement {
private final KotlinExpression expression;

View File

@@ -16,7 +16,6 @@
package io.spring.initializr.generator.language.kotlin;
import java.util.Arrays;
import java.util.List;
/**
@@ -24,22 +23,15 @@ import java.util.List;
*
* @author Stephane Nicoll
*/
public class KotlinFunctionInvocation extends KotlinExpression {
private final String target;
public class KotlinFunctionInvocation implements KotlinExpression {
private final String name;
private final List<String> arguments;
public KotlinFunctionInvocation(String target, String name, String... arguments) {
this.target = target;
public KotlinFunctionInvocation(String name, List<String> arguments) {
this.name = name;
this.arguments = Arrays.asList(arguments);
}
public String getTarget() {
return this.target;
this.arguments = arguments;
}
public String getName() {

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2019 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.kotlin;
import java.util.Arrays;
import java.util.List;
/**
* A method invocation in Kotlin.
*
* @author Matt Berteaux
*/
public class KotlinMethodInvocation implements KotlinExpression {
private final String target;
private final String name;
private final List<String> arguments;
public KotlinMethodInvocation(String target, String name, String... arguments) {
this.target = target;
this.name = name;
this.arguments = Arrays.asList(arguments);
}
public String getTarget() {
return this.target;
}
public String getName() {
return this.name;
}
public List<String> getArguments() {
return this.arguments;
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2012-2019 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.kotlin;
/**
* A primitive for the Kotlin language.
*
* @author Matt Berteaux
*/
public interface KotlinPrimitive extends KotlinExpression {
}

View File

@@ -0,0 +1,271 @@
/*
* Copyright 2012-2019 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.kotlin;
import java.util.function.Supplier;
/**
* Kotlin primitives.
*
* @author Matt Berteaux
*/
public final class KotlinPrimitives {
private KotlinPrimitives() {
// hide public constructor
}
public static KotlinPrimitive byteValue(Byte value) {
return new KotlinByte(value);
}
public static KotlinPrimitive shortValue(Short value) {
return new KotlinShort(value);
}
public static KotlinPrimitive integerValue(Integer value) {
return new KotlinInt(value);
}
public static KotlinPrimitive doubleValue(Double value) {
return new KotlinDouble(value);
}
public static KotlinPrimitive longValue(Long value) {
return new KotlinLong(value);
}
public static KotlinPrimitive floatValue(Float value) {
return new KotlinFloat(value);
}
public static KotlinPrimitive charValue(String charString) {
return new KotlinChar(charString);
}
public static KotlinPrimitive booleanValue(Boolean value) {
return new KotlinBoolean(value);
}
private static String valueOrNull(Object value, Supplier<String> nonNullSupplier) {
if (value == null) {
return "null";
}
return nonNullSupplier.get();
}
public static final class KotlinByte implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_TYPE_CLASS = "kotlin.Byte";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Byte";
protected final Byte value;
private KotlinByte(Byte value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Byte.toString(this.value));
}
}
public static final class KotlinShort implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "kotlin.Short";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Short";
protected final Short value;
private KotlinShort(Short value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Short.toString(this.value));
}
}
public static final class KotlinInt implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "kotlin.Int";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Int";
protected final Integer value;
private KotlinInt(Integer value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Integer.toString(this.value));
}
}
public static final class KotlinDouble implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "kotlin.Double";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Double";
protected final Double value;
private KotlinDouble(Double value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Double.toString(this.value));
}
}
public static final class KotlinLong implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "kotlin.Long";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Long";
protected final Long value;
private KotlinLong(Long value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value + "L");
}
}
public static final class KotlinFloat implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "kotlin.Float";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Float";
protected final Float value;
private KotlinFloat(Float value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value + "f");
}
}
public static final class KotlinBoolean implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "kotlin.Boolean";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Boolean";
protected final Boolean value;
private KotlinBoolean(Boolean value) {
this.value = value;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> Boolean.toString(this.value));
}
}
public static final class KotlinChar implements KotlinPrimitive {
/**
* The class name of the boxed type.
*/
public static final String BOXED_CLASS_NAME = "kotlin.Char";
/**
* The name of the unboxed type.
*/
public static final String TYPE = "kotlin.Char";
protected final String value;
private KotlinChar(String charString) {
this.value = charString;
}
@Override
public String toString() {
return valueOrNull(this.value, () -> this.value);
}
}
}

View File

@@ -0,0 +1,267 @@
/*
* Copyright 2012-2019 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.kotlin;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
/**
* Declaration of a property in Kotlin.
*
* @author Matt Berteaux
*/
public final class KotlinPropertyDeclaration implements Annotatable {
private final List<Annotation> annotations = new ArrayList<>();
private final boolean isVal;
private final String name;
private final String returnType;
private final KotlinExpressionStatement valueExpression;
private final Accessor getter;
private final Accessor setter;
private KotlinPropertyDeclaration(Builder builder) {
this.name = builder.name;
this.returnType = builder.returnType;
this.isVal = builder.isVal;
this.valueExpression = builder.initializerStatement;
this.getter = builder.getter;
this.setter = builder.setter;
}
public static ValBuilder val(String name) {
return new ValBuilder(name);
}
public static VarBuilder var(String name) {
return new VarBuilder(name);
}
boolean isVal() {
return this.isVal;
}
String getName() {
return this.name;
}
String getReturnType() {
return this.returnType;
}
KotlinExpressionStatement getValueExpression() {
return this.valueExpression;
}
Accessor getGetter() {
return this.getter;
}
Accessor getSetter() {
return this.setter;
}
public boolean hasGetter() {
return getGetter() != null;
}
public boolean hasSetter() {
return getSetter() != null;
}
@Override
public void annotate(Annotation annotation) {
this.annotations.add(annotation);
}
@Override
public List<Annotation> getAnnotations() {
return Collections.unmodifiableList(this.annotations);
}
/**
* Builder for creating a {@link KotlinPropertyDeclaration}.
*
* @param <T> a {@link Builder} subclass.
*/
public abstract static class Builder<T extends Builder<T>> {
private final boolean isVal;
private final String name;
private String returnType;
private KotlinExpressionStatement initializerStatement;
private Accessor getter;
private Accessor setter;
private Builder(String name, boolean isVal) {
this.name = name;
this.isVal = isVal;
}
protected abstract T self();
@SuppressWarnings("unchecked")
public AccessorBuilder getter() {
return new AccessorBuilder<>((T) this, (created) -> this.getter = created);
}
@SuppressWarnings("unchecked")
public AccessorBuilder setter() {
return new AccessorBuilder<>((T) this, (created) -> this.setter = created);
}
public T returning(String returnType) {
this.returnType = returnType;
return self();
}
public KotlinPropertyDeclaration emptyValue() {
return new KotlinPropertyDeclaration(this);
}
public KotlinPropertyDeclaration value(KotlinExpression expression) {
this.initializerStatement = new KotlinExpressionStatement(expression);
return new KotlinPropertyDeclaration(this);
}
}
public static final class ValBuilder extends Builder<ValBuilder> {
private ValBuilder(String name) {
super(name, true);
}
@Override
protected ValBuilder self() {
return this;
}
}
public static final class VarBuilder extends Builder<VarBuilder> {
private VarBuilder(String name) {
super(name, false);
}
public KotlinPropertyDeclaration empty() {
return new KotlinPropertyDeclaration(this);
}
@Override
protected VarBuilder self() {
return this;
}
}
public static final class AccessorBuilder<T extends Builder<T>> {
private final List<Annotation> annotations = new ArrayList<>();
private KotlinExpressionStatement body;
private boolean isPrivate = false;
private final T parent;
private final Consumer<Accessor> accessorFunction;
private AccessorBuilder(T parent, Consumer<Accessor> accessorFunction) {
this.parent = parent;
this.accessorFunction = accessorFunction;
}
public AccessorBuilder isPrivate() {
this.isPrivate = true;
return this;
}
public AccessorBuilder withAnnotation(Annotation annotation) {
this.annotations.add(annotation);
return this;
}
public AccessorBuilder withBody(KotlinExpressionStatement expressionStatement) {
this.body = expressionStatement;
return this;
}
public T buildAccessor() {
this.accessorFunction.accept(new Accessor(this));
return this.parent;
}
}
static final class Accessor implements Annotatable {
private final List<Annotation> annotations = new ArrayList<>();
private final KotlinExpressionStatement body;
private final boolean isPrivate;
@SuppressWarnings("unchecked")
Accessor(AccessorBuilder builder) {
this.annotations.addAll(builder.annotations);
this.body = builder.body;
this.isPrivate = builder.isPrivate;
}
boolean isPrivate() {
return this.isPrivate;
}
boolean isEmptyBody() {
return this.body == null;
}
KotlinExpressionStatement getBody() {
return this.body;
}
@Override
public void annotate(Annotation annotation) {
this.annotations.add(annotation);
}
@Override
public List<Annotation> getAnnotations() {
return Collections.unmodifiableList(this.annotations);
}
}
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2012-2019 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.kotlin;
/**
* Modifiers that can modify a Kotlin property.
*
* @author Matt Berteaux
*/
public enum KotlinPropertyModifier {
/**
* Represents a private property.
*/
PRIVATE,
/**
* Represents a lateinit property.
*/
LATE_INIT
}

View File

@@ -17,37 +17,23 @@
package io.spring.initializr.generator.language.kotlin;
import java.util.Arrays;
import java.util.List;
/**
* Invocation of a function with a reified type parameter.
*
* @author Stephane Nicoll
*/
public class KotlinReifiedFunctionInvocation extends KotlinExpression {
private final String name;
public class KotlinReifiedFunctionInvocation extends KotlinFunctionInvocation implements KotlinExpression {
private final String targetClass;
private final List<String> arguments;
public KotlinReifiedFunctionInvocation(String name, String targetClass, String... arguments) {
this.name = name;
super(name, Arrays.asList(arguments));
this.targetClass = targetClass;
this.arguments = Arrays.asList(arguments);
}
public String getName() {
return this.name;
}
public String getTargetClass() {
return this.targetClass;
}
public List<String> getArguments() {
return this.arguments;
}
}

View File

@@ -20,17 +20,12 @@ package io.spring.initializr.generator.language.kotlin;
* A return statement.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
public class KotlinReturnStatement extends KotlinStatement {
private final KotlinExpression expression;
public class KotlinReturnStatement extends KotlinExpressionStatement {
public KotlinReturnStatement(KotlinExpression expression) {
this.expression = expression;
}
public KotlinExpression getExpression() {
return this.expression;
super(expression);
}
}

View File

@@ -42,6 +42,7 @@ import io.spring.initializr.generator.language.SourceCodeWriter;
* A {@link SourceCodeWriter} that writes {@link SourceCode} in Kotlin.
*
* @author Stephane Nicoll
* @author Matt Berteaux
*/
public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode> {
@@ -53,7 +54,7 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
@Override
public void writeTo(Path directory, KotlinSourceCode sourceCode) throws IOException {
if (!Files.exists(directory)) {
if (!directory.toFile().exists()) {
Files.createDirectories(directory);
}
for (KotlinCompilationUnit compilationUnit : sourceCode.getCompilationUnits()) {
@@ -82,20 +83,33 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
if (type.getExtends() != null) {
writer.print(" : " + getUnqualifiedName(type.getExtends()) + "()");
}
List<KotlinPropertyDeclaration> propertyDeclarations = type.getPropertyDeclarations();
List<KotlinFunctionDeclaration> functionDeclarations = type.getFunctionDeclarations();
if (!functionDeclarations.isEmpty()) {
boolean hasDeclarations = !propertyDeclarations.isEmpty() || !functionDeclarations.isEmpty();
if (hasDeclarations) {
writer.println(" {");
}
if (!propertyDeclarations.isEmpty()) {
writer.indented(() -> {
for (KotlinPropertyDeclaration propertyDeclaration : propertyDeclarations) {
writeProperty(writer, propertyDeclaration);
}
});
}
if (!functionDeclarations.isEmpty()) {
writer.indented(() -> {
for (KotlinFunctionDeclaration functionDeclaration : functionDeclarations) {
writeFunction(writer, functionDeclaration);
}
});
writer.println();
writer.println("}");
}
else {
writer.println("");
}
if (hasDeclarations) {
writer.println("}");
}
}
List<KotlinFunctionDeclaration> topLevelFunctions = compilationUnit.getTopLevelFunctions();
if (!topLevelFunctions.isEmpty()) {
@@ -107,6 +121,50 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
}
}
private void writeProperty(IndentingWriter writer, KotlinPropertyDeclaration propertyDeclaration) {
writer.println();
if (propertyDeclaration.isVal()) {
writer.print("val ");
}
else {
writer.print("var ");
}
writer.print(propertyDeclaration.getName());
if (propertyDeclaration.getReturnType() != null) {
writer.print(": " + getUnqualifiedName(propertyDeclaration.getReturnType()));
}
if (propertyDeclaration.getValueExpression() != null) {
writer.print(" = ");
writeExpression(writer, propertyDeclaration.getValueExpression().getExpression());
}
if (propertyDeclaration.hasGetter()) {
writer.println();
writer.indented(() -> writeAccessor(writer, "get", propertyDeclaration.getGetter()));
}
if (propertyDeclaration.hasSetter()) {
writer.println();
writer.indented(() -> writeAccessor(writer, "set", propertyDeclaration.getSetter()));
}
writer.println();
}
private void writeAccessor(IndentingWriter writer, String accessorName,
KotlinPropertyDeclaration.Accessor accessor) {
if (!accessor.getAnnotations().isEmpty()) {
for (Annotation annotation : accessor.getAnnotations()) {
writeAnnotation(writer, annotation, false);
}
}
if (accessor.isPrivate()) {
writer.print("private ");
}
writer.print(accessorName);
if (!accessor.isEmptyBody()) {
writer.print("() = ");
writeExpression(writer, accessor.getBody().getExpression());
}
}
private void writeFunction(IndentingWriter writer, KotlinFunctionDeclaration functionDeclaration) {
writer.println();
writeAnnotations(writer, functionDeclaration);
@@ -127,66 +185,19 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
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) {
if (statement instanceof KotlinReturnStatement) {
writer.print("return ");
writeExpression(writer, ((KotlinReturnStatement) statement).getExpression());
}
else if (statement instanceof KotlinExpressionStatement) {
writeExpression(writer, ((KotlinExpressionStatement) statement).getExpression());
}
writer.println("");
}
});
writer.println("}");
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
for (Annotation annotation : annotatable.getAnnotations()) {
writeAnnotation(writer, annotation);
}
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
writer.print("@" + getUnqualifiedName(annotation.getName()));
List<Annotation.Attribute> attributes = annotation.getAttributes();
if (!attributes.isEmpty()) {
writer.print("(");
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
writer.print(formatAnnotationAttribute(attributes.get(0)));
}
else {
writer.print(attributes.stream()
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
.collect(Collectors.joining(", ")));
}
writer.print(")");
}
writer.println();
}
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
List<String> values = attribute.getValues();
if (attribute.getType().equals(Class.class)) {
return formatValues(values, (value) -> String.format("%s::class", getUnqualifiedName(value)));
}
if (Enum.class.isAssignableFrom(attribute.getType())) {
return formatValues(values, (value) -> {
String enumValue = value.substring(value.lastIndexOf(".") + 1);
String enumClass = value.substring(0, value.lastIndexOf("."));
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
});
}
if (attribute.getType().equals(String.class)) {
return formatValues(values, (value) -> String.format("\"%s\"", value));
}
return formatValues(values, (value) -> String.format("%s", value));
}
private String formatValues(List<String> values, Function<String, String> formatter) {
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
return (values.size() > 1) ? "[" + result + "]" : result;
}
private void writeModifiers(IndentingWriter writer, List<KotlinModifier> declaredModifiers) {
String modifiers = declaredModifiers.stream().filter((entry) -> !entry.equals(KotlinModifier.PUBLIC)).sorted()
.map((entry) -> entry.toString().toLowerCase(Locale.ENGLISH)).collect(Collectors.joining(" "));
@@ -197,15 +208,25 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
}
private void writeExpression(IndentingWriter writer, KotlinExpression expression) {
if (expression instanceof KotlinFunctionInvocation) {
writeFunctionInvocation(writer, (KotlinFunctionInvocation) expression);
if (expression instanceof KotlinMethodInvocation) {
writeMethodInvocation(writer, (KotlinMethodInvocation) expression);
}
else if (expression instanceof KotlinReifiedFunctionInvocation) {
writeReifiedFunctionInvocation(writer, (KotlinReifiedFunctionInvocation) expression);
}
else if (expression instanceof KotlinFunctionInvocation) {
writeFunctionInvocation(writer, (KotlinFunctionInvocation) expression);
}
else if (expression != null) {
writer.print(expression.toString());
}
}
private void writeFunctionInvocation(IndentingWriter writer, KotlinFunctionInvocation functionInvocation) {
writer.print(functionInvocation.getName() + "(" + String.join(", ", functionInvocation.getArguments()) + ")");
}
private void writeMethodInvocation(IndentingWriter writer, KotlinMethodInvocation functionInvocation) {
writer.print(getUnqualifiedName(functionInvocation.getTarget()) + "." + functionInvocation.getName() + "("
+ String.join(", ", functionInvocation.getArguments()) + ")");
}
@@ -233,6 +254,8 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
imports.add(typeDeclaration.getExtends());
}
imports.addAll(getRequiredImports(typeDeclaration.getAnnotations(), this::determineImports));
typeDeclaration.getPropertyDeclarations()
.forEach(((propertyDeclaration) -> imports.addAll(determinePropertyImports(propertyDeclaration))));
typeDeclaration.getFunctionDeclarations()
.forEach((functionDeclaration) -> imports.addAll(determineFunctionImports(functionDeclaration)));
}
@@ -242,6 +265,14 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
return new LinkedHashSet<>(imports);
}
private Set<String> determinePropertyImports(KotlinPropertyDeclaration propertyDeclaration) {
Set<String> imports = new LinkedHashSet<>();
if (requiresImport(propertyDeclaration.getReturnType())) {
imports.add(propertyDeclaration.getReturnType());
}
return imports;
}
private Set<String> determineFunctionImports(KotlinFunctionDeclaration functionDeclaration) {
Set<String> imports = new LinkedHashSet<>();
if (requiresImport(functionDeclaration.getReturnType())) {
@@ -251,8 +282,8 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
imports.addAll(getRequiredImports(functionDeclaration.getParameters(),
(parameter) -> Collections.singleton(parameter.getType())));
imports.addAll(getRequiredImports(
getKotlinExpressions(functionDeclaration).filter(KotlinFunctionInvocation.class::isInstance)
.map(KotlinFunctionInvocation.class::cast),
getKotlinExpressions(functionDeclaration).filter(KotlinMethodInvocation.class::isInstance)
.map(KotlinMethodInvocation.class::cast),
(invocation) -> Collections.singleton(invocation.getTarget())));
imports.addAll(getRequiredImports(
getKotlinExpressions(functionDeclaration).filter(KotlinReifiedFunctionInvocation.class::isInstance)
@@ -261,6 +292,78 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
return imports;
}
private Stream<KotlinExpression> getKotlinExpressions(KotlinFunctionDeclaration functionDeclaration) {
return functionDeclaration.getStatements().stream().filter(KotlinExpressionStatement.class::isInstance)
.map(KotlinExpressionStatement.class::cast).map(KotlinExpressionStatement::getExpression);
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
writeAnnotations(writer, annotatable, true);
}
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable, boolean newLine) {
for (Annotation annotation : annotatable.getAnnotations()) {
writeAnnotation(writer, annotation, newLine);
}
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation, boolean newLine) {
writer.print("@" + getUnqualifiedName(annotation.getName()));
List<Annotation.Attribute> attributes = annotation.getAttributes();
if (!attributes.isEmpty()) {
writer.print("(");
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
writer.print(formatAnnotationAttribute(attributes.get(0)));
}
else {
writer.print(attributes.stream()
.map((attribute) -> attribute.getName() + " = " + formatAnnotationAttribute(attribute))
.collect(Collectors.joining(", ")));
}
writer.print(")");
}
if (newLine) {
writer.println();
}
else {
writer.print(" ");
}
}
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
writeAnnotation(writer, annotation, true);
}
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
List<String> values = attribute.getValues();
if (attribute.getType().equals(Class.class)) {
return formatValues(values, (value) -> String.format(annotationFormatString(), getUnqualifiedName(value)));
}
if (Enum.class.isAssignableFrom(attribute.getType())) {
return formatValues(values, (value) -> {
String enumValue = value.substring(value.lastIndexOf(".") + 1);
String enumClass = value.substring(0, value.lastIndexOf("."));
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
});
}
if (attribute.getType().equals(String.class)) {
return formatValues(values, (value) -> String.format("\"%s\"", value));
}
return formatValues(values, (value) -> String.format("%s", value));
}
private String formatValues(List<String> values, Function<String, String> formatter) {
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
return (values.size() > 1) ? formatAnnotationArray(result) : result;
}
private String getUnqualifiedName(String name) {
if (!name.contains(".")) {
return name;
}
return name.substring(name.lastIndexOf(".") + 1);
}
private Collection<String> determineImports(Annotation annotation) {
List<String> imports = new ArrayList<>();
imports.add(annotation.getName());
@@ -276,11 +379,6 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
return imports;
}
private Stream<KotlinExpression> getKotlinExpressions(KotlinFunctionDeclaration functionDeclaration) {
return functionDeclaration.getStatements().stream().filter(KotlinExpressionStatement.class::isInstance)
.map(KotlinExpressionStatement.class::cast).map(KotlinExpressionStatement::getExpression);
}
private <T> List<String> getRequiredImports(List<T> candidates, Function<T, Collection<String>> mapping) {
return getRequiredImports(candidates.stream(), mapping);
}
@@ -290,19 +388,20 @@ public class KotlinSourceCodeWriter implements SourceCodeWriter<KotlinSourceCode
.collect(Collectors.toList());
}
private String getUnqualifiedName(String name) {
if (!name.contains(".")) {
return name;
}
return name.substring(name.lastIndexOf(".") + 1);
}
private boolean requiresImport(String name) {
if (name == null || !name.contains(".")) {
return false;
}
String packageName = name.substring(0, name.lastIndexOf('.'));
return !"java.lang".equals(packageName);
return !("java.lang".equals(packageName) || "kotlin".equals(packageName));
}
private String annotationFormatString() {
return "%s::class";
}
private String formatAnnotationArray(String values) {
return "[" + values + "]";
}
}

View File

@@ -21,6 +21,6 @@ package io.spring.initializr.generator.language.kotlin;
*
* @author Stephane Nicoll
*/
public class KotlinStatement {
public interface KotlinStatement {
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2012-2019 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.kotlin;
/**
* Represents a Kotlin String type.
*
* @author Matt Berteaux
*/
public final class KotlinString implements KotlinExpression {
/**
* The class name of this type.
*/
public static final String CLASS_NAME = "kotlin.String";
private final String value;
private KotlinString(String value) {
this.value = value;
}
public String getValue() {
return this.value;
}
@Override
public String toString() {
return "\"" + this.value + "\"";
}
public static KotlinString stringValue(String value) {
return new KotlinString(value);
}
}

View File

@@ -33,6 +33,8 @@ public class KotlinTypeDeclaration extends TypeDeclaration {
private final List<KotlinFunctionDeclaration> functionDeclarations = new ArrayList<>();
private final List<KotlinPropertyDeclaration> propertyDeclarations = new ArrayList<>();
KotlinTypeDeclaration(String name) {
super(name);
}
@@ -53,4 +55,12 @@ public class KotlinTypeDeclaration extends TypeDeclaration {
return this.functionDeclarations;
}
public void addPropertyDeclaration(KotlinPropertyDeclaration propertyDeclaration) {
this.propertyDeclarations.add(propertyDeclaration);
}
public List<KotlinPropertyDeclaration> getPropertyDeclarations() {
return this.propertyDeclarations;
}
}

View File

@@ -26,6 +26,7 @@ import java.util.List;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.java.JavaPrimitives;
import io.spring.initializr.generator.test.io.TextTestUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
@@ -114,6 +115,61 @@ class GroovySourceCodeWriterTests {
" SpringApplication.run(Test, args)", " }", "", "}");
}
@Test
void field() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(GroovyFieldDeclaration.field("testString").returning(GroovyString.CLASS_NAME));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " String testString", "",
"}");
}
@Test
void fieldsWithValues() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(GroovyFieldDeclaration.field("testInteger").value(GroovyPrimitives.integerValue(42))
.returning(GroovyPrimitives.GroovyInteger.BOXED_CLASS_NAME));
test.addFieldDeclaration(GroovyFieldDeclaration.field("testDouble").modifiers(Modifier.PRIVATE)
.value(GroovyPrimitives.doubleValue(1986d)).returning(JavaPrimitives.JavaDouble.TYPE));
test.addFieldDeclaration(GroovyFieldDeclaration.field("testLong").value(GroovyPrimitives.longValue(1986L))
.returning(GroovyPrimitives.GroovyLong.TYPE));
test.addFieldDeclaration(GroovyFieldDeclaration.field("testNullBoolean")
.value(GroovyPrimitives.booleanValue(null)).returning(GroovyPrimitives.GroovyBoolean.BOXED_CLASS_NAME));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " Integer testInteger = 42",
"", " private double testDouble = 1986.0", "", " long testLong = 1986L", "",
" Boolean testNullBoolean = null", "", "}");
}
@Test
void privateField() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(GroovyFieldDeclaration.field("testString").modifiers(Modifier.PRIVATE)
.returning(GroovyString.CLASS_NAME));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" private String testString", "", "}");
}
@Test
void fieldAnnotation() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFieldDeclaration(GroovyFieldDeclaration.field("testString")
.withAnnotation(Annotation.name("org.springframework.beans.factory.annotation.Autowired"))
.returning(GroovyString.CLASS_NAME));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " @Autowired",
" String testString", "", "}");
}
@Test
void annotationWithSimpleAttribute() throws IOException {
List<String> lines = writeClassAnnotation(Annotation.name("org.springframework.test.TestApplication",

View File

@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThat;
* Tests for {@link JavaSourceCodeWriter}.
*
* @author Andy Wilkinson
* @author Matt Berteaux
*/
class JavaSourceCodeWriterTests {
@@ -88,13 +89,70 @@ class JavaSourceCodeWriterTests {
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"))
.modifiers(Modifier.PUBLIC).parameters(new Parameter(JavaString.CLASS_NAME, "value"))
.body(new JavaReturnStatement(new JavaMethodInvocation("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
void field() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.modifiers(Modifier.PUBLIC);
test.addFieldDeclaration(
JavaFieldDeclaration.field("testString").modifiers(Modifier.PRIVATE).returning(JavaString.CLASS_NAME));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "", "public class Test {", "",
" private String testString;", "", "}");
}
@Test
void fieldAnnotation() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.modifiers(Modifier.PUBLIC);
JavaFieldDeclaration field = JavaFieldDeclaration.field("testString").modifiers(Modifier.PRIVATE)
.returning(JavaString.CLASS_NAME);
field.annotate(Annotation.name("org.springframework.beans.factory.annotation.Autowired"));
test.addFieldDeclaration(field);
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "",
"import org.springframework.beans.factory.annotation.Autowired;", "", "public class Test {", "",
" @Autowired", " private String testString;", "", "}");
}
@Test
void fields() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.modifiers(Modifier.PUBLIC);
test.addFieldDeclaration(JavaFieldDeclaration.field("testString").modifiers(Modifier.PRIVATE)
.value(JavaString.stringValue("Test String")).returning(JavaString.CLASS_NAME));
test.addFieldDeclaration(JavaFieldDeclaration.field("testChar").modifiers(Modifier.PRIVATE | Modifier.TRANSIENT)
.value(JavaPrimitives.charValue("\\u03a9")).returning(JavaPrimitives.JavaChar.TYPE));
test.addFieldDeclaration(JavaFieldDeclaration.field("testInt").modifiers(Modifier.PRIVATE | Modifier.FINAL)
.value(JavaPrimitives.integerValue(1337)).returning(JavaPrimitives.JavaInteger.TYPE));
test.addFieldDeclaration(JavaFieldDeclaration.field("testDouble").modifiers(Modifier.PRIVATE)
.value(JavaPrimitives.doubleValue(3.14)).returning(JavaPrimitives.JavaDouble.BOXED_CLASS_NAME));
test.addFieldDeclaration(JavaFieldDeclaration.field("testLong").modifiers(Modifier.PRIVATE)
.value(JavaPrimitives.longValue(1986L)).returning(JavaPrimitives.JavaLong.BOXED_CLASS_NAME));
test.addFieldDeclaration(JavaFieldDeclaration.field("testFloat").value(JavaPrimitives.floatValue(99.999f))
.returning(JavaPrimitives.JavaFloat.TYPE));
test.addFieldDeclaration(JavaFieldDeclaration.field("testBool").packagePrivate()
.value(JavaPrimitives.booleanValue(true)).returning(JavaPrimitives.JavaBoolean.TYPE));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "", "public class Test {", "",
" private String testString = \"Test String\";", "",
" private transient char testChar = '\\u03a9';", "", " private final int testInt = 1337;", "",
" private Double testDouble = 3.14;", "", " private Long testLong = 1986L;", "",
" public float testFloat = 99.999f;", "", " boolean testBool = true;", "", "}");
}
@Test
void springBootApplication() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();

View File

@@ -89,7 +89,7 @@ class KotlinSourceCodeWriterTests {
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addFunctionDeclaration(KotlinFunctionDeclaration.function("reverse").returning("java.lang.String")
.parameters(new Parameter("java.lang.String", "echo"))
.body(new KotlinReturnStatement(new KotlinFunctionInvocation("echo", "reversed"))));
.body(new KotlinReturnStatement(new KotlinMethodInvocation("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()", " }", "", "}");
@@ -103,7 +103,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(new KotlinReturnStatement(new KotlinMethodInvocation("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()", " }", "", "}");
@@ -178,6 +178,102 @@ class KotlinSourceCodeWriterTests {
"@TestApplication(target = One::class, unit = ChronoUnit.NANOS)", "class Test");
}
@Test
void valProperty() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(
KotlinPropertyDeclaration.val("testProp").returning(KotlinString.CLASS_NAME).emptyValue());
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " val testProp: String", "",
"}");
}
@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(KotlinString.CLASS_NAME)
.value(KotlinString.stringValue("This is a TEST")));
test.addPropertyDeclaration(KotlinPropertyDeclaration.val("withGetter").returning(KotlinString.CLASS_NAME)
.getter().withBody(new KotlinExpressionStatement(new KotlinMethodInvocation("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
void varProperty() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testProp").returning(KotlinString.CLASS_NAME)
.value(KotlinString.stringValue("This is a test")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" var testProp: String = \"This is a test\"", "", "}");
}
@Test
void varPrivateSetterProperty() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testProp").returning(KotlinString.CLASS_NAME)
.setter().isPrivate().buildAccessor().value(KotlinString.stringValue("This is a test")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" var testProp: String = \"This is a test\"", " private set", "", "}");
}
@Test
void varAnnotateSetterProperty() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testProp").returning(KotlinString.CLASS_NAME)
.setter().withAnnotation(Annotation.name("org.springframework.beans.factory.annotation.Autowired"))
.buildAccessor().value(KotlinString.stringValue("This is a test")));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" var testProp: String = \"This is a test\"", " @Autowired set", "", "}");
}
@Test
void varProperties() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testProp")
.returning(KotlinPrimitives.KotlinInt.BOXED_CLASS_NAME).value(KotlinPrimitives.integerValue(42)));
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testDouble")
.returning(KotlinPrimitives.KotlinDouble.BOXED_CLASS_NAME).value(KotlinPrimitives.doubleValue(1986d)));
test.addPropertyDeclaration(
KotlinPropertyDeclaration.var("testFloat").value(KotlinPrimitives.floatValue(99.999f)));
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testLong")
.returning(KotlinPrimitives.KotlinLong.BOXED_CLASS_NAME).value(KotlinPrimitives.longValue(1986L)));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " var testProp: Int = 42",
"", " var testDouble: Double = 1986.0", "", " var testFloat = 99.999f", "",
" var testLong: Long = 1986L", "", "}");
}
@Test
void varEmptyProperty() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addPropertyDeclaration(KotlinPropertyDeclaration.var("testProp")
.returning(KotlinPrimitives.KotlinInt.BOXED_CLASS_NAME).empty());
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "", " var testProp: Int", "",
"}");
}
private List<String> writeClassAnnotation(Annotation annotation) throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");