Add Groovy language support

This commit provides a Groovy language implementation with a writer that
can generate a `.groovy` source file based on a configurable model.

Closes gh-813
This commit is contained in:
Stephane Nicoll
2019-02-07 15:06:41 +01:00
parent bdfa852675
commit b5ba883b02
16 changed files with 1070 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.CompilationUnit;
/**
* A Groovy-specific {@link CompilationUnit}.
*
* @author Stephane Nicoll
*/
public class GroovyCompilationUnit extends CompilationUnit<GroovyTypeDeclaration> {
GroovyCompilationUnit(String packageName, String name) {
super(packageName, name);
}
@Override
protected GroovyTypeDeclaration doCreateTypeDeclaration(String name) {
return new GroovyTypeDeclaration(name);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
/**
* A Groovy expression.
*
* @author Stephane Nicoll
*/
public class GroovyExpression {
}

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
/**
* A statement that contains a single expression.
*
* @author Stephane Nicoll
*/
public class GroovyExpressionStatement extends GroovyStatement {
private final GroovyExpression expression;
public GroovyExpressionStatement(GroovyExpression expression) {
this.expression = expression;
}
public GroovyExpression getExpression() {
return this.expression;
}
}

View File

@@ -0,0 +1,42 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.AbstractLanguage;
import io.spring.initializr.generator.language.Language;
/**
* Groovy {@link Language}.
*
* @author Stephane Nicoll
*/
public final class GroovyLanguage extends AbstractLanguage {
/**
* Groovy {@link Language} identifier.
*/
public static final String ID = "groovy";
public GroovyLanguage() {
this(DEFAULT_JVM_VERSION);
}
public GroovyLanguage(String jvmVersion) {
super(ID, jvmVersion);
}
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.Language;
import io.spring.initializr.generator.language.LanguageFactory;
/**
* A {@link LanguageFactory} for Groovy.
*
* @author Stephane Nicoll
*/
class GroovyLanguageFactory implements LanguageFactory {
@Override
public Language createLanguage(String id, String jvmVersion) {
if (GroovyLanguage.ID.equals(id)) {
return new GroovyLanguage(jvmVersion);
}
return null;
}
}

View File

@@ -0,0 +1,130 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.Parameter;
/**
* Declaration of a method written in Groovy.
*
* @author Stephane Nicoll
*/
public final class GroovyMethodDeclaration implements Annotatable {
private final List<Annotation> annotations = new ArrayList<>();
private final String name;
private final String returnType;
private final int modifiers;
private final List<Parameter> parameters;
private final List<GroovyStatement> statements;
private GroovyMethodDeclaration(String name, String returnType, int modifiers,
List<Parameter> parameters, List<GroovyStatement> statements) {
this.name = name;
this.returnType = returnType;
this.modifiers = modifiers;
this.parameters = parameters;
this.statements = statements;
}
public static Builder method(String name) {
return new Builder(name);
}
String getName() {
return this.name;
}
String getReturnType() {
return this.returnType;
}
List<Parameter> getParameters() {
return this.parameters;
}
int getModifiers() {
return this.modifiers;
}
public List<GroovyStatement> getStatements() {
return this.statements;
}
@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 GroovyMethodDeclaration}.
*/
public static final class Builder {
private final String name;
private List<Parameter> parameters = new ArrayList<>();
private String returnType = "void";
private int modifiers = Modifier.PUBLIC;
private Builder(String name) {
this.name = name;
}
public Builder modifiers(int modifiers) {
this.modifiers = modifiers;
return this;
}
public Builder returning(String returnType) {
this.returnType = returnType;
return this;
}
public Builder parameters(Parameter... parameters) {
this.parameters = Arrays.asList(parameters);
return this;
}
public GroovyMethodDeclaration body(GroovyStatement... statements) {
return new GroovyMethodDeclaration(this.name, this.returnType, this.modifiers,
this.parameters, Arrays.asList(statements));
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import java.util.Arrays;
import java.util.List;
/**
* An invocation of a method.
*
* @author Stephane Nicoll
*/
public class GroovyMethodInvocation extends GroovyExpression {
private final String target;
private final String name;
private final List<String> arguments;
public GroovyMethodInvocation(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,36 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
/**
* A return statement.
*
* @author Stephane Nicoll
*/
public class GroovyReturnStatement extends GroovyStatement {
private final GroovyExpression expression;
public GroovyReturnStatement(GroovyExpression expression) {
this.expression = expression;
}
public GroovyExpression getExpression() {
return this.expression;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import io.spring.initializr.generator.language.SourceCode;
/**
* Groovy {@link SourceCode}.
*
* @author Stephane Nicoll
*/
public class GroovySourceCode
extends SourceCode<GroovyTypeDeclaration, GroovyCompilationUnit> {
public GroovySourceCode() {
super(GroovyCompilationUnit::new);
}
}

View File

@@ -0,0 +1,309 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import io.spring.initializr.generator.io.IndentingWriter;
import io.spring.initializr.generator.io.IndentingWriterFactory;
import io.spring.initializr.generator.language.Annotatable;
import io.spring.initializr.generator.language.Annotation;
import io.spring.initializr.generator.language.Parameter;
import io.spring.initializr.generator.language.SourceCode;
import io.spring.initializr.generator.language.SourceCodeWriter;
/**
* A {@link SourceCodeWriter} that writes {@link SourceCode} in Groovy.
*
* @author Stephane Nicoll
*/
public class GroovySourceCodeWriter implements SourceCodeWriter<GroovySourceCode> {
private static final Map<Predicate<Integer>, String> METHOD_MODIFIERS;
static {
Map<Predicate<Integer>, String> methodModifiers = new LinkedHashMap<>();
methodModifiers.put(Modifier::isProtected, "protected");
methodModifiers.put(Modifier::isPrivate, "private");
methodModifiers.put(Modifier::isAbstract, "abstract");
methodModifiers.put(Modifier::isStatic, "static");
methodModifiers.put(Modifier::isFinal, "final");
methodModifiers.put(Modifier::isSynchronized, "synchronized");
methodModifiers.put(Modifier::isNative, "native");
methodModifiers.put(Modifier::isStrict, "strictfp");
METHOD_MODIFIERS = methodModifiers;
}
private final IndentingWriterFactory indentingWriterFactory;
public GroovySourceCodeWriter(IndentingWriterFactory indentingWriterFactory) {
this.indentingWriterFactory = indentingWriterFactory;
}
@Override
public void writeTo(Path directory, GroovySourceCode sourceCode) throws IOException {
if (!Files.exists(directory)) {
Files.createDirectories(directory);
}
for (GroovyCompilationUnit compilationUnit : sourceCode.getCompilationUnits()) {
writeTo(directory, compilationUnit);
}
}
private void writeTo(Path directory, GroovyCompilationUnit compilationUnit)
throws IOException {
Path output = fileForCompilationUnit(directory, compilationUnit);
Files.createDirectories(output.getParent());
try (IndentingWriter writer = this.indentingWriterFactory
.createIndentingWriter("groovy", Files.newBufferedWriter(output))) {
writer.println("package " + compilationUnit.getPackageName());
writer.println();
Set<String> imports = determineImports(compilationUnit);
if (!imports.isEmpty()) {
for (String importedType : imports) {
writer.println("import " + importedType);
}
writer.println();
}
for (GroovyTypeDeclaration type : compilationUnit.getTypeDeclarations()) {
writeAnnotations(writer, type);
writer.print("class " + type.getName());
if (type.getExtends() != null) {
writer.print(" extends " + getUnqualifiedName(type.getExtends()));
}
writer.println(" {");
writer.println();
List<GroovyMethodDeclaration> methodDeclarations = type
.getMethodDeclarations();
if (!methodDeclarations.isEmpty()) {
writer.indented(() -> {
for (GroovyMethodDeclaration methodDeclaration : methodDeclarations) {
writeMethodDeclaration(writer, methodDeclaration);
}
});
}
writer.println("}");
writer.println("");
}
}
}
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(")");
}
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 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 writeMethodDeclaration(IndentingWriter writer,
GroovyMethodDeclaration methodDeclaration) {
writeAnnotations(writer, methodDeclaration);
writeMethodModifiers(writer, methodDeclaration);
writer.print(getUnqualifiedName(methodDeclaration.getReturnType()) + " "
+ methodDeclaration.getName() + "(");
List<Parameter> parameters = methodDeclaration.getParameters();
if (!parameters.isEmpty()) {
writer.print(parameters
.stream().map((parameter) -> getUnqualifiedName(parameter.getType())
+ " " + parameter.getName())
.collect(Collectors.joining(", ")));
}
writer.println(") {");
writer.indented(() -> {
List<GroovyStatement> statements = methodDeclaration.getStatements();
for (GroovyStatement statement : statements) {
if (statement instanceof GroovyExpressionStatement) {
writeExpression(writer,
((GroovyExpressionStatement) statement).getExpression());
}
else if (statement instanceof GroovyReturnStatement) {
writeExpression(writer,
((GroovyReturnStatement) statement).getExpression());
}
writer.println();
}
});
writer.println("}");
writer.println();
}
private void writeMethodModifiers(IndentingWriter writer,
GroovyMethodDeclaration methodDeclaration) {
String modifiers = METHOD_MODIFIERS.entrySet().stream()
.filter((entry) -> entry.getKey().test(methodDeclaration.getModifiers()))
.map(Entry::getValue).collect(Collectors.joining(" "));
if (!modifiers.isEmpty()) {
writer.print(modifiers);
writer.print(" ");
}
}
private void writeExpression(IndentingWriter writer, GroovyExpression expression) {
if (expression instanceof GroovyMethodInvocation) {
writeMethodInvocation(writer, (GroovyMethodInvocation) expression);
}
}
private void writeMethodInvocation(IndentingWriter writer,
GroovyMethodInvocation methodInvocation) {
writer.print(getUnqualifiedName(methodInvocation.getTarget()) + "."
+ methodInvocation.getName() + "("
+ String.join(", ", methodInvocation.getArguments()) + ")");
}
private Path fileForCompilationUnit(Path directory,
GroovyCompilationUnit compilationUnit) {
return directoryForPackage(directory, compilationUnit.getPackageName())
.resolve(compilationUnit.getName() + ".groovy");
}
private Path directoryForPackage(Path directory, String packageName) {
return directory.resolve(packageName.replace('.', '/'));
}
private Set<String> determineImports(GroovyCompilationUnit compilationUnit) {
List<String> imports = new ArrayList<>();
for (GroovyTypeDeclaration typeDeclaration : compilationUnit
.getTypeDeclarations()) {
if (requiresImport(typeDeclaration.getExtends())) {
imports.add(typeDeclaration.getExtends());
}
imports.addAll(getRequiredImports(typeDeclaration.getAnnotations(),
this::determineImports));
for (GroovyMethodDeclaration methodDeclaration : typeDeclaration
.getMethodDeclarations()) {
if (requiresImport(methodDeclaration.getReturnType())) {
imports.add(methodDeclaration.getReturnType());
}
imports.addAll(getRequiredImports(methodDeclaration.getAnnotations(),
this::determineImports));
imports.addAll(getRequiredImports(methodDeclaration.getParameters(),
(parameter) -> Collections.singletonList(parameter.getType())));
imports.addAll(getRequiredImports(
methodDeclaration.getStatements().stream()
.filter(GroovyExpressionStatement.class::isInstance)
.map(GroovyExpressionStatement.class::cast)
.map(GroovyExpressionStatement::getExpression)
.filter(GroovyMethodInvocation.class::isInstance)
.map(GroovyMethodInvocation.class::cast),
(methodInvocation) -> Collections
.singleton(methodInvocation.getTarget())));
}
}
Collections.sort(imports);
return new LinkedHashSet<>(imports);
}
private Collection<String> determineImports(Annotation annotation) {
List<String> imports = new ArrayList<>();
imports.add(annotation.getName());
annotation.getAttributes().forEach((attribute) -> {
if (attribute.getType() == Class.class) {
imports.addAll(attribute.getValues());
}
if (Enum.class.isAssignableFrom(attribute.getType())) {
imports.addAll(attribute.getValues().stream()
.map((value) -> value.substring(0, value.lastIndexOf(".")))
.collect(Collectors.toList()));
}
});
return imports;
}
private <T> List<String> getRequiredImports(List<T> candidates,
Function<T, Collection<String>> mapping) {
return getRequiredImports(candidates.stream(), mapping);
}
private <T> List<String> getRequiredImports(Stream<T> candidates,
Function<T, Collection<String>> mapping) {
return candidates.map(mapping).flatMap(Collection::stream)
.filter(this::requiresImport).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);
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
/**
* A statement in Groovy.
*
* @author Stephane Nicoll
*/
public class GroovyStatement {
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import java.util.ArrayList;
import java.util.List;
import io.spring.initializr.generator.language.TypeDeclaration;
/**
* A {@link TypeDeclaration declaration } of a type written in Groovy.
*
* @author Stephane Nicoll
*/
public class GroovyTypeDeclaration extends TypeDeclaration {
private final List<GroovyMethodDeclaration> methodDeclarations = new ArrayList<>();
GroovyTypeDeclaration(String name) {
super(name);
}
public void addMethodDeclaration(GroovyMethodDeclaration methodDeclaration) {
this.methodDeclarations.add(methodDeclaration);
}
public List<GroovyMethodDeclaration> getMethodDeclarations() {
return this.methodDeclarations;
}
}

View File

@@ -0,0 +1,23 @@
/*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Groovy language. Provides a
* {@link io.spring.initializr.generator.language.groovy.GroovyCompilationUnit compilation
* unit} implementation and a write for Groovy
* {@link io.spring.initializr.generator.language.groovy.GroovySourceCode source code}.
*/
package io.spring.initializr.generator.language.groovy;

View File

@@ -1,3 +1,4 @@
io.spring.initializr.generator.language.LanguageFactory=\
io.spring.initializr.generator.language.groovy.GroovyLanguageFactory,\
io.spring.initializr.generator.language.java.JavaLanguageFactory,\
io.spring.initializr.generator.language.kotlin.KotlinLanguageFactory

View File

@@ -16,6 +16,7 @@
package io.spring.initializr.generator.language;
import io.spring.initializr.generator.language.groovy.GroovyLanguage;
import io.spring.initializr.generator.language.java.JavaLanguage;
import io.spring.initializr.generator.language.kotlin.KotlinLanguage;
import org.junit.jupiter.api.Test;
@@ -48,6 +49,15 @@ class LanguageTests {
assertThat(kotlin.jvmVersion()).isEqualTo("1.8");
}
@Test
void groovyLanguage() {
Language groovy = Language.forId("groovy", "1.8");
assertThat(groovy).isInstanceOf(GroovyLanguage.class);
assertThat(groovy.id()).isEqualTo("groovy");
assertThat(groovy.toString()).isEqualTo("groovy");
assertThat(groovy.jvmVersion()).isEqualTo("1.8");
}
@Test
void unknownLanguage() {
assertThatIllegalStateException()

View File

@@ -0,0 +1,226 @@
/*
* Copyright 2012-2018 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.spring.initializr.generator.language.groovy;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.temporal.ChronoUnit;
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 org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link GroovySourceCodeWriter}.
*
* @author Stephane Nicoll
*/
class GroovySourceCodeWriterTests {
@TempDir
Path directory;
private final GroovySourceCodeWriter writer = new GroovySourceCodeWriter(
IndentingWriterFactory.withDefaultSettings());
@Test
void emptyCompilationUnit() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
sourceCode.createCompilationUnit("com.example", "Test");
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "");
}
@Test
void emptyTypeDeclaration() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode
.createCompilationUnit("com.example", "Test");
compilationUnit.createTypeDeclaration("Test");
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
"}", "");
}
@Test
void emptyTypeDeclarationWithSuperClass() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode
.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.extend("com.example.build.TestParent");
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "",
"import com.example.build.TestParent", "",
"class Test extends TestParent {", "", "}", "");
}
@Test
void method() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode
.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.addMethodDeclaration(
GroovyMethodDeclaration.method("trim").returning("java.lang.String")
.parameters(new Parameter("java.lang.String", "value"))
.body(new GroovyReturnStatement(
new GroovyMethodInvocation("value", "trim"))));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "class Test {", "",
" String trim(String value) {", " value.trim()", " }", "",
"}", "");
}
@Test
void springBootApplication() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode
.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.annotate(Annotation
.name("org.springframework.boot.autoconfigure.SpringBootApplication"));
test.addMethodDeclaration(GroovyMethodDeclaration.method("main")
.modifiers(Modifier.PUBLIC | Modifier.STATIC).returning("void")
.parameters(new Parameter("java.lang.String[]", "args"))
.body(new GroovyExpressionStatement(new GroovyMethodInvocation(
"org.springframework.boot.SpringApplication", "run", "Test",
"args"))));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "",
"import org.springframework.boot.SpringApplication",
"import org.springframework.boot.autoconfigure.SpringBootApplication", "",
"@SpringBootApplication", "class Test {", "",
" static void main(String[] args) {",
" SpringApplication.run(Test, args)", " }", "", "}", "");
}
@Test
void annotationWithSimpleAttribute() throws IOException {
List<String> lines = writeClassAnnotation(
Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("counter", Integer.class, "42")));
assertThat(lines).containsExactly("package com.example", "",
"import org.springframework.test.TestApplication", "",
"@TestApplication(counter = 42)", "class Test {", "", "}", "");
}
@Test
void annotationWithSimpleStringAttribute() throws IOException {
List<String> lines = writeClassAnnotation(
Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("name", String.class, "test")));
assertThat(lines).containsExactly("package com.example", "",
"import org.springframework.test.TestApplication", "",
"@TestApplication(name = \"test\")", "class Test {", "", "}", "");
}
@Test
void annotationWithOnlyValueAttribute() throws IOException {
List<String> lines = writeClassAnnotation(
Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("value", String.class, "test")));
assertThat(lines).containsExactly("package com.example", "",
"import org.springframework.test.TestApplication", "",
"@TestApplication(\"test\")", "class Test {", "", "}", "");
}
@Test
void annotationWithSimpleEnumAttribute() throws IOException {
List<String> lines = writeClassAnnotation(
Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("unit", Enum.class,
"java.time.temporal.ChronoUnit.SECONDS")));
assertThat(lines).containsExactly("package com.example", "",
"import java.time.temporal.ChronoUnit",
"import org.springframework.test.TestApplication", "",
"@TestApplication(unit = ChronoUnit.SECONDS)", "class Test {", "", "}",
"");
}
@Test
void annotationWithClassArrayAttribute() throws IOException {
List<String> lines = writeClassAnnotation(
Annotation.name("org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class,
"com.example.One", "com.example.Two")));
assertThat(lines).containsExactly("package com.example", "",
"import com.example.One", "import com.example.Two",
"import org.springframework.test.TestApplication", "",
"@TestApplication(target = { One, Two })", "class Test {", "", "}", "");
}
@Test
void annotationWithSeveralAttributes() throws IOException {
List<String> lines = writeClassAnnotation(Annotation.name(
"org.springframework.test.TestApplication",
(builder) -> builder.attribute("target", Class.class, "com.example.One")
.attribute("unit", ChronoUnit.class,
"java.time.temporal.ChronoUnit.NANOS")));
assertThat(lines).containsExactly("package com.example", "",
"import com.example.One", "import java.time.temporal.ChronoUnit",
"import org.springframework.test.TestApplication", "",
"@TestApplication(target = One, unit = ChronoUnit.NANOS)", "class Test {",
"", "}", "");
}
private List<String> writeClassAnnotation(Annotation annotation) throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode
.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.annotate(annotation);
return writeSingleType(sourceCode, "com/example/Test.groovy");
}
@Test
void methodWithSimpleAnnotation() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode
.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
GroovyMethodDeclaration method = GroovyMethodDeclaration.method("something")
.returning("void").parameters().body();
method.annotate(Annotation.name("com.example.test.TestAnnotation"));
test.addMethodDeclaration(method);
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "",
"import com.example.test.TestAnnotation", "", "class Test {", "",
" @TestAnnotation", " void something() {", " }", "", "}", "");
}
private List<String> writeSingleType(GroovySourceCode sourceCode, String location)
throws IOException {
Path source = writeSourceCode(sourceCode).resolve(location);
assertThat(source).isRegularFile();
return Files.readAllLines(source);
}
private Path writeSourceCode(GroovySourceCode sourceCode) throws IOException {
Path projectDirectory = Files.createTempDirectory(this.directory, "project-");
this.writer.writeTo(projectDirectory, sourceCode);
return projectDirectory;
}
}