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 <snicoll@pivotal.io>
This commit is contained in:
Andy Wilkinson 2019-02-07 14:45:48 +01:00 committed by Stephane Nicoll
parent b6e675de40
commit 77bb7eb3ac
12 changed files with 613 additions and 0 deletions

View File

@ -0,0 +1,50 @@
/*
* Copyright 2012-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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();
}
}

View File

@ -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<Annotation> getAnnotations();
}

View File

@ -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<Attribute> 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<Attribute> getAttributes() {
return this.attributes;
}
public static Annotation name(String name) {
return name(name, null);
}
public static Annotation name(String name, Consumer<Builder> 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<String, Attribute> 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<String> 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<String> getValues() {
return this.values;
}
}
}

View File

@ -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 <T> the concrete type declaration supported by the compilation unit
* @author Andy Wilkinson
*/
public abstract class CompilationUnit<T extends TypeDeclaration> {
private final String packageName;
private final String name;
private final List<T> 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<T> getTypeDeclarations() {
return Collections.unmodifiableList(this.typeDeclarations);
}
protected abstract T doCreateTypeDeclaration(String name);
}

View File

@ -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 + "'"));
}
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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 <T> types
* @param <C> compilation units
* @author Andy Wilkinson
*/
public abstract class SourceCode<T extends TypeDeclaration, C extends CompilationUnit<T>> {
private final List<C> compilationUnits = new ArrayList<>();
private final BiFunction<String, String, C> compilationUnitFactory;
protected SourceCode(BiFunction<String, String, C> 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<C> getCompilationUnits() {
return Collections.unmodifiableList(this.compilationUnits);
}
}

View File

@ -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 <S> the type of source code that can be written by this writer
* @author Andy Wilkinson
*/
public interface SourceCodeWriter<S extends SourceCode<?, ?>> {
/**
* 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;
}

View File

@ -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<Annotation> 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<Annotation> getAnnotations() {
return Collections.unmodifiableList(this.annotations);
}
public String getName() {
return this.name;
}
public String getExtends() {
return this.extendedClassName;
}
}

View File

@ -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;

View File

@ -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");
}
}