I was applied a latest Spring 6 snapshot version, I found the PathMatchingResourcePatternResolver
handles path names differently than Spring 5 latest GA version(5.3.23) on Mac OS.
For example, If the file name contains voiced sound characters (e.g. バ
), the result will be different.
Is this behavior by design or a regression bug as implemented in Spring 6? If this behavior was designed, I will rewrite my library to adjust Spring 6 specification.
Reproducing
Directory structures:
...
└── target
└── test-classes
└── org
└── example
└── バリューオブジェクト
└── ジドウシャ.class
Test Cases:
package org.example;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import java.io.IOException;
import java.util.stream.Collectors;
class AppTest {
String path;
@BeforeEach
void setup() throws IOException {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResources("classpath*:org/example/バリューオブジェクト/**/*.class")[0];
path = resource.getURL().toString().replaceFirst(".*/test-classes/", "");
System.out.println(path);
System.out.println(path.length());
}
@Test
void successRunningOnSpring5() {
// o r g / e x a m p l e / バ リ ュ ー オ ブ ジ ェ ク ト / ジ ド ウ シ ャ . c l a s s
// 6f,72,67,2f,65,78,61,6d,70,6c,65,2f,30d0 ,30ea,30e5,30fc,30aa,30d6 ,30b8 ,30a7,30af,30c8,2f,30b8 ,30c9 ,30a6,30b7,30e3,2e,63,6c,61,73,73
System.out.println(path.chars().mapToObj(Integer::toHexString).collect(Collectors.joining(",")));
Assertions.assertEquals(34, path.length());
Assertions.assertEquals("org/example/バリューオブジェクト/ジドウシャ.class", path);
}
@Test
void successRunningOnSpring6() {
// o r g / e x a m p l e / ハ ゙ リ ュ ー オ ブ シ ゙ ェ ク ト / シ ゙ ト ゙ ウ シ ャ . c l a s s
// 6f,72,67,2f,65,78,61,6d,70,6c,65,2f,30cf,3099,30ea,30e5,30fc,30aa,30d5,3099,30b7,3099,30a7,30af,30c8,2f,30b7,3099,30c8,3099,30a6,30b7,30e3,2e,63,6c,61,73,73
System.out.println(path.chars().mapToObj(Integer::toHexString).collect(Collectors.joining(",")));
Assertions.assertEquals(39, path.length());
Assertions.assertEquals("org/example/ハ\u3099リューオフ\u3099シ\u3099ェクト/シ\u3099ト\u3099ウシャ.class", path);
}
}
Repro project
https://github.com/kazuki43zoo/repro-spring-project-gh-29243
How to run with Spring 5 GA version:
./mvnw clean test
- AppTest#successRunningOnSpring5 : success
- AppTest#successRunningOnSpring6 : fail
How to run with Spring 6 snapshot version:
./mvnw clean test -Pspring6
- AppTest#successRunningOnSpring5 : fail
- AppTest#successRunningOnSpring6 : sucess
Comment From: bclozel
Thanks for reporting this @kazuki43zoo this is probably linked to #29163
Comment From: kazuki43zoo
Note: Occurred on Mac OS and Windows OS. Linux OS works fine.
Comment From: kazuki43zoo
Windows OS is another error. Probably it's same with https://github.com/spring-projects/spring-framework/issues/29226.
Comment From: sbrannen
Hi @kazuki43zoo,
Thanks for bringing this to our attention.
My initial analysis shows that there are two issues here.
- The path is URL-encoded.
- The path is not in normalized Unicode form.
You can address # 1 by accessing the URL-decoded path via the URI
instead of the URL
as follows.
path = resource.getURI().getPath().replaceFirst(".*/test-classes/", "");
As stated in the Javadoc for java.net.URL
, you can use java.net.URI
to avoid issues with URL-encoding of the path (URI#getPath
vs. URI#getRawPath
).
You can address # 2 by normalizing the path using java.text.Normalizer
as follows.
path = Normalizer.normalize(path, Normalizer.Form.NFC);
Please let us know if those modifications to your test allow the test to pass.
In addition, I will investigate if we can align with the behavior in Spring Framework 5.3.x.
Comment From: sbrannen
Team Decision:
After further consideration, we have decided that the use of Resource#getURL
and Resource#getURI
should be avoided when performing direct comparisons against paths in a file system since those methods make no guarantees about the encoding of the paths with regard to the underlying file system.
Instead, if you wish to perform a direct comparison against a path in a Resource
, you should use either the File
or Path
abstraction.
For example, if you change your original code to the following your tests should pass.
path = resource.getFile().getAbsolutePath().replaceFirst(".*/test-classes/", "");
In light of the above, we are closing this issue.
Comment From: sbrannen
As a side note, if you still need to normalize paths you might find that your assertion library provides such support.
For example, AssertJ provides assertThat(path).isEqualToNormalizingUnicode(...)
.
Comment From: kazuki43zoo
@sbrannen Thanks for good advice!!