Skip to content

Commit 97b98c3

Browse files
committed
Support default package for TypeReference in ResourceHintsPredicates
Prior to this commit, if the TypeReference supplied to ResourceHintsPredicates.forResource(TypeReference,String) was for a class declared in the default package (i.e., without a package), the resolveAbsoluteResourceName() method incorrectly prepended two leading slashes (//) to the absolute resource name, causing correct matches to fail. This commit fixes this by adding special handling for a TypeReference without a package name. In addition, this commit introduces lenient handling of resource names by consistently removing a leading slash in ResourceHintsPredicates.forResource(*) methods. The latter aligns with absolute resource path handling in other places in the framework, such as ClassPathResource. Closes gh-29086
1 parent 135f907 commit 97b98c3

File tree

3 files changed

+49
-23
lines changed

3 files changed

+49
-23
lines changed

spring-core-test/src/test/java/org/springframework/aot/agent/InstrumentedMethodTests.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
* Tests for {@link InstrumentedMethod}.
3737
*
3838
* @author Brian Clozel
39+
* @author Sam Brannen
3940
*/
4041
class InstrumentedMethodTests {
4142

@@ -556,31 +557,31 @@ void resourceBundleGetBundleShouldNotMatchBundleNameHintWhenWrongName() {
556557
void classGetResourceShouldMatchResourcePatternWhenAbsolute() {
557558
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
558559
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
559-
hints.resources().registerPattern("/some/*");
560+
hints.resources().registerPattern("some/*");
560561
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
561562
}
562563

563564
@Test
564565
void classGetResourceShouldMatchResourcePatternWhenRelative() {
565566
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
566567
.onInstance(InstrumentedMethodTests.class).withArgument("resource.txt").build();
567-
hints.resources().registerPattern("/org/springframework/aot/agent/*");
568+
hints.resources().registerPattern("org/springframework/aot/agent/*");
568569
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
569570
}
570571

571572
@Test
572573
void classGetResourceShouldNotMatchResourcePatternWhenInvalid() {
573574
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
574575
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
575-
hints.resources().registerPattern("/other/*");
576+
hints.resources().registerPattern("other/*");
576577
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
577578
}
578579

579580
@Test
580581
void classGetResourceShouldNotMatchResourcePatternWhenExcluded() {
581582
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
582583
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
583-
hints.resources().registerPattern(resourceHint -> resourceHint.includes("/some/*").excludes("/some/path/*"));
584+
hints.resources().registerPattern(resourceHint -> resourceHint.includes("some/*").excludes("some/path/*"));
584585
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
585586
}
586587

spring-core/src/main/java/org/springframework/aot/hint/predicate/ResourceHintsPredicates.java

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
*
3535
* @author Brian Clozel
3636
* @author Stephane Nicoll
37+
* @author Sam Brannen
3738
* @since 6.0
3839
*/
3940
public class ResourceHintsPredicates {
@@ -58,7 +59,11 @@ public Predicate<RuntimeHints> forBundle(String bundleName) {
5859
* Return a predicate that checks whether a resource hint is registered for the given
5960
* resource name, located in the given type's package.
6061
* <p>For example, {@code forResource(org.example.MyClass, "myResource.txt")}
61-
* will match for {@code "/org/example/myResource.txt"}.
62+
* will match against {@code "org/example/myResource.txt"}.
63+
* <p>If the given resource name is an absolute path (i.e., starts with a
64+
* leading slash), the supplied type will be ignored. For example,
65+
* {@code forResource(org.example.MyClass, "/myResource.txt")} will match against
66+
* {@code "myResource.txt"}.
6267
* @param type the type's package where to look for the resource
6368
* @param resourceName the resource name
6469
* @return the {@link RuntimeHints} predicate
@@ -69,32 +74,39 @@ public Predicate<RuntimeHints> forResource(TypeReference type, String resourceNa
6974
}
7075

7176
private String resolveAbsoluteResourceName(TypeReference type, String resourceName) {
77+
// absolute path
7278
if (resourceName.startsWith("/")) {
79+
return resourceName.substring(1);
80+
}
81+
// default package
82+
else if (type.getPackageName().isEmpty()) {
7383
return resourceName;
7484
}
85+
// relative path
7586
else {
76-
return "/" + type.getPackageName().replace('.', '/')
77-
+ "/" + resourceName;
87+
return type.getPackageName().replace('.', '/') + "/" + resourceName;
7888
}
7989
}
8090

8191
/**
8292
* Return a predicate that checks whether a resource hint is registered for
8393
* the given resource name.
84-
* @param resourceName the full resource name
94+
* <p>A leading slash will be removed.
95+
* @param resourceName the absolute resource name
8596
* @return the {@link RuntimeHints} predicate
8697
*/
8798
public Predicate<RuntimeHints> forResource(String resourceName) {
99+
String resourceNameToUse = (resourceName.startsWith("/") ? resourceName.substring(1) : resourceName);
88100
return hints -> {
89101
AggregatedResourcePatternHints aggregatedResourcePatternHints = AggregatedResourcePatternHints.of(
90102
hints.resources());
91103
boolean isExcluded = aggregatedResourcePatternHints.excludes().stream().anyMatch(excluded ->
92-
CACHED_RESOURCE_PATTERNS.get(excluded).matcher(resourceName).matches());
104+
CACHED_RESOURCE_PATTERNS.get(excluded).matcher(resourceNameToUse).matches());
93105
if (isExcluded) {
94106
return false;
95107
}
96108
return aggregatedResourcePatternHints.includes().stream().anyMatch(included ->
97-
CACHED_RESOURCE_PATTERNS.get(included).matcher(resourceName).matches());
109+
CACHED_RESOURCE_PATTERNS.get(included).matcher(resourceNameToUse).matches());
98110
};
99111
}
100112

spring-core/src/test/java/org/springframework/aot/hint/predicate/ResourceHintsPredicatesTests.java

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.util.function.Predicate;
2020

21-
import org.junit.jupiter.api.BeforeEach;
2221
import org.junit.jupiter.api.Test;
2322

2423
import org.springframework.aot.hint.RuntimeHints;
@@ -30,40 +29,54 @@
3029
* Tests for {@link ReflectionHintsPredicates}.
3130
*
3231
* @author Brian Clozel
32+
* @author Sam Brannen
3333
*/
3434
class ResourceHintsPredicatesTests {
3535

3636
private final ResourceHintsPredicates resources = new ResourceHintsPredicates();
3737

38-
private RuntimeHints runtimeHints;
38+
private final RuntimeHints runtimeHints = new RuntimeHints();
3939

4040

41-
@BeforeEach
42-
void setup() {
43-
this.runtimeHints = new RuntimeHints();
44-
}
45-
4641
@Test
4742
void resourcePatternMatchesResourceName() {
48-
this.runtimeHints.resources().registerPattern("/test/*");
43+
this.runtimeHints.resources().registerPattern("test/*");
4944
assertPredicateMatches(resources.forResource("/test/spring.properties"));
5045
}
5146

5247
@Test
5348
void resourcePatternDoesNotMatchResourceName() {
54-
this.runtimeHints.resources().registerPattern("/test/spring.*");
49+
this.runtimeHints.resources().registerPattern("test/spring.*");
5550
assertPredicateDoesNotMatch(resources.forResource("/test/other.properties"));
5651
}
5752

5853
@Test
5954
void resourcePatternMatchesTypeAndResourceName() {
60-
this.runtimeHints.resources().registerPattern("/org/springframework/aot/hint/predicate/spring.*");
55+
this.runtimeHints.resources().registerPattern("org/springframework/aot/hint/predicate/spring.*");
6156
assertPredicateMatches(resources.forResource(TypeReference.of(getClass()), "spring.properties"));
6257
}
6358

59+
@Test
60+
void resourcePatternMatchesTypeAndAbsoluteResourceName() {
61+
this.runtimeHints.resources().registerPattern("spring.*");
62+
assertPredicateMatches(resources.forResource(TypeReference.of(getClass()), "/spring.properties"));
63+
}
64+
65+
@Test
66+
void resourcePatternMatchesTypeInDefaultPackageAndResourceName() {
67+
this.runtimeHints.resources().registerPattern("spring.*");
68+
assertPredicateMatches(resources.forResource(TypeReference.of("DummyClass"), "spring.properties"));
69+
}
70+
71+
@Test
72+
void resourcePatternMatchesTypeInDefaultPackageAndAbsoluteResourceName() {
73+
this.runtimeHints.resources().registerPattern("spring.*");
74+
assertPredicateMatches(resources.forResource(TypeReference.of("DummyClass"), "/spring.properties"));
75+
}
76+
6477
@Test
6578
void resourcePatternDoesNotMatchTypeAndResourceName() {
66-
this.runtimeHints.resources().registerPattern("/spring.*");
79+
this.runtimeHints.resources().registerPattern("spring.*");
6780
assertPredicateDoesNotMatch(resources.forResource(TypeReference.of(getClass()), "spring.properties"));
6881
}
6982

@@ -81,11 +94,11 @@ void resourceBundleDoesNotMatchBundleName() {
8194

8295

8396
private void assertPredicateMatches(Predicate<RuntimeHints> predicate) {
84-
assertThat(predicate.test(this.runtimeHints)).isTrue();
97+
assertThat(predicate).accepts(this.runtimeHints);
8598
}
8699

87100
private void assertPredicateDoesNotMatch(Predicate<RuntimeHints> predicate) {
88-
assertThat(predicate.test(this.runtimeHints)).isFalse();
101+
assertThat(predicate).rejects(this.runtimeHints);
89102
}
90103

91104
}

0 commit comments

Comments
 (0)