mirror of
https://gitee.com/dcren/initializr.git
synced 2025-04-05 17:38:06 +08:00
Introduce class name
Closes gh-1425
This commit is contained in:
parent
d22201b2d6
commit
1d9e6b5b7b
initializr-generator/src
main/java/io/spring/initializr/generator/language
test/java
com/example
io/spring/initializr/generator/language
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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 java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import javax.lang.model.SourceVersion;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Type reference abstraction to refer to a {@link Class} that is not available on the
|
||||
* classpath.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
public final class ClassName {
|
||||
|
||||
private static final List<String> PRIMITIVE_NAMES = List.of("boolean", "byte", "short", "int", "long", "char",
|
||||
"float", "double", "void");
|
||||
|
||||
private final String packageName;
|
||||
|
||||
private final String simpleName;
|
||||
|
||||
private final ClassName enclosingType;
|
||||
|
||||
private String canonicalName;
|
||||
|
||||
private ClassName(String packageName, String simpleName, ClassName enclosingType) {
|
||||
this.packageName = packageName;
|
||||
this.simpleName = simpleName;
|
||||
this.enclosingType = enclosingType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ClassName} based on the specified fully qualified name. The format
|
||||
* of the class name must follow {@linkplain Class#getName()}, in particular inner
|
||||
* classes should be separated by a {@code $}.
|
||||
* @param fqName the fully qualified name of the class
|
||||
* @return a class name
|
||||
*/
|
||||
public static ClassName of(String fqName) {
|
||||
Assert.notNull(fqName, "'className' must not be null");
|
||||
if (!isValidClassName(fqName)) {
|
||||
throw new IllegalStateException("Invalid class name '" + fqName + "'");
|
||||
}
|
||||
if (!fqName.contains("$")) {
|
||||
return createClassName(fqName);
|
||||
}
|
||||
String[] elements = fqName.split("(?<!\\$)\\$(?!\\$)");
|
||||
ClassName className = createClassName(elements[0]);
|
||||
for (int i = 1; i < elements.length; i++) {
|
||||
className = new ClassName(className.getPackageName(), elements[i], className);
|
||||
}
|
||||
return className;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link ClassName} based on the specified {@link Class}.
|
||||
* @param type the class to wrap
|
||||
* @return a class name
|
||||
*/
|
||||
public static ClassName of(Class<?> type) {
|
||||
return of(type.getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the fully qualified name.
|
||||
* @return the reflection target name
|
||||
*/
|
||||
public String getName() {
|
||||
ClassName enclosingType = getEnclosingType();
|
||||
String simpleName = getSimpleName();
|
||||
return (enclosingType != null) ? (enclosingType.getName() + '$' + simpleName)
|
||||
: addPackageIfNecessary(simpleName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the package name.
|
||||
* @return the package name
|
||||
*/
|
||||
public String getPackageName() {
|
||||
return this.packageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@linkplain Class#getSimpleName() simple name}.
|
||||
* @return the simple name
|
||||
*/
|
||||
public String getSimpleName() {
|
||||
return this.simpleName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the enclosing class name, or {@code null} if this instance does not have an
|
||||
* enclosing type.
|
||||
* @return the enclosing type, if any
|
||||
*/
|
||||
public ClassName getEnclosingType() {
|
||||
return this.enclosingType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@linkplain Class#getCanonicalName() canonical name}.
|
||||
* @return the canonical name
|
||||
*/
|
||||
public String getCanonicalName() {
|
||||
if (this.canonicalName == null) {
|
||||
StringBuilder names = new StringBuilder();
|
||||
buildName(this, names);
|
||||
this.canonicalName = addPackageIfNecessary(names.toString());
|
||||
}
|
||||
return this.canonicalName;
|
||||
}
|
||||
|
||||
private boolean isPrimitive() {
|
||||
return isPrimitive(getSimpleName());
|
||||
}
|
||||
|
||||
private static boolean isPrimitive(String name) {
|
||||
return PRIMITIVE_NAMES.stream().anyMatch(name::startsWith);
|
||||
}
|
||||
|
||||
private String addPackageIfNecessary(String part) {
|
||||
if (this.packageName.isEmpty() || this.packageName.equals("java.lang") && isPrimitive()) {
|
||||
return part;
|
||||
}
|
||||
return this.packageName + '.' + part;
|
||||
}
|
||||
|
||||
private static boolean isValidClassName(String className) {
|
||||
for (String s : className.split("\\.", -1)) {
|
||||
String candidate = s.replace("[", "").replace("]", "");
|
||||
if (!SourceVersion.isIdentifier(candidate)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static ClassName createClassName(String className) {
|
||||
int i = className.lastIndexOf('.');
|
||||
if (i != -1) {
|
||||
return new ClassName(className.substring(0, i), className.substring(i + 1), null);
|
||||
}
|
||||
else {
|
||||
String packageName = (isPrimitive(className)) ? "java.lang" : "";
|
||||
return new ClassName(packageName, className, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static void buildName(ClassName className, StringBuilder sb) {
|
||||
if (className == null) {
|
||||
return;
|
||||
}
|
||||
String typeName = (className.getEnclosingType() != null) ? "." + className.getSimpleName()
|
||||
: className.getSimpleName();
|
||||
sb.insert(0, typeName);
|
||||
buildName(className.getEnclosingType(), sb);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object other) {
|
||||
if (this == other) {
|
||||
return true;
|
||||
}
|
||||
if (!(other instanceof ClassName className)) {
|
||||
return false;
|
||||
}
|
||||
return getCanonicalName().equals(className.getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(getCanonicalName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return getCanonicalName();
|
||||
}
|
||||
|
||||
}
|
@ -37,8 +37,8 @@ import org.springframework.util.ClassUtils;
|
||||
* that. Emit {@code "null"} if the value is {@code null}. Does not handle multi-line
|
||||
* strings.
|
||||
* <li>{@code $T} emits a type reference. Arguments for types may be plain
|
||||
* {@linkplain Class classes}, fully qualified class names, and fully qualified
|
||||
* functions.</li>
|
||||
* {@linkplain Class classes}, {@linkplain ClassName class names}, fully qualified class
|
||||
* names, and fully qualified functions.</li>
|
||||
* <li>{@code $$} emits a dollar sign.
|
||||
* <li>{@code $]} ends a statement and emits the configured
|
||||
* {@linkplain FormattingOptions#statementSeparator() statement separator}.
|
||||
@ -253,9 +253,13 @@ public final class CodeBlock {
|
||||
this.imports.add(type.getName());
|
||||
return type.getSimpleName();
|
||||
}
|
||||
if (arg instanceof String className) {
|
||||
this.imports.add(className);
|
||||
return ClassUtils.getShortName(className);
|
||||
if (arg instanceof ClassName className) {
|
||||
this.imports.add(className.getName());
|
||||
return className.getSimpleName();
|
||||
}
|
||||
if (arg instanceof String fqName) {
|
||||
this.imports.add(fqName);
|
||||
return ClassUtils.getShortName(fqName);
|
||||
}
|
||||
throw new IllegalArgumentException("Failed to extract type from '%s'".formatted(arg));
|
||||
}
|
||||
|
29
initializr-generator/src/test/java/com/example/Example.java
Normal file
29
initializr-generator/src/test/java/com/example/Example.java
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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 com.example;
|
||||
|
||||
public class Example {
|
||||
|
||||
public static class Inner {
|
||||
|
||||
public static class Nested {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright 2012-2023 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 java.util.stream.Stream;
|
||||
|
||||
import com.example.Example;
|
||||
import com.example.Example.Inner;
|
||||
import com.example.Example.Inner.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||
|
||||
/**
|
||||
* Tests for {@link ClassName}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class ClassNameTests {
|
||||
|
||||
@Test
|
||||
void classNameWithTopLevelClassName() {
|
||||
classNameWithTopLevelClass(ClassName.of("com.example.Example"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void classNameWithTopLevelClass() {
|
||||
classNameWithTopLevelClass(ClassName.of(Example.class));
|
||||
}
|
||||
|
||||
private void classNameWithTopLevelClass(ClassName className) {
|
||||
assertThat(className.getName()).isEqualTo("com.example.Example");
|
||||
assertThat(className.getCanonicalName()).isEqualTo("com.example.Example");
|
||||
assertThat(className.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(className.getSimpleName()).isEqualTo("Example");
|
||||
assertThat(className.getEnclosingType()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void classNameWithInnerClassName() {
|
||||
classNameWithInnerClass(ClassName.of("com.example.Example$Inner"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void classNameWithInnerClass() {
|
||||
classNameWithInnerClass(ClassName.of(Inner.class));
|
||||
}
|
||||
|
||||
private void classNameWithInnerClass(ClassName className) {
|
||||
assertThat(className.getName()).isEqualTo("com.example.Example$Inner");
|
||||
assertThat(className.getCanonicalName()).isEqualTo("com.example.Example.Inner");
|
||||
assertThat(className.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(className.getSimpleName()).isEqualTo("Inner");
|
||||
assertThat(className.getEnclosingType()).satisfies((enclosingType) -> {
|
||||
assertThat(enclosingType.getCanonicalName()).isEqualTo("com.example.Example");
|
||||
assertThat(enclosingType.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(enclosingType.getSimpleName()).isEqualTo("Example");
|
||||
assertThat(enclosingType.getEnclosingType()).isNull();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void classNameWithNestedInnerClassName() {
|
||||
classNameWithNestedInnerClass(ClassName.of("com.example.Example$Inner$Nested"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void classNameWithNestedInnerClass() {
|
||||
classNameWithNestedInnerClass(ClassName.of(Nested.class));
|
||||
}
|
||||
|
||||
private void classNameWithNestedInnerClass(ClassName className) {
|
||||
assertThat(className.getName()).isEqualTo("com.example.Example$Inner$Nested");
|
||||
assertThat(className.getCanonicalName()).isEqualTo("com.example.Example.Inner.Nested");
|
||||
assertThat(className.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(className.getSimpleName()).isEqualTo("Nested");
|
||||
assertThat(className.getEnclosingType()).satisfies((enclosingType) -> {
|
||||
assertThat(enclosingType.getCanonicalName()).isEqualTo("com.example.Example.Inner");
|
||||
assertThat(enclosingType.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(enclosingType.getSimpleName()).isEqualTo("Inner");
|
||||
assertThat(enclosingType.getEnclosingType()).satisfies((parentEnclosingType) -> {
|
||||
assertThat(parentEnclosingType.getCanonicalName()).isEqualTo("com.example.Example");
|
||||
assertThat(parentEnclosingType.getPackageName()).isEqualTo("com.example");
|
||||
assertThat(parentEnclosingType.getSimpleName()).isEqualTo("Example");
|
||||
assertThat(parentEnclosingType.getEnclosingType()).isNull();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("primitivesAndPrimitivesArray")
|
||||
void primitivesAreHandledProperly(ClassName className, String expectedName) {
|
||||
assertThat(className.getName()).isEqualTo(expectedName);
|
||||
assertThat(className.getCanonicalName()).isEqualTo(expectedName);
|
||||
assertThat(className.getPackageName()).isEqualTo("java.lang");
|
||||
}
|
||||
|
||||
static Stream<Arguments> primitivesAndPrimitivesArray() {
|
||||
return Stream.of(Arguments.of(ClassName.of("boolean"), "boolean"), Arguments.of(ClassName.of("byte"), "byte"),
|
||||
Arguments.of(ClassName.of("short"), "short"), Arguments.of(ClassName.of("int"), "int"),
|
||||
Arguments.of(ClassName.of("long"), "long"), Arguments.of(ClassName.of("char"), "char"),
|
||||
Arguments.of(ClassName.of("float"), "float"), Arguments.of(ClassName.of("double"), "double"),
|
||||
Arguments.of(ClassName.of("boolean[]"), "boolean[]"), Arguments.of(ClassName.of("byte[]"), "byte[]"),
|
||||
Arguments.of(ClassName.of("short[]"), "short[]"), Arguments.of(ClassName.of("int[]"), "int[]"),
|
||||
Arguments.of(ClassName.of("long[]"), "long[]"), Arguments.of(ClassName.of("char[]"), "char[]"),
|
||||
Arguments.of(ClassName.of("float[]"), "float[]"), Arguments.of(ClassName.of("double[]"), "double[]"));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("arrays")
|
||||
void arraysHaveSuitableReflectionTargetName(ClassName typeReference, String expectedName) {
|
||||
assertThat(typeReference.getName()).isEqualTo(expectedName);
|
||||
}
|
||||
|
||||
static Stream<Arguments> arrays() {
|
||||
return Stream.of(Arguments.of(ClassName.of("java.lang.Object[]"), "java.lang.Object[]"),
|
||||
Arguments.of(ClassName.of("java.lang.Integer[]"), "java.lang.Integer[]"),
|
||||
Arguments.of(ClassName.of("com.example.Test[]"), "com.example.Test[]"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void classNameInRootPackage() {
|
||||
ClassName type = ClassName.of("MyRootClass");
|
||||
assertThat(type.getCanonicalName()).isEqualTo("MyRootClass");
|
||||
assertThat(type.getPackageName()).isEmpty();
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "{0}")
|
||||
@ValueSource(strings = { "com.example.Tes(t", "com.example..Test" })
|
||||
void classNameWithInvalidClassName(String invalidClassName) {
|
||||
assertThatIllegalStateException().isThrownBy(() -> ClassName.of(invalidClassName))
|
||||
.withMessageContaining("Invalid class name");
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithIdenticalNameIsTrue() {
|
||||
assertThat(ClassName.of(String.class)).isEqualTo(ClassName.of("java.lang.String"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void equalsWithNonClassNameIsFalse() {
|
||||
assertThat(ClassName.of(String.class)).isNotEqualTo("java.lang.String");
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringUsesCanonicalName() {
|
||||
assertThat(ClassName.of(String.class)).hasToString("java.lang.String");
|
||||
}
|
||||
|
||||
}
|
@ -125,6 +125,13 @@ class CodeBlockTests {
|
||||
|
||||
@Test
|
||||
void codeBlockWithTypePlaceholderAndClassNameAddsImport() {
|
||||
CodeBlock code = CodeBlock.of("return $T.truncate(myString)", ClassName.of(StringUtils.class));
|
||||
assertThat(writeJava(code)).isEqualTo("return StringUtils.truncate(myString)");
|
||||
assertThat(code.getImports()).containsExactly(StringUtils.class.getName());
|
||||
}
|
||||
|
||||
@Test
|
||||
void codeBlockWithTypePlaceholderAndFullyQualifiedClassNameAddsImport() {
|
||||
CodeBlock code = CodeBlock.of("return $T.truncate(myString)", "com.example.StringUtils");
|
||||
assertThat(writeJava(code)).isEqualTo("return StringUtils.truncate(myString)");
|
||||
assertThat(code.getImports()).containsExactly("com.example.StringUtils");
|
||||
|
Loading…
Reference in New Issue
Block a user