From 77bb7eb3ac4dd27b8d022d3ff634dc17c4fe6b5f Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Thu, 7 Feb 2019 14:45:48 +0100 Subject: [PATCH] Add language abstraction This commit adds a `Language` on the JVM abstraction with well known concepts such as `Annotation`, `Parameter`, `CompilationUnit` and `TypeDeclaration` that concrete language implementations can reuse. See gh-813 Co-authored-by: Stephane Nicoll --- .../generator/language/AbstractLanguage.java | 50 ++++++++ .../generator/language/Annotatable.java | 32 +++++ .../generator/language/Annotation.java | 116 ++++++++++++++++++ .../generator/language/CompilationUnit.java | 62 ++++++++++ .../generator/language/Language.java | 49 ++++++++ .../generator/language/LanguageFactory.java | 35 ++++++ .../generator/language/Parameter.java | 43 +++++++ .../generator/language/SourceCode.java | 55 +++++++++ .../generator/language/SourceCodeWriter.java | 38 ++++++ .../generator/language/TypeDeclaration.java | 62 ++++++++++ .../generator/language/package-info.java | 20 +++ .../generator/language/AnnotationTests.java | 51 ++++++++ 12 files changed, 613 insertions(+) create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/AbstractLanguage.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotation.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/Language.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/LanguageFactory.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCode.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCodeWriter.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java create mode 100644 initializr-generator/src/main/java/io/spring/initializr/generator/language/package-info.java create mode 100644 initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationTests.java diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/AbstractLanguage.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AbstractLanguage.java new file mode 100644 index 00000000..ec1fa8e7 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/AbstractLanguage.java @@ -0,0 +1,50 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * 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; + +/** + * Base {@link Language} implementation. + * + * @author Stephane Nicoll + */ +public abstract class AbstractLanguage implements Language { + + private final String id; + + private final String jvmVersion; + + protected AbstractLanguage(String id, String jvmVersion) { + this.id = id; + this.jvmVersion = (jvmVersion != null) ? jvmVersion : DEFAULT_JVM_VERSION; + } + + @Override + public String id() { + return this.id; + } + + @Override + public String jvmVersion() { + return this.jvmVersion; + } + + @Override + public String toString() { + return id(); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java new file mode 100644 index 00000000..5dadc729 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotatable.java @@ -0,0 +1,32 @@ +/* + * 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; + +import java.util.List; + +/** + * A representation of something that can be annotated. + * + * @author Andy Wilkinson + */ +public interface Annotatable { + + void annotate(Annotation annotation); + + List getAnnotations(); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotation.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotation.java new file mode 100644 index 00000000..86f48047 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Annotation.java @@ -0,0 +1,116 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +/** + * An annotation. + * + * @author Andy Wilkinson + * @author Stephane Nicoll + */ +public final class Annotation { + + private final String name; + + private final List attributes; + + private Annotation(Builder builder) { + this.name = builder.name; + this.attributes = Collections + .unmodifiableList(new ArrayList<>(builder.attributes.values())); + } + + public String getName() { + return this.name; + } + + public List getAttributes() { + return this.attributes; + } + + public static Annotation name(String name) { + return name(name, null); + } + + public static Annotation name(String name, Consumer annotation) { + Builder builder = new Builder(name); + if (annotation != null) { + annotation.accept(builder); + } + return new Annotation(builder); + } + + /** + * Builder for creating an {@link Annotation}. + */ + public static final class Builder { + + private final String name; + + private final Map attributes = new LinkedHashMap<>(); + + private Builder(String name) { + this.name = name; + } + + public Builder attribute(String name, Class type, String... values) { + this.attributes.put(name, new Attribute(name, type, values)); + return this; + } + + } + + /** + * Define an attribute of an annotation. + */ + public static final class Attribute { + + private final String name; + + private final Class type; + + private final List values; + + private Attribute(String name, Class type, String... values) { + this.name = name; + this.type = type; + this.values = Arrays.asList(values); + } + + public String getName() { + return this.name; + } + + public Class getType() { + return this.type; + } + + public List getValues() { + return this.values; + } + + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java new file mode 100644 index 00000000..97f78af4 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/CompilationUnit.java @@ -0,0 +1,62 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A compilation unit that represents an individual source file. + * + * @param the concrete type declaration supported by the compilation unit + * @author Andy Wilkinson + */ +public abstract class CompilationUnit { + + private final String packageName; + + private final String name; + + private final List typeDeclarations = new ArrayList<>(); + + public CompilationUnit(String packageName, String name) { + this.packageName = packageName; + this.name = name; + } + + public String getPackageName() { + return this.packageName; + } + + public String getName() { + return this.name; + } + + public T createTypeDeclaration(String name) { + T typeDeclaration = doCreateTypeDeclaration(name); + this.typeDeclarations.add(typeDeclaration); + return typeDeclaration; + } + + public List getTypeDeclarations() { + return Collections.unmodifiableList(this.typeDeclarations); + } + + protected abstract T doCreateTypeDeclaration(String name); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Language.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Language.java new file mode 100644 index 00000000..d879b244 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Language.java @@ -0,0 +1,49 @@ +/* + * 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; + +import java.util.Objects; + +import org.springframework.core.io.support.SpringFactoriesLoader; + +/** + * A language in which a generated project can be written. + * + * @author Andy Wilkinson + */ +public interface Language { + + /** + * The default JVM version to use if none is specified. + */ + String DEFAULT_JVM_VERSION = "1.8"; + + String id(); + + String jvmVersion(); + + static Language forId(String id, String jvmVersion) { + return SpringFactoriesLoader + .loadFactories(LanguageFactory.class, + LanguageFactory.class.getClassLoader()) + .stream().map((factory) -> factory.createLanguage(id, jvmVersion)) + .filter(Objects::nonNull).findFirst() + .orElseThrow(() -> new IllegalStateException( + "Unrecognized language id '" + id + "'")); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/LanguageFactory.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/LanguageFactory.java new file mode 100644 index 00000000..4f9d61f9 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/LanguageFactory.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * A factory for creating a {@link Language}. + * + * @author Andy Wilkinson + */ +public interface LanguageFactory { + + /** + * Creates and returns a {@link Language} for the given id and JVM version. If the + * factory does not recognise the given {@code id}, {@code null} should be returned. + * @param id the id of the language + * @param jvmVersion the jvm version or {@code null} to use the default + * @return the language or {@code null} + */ + Language createLanguage(String id, String jvmVersion); + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java new file mode 100644 index 00000000..229a6316 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/Parameter.java @@ -0,0 +1,43 @@ +/* + * 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; + +/** + * A parameter, typically of a method or function. + * + * @author Andy Wilkinson + */ +public class Parameter { + + private final String type; + + private final String name; + + public Parameter(String type, String name) { + this.type = type; + this.name = name; + } + + public String getType() { + return this.type; + } + + public String getName() { + return this.name; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCode.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCode.java new file mode 100644 index 00000000..e696d54c --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCode.java @@ -0,0 +1,55 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.function.BiFunction; + +/** + * Representation of application source code. + * + * @param types + * @param compilation units + * @author Andy Wilkinson + */ +public abstract class SourceCode> { + + private final List compilationUnits = new ArrayList<>(); + + private final BiFunction compilationUnitFactory; + + protected SourceCode(BiFunction compilationUnitFactory) { + this.compilationUnitFactory = compilationUnitFactory; + } + + public C createCompilationUnit(String packageName, String name) { + C compilationUnit = this.compilationUnitFactory.apply(packageName, name); + this.compilationUnits.add(compilationUnit); + return compilationUnit; + } + + /** + * Returns an unmodifiable view of the {@link CompilationUnit compilation units}. + * @return the compilation units + */ + public List getCompilationUnits() { + return Collections.unmodifiableList(this.compilationUnits); + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCodeWriter.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCodeWriter.java new file mode 100644 index 00000000..4705c330 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/SourceCodeWriter.java @@ -0,0 +1,38 @@ +/* + * 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; + +import java.io.IOException; +import java.nio.file.Path; + +/** + * A writer for some {@link SourceCode}. + * + * @param the type of source code that can be written by this writer + * @author Andy Wilkinson + */ +public interface SourceCodeWriter> { + + /** + * Writes, to the given {@code directory}, the given {@code sourceCode}. + * @param directory the root directory beneath which the source code is written + * @param sourceCode the source code to write + * @throws IOException if writing fails + */ + void writeTo(Path directory, S sourceCode) throws IOException; + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java new file mode 100644 index 00000000..2a103438 --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/TypeDeclaration.java @@ -0,0 +1,62 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A type declared in a {@link CompilationUnit}. + * + * @author Andy Wilkinson + */ +public class TypeDeclaration implements Annotatable { + + private final List annotations = new ArrayList<>(); + + private final String name; + + private String extendedClassName; + + public TypeDeclaration(String name) { + this.name = name; + } + + public void extend(String name) { + this.extendedClassName = name; + } + + @Override + public void annotate(Annotation annotation) { + this.annotations.add(annotation); + } + + @Override + public List getAnnotations() { + return Collections.unmodifiableList(this.annotations); + } + + public String getName() { + return this.name; + } + + public String getExtends() { + return this.extendedClassName; + } + +} diff --git a/initializr-generator/src/main/java/io/spring/initializr/generator/language/package-info.java b/initializr-generator/src/main/java/io/spring/initializr/generator/language/package-info.java new file mode 100644 index 00000000..eee1300d --- /dev/null +++ b/initializr-generator/src/main/java/io/spring/initializr/generator/language/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Language abstraction. + */ +package io.spring.initializr.generator.language; diff --git a/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationTests.java b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationTests.java new file mode 100644 index 00000000..31dee2a8 --- /dev/null +++ b/initializr-generator/src/test/java/io/spring/initializr/generator/language/AnnotationTests.java @@ -0,0 +1,51 @@ +/* + * 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; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link Annotation}. + * + * @author Stephane Nicoll + */ +class AnnotationTests { + + @Test + void annotationWithNoAttribute() { + Annotation annotation = Annotation.name("com.example.Test"); + assertThat(annotation.getName()).isEqualTo("com.example.Test"); + assertThat(annotation.getAttributes()).isEmpty(); + } + + @Test + void annotationWithSingleAttribute() { + Annotation annotation = Annotation.name("com.example.Test", + (builder) -> builder.attribute("test", Enum.class, + "com.example.Unit.CENTURIES, com.example.Unit.NANOS")); + assertThat(annotation.getName()).isEqualTo("com.example.Test"); + assertThat(annotation.getAttributes()).hasSize(1); + Annotation.Attribute attribute = annotation.getAttributes().get(0); + assertThat(attribute.getName()).isEqualTo("test"); + assertThat(attribute.getType()).isEqualTo(Enum.class); + assertThat(attribute.getValues()) + .containsExactly("com.example.Unit.CENTURIES, com.example.Unit.NANOS"); + } + +}