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