Consistently sort jar entries in doFindPathMatchingJarResources

Includes consistent clearCache() behavior for manifest entries.

Closes gh-33771
See gh-33705
This commit is contained in:
Juergen Hoeller 2024-10-22 21:29:22 +02:00
parent 1c69a3c521
commit 081d0b33d4
1 changed files with 21 additions and 22 deletions

View File

@ -235,9 +235,6 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private static final Predicate<ResolvedModule> isNotSystemModule = private static final Predicate<ResolvedModule> isNotSystemModule =
resolvedModule -> !systemModuleNames.contains(resolvedModule.name()); resolvedModule -> !systemModuleNames.contains(resolvedModule.name());
@Nullable
private static Set<ClassPathManifestEntry> classPathManifestEntriesCache;
@Nullable @Nullable
private static Method equinoxResolveMethod; private static Method equinoxResolveMethod;
@ -261,7 +258,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
private final Map<String, Resource[]> rootDirCache = new ConcurrentHashMap<>(); private final Map<String, Resource[]> rootDirCache = new ConcurrentHashMap<>();
private final Map<String, NavigableSet<String>> jarEntryCache = new ConcurrentHashMap<>(); private final Map<String, NavigableSet<String>> jarEntriesCache = new ConcurrentHashMap<>();
@Nullable
private volatile Set<ClassPathManifestEntry> manifestEntriesCache;
/** /**
@ -377,7 +377,8 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
*/ */
public void clearCache() { public void clearCache() {
this.rootDirCache.clear(); this.rootDirCache.clear();
this.jarEntryCache.clear(); this.jarEntriesCache.clear();
this.manifestEntriesCache = null;
} }
@ -530,10 +531,10 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
* @since 4.3 * @since 4.3
*/ */
protected void addClassPathManifestEntries(Set<Resource> result) { protected void addClassPathManifestEntries(Set<Resource> result) {
Set<ClassPathManifestEntry> entries = classPathManifestEntriesCache; Set<ClassPathManifestEntry> entries = this.manifestEntriesCache;
if (entries == null) { if (entries == null) {
entries = getClassPathManifestEntries(); entries = getClassPathManifestEntries();
classPathManifestEntriesCache = entries; this.manifestEntriesCache = entries;
} }
for (ClassPathManifestEntry entry : entries) { for (ClassPathManifestEntry entry : entries) {
if (!result.contains(entry.resource()) && if (!result.contains(entry.resource()) &&
@ -544,7 +545,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
} }
private Set<ClassPathManifestEntry> getClassPathManifestEntries() { private Set<ClassPathManifestEntry> getClassPathManifestEntries() {
Set<ClassPathManifestEntry> manifestEntries = new HashSet<>(); Set<ClassPathManifestEntry> manifestEntries = new LinkedHashSet<>();
Set<File> seen = new HashSet<>(); Set<File> seen = new HashSet<>();
try { try {
String paths = System.getProperty("java.class.path"); String paths = System.getProperty("java.class.path");
@ -578,9 +579,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
File parent = jar.getAbsoluteFile().getParentFile(); File parent = jar.getAbsoluteFile().getParentFile();
try (JarFile jarFile = new JarFile(jar)) { try (JarFile jarFile = new JarFile(jar)) {
Manifest manifest = jarFile.getManifest(); Manifest manifest = jarFile.getManifest();
Attributes attributes = (manifest != null) ? manifest.getMainAttributes() : null; Attributes attributes = (manifest != null ? manifest.getMainAttributes() : null);
String classPath = (attributes != null) ? attributes.getValue(Name.CLASS_PATH) : null; String classPath = (attributes != null ? attributes.getValue(Name.CLASS_PATH) : null);
Set<ClassPathManifestEntry> manifestEntries = new HashSet<>(); Set<ClassPathManifestEntry> manifestEntries = new LinkedHashSet<>();
if (StringUtils.hasLength(classPath)) { if (StringUtils.hasLength(classPath)) {
StringTokenizer tokenizer = new StringTokenizer(classPath); StringTokenizer tokenizer = new StringTokenizer(classPath);
while (tokenizer.hasMoreTokens()) { while (tokenizer.hasMoreTokens()) {
@ -806,11 +807,11 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
if (separatorIndex != -1) { if (separatorIndex != -1) {
jarFileUrl = urlFile.substring(0, separatorIndex); jarFileUrl = urlFile.substring(0, separatorIndex);
rootEntryPath = urlFile.substring(separatorIndex + 2); // both separators are 2 chars rootEntryPath = urlFile.substring(separatorIndex + 2); // both separators are 2 chars
NavigableSet<String> entryCache = this.jarEntryCache.get(jarFileUrl); NavigableSet<String> entriesCache = this.jarEntriesCache.get(jarFileUrl);
if (entryCache != null) { if (entriesCache != null) {
Set<Resource> result = new LinkedHashSet<>(64); Set<Resource> result = new LinkedHashSet<>(64);
// Search sorted entries from first entry with rootEntryPath prefix // Search sorted entries from first entry with rootEntryPath prefix
for (String entryPath : entryCache.tailSet(rootEntryPath, false)) { for (String entryPath : entriesCache.tailSet(rootEntryPath, false)) {
if (!entryPath.startsWith(rootEntryPath)) { if (!entryPath.startsWith(rootEntryPath)) {
// We are beyond the potential matches in the current TreeSet. // We are beyond the potential matches in the current TreeSet.
break; break;
@ -870,11 +871,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
rootEntryPath = rootEntryPath + "/"; rootEntryPath = rootEntryPath + "/";
} }
Set<Resource> result = new LinkedHashSet<>(64); Set<Resource> result = new LinkedHashSet<>(64);
NavigableSet<String> entryCache = new TreeSet<>(); NavigableSet<String> entriesCache = new TreeSet<>();
for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements(); ) { for (String entryPath : jarFile.stream().map(JarEntry::getName).sorted().toList()) {
JarEntry entry = entries.nextElement(); entriesCache.add(entryPath);
String entryPath = entry.getName();
entryCache.add(entryPath);
if (entryPath.startsWith(rootEntryPath)) { if (entryPath.startsWith(rootEntryPath)) {
String relativePath = entryPath.substring(rootEntryPath.length()); String relativePath = entryPath.substring(rootEntryPath.length());
if (getPathMatcher().match(subPattern, relativePath)) { if (getPathMatcher().match(subPattern, relativePath)) {
@ -883,7 +882,7 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
} }
} }
// Cache jar entries in TreeSet for efficient searching on re-encounter. // Cache jar entries in TreeSet for efficient searching on re-encounter.
this.jarEntryCache.put(jarFileUrl, entryCache); this.jarEntriesCache.put(jarFileUrl, entriesCache);
return result; return result;
} }
finally { finally {
@ -1236,9 +1235,9 @@ public class PathMatchingResourcePatternResolver implements ResourcePatternResol
} }
} }
private static Resource asJarFileResource(String path) private static Resource asJarFileResource(String path) throws MalformedURLException {
throws MalformedURLException {
return new UrlResource(JARFILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR); return new UrlResource(JARFILE_URL_PREFIX + path + ResourceUtils.JAR_URL_SEPARATOR);
} }
} }
} }