Skip to content

Commit

Permalink
Support interface implementation in SourceCodeWriter
Browse files Browse the repository at this point in the history
Closes gh-1617
  • Loading branch information
mhalbritter committed Feb 3, 2025
1 parent 95200d2 commit 2386966
Show file tree
Hide file tree
Showing 8 changed files with 204 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@

package io.spring.initializr.generator.language;

import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
* A type declared in a {@link CompilationUnit}.
*
* @author Andy Wilkinson
* @author Moritz Halbritter
*/
public class TypeDeclaration implements Annotatable {

Expand All @@ -29,14 +34,40 @@ public class TypeDeclaration implements Annotatable {

private String extendedClassName;

private List<String> implementsClassNames = Collections.emptyList();

/**
* Creates a new instance.
* @param name the type name
*/
public TypeDeclaration(String name) {
this.name = name;
}

/**
* Extend the class with the given name.
* @param name the name of the class to extend
*/
public void extend(String name) {
this.extendedClassName = name;
}

/**
* Implement the given interfaces.
* @param names the names of the interfaces to implement
*/
public void implement(Collection<String> names) {
this.implementsClassNames = List.copyOf(names);
}

/**
* Implement the given interfaces.
* @param names the names of the interfaces to implement
*/
public void implement(String... names) {
this.implementsClassNames = List.of(names);
}

@Override
public AnnotationContainer annotations() {
return this.annotations;
Expand All @@ -50,4 +81,8 @@ public String getExtends() {
return this.extendedClassName;
}

public List<String> getImplements() {
return this.implementsClassNames;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@
import io.spring.initializr.generator.language.SourceCodeWriter;
import io.spring.initializr.generator.language.SourceStructure;

import org.springframework.util.CollectionUtils;

/**
* A {@link SourceCodeWriter} that writes {@link SourceCode} in Groovy.
*
Expand Down Expand Up @@ -125,6 +127,9 @@ private void writeTo(SourceStructure structure, GroovyCompilationUnit compilatio
if (type.getExtends() != null) {
writer.print(" extends " + getUnqualifiedName(type.getExtends()));
}
if (!CollectionUtils.isEmpty(type.getImplements())) {
writeImplements(type, writer);
}
writer.println(" {");
writer.println();
List<GroovyFieldDeclaration> fieldDeclarations = type.getFieldDeclarations();
Expand All @@ -148,6 +153,18 @@ private void writeTo(SourceStructure structure, GroovyCompilationUnit compilatio
}
}

private void writeImplements(GroovyTypeDeclaration type, IndentingWriter writer) {
writer.print(" implements ");
Iterator<String> iterator = type.getImplements().iterator();
while (iterator.hasNext()) {
String name = iterator.next();
writer.print(getUnqualifiedName(name));
if (iterator.hasNext()) {
writer.print(", ");
}
}
}

private void writeAnnotations(IndentingWriter writer, Annotatable annotatable, Runnable separator) {
annotatable.annotations().values().forEach((annotation) -> {
annotation.write(writer, FORMATTING_OPTIONS);
Expand Down Expand Up @@ -216,6 +233,7 @@ private Set<String> determineImports(GroovyCompilationUnit compilationUnit) {
List<String> imports = new ArrayList<>();
for (GroovyTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
imports.add(typeDeclaration.getExtends());
imports.addAll(typeDeclaration.getImplements());
imports.addAll(appendImports(typeDeclaration.annotations().values(), Annotation::getImports));
for (GroovyFieldDeclaration fieldDeclaration : typeDeclaration.getFieldDeclarations()) {
imports.add(fieldDeclaration.getReturnType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,14 @@
import io.spring.initializr.generator.language.SourceCodeWriter;
import io.spring.initializr.generator.language.SourceStructure;

import org.springframework.util.CollectionUtils;

/**
* A {@link SourceCodeWriter} that writes {@link SourceCode} in Java.
*
* @author Andy Wilkinson
* @author Matt Berteaux
* @author Moritz Halbritter
*/
public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {

Expand Down Expand Up @@ -122,6 +125,9 @@ private void writeTo(SourceStructure structure, JavaCompilationUnit compilationU
if (type.getExtends() != null) {
writer.print(" extends " + getUnqualifiedName(type.getExtends()));
}
if (!CollectionUtils.isEmpty(type.getImplements())) {
writeImplements(type, writer);
}
writer.println(" {");
writer.println();
List<JavaFieldDeclaration> fieldDeclarations = type.getFieldDeclarations();
Expand All @@ -145,6 +151,18 @@ private void writeTo(SourceStructure structure, JavaCompilationUnit compilationU
}
}

private void writeImplements(JavaTypeDeclaration type, IndentingWriter writer) {
writer.print(" implements ");
Iterator<String> iterator = type.getImplements().iterator();
while (iterator.hasNext()) {
String name = iterator.next();
writer.print(getUnqualifiedName(name));
if (iterator.hasNext()) {
writer.print(", ");
}
}
}

private void writeAnnotations(IndentingWriter writer, Annotatable annotatable, Runnable separator) {
annotatable.annotations().values().forEach((annotation) -> {
annotation.write(writer, CodeBlock.JAVA_FORMATTING_OPTIONS);
Expand Down Expand Up @@ -213,7 +231,7 @@ private Set<String> determineImports(JavaCompilationUnit compilationUnit) {
List<String> imports = new ArrayList<>();
for (JavaTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
imports.add(typeDeclaration.getExtends());

imports.addAll(typeDeclaration.getImplements());
imports.addAll(appendImports(typeDeclaration.annotations().values(), Annotation::getImports));
for (JavaFieldDeclaration fieldDeclaration : typeDeclaration.getFieldDeclarations()) {
imports.add(fieldDeclaration.getReturnType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
import io.spring.initializr.generator.language.SourceCodeWriter;
import io.spring.initializr.generator.language.SourceStructure;

import org.springframework.util.CollectionUtils;

/**
* A {@link SourceCodeWriter} that writes {@link SourceCode} in Kotlin.
*
Expand Down Expand Up @@ -95,9 +97,14 @@ private void writeTo(SourceStructure structure, KotlinCompilationUnit compilatio
writeAnnotations(writer, type);
writeModifiers(writer, type.getModifiers());
writer.print("class " + type.getName());
if (type.getExtends() != null) {
boolean hasExtends = type.getExtends() != null;
if (hasExtends) {
writer.print(" : " + getUnqualifiedName(type.getExtends()) + "()");
}
if (!CollectionUtils.isEmpty(type.getImplements())) {
writer.print(hasExtends ? ", " : " : ");
writeImplements(type, writer);
}
List<KotlinPropertyDeclaration> propertyDeclarations = type.getPropertyDeclarations();
List<KotlinFunctionDeclaration> functionDeclarations = type.getFunctionDeclarations();
boolean hasDeclarations = !propertyDeclarations.isEmpty() || !functionDeclarations.isEmpty();
Expand Down Expand Up @@ -136,6 +143,17 @@ private void writeTo(SourceStructure structure, KotlinCompilationUnit compilatio
}
}

private void writeImplements(KotlinTypeDeclaration type, IndentingWriter writer) {
Iterator<String> iterator = type.getImplements().iterator();
while (iterator.hasNext()) {
String name = iterator.next();
writer.print(getUnqualifiedName(name));
if (iterator.hasNext()) {
writer.print(", ");
}
}
}

private String escapeKotlinKeywords(String packageName) {
return Arrays.stream(packageName.split("\\."))
.map((segment) -> this.language.isKeyword(segment) ? "`" + segment + "`" : segment)
Expand Down Expand Up @@ -240,6 +258,7 @@ private Set<String> determineImports(KotlinCompilationUnit compilationUnit) {
List<String> imports = new ArrayList<>();
for (KotlinTypeDeclaration typeDeclaration : compilationUnit.getTypeDeclarations()) {
imports.add(typeDeclaration.getExtends());
imports.addAll(typeDeclaration.getImplements());
imports.addAll(appendImports(typeDeclaration.annotations().values(), Annotation::getImports));
typeDeclaration.getPropertyDeclarations()
.forEach(((propertyDeclaration) -> imports.addAll(determinePropertyImports(propertyDeclaration))));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright 2012-2025 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.spring.initializr.generator.language;

import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Tests for {@link TypeDeclaration}.
*
* @author Moritz Halbritter
*/
class TypeDeclarationTests {

@Test
void implementWithVarArgs() {
TypeDeclaration declaration = new TypeDeclaration("Test");
declaration.implement("Interface1", "Interface2");
assertThat(declaration.getImplements()).containsExactly("Interface1", "Interface2");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
*
* @author Stephane Nicoll
* @author Matt Berteaux
* @author Moritz Halbritter
*/
class GroovySourceCodeWriterTests {

Expand Down Expand Up @@ -109,6 +110,30 @@ void emptyTypeDeclarationWithSuperClass() throws IOException {
"class Test extends TestParent {", "", "}");
}

@Test
void shouldAddImplements() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.implement(List.of("com.example.build.Interface1", "com.example.build.Interface2"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "import com.example.build.Interface1",
"import com.example.build.Interface2", "", "class Test implements Interface1, Interface2 {", "", "}");
}

@Test
void shouldAddExtendsAndImplements() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
GroovyCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
GroovyTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.extend("com.example.build.TestParent");
test.implement(List.of("com.example.build.Interface1", "com.example.build.Interface2"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.groovy");
assertThat(lines).containsExactly("package com.example", "", "import com.example.build.Interface1",
"import com.example.build.Interface2", "import com.example.build.TestParent", "",
"class Test extends TestParent implements Interface1, Interface2 {", "", "}");
}

@Test
void method() throws IOException {
GroovySourceCode sourceCode = new GroovySourceCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
*
* @author Andy Wilkinson
* @author Matt Berteaux
* @author Moritz Halbritter
*/
class JavaSourceCodeWriterTests {

Expand Down Expand Up @@ -108,6 +109,30 @@ void emptyTypeDeclarationWithSuperClass() throws IOException {
"class Test extends TestParent {", "", "}");
}

@Test
void shouldAddImplements() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.implement(List.of("com.example.build.Interface1", "com.example.build.Interface2"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "", "import com.example.build.Interface1;",
"import com.example.build.Interface2;", "", "class Test implements Interface1, Interface2 {", "", "}");
}

@Test
void shouldAddExtendsAndImplements() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
JavaCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.extend("com.example.build.TestParent");
test.implement(List.of("com.example.build.Interface1", "com.example.build.Interface2"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
assertThat(lines).containsExactly("package com.example;", "", "import com.example.build.Interface1;",
"import com.example.build.Interface2;", "import com.example.build.TestParent;", "",
"class Test extends TestParent implements Interface1, Interface2 {", "", "}");
}

@Test
void method() throws IOException {
JavaSourceCode sourceCode = new JavaSourceCode();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
*
* @author Stephane Nicoll
* @author Matt Berteaux
* @author Moritz Halbritter
*/
class KotlinSourceCodeWriterTests {

Expand Down Expand Up @@ -108,6 +109,30 @@ void emptyTypeDeclarationWithSuperClass() throws IOException {
"class Test : TestParent()");
}

@Test
void shouldImplementInterfaces() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.implement(List.of("com.example.build.Interface1", "com.example.build.Interface2"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "import com.example.build.Interface1",
"import com.example.build.Interface2", "", "class Test : Interface1, Interface2");
}

@Test
void shouldExtendAndImplement() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
KotlinCompilationUnit compilationUnit = sourceCode.createCompilationUnit("com.example", "Test");
KotlinTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
test.extend("com.example.build.TestParent");
test.implement(List.of("com.example.build.Interface1", "com.example.build.Interface2"));
List<String> lines = writeSingleType(sourceCode, "com/example/Test.kt");
assertThat(lines).containsExactly("package com.example", "", "import com.example.build.Interface1",
"import com.example.build.Interface2", "import com.example.build.TestParent", "",
"class Test : TestParent(), Interface1, Interface2");
}

@Test
void function() throws IOException {
KotlinSourceCode sourceCode = new KotlinSourceCode();
Expand Down

0 comments on commit 2386966

Please sign in to comment.