Browse Source
Previously, the generated classes from an InMemoryGeneratedFiles were not taken into account and if generated code refers to any of them, compilation failed. This commit introduces a ClasFile abstraction, similar to ResourceFile for resources that represents an existing generated class. Closes gh-29141 Co-authored-by: Andy Wilkinson <wilkinsona@vmware.com>pull/29162/head
11 changed files with 611 additions and 31 deletions
@ -0,0 +1,105 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2022 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 org.springframework.aot.test.generate.file; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
|
||||||
|
import org.springframework.core.io.InputStreamSource; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.FileCopyUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* In memory representation of a Java class. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
* @since 6.0 |
||||||
|
*/ |
||||||
|
public final class ClassFile { |
||||||
|
|
||||||
|
private static final String CLASS_SUFFIX = ".class"; |
||||||
|
|
||||||
|
private final String name; |
||||||
|
|
||||||
|
private final byte[] content; |
||||||
|
|
||||||
|
private ClassFile(String name, byte[] content) { |
||||||
|
this.name = name; |
||||||
|
this.content = content; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the fully qualified name of the class. |
||||||
|
* @return the class name |
||||||
|
*/ |
||||||
|
public String getName() { |
||||||
|
return this.name; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the bytecode content. |
||||||
|
* @return the class content |
||||||
|
*/ |
||||||
|
public byte[] getContent() { |
||||||
|
return this.content; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Factory method to create a new {@link ClassFile} from the given |
||||||
|
* {@code content}. |
||||||
|
* @param name the fully qualified name of the class
|
||||||
|
* @param content the bytecode of the class
|
||||||
|
* @return a {@link ClassFile} instance |
||||||
|
*/ |
||||||
|
public static ClassFile of(String name, byte[] content) { |
||||||
|
return new ClassFile(name, content); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Factory method to create a new {@link ClassFile} from the given |
||||||
|
* {@link InputStreamSource}. |
||||||
|
* @param name the fully qualified name of the class
|
||||||
|
* @param inputStreamSource the bytecode of the class
|
||||||
|
* @return a {@link ClassFile} instance |
||||||
|
*/ |
||||||
|
public static ClassFile of(String name, InputStreamSource inputStreamSource) { |
||||||
|
return of(name, toBytes(inputStreamSource)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the name of a class based on its relative path. |
||||||
|
* @param path the path of the class
|
||||||
|
* @return the class name |
||||||
|
*/ |
||||||
|
public static String toClassName(String path) { |
||||||
|
Assert.hasText(path, "'path' must not be empty"); |
||||||
|
if (!path.endsWith(CLASS_SUFFIX)) { |
||||||
|
throw new IllegalArgumentException("Path '" + path + "' must end with '.class'"); |
||||||
|
} |
||||||
|
String name = path.replace('/', '.'); |
||||||
|
return name.substring(0, name.length() - CLASS_SUFFIX.length()); |
||||||
|
} |
||||||
|
|
||||||
|
private static byte[] toBytes(InputStreamSource inputStreamSource) { |
||||||
|
try { |
||||||
|
return FileCopyUtils.copyToByteArray(inputStreamSource.getInputStream()); |
||||||
|
} |
||||||
|
catch (IOException ex) { |
||||||
|
throw new IllegalStateException("Unable to read content", ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,139 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2022 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 org.springframework.aot.test.generate.file; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.Iterator; |
||||||
|
import java.util.LinkedHashMap; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.stream.Stream; |
||||||
|
|
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
|
||||||
|
/** |
||||||
|
* An immutable collection of {@link ClassFile} instances. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
* @since 6.0 |
||||||
|
*/ |
||||||
|
public final class ClassFiles implements Iterable<ClassFile> { |
||||||
|
|
||||||
|
private static final ClassFiles NONE = new ClassFiles(Collections.emptyMap()); |
||||||
|
|
||||||
|
private final Map<String, ClassFile> files; |
||||||
|
|
||||||
|
private ClassFiles(Map<String, ClassFile> files) { |
||||||
|
this.files = files; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Return a {@link ClassFiles} instance with no items. |
||||||
|
* @return the empty instance |
||||||
|
*/ |
||||||
|
public static ClassFiles none() { |
||||||
|
return NONE; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Factory method that can be used to create a {@link ClassFiles} |
||||||
|
* instance containing the specified classes. |
||||||
|
* @param ClassFiles the classes to include |
||||||
|
* @return a {@link ClassFiles} instance |
||||||
|
*/ |
||||||
|
public static ClassFiles of(ClassFile... ClassFiles) { |
||||||
|
return none().and(ClassFiles); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a new {@link ClassFiles} instance that merges classes from |
||||||
|
* another array of {@link ClassFile} instances. |
||||||
|
* @param classFiles the instances to merge |
||||||
|
* @return a new {@link ClassFiles} instance containing merged content |
||||||
|
*/ |
||||||
|
public ClassFiles and(ClassFile... classFiles) { |
||||||
|
Map<String, ClassFile> merged = new LinkedHashMap<>(this.files); |
||||||
|
Arrays.stream(classFiles).forEach(file -> merged.put(file.getName(), file)); |
||||||
|
return new ClassFiles(Collections.unmodifiableMap(merged)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return a new {@link ClassFiles} instance that merges classes from another |
||||||
|
* iterable of {@link ClassFiles} instances. |
||||||
|
* @param classFiles the instances to merge |
||||||
|
* @return a new {@link ClassFiles} instance containing merged content |
||||||
|
*/ |
||||||
|
public ClassFiles and(Iterable<ClassFile> classFiles) { |
||||||
|
Map<String, ClassFile> merged = new LinkedHashMap<>(this.files); |
||||||
|
classFiles.forEach(file -> merged.put(file.getName(), file)); |
||||||
|
return new ClassFiles(Collections.unmodifiableMap(merged)); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Iterator<ClassFile> iterator() { |
||||||
|
return this.files.values().iterator(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Stream the {@link ClassFile} instances contained in this collection. |
||||||
|
* @return a stream of classes |
||||||
|
*/ |
||||||
|
public Stream<ClassFile> stream() { |
||||||
|
return this.files.values().stream(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns {@code true} if this collection is empty. |
||||||
|
* @return if this collection is empty |
||||||
|
*/ |
||||||
|
public boolean isEmpty() { |
||||||
|
return this.files.isEmpty(); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get the {@link ClassFile} with the given class name. |
||||||
|
* @param name the fully qualified name to find |
||||||
|
* @return a {@link ClassFile} instance or {@code null} |
||||||
|
*/ |
||||||
|
@Nullable |
||||||
|
public ClassFile get(String name) { |
||||||
|
return this.files.get(name); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public boolean equals(Object obj) { |
||||||
|
if (this == obj) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
if (obj == null || getClass() != obj.getClass()) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
return this.files.equals(((ClassFiles) obj).files); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public int hashCode() { |
||||||
|
return this.files.hashCode(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return this.files.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,60 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2022 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 org.springframework.aot.test.generate.file; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import org.springframework.core.io.ByteArrayResource; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link ClassFile}. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
*/ |
||||||
|
class ClassFileTests { |
||||||
|
|
||||||
|
private final static byte[] TEST_CONTENT = new byte[]{'a'}; |
||||||
|
|
||||||
|
@Test |
||||||
|
void ofNameAndByteArrayCreatesClass() { |
||||||
|
ClassFile classFile = ClassFile.of("com.example.Test", TEST_CONTENT); |
||||||
|
assertThat(classFile.getName()).isEqualTo("com.example.Test"); |
||||||
|
assertThat(classFile.getContent()).isEqualTo(TEST_CONTENT); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void ofNameAndInputStreamResourceCreatesClass() { |
||||||
|
ClassFile classFile = ClassFile.of("com.example.Test", |
||||||
|
new ByteArrayResource(TEST_CONTENT)); |
||||||
|
assertThat(classFile.getName()).isEqualTo("com.example.Test"); |
||||||
|
assertThat(classFile.getContent()).isEqualTo(TEST_CONTENT); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void toClassNameWithPathToClassFile() { |
||||||
|
assertThat(ClassFile.toClassName("com/example/Test.class")).isEqualTo("com.example.Test"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void toClassNameWithPathToTextFile() { |
||||||
|
assertThatIllegalArgumentException().isThrownBy(() -> ClassFile.toClassName("com/example/Test.txt")); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,116 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2022 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 org.springframework.aot.test.generate.file; |
||||||
|
|
||||||
|
import java.util.Iterator; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||||
|
import static org.assertj.core.api.Assertions.assertThatObject; |
||||||
|
|
||||||
|
/** |
||||||
|
* Tests for {@link ClassFiles}. |
||||||
|
* |
||||||
|
* @author Stephane Nicoll |
||||||
|
*/ |
||||||
|
class ClassFilesTests { |
||||||
|
|
||||||
|
private static final ClassFile CLASS_FILE_1 = ClassFile.of( |
||||||
|
"com.example.Test1", new byte[] { 'a' }); |
||||||
|
|
||||||
|
private static final ClassFile CLASS_FILE_2 = ClassFile.of( |
||||||
|
"com.example.Test2", new byte[] { 'b' }); |
||||||
|
|
||||||
|
@Test |
||||||
|
void noneReturnsNone() { |
||||||
|
ClassFiles none = ClassFiles.none(); |
||||||
|
assertThat(none).isNotNull(); |
||||||
|
assertThat(none.isEmpty()).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void ofCreatesClassFiles() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
assertThat(classFiles).containsExactly(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void andAddsClassFiles() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1); |
||||||
|
ClassFiles added = classFiles.and(CLASS_FILE_2); |
||||||
|
assertThat(classFiles).containsExactly(CLASS_FILE_1); |
||||||
|
assertThat(added).containsExactly(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void andClassFilesAddsClassFiles() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1); |
||||||
|
ClassFiles added = classFiles.and(ClassFiles.of(CLASS_FILE_2)); |
||||||
|
assertThat(classFiles).containsExactly(CLASS_FILE_1); |
||||||
|
assertThat(added).containsExactly(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void iteratorIteratesClassFiles() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
Iterator<ClassFile> iterator = classFiles.iterator(); |
||||||
|
assertThat(iterator.next()).isEqualTo(CLASS_FILE_1); |
||||||
|
assertThat(iterator.next()).isEqualTo(CLASS_FILE_2); |
||||||
|
assertThat(iterator.hasNext()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void streamStreamsClassFiles() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
assertThat(classFiles.stream()).containsExactly(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void isEmptyWhenEmptyReturnsTrue() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(); |
||||||
|
assertThat(classFiles.isEmpty()).isTrue(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void isEmptyWhenNotEmptyReturnsFalse() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1); |
||||||
|
assertThat(classFiles.isEmpty()).isFalse(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void getWhenHasFileReturnsFile() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_1); |
||||||
|
assertThat(classFiles.get("com.example.Test1")).isNotNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void getWhenMissingFileReturnsNull() { |
||||||
|
ClassFiles classFiles = ClassFiles.of(CLASS_FILE_2); |
||||||
|
assertThatObject(classFiles.get("com.example.another.Test2")).isNull(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void equalsAndHashCode() { |
||||||
|
ClassFiles s1 = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
ClassFiles s2 = ClassFiles.of(CLASS_FILE_1, CLASS_FILE_2); |
||||||
|
ClassFiles s3 = ClassFiles.of(CLASS_FILE_1); |
||||||
|
assertThat(s1.hashCode()).isEqualTo(s2.hashCode()); |
||||||
|
assertThatObject(s1).isEqualTo(s2).isNotEqualTo(s3); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Binary file not shown.
Binary file not shown.
Loading…
Reference in new issue