diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java new file mode 100644 index 00000000..51df77a2 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyCompilationUnit.java @@ -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 { + + GroovyCompilationUnit(String packageName, String name) { + super(packageName, name); + } + + @Override + protected GroovyTypeDeclaration doCreateTypeDeclaration(String name) { + return new GroovyTypeDeclaration(name); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyExpression.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyExpression.java new file mode 100644 index 00000000..883ce04a --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyExpression.java @@ -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 { + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyExpressionStatement.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyExpressionStatement.java new file mode 100644 index 00000000..38d69519 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyExpressionStatement.java @@ -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; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyLanguage.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyLanguage.java new file mode 100644 index 00000000..a1d9ff07 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyLanguage.java @@ -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); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyLanguageFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyLanguageFactory.java new file mode 100644 index 00000000..8331d926 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyLanguageFactory.java @@ -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; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java new file mode 100644 index 00000000..c2c3021b --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodDeclaration.java @@ -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 annotations = new ArrayList<>(); + + private final String name; + + private final String returnType; + + private final int modifiers; + + private final List parameters; + + private final List statements; + + private GroovyMethodDeclaration(String name, String returnType, int modifiers, + List parameters, List 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 getParameters() { + return this.parameters; + } + + int getModifiers() { + return this.modifiers; + } + + public List getStatements() { + return this.statements; + } + + @Override + public void annotate(Annotation annotation) { + this.annotations.add(annotation); + } + + @Override + public List getAnnotations() { + return Collections.unmodifiableList(this.annotations); + } + + /** + * Builder for creating a {@link GroovyMethodDeclaration}. + */ + public static final class Builder { + + private final String name; + + private List 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)); + } + + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodInvocation.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodInvocation.java new file mode 100644 index 00000000..a1e5305b --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyMethodInvocation.java @@ -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 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 getArguments() { + return this.arguments; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyReturnStatement.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyReturnStatement.java new file mode 100644 index 00000000..8bd3011d --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyReturnStatement.java @@ -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; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovySourceCode.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovySourceCode.java new file mode 100644 index 00000000..cc73c88b --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovySourceCode.java @@ -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 { + + public GroovySourceCode() { + super(GroovyCompilationUnit::new); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriter.java new file mode 100644 index 00000000..a41f5587 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriter.java @@ -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 { + + private static final Map, String> METHOD_MODIFIERS; + + static { + Map, 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 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 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 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 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 values, Function 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 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 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 determineImports(GroovyCompilationUnit compilationUnit) { + List 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 determineImports(Annotation annotation) { + List 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 List getRequiredImports(List candidates, + Function> mapping) { + return getRequiredImports(candidates.stream(), mapping); + } + + private List getRequiredImports(Stream candidates, + Function> 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); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyStatement.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyStatement.java new file mode 100644 index 00000000..748df61d --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyStatement.java @@ -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 { + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java new file mode 100644 index 00000000..6b36099c --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/GroovyTypeDeclaration.java @@ -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 methodDeclarations = new ArrayList<>(); + + GroovyTypeDeclaration(String name) { + super(name); + } + + public void addMethodDeclaration(GroovyMethodDeclaration methodDeclaration) { + this.methodDeclarations.add(methodDeclaration); + } + + public List getMethodDeclarations() { + return this.methodDeclarations; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/package-info.java new file mode 100644 index 00000000..66ba94f5 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/groovy/package-info.java @@ -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; diff --git a/initializr-generator/src/main/resources/META-INF/spring.factories b/initializr-generator/src/main/resources/META-INF/spring.factories index e2547013..47cf070d 100644 --- a/initializr-generator/src/main/resources/META-INF/spring.factories +++ b/initializr-generator/src/main/resources/META-INF/spring.factories @@ -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 diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/LanguageTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/LanguageTests.java index 53ece0df..4169ac3f 100644 --- a/initializr-generator/src/test/java/io/spring/initializr/generator/language/LanguageTests.java +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/LanguageTests.java @@ -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() diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java new file mode 100644 index 00000000..85606a6f --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/groovy/GroovySourceCodeWriterTests.java @@ -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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; + } + +}