Skip to content

Commit 6061fdf

Browse files
committed
Introduce ModuleResource for resources loaded from a given Module
Closes gh-28507
1 parent 9b662e8 commit 6061fdf

File tree

2 files changed

+186
-0
lines changed

2 files changed

+186
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.io;
18+
19+
import java.io.FileNotFoundException;
20+
import java.io.IOException;
21+
import java.io.InputStream;
22+
23+
import org.springframework.lang.Nullable;
24+
import org.springframework.util.Assert;
25+
import org.springframework.util.StringUtils;
26+
27+
/**
28+
* {@link Resource} implementation for {@link java.lang.Module} resolution,
29+
* performing {@link #getInputStream()} access via {@link Module#getResourceAsStream}.
30+
*
31+
* <p>Alternatively, consider accessing resources in a module path layout
32+
* via {@link ClassPathResource} for exported resources, or specifically
33+
* via {@link ClassPathResource#ClassPathResource(String, Class)}
34+
* for local resolution within the containing module of a specific class.
35+
*
36+
* @author Juergen Hoeller
37+
* @since 6.1
38+
* @see Module#getResourceAsStream
39+
* @see ClassPathResource
40+
*/
41+
public class ModuleResource extends AbstractResource {
42+
43+
private final Module module;
44+
45+
private final String path;
46+
47+
48+
/**
49+
* Create a new {@code ModuleResource} for the given {@link Module}
50+
* and the given resource path.
51+
* @param module the runtime module to search within
52+
* @param path the resource path within the module
53+
*/
54+
public ModuleResource(Module module, String path) {
55+
Assert.notNull(module, "Module must not be null");
56+
Assert.notNull(path, "Path must not be null");
57+
this.module = module;
58+
this.path = path;
59+
}
60+
61+
62+
/**
63+
* Return the {@link Module} for this resource.
64+
*/
65+
public final Module getModule() {
66+
return this.module;
67+
}
68+
69+
/**
70+
* Return the path for this resource.
71+
*/
72+
public final String getPath() {
73+
return this.path;
74+
}
75+
76+
77+
@Override
78+
public InputStream getInputStream() throws IOException {
79+
InputStream is = this.module.getResourceAsStream(this.path);
80+
if (is == null) {
81+
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
82+
}
83+
return is;
84+
}
85+
86+
@Override
87+
public Resource createRelative(String relativePath) {
88+
String pathToUse = StringUtils.applyRelativePath(this.path, relativePath);
89+
return new ModuleResource(this.module, pathToUse);
90+
}
91+
92+
@Override
93+
public String getDescription() {
94+
return "module resource [" + this.path + "]" +
95+
(this.module.isNamed() ? " from module '" + this.module.getName() + "'" : "");
96+
}
97+
98+
99+
@Override
100+
public boolean equals(@Nullable Object obj) {
101+
return (this == obj || (obj instanceof ModuleResource that &&
102+
this.module.equals(that.module) && this.path.equals(that.path)));
103+
}
104+
105+
@Override
106+
public int hashCode() {
107+
return this.module.hashCode() * 31 + this.path.hashCode();
108+
}
109+
110+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.core.io;
18+
19+
import java.beans.Introspector;
20+
import java.io.FileNotFoundException;
21+
import java.io.IOException;
22+
23+
import org.junit.jupiter.api.Test;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
27+
28+
/**
29+
* Unit tests for the {@link ModuleResource} class.
30+
*
31+
* @author Juergen Hoeller
32+
* @since 6.1
33+
*/
34+
class ModuleResourceTests {
35+
36+
@Test
37+
void existingResource() throws IOException {
38+
ModuleResource mr = new ModuleResource(Introspector.class.getModule(), "java/beans/Introspector.class");
39+
assertThat(mr.exists()).isTrue();
40+
assertThat(mr.isReadable()).isTrue();
41+
assertThat(mr.isOpen()).isFalse();
42+
assertThat(mr.isFile()).isFalse();
43+
assertThat(mr.getDescription()).contains(mr.getModule().getName());
44+
assertThat(mr.getDescription()).contains(mr.getPath());
45+
46+
Resource cpr = new ClassPathResource("java/beans/Introspector.class");
47+
assertThat(mr.getContentAsByteArray()).isEqualTo(cpr.getContentAsByteArray());
48+
assertThat(mr.contentLength()).isEqualTo(cpr.contentLength());
49+
}
50+
51+
@Test
52+
void nonExistingResource() {
53+
ModuleResource mr = new ModuleResource(Introspector.class.getModule(), "java/beans/Introspecter.class");
54+
assertThat(mr.exists()).isFalse();
55+
assertThat(mr.isReadable()).isFalse();
56+
assertThat(mr.isOpen()).isFalse();
57+
assertThat(mr.isFile()).isFalse();
58+
assertThat(mr.getDescription()).contains(mr.getModule().getName());
59+
assertThat(mr.getDescription()).contains(mr.getPath());
60+
61+
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(mr::getContentAsByteArray);
62+
assertThatExceptionOfType(FileNotFoundException.class).isThrownBy(mr::contentLength);
63+
}
64+
65+
@Test
66+
void equalsAndHashCode() {
67+
Resource mr1 = new ModuleResource(Introspector.class.getModule(), "java/beans/Introspector.class");
68+
Resource mr2 = new ModuleResource(Introspector.class.getModule(), "java/beans/Introspector.class");
69+
Resource mr3 = new ModuleResource(Introspector.class.getModule(), "java/beans/Introspecter.class");
70+
assertThat(mr1).isEqualTo(mr2);
71+
assertThat(mr1).isNotEqualTo(mr3);
72+
assertThat(mr1).hasSameHashCodeAs(mr2);
73+
assertThat(mr1).doesNotHaveSameHashCodeAs(mr3);
74+
}
75+
76+
}

0 commit comments

Comments
 (0)