mirror of
https://gitee.com/dcren/initializr.git
synced 2025-04-05 17:38:06 +08:00
Add Java language support
This commit provides a Java language implementation with a writer that can generate a `.java` source file based on a configurable model. See gh-813 Co-authored-by: Stephane Nicoll <snicoll@pivotal.io>
This commit is contained in:
parent
77bb7eb3ac
commit
d0d4809ee9
initializr-generator/src
main
java/io/spring/initializr/generator/language/java
JavaCompilationUnit.javaJavaExpression.javaJavaExpressionStatement.javaJavaLanguage.javaJavaLanguageFactory.javaJavaMethodDeclaration.javaJavaMethodInvocation.javaJavaReturnStatement.javaJavaSourceCode.javaJavaSourceCodeWriter.javaJavaStatement.javaJavaTypeDeclaration.javapackage-info.java
resources/META-INF
test/java/io/spring/initializr/generator/language
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
import io.spring.initializr.generator.language.CompilationUnit;
|
||||
|
||||
/**
|
||||
* A Java-specific {@link CompilationUnit}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaCompilationUnit extends CompilationUnit<JavaTypeDeclaration> {
|
||||
|
||||
JavaCompilationUnit(String packageName, String name) {
|
||||
super(packageName, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JavaTypeDeclaration doCreateTypeDeclaration(String name) {
|
||||
return new JavaTypeDeclaration(name);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
/**
|
||||
* A Java expression.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaExpression {
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
/**
|
||||
* A statement that contains a single expression.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaExpressionStatement extends JavaStatement {
|
||||
|
||||
private final JavaExpression expression;
|
||||
|
||||
public JavaExpressionStatement(JavaExpression expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public JavaExpression getExpression() {
|
||||
return this.expression;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
import io.spring.initializr.generator.language.AbstractLanguage;
|
||||
import io.spring.initializr.generator.language.Language;
|
||||
|
||||
/**
|
||||
* Java {@link Language}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public final class JavaLanguage extends AbstractLanguage {
|
||||
|
||||
/**
|
||||
* Java {@link Language} identifier.
|
||||
*/
|
||||
public static final String ID = "java";
|
||||
|
||||
public JavaLanguage() {
|
||||
this(DEFAULT_JVM_VERSION);
|
||||
}
|
||||
|
||||
public JavaLanguage(String jvmVersion) {
|
||||
super(ID, jvmVersion);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
import io.spring.initializr.generator.language.Language;
|
||||
import io.spring.initializr.generator.language.LanguageFactory;
|
||||
|
||||
/**
|
||||
* A {@link LanguageFactory} for Java.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class JavaLanguageFactory implements LanguageFactory {
|
||||
|
||||
@Override
|
||||
public Language createLanguage(String id, String jvmVersion) {
|
||||
if (JavaLanguage.ID.equals(id)) {
|
||||
return new JavaLanguage(jvmVersion);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
@ -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.java;
|
||||
|
||||
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 Java.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public final class JavaMethodDeclaration implements Annotatable {
|
||||
|
||||
private final List<Annotation> annotations = new ArrayList<>();
|
||||
|
||||
private final String name;
|
||||
|
||||
private final String returnType;
|
||||
|
||||
private final int modifiers;
|
||||
|
||||
private final List<Parameter> parameters;
|
||||
|
||||
private final List<JavaStatement> statements;
|
||||
|
||||
private JavaMethodDeclaration(String name, String returnType, int modifiers,
|
||||
List<Parameter> parameters, List<JavaStatement> statements) {
|
||||
this.name = name;
|
||||
this.returnType = returnType;
|
||||
this.modifiers = modifiers;
|
||||
this.parameters = parameters;
|
||||
this.statements = statements;
|
||||
}
|
||||
|
||||
public static Builder method(String name) {
|
||||
return new Builder(name);
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
String getReturnType() {
|
||||
return this.returnType;
|
||||
}
|
||||
|
||||
List<Parameter> getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
int getModifiers() {
|
||||
return this.modifiers;
|
||||
}
|
||||
|
||||
public List<JavaStatement> getStatements() {
|
||||
return this.statements;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void annotate(Annotation annotation) {
|
||||
this.annotations.add(annotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Annotation> getAnnotations() {
|
||||
return Collections.unmodifiableList(this.annotations);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for creating a {@link JavaMethodDeclaration}.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private final String name;
|
||||
|
||||
private List<Parameter> parameters = new ArrayList<>();
|
||||
|
||||
private String returnType = "void";
|
||||
|
||||
private int modifiers = Modifier.PUBLIC;
|
||||
|
||||
private Builder(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Builder modifiers(int modifiers) {
|
||||
this.modifiers = modifiers;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder returning(String returnType) {
|
||||
this.returnType = returnType;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder parameters(Parameter... parameters) {
|
||||
this.parameters = Arrays.asList(parameters);
|
||||
return this;
|
||||
}
|
||||
|
||||
public JavaMethodDeclaration body(JavaStatement... statements) {
|
||||
return new JavaMethodDeclaration(this.name, this.returnType, this.modifiers,
|
||||
this.parameters, Arrays.asList(statements));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An invocation of a method.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaMethodInvocation extends JavaExpression {
|
||||
|
||||
private final String target;
|
||||
|
||||
private final String name;
|
||||
|
||||
private final List<String> arguments;
|
||||
|
||||
public JavaMethodInvocation(String target, String name, String... arguments) {
|
||||
this.target = target;
|
||||
this.name = name;
|
||||
this.arguments = Arrays.asList(arguments);
|
||||
}
|
||||
|
||||
public String getTarget() {
|
||||
return this.target;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public List<String> getArguments() {
|
||||
return this.arguments;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
/**
|
||||
* A return statement.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaReturnStatement extends JavaStatement {
|
||||
|
||||
private final JavaExpression expression;
|
||||
|
||||
public JavaReturnStatement(JavaExpression expression) {
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
public JavaExpression getExpression() {
|
||||
return this.expression;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
import io.spring.initializr.generator.language.SourceCode;
|
||||
|
||||
/**
|
||||
* Java {@link SourceCode}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaSourceCode extends SourceCode<JavaTypeDeclaration, JavaCompilationUnit> {
|
||||
|
||||
public JavaSourceCode() {
|
||||
super(JavaCompilationUnit::new);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,312 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
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 Java.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaSourceCodeWriter implements SourceCodeWriter<JavaSourceCode> {
|
||||
|
||||
private static final Map<Predicate<Integer>, String> METHOD_MODIFIERS;
|
||||
|
||||
static {
|
||||
Map<Predicate<Integer>, String> methodModifiers = new LinkedHashMap<>();
|
||||
methodModifiers.put(Modifier::isPublic, "public");
|
||||
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 JavaSourceCodeWriter(IndentingWriterFactory indentingWriterFactory) {
|
||||
this.indentingWriterFactory = indentingWriterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(Path directory, JavaSourceCode sourceCode) throws IOException {
|
||||
if (!Files.exists(directory)) {
|
||||
Files.createDirectories(directory);
|
||||
}
|
||||
for (JavaCompilationUnit compilationUnit : sourceCode.getCompilationUnits()) {
|
||||
writeTo(directory, compilationUnit);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeTo(Path directory, JavaCompilationUnit compilationUnit)
|
||||
throws IOException {
|
||||
Path output = fileForCompilationUnit(directory, compilationUnit);
|
||||
Files.createDirectories(output.getParent());
|
||||
try (IndentingWriter writer = this.indentingWriterFactory
|
||||
.createIndentingWriter("java", Files.newBufferedWriter(output))) {
|
||||
writer.println("package " + compilationUnit.getPackageName() + ";");
|
||||
writer.println();
|
||||
Set<String> imports = determineImports(compilationUnit);
|
||||
if (!imports.isEmpty()) {
|
||||
for (String importedType : imports) {
|
||||
writer.println("import " + importedType + ";");
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
for (JavaTypeDeclaration type : compilationUnit.getTypeDeclarations()) {
|
||||
writeAnnotations(writer, type);
|
||||
writer.print("public class " + type.getName());
|
||||
if (type.getExtends() != null) {
|
||||
writer.print(" extends " + getUnqualifiedName(type.getExtends()));
|
||||
}
|
||||
writer.println(" {");
|
||||
writer.println();
|
||||
List<JavaMethodDeclaration> methodDeclarations = type
|
||||
.getMethodDeclarations();
|
||||
if (!methodDeclarations.isEmpty()) {
|
||||
writer.indented(() -> {
|
||||
for (JavaMethodDeclaration methodDeclaration : methodDeclarations) {
|
||||
writeMethodDeclaration(writer, methodDeclaration);
|
||||
}
|
||||
});
|
||||
}
|
||||
writer.println("}");
|
||||
writer.println("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAnnotations(IndentingWriter writer, Annotatable annotatable) {
|
||||
annotatable.getAnnotations()
|
||||
.forEach((annotation) -> writeAnnotation(writer, annotation));
|
||||
}
|
||||
|
||||
private void writeAnnotation(IndentingWriter writer, Annotation annotation) {
|
||||
writer.print("@" + getUnqualifiedName(annotation.getName()));
|
||||
List<Annotation.Attribute> attributes = annotation.getAttributes();
|
||||
if (!attributes.isEmpty()) {
|
||||
writer.print("(");
|
||||
if (attributes.size() == 1 && attributes.get(0).getName().equals("value")) {
|
||||
writer.print(formatAnnotationAttribute(attributes.get(0)));
|
||||
}
|
||||
else {
|
||||
writer.print(attributes.stream()
|
||||
.map((attribute) -> attribute.getName() + " = "
|
||||
+ formatAnnotationAttribute(attribute))
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
writer.print(")");
|
||||
}
|
||||
writer.println();
|
||||
}
|
||||
|
||||
private String formatAnnotationAttribute(Annotation.Attribute attribute) {
|
||||
List<String> values = attribute.getValues();
|
||||
if (attribute.getType().equals(Class.class)) {
|
||||
return formatValues(values,
|
||||
(value) -> String.format("%s.class", getUnqualifiedName(value)));
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
return formatValues(values, (value) -> {
|
||||
String enumValue = value.substring(value.lastIndexOf(".") + 1);
|
||||
String enumClass = value.substring(0, value.lastIndexOf("."));
|
||||
return String.format("%s.%s", getUnqualifiedName(enumClass), enumValue);
|
||||
});
|
||||
}
|
||||
if (attribute.getType().equals(String.class)) {
|
||||
return formatValues(values, (value) -> String.format("\"%s\"", value));
|
||||
}
|
||||
return formatValues(values, (value) -> String.format("%s", value));
|
||||
}
|
||||
|
||||
private String formatValues(List<String> values, Function<String, String> formatter) {
|
||||
String result = values.stream().map(formatter).collect(Collectors.joining(", "));
|
||||
return (values.size() > 1) ? "{ " + result + " }" : result;
|
||||
}
|
||||
|
||||
private void writeMethodDeclaration(IndentingWriter writer,
|
||||
JavaMethodDeclaration methodDeclaration) {
|
||||
writeAnnotations(writer, methodDeclaration);
|
||||
writeMethodModifiers(writer, methodDeclaration);
|
||||
writer.print(getUnqualifiedName(methodDeclaration.getReturnType()) + " "
|
||||
+ methodDeclaration.getName() + "(");
|
||||
List<Parameter> parameters = methodDeclaration.getParameters();
|
||||
if (!parameters.isEmpty()) {
|
||||
writer.print(parameters
|
||||
.stream().map((parameter) -> getUnqualifiedName(parameter.getType())
|
||||
+ " " + parameter.getName())
|
||||
.collect(Collectors.joining(", ")));
|
||||
}
|
||||
writer.println(") {");
|
||||
writer.indented(() -> {
|
||||
List<JavaStatement> statements = methodDeclaration.getStatements();
|
||||
for (JavaStatement statement : statements) {
|
||||
if (statement instanceof JavaExpressionStatement) {
|
||||
writeExpression(writer,
|
||||
((JavaExpressionStatement) statement).getExpression());
|
||||
}
|
||||
else if (statement instanceof JavaReturnStatement) {
|
||||
writer.print("return ");
|
||||
writeExpression(writer,
|
||||
((JavaReturnStatement) statement).getExpression());
|
||||
}
|
||||
writer.println(";");
|
||||
}
|
||||
});
|
||||
writer.println("}");
|
||||
writer.println();
|
||||
}
|
||||
|
||||
private void writeMethodModifiers(IndentingWriter writer,
|
||||
JavaMethodDeclaration 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, JavaExpression expression) {
|
||||
if (expression instanceof JavaMethodInvocation) {
|
||||
writeMethodInvocation(writer, (JavaMethodInvocation) expression);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMethodInvocation(IndentingWriter writer,
|
||||
JavaMethodInvocation methodInvocation) {
|
||||
writer.print(getUnqualifiedName(methodInvocation.getTarget()) + "."
|
||||
+ methodInvocation.getName() + "("
|
||||
+ String.join(", ", methodInvocation.getArguments()) + ")");
|
||||
}
|
||||
|
||||
private Path fileForCompilationUnit(Path directory,
|
||||
JavaCompilationUnit compilationUnit) {
|
||||
return directoryForPackage(directory, compilationUnit.getPackageName())
|
||||
.resolve(compilationUnit.getName() + ".java");
|
||||
}
|
||||
|
||||
private Path directoryForPackage(Path directory, String packageName) {
|
||||
return directory.resolve(packageName.replace('.', '/'));
|
||||
}
|
||||
|
||||
private Set<String> determineImports(JavaCompilationUnit compilationUnit) {
|
||||
List<String> imports = new ArrayList<>();
|
||||
for (JavaTypeDeclaration typeDeclaration : compilationUnit
|
||||
.getTypeDeclarations()) {
|
||||
if (requiresImport(typeDeclaration.getExtends())) {
|
||||
imports.add(typeDeclaration.getExtends());
|
||||
}
|
||||
imports.addAll(getRequiredImports(typeDeclaration.getAnnotations(),
|
||||
this::determineImports));
|
||||
for (JavaMethodDeclaration 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(JavaExpressionStatement.class::isInstance)
|
||||
.map(JavaExpressionStatement.class::cast)
|
||||
.map(JavaExpressionStatement::getExpression)
|
||||
.filter(JavaMethodInvocation.class::isInstance)
|
||||
.map(JavaMethodInvocation.class::cast),
|
||||
(methodInvocation) -> Collections
|
||||
.singleton(methodInvocation.getTarget())));
|
||||
}
|
||||
}
|
||||
Collections.sort(imports);
|
||||
return new LinkedHashSet<>(imports);
|
||||
}
|
||||
|
||||
private Collection<String> determineImports(Annotation annotation) {
|
||||
List<String> imports = new ArrayList<>();
|
||||
imports.add(annotation.getName());
|
||||
annotation.getAttributes().forEach((attribute) -> {
|
||||
if (attribute.getType() == Class.class) {
|
||||
imports.addAll(attribute.getValues());
|
||||
}
|
||||
if (Enum.class.isAssignableFrom(attribute.getType())) {
|
||||
imports.addAll(attribute.getValues().stream()
|
||||
.map((value) -> value.substring(0, value.lastIndexOf(".")))
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
});
|
||||
return imports;
|
||||
}
|
||||
|
||||
private <T> List<String> getRequiredImports(List<T> candidates,
|
||||
Function<T, Collection<String>> mapping) {
|
||||
return getRequiredImports(candidates.stream(), mapping);
|
||||
}
|
||||
|
||||
private <T> List<String> getRequiredImports(Stream<T> candidates,
|
||||
Function<T, Collection<String>> mapping) {
|
||||
return candidates.map(mapping).flatMap(Collection::stream)
|
||||
.filter(this::requiresImport).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private String getUnqualifiedName(String name) {
|
||||
if (!name.contains(".")) {
|
||||
return name;
|
||||
}
|
||||
return name.substring(name.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
private boolean requiresImport(String name) {
|
||||
if (name == null || !name.contains(".")) {
|
||||
return false;
|
||||
}
|
||||
String packageName = name.substring(0, name.lastIndexOf('.'));
|
||||
return !"java.lang".equals(packageName);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
/**
|
||||
* A statement in Java.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaStatement {
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.spring.initializr.generator.language.TypeDeclaration;
|
||||
|
||||
/**
|
||||
* A {@link TypeDeclaration declaration } of a type written in Java.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class JavaTypeDeclaration extends TypeDeclaration {
|
||||
|
||||
private final List<JavaMethodDeclaration> methodDeclarations = new ArrayList<>();
|
||||
|
||||
JavaTypeDeclaration(String name) {
|
||||
super(name);
|
||||
}
|
||||
|
||||
public void addMethodDeclaration(JavaMethodDeclaration methodDeclaration) {
|
||||
this.methodDeclarations.add(methodDeclaration);
|
||||
}
|
||||
|
||||
public List<JavaMethodDeclaration> getMethodDeclarations() {
|
||||
return this.methodDeclarations;
|
||||
}
|
||||
|
||||
}
|
@ -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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Java language. Provides a
|
||||
* {@link io.spring.initializr.generator.language.java.JavaCompilationUnit compilation
|
||||
* unit} implementation and a write for Java
|
||||
* {@link io.spring.initializr.generator.language.java.JavaSourceCode source code}.
|
||||
*/
|
||||
package io.spring.initializr.generator.language.java;
|
@ -0,0 +1,2 @@
|
||||
io.spring.initializr.generator.language.LanguageFactory=\
|
||||
io.spring.initializr.generator.language.java.JavaLanguageFactory
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
import io.spring.initializr.generator.language.java.JavaLanguage;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link Language}
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class LanguageTests {
|
||||
|
||||
@Test
|
||||
void javaLanguage() {
|
||||
Language java = Language.forId("java", "11");
|
||||
assertThat(java).isInstanceOf(JavaLanguage.class);
|
||||
assertThat(java.id()).isEqualTo("java");
|
||||
assertThat(java.toString()).isEqualTo("java");
|
||||
assertThat(java.jvmVersion()).isEqualTo("11");
|
||||
}
|
||||
|
||||
@Test
|
||||
void unknownLanguage() {
|
||||
assertThatIllegalStateException()
|
||||
.isThrownBy(() -> Language.forId("unknown", null))
|
||||
.withMessageContaining("Unrecognized language id 'unknown'");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,228 @@
|
||||
/*
|
||||
* 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.java;
|
||||
|
||||
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 JavaSourceCodeWriter}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
class JavaSourceCodeWriterTests {
|
||||
|
||||
@TempDir
|
||||
Path directory;
|
||||
|
||||
private final JavaSourceCodeWriter writer = new JavaSourceCodeWriter(
|
||||
IndentingWriterFactory.withDefaultSettings());
|
||||
|
||||
@Test
|
||||
void emptyCompilationUnit() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
sourceCode.createCompilationUnit("com.example", "Test");
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyTypeDeclaration() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode
|
||||
.createCompilationUnit("com.example", "Test");
|
||||
compilationUnit.createTypeDeclaration("Test");
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"public class Test {", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void emptyTypeDeclarationWithSuperClass() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode
|
||||
.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.extend("com.example.build.TestParent");
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import com.example.build.TestParent;", "",
|
||||
"public class Test extends TestParent {", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void method() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode
|
||||
.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.addMethodDeclaration(
|
||||
JavaMethodDeclaration.method("trim").returning("java.lang.String")
|
||||
.parameters(new Parameter("java.lang.String", "value"))
|
||||
.body(new JavaReturnStatement(
|
||||
new JavaMethodInvocation("value", "trim"))));
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"public class Test {", "", " public String trim(String value) {",
|
||||
" return value.trim();", " }", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void springBootApplication() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode
|
||||
.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(Annotation
|
||||
.name("org.springframework.boot.autoconfigure.SpringBootApplication"));
|
||||
test.addMethodDeclaration(JavaMethodDeclaration.method("main")
|
||||
.modifiers(Modifier.PUBLIC | Modifier.STATIC).returning("void")
|
||||
.parameters(new Parameter("java.lang.String[]", "args"))
|
||||
.body(new JavaExpressionStatement(new JavaMethodInvocation(
|
||||
"org.springframework.boot.SpringApplication", "run", "Test.class",
|
||||
"args"))));
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.boot.SpringApplication;",
|
||||
"import org.springframework.boot.autoconfigure.SpringBootApplication;",
|
||||
"", "@SpringBootApplication", "public class Test {", "",
|
||||
" public static void main(String[] args) {",
|
||||
" SpringApplication.run(Test.class, args);", " }", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(
|
||||
Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("counter", Integer.class, "42")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(counter = 42)", "public class Test {", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleStringAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(
|
||||
Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("name", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(name = \"test\")", "public class Test {", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithOnlyValueAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(
|
||||
Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("value", String.class, "test")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(\"test\")", "public class Test {", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSimpleEnumAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(
|
||||
Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("unit", Enum.class,
|
||||
"java.time.temporal.ChronoUnit.SECONDS")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import java.time.temporal.ChronoUnit;",
|
||||
"import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(unit = ChronoUnit.SECONDS)", "public class Test {", "",
|
||||
"}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithClassArrayAttribute() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(
|
||||
Annotation.name("org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("target", Class.class,
|
||||
"com.example.One", "com.example.Two")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import com.example.One;", "import com.example.Two;",
|
||||
"import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(target = { One.class, Two.class })",
|
||||
"public class Test {", "", "}", "");
|
||||
}
|
||||
|
||||
@Test
|
||||
void annotationWithSeveralAttributes() throws IOException {
|
||||
List<String> lines = writeClassAnnotation(Annotation.name(
|
||||
"org.springframework.test.TestApplication",
|
||||
(builder) -> builder.attribute("target", Class.class, "com.example.One")
|
||||
.attribute("unit", ChronoUnit.class,
|
||||
"java.time.temporal.ChronoUnit.NANOS")));
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import com.example.One;", "import java.time.temporal.ChronoUnit;",
|
||||
"import org.springframework.test.TestApplication;", "",
|
||||
"@TestApplication(target = One.class, unit = ChronoUnit.NANOS)",
|
||||
"public class Test {", "", "}", "");
|
||||
}
|
||||
|
||||
private List<String> writeClassAnnotation(Annotation annotation) throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode
|
||||
.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
test.annotate(annotation);
|
||||
return writeSingleType(sourceCode, "com/example/Test.java");
|
||||
}
|
||||
|
||||
@Test
|
||||
void methodWithSimpleAnnotation() throws IOException {
|
||||
JavaSourceCode sourceCode = new JavaSourceCode();
|
||||
JavaCompilationUnit compilationUnit = sourceCode
|
||||
.createCompilationUnit("com.example", "Test");
|
||||
JavaTypeDeclaration test = compilationUnit.createTypeDeclaration("Test");
|
||||
JavaMethodDeclaration method = JavaMethodDeclaration.method("something")
|
||||
.returning("void").parameters().body();
|
||||
method.annotate(Annotation.name("com.example.test.TestAnnotation"));
|
||||
test.addMethodDeclaration(method);
|
||||
List<String> lines = writeSingleType(sourceCode, "com/example/Test.java");
|
||||
assertThat(lines).containsExactly("package com.example;", "",
|
||||
"import com.example.test.TestAnnotation;", "", "public class Test {", "",
|
||||
" @TestAnnotation", " public void something() {", " }", "", "}",
|
||||
"");
|
||||
}
|
||||
|
||||
private List<String> writeSingleType(JavaSourceCode sourceCode, String location)
|
||||
throws IOException {
|
||||
Path source = writeSourceCode(sourceCode).resolve(location);
|
||||
assertThat(source).isRegularFile();
|
||||
return Files.readAllLines(source);
|
||||
}
|
||||
|
||||
private Path writeSourceCode(JavaSourceCode sourceCode) throws IOException {
|
||||
Path projectDirectory = Files.createTempDirectory(this.directory, "project-");
|
||||
this.writer.writeTo(projectDirectory, sourceCode);
|
||||
return projectDirectory;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user