Skip to content

Commit 3a8ffe7

Browse files
committed
Merge branch 'podcherklife-support-partially-case-sensitive-fs' into devel
2 parents daca1f2 + bf631ce commit 3a8ffe7

File tree

3 files changed

+160
-35
lines changed

3 files changed

+160
-35
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.infinity.util.io;
2+
3+
import java.nio.file.Files;
4+
import java.nio.file.Path;
5+
import java.util.Map;
6+
import java.util.concurrent.ConcurrentHashMap;
7+
8+
import org.infinity.util.io.CaseAwareSingleDirectoryPathResolver.ResolutionResult;
9+
10+
public class CaseAwarePathResolver {
11+
12+
private final Map<Path, CaseAwareSingleDirectoryPathResolver> roots = new ConcurrentHashMap<>();
13+
14+
public Path resolve(Path path, boolean requireFullyResolvedPath) {
15+
if (!path.isAbsolute()) {
16+
throw new IllegalArgumentException("Provided path should be absolute, got " + path + " instead");
17+
}
18+
if (Files.exists(path)) {
19+
return path;
20+
}
21+
22+
ResolutionResult resolutionResult = roots
23+
.computeIfAbsent(path.getRoot(), (root) -> new CaseAwareSingleDirectoryPathResolver(root))
24+
.resolve(path.subpath(0, path.getNameCount()));
25+
26+
if (resolutionResult.getUnresolvedPart() == null) {
27+
return resolutionResult.getResolvedPart();
28+
} else if (requireFullyResolvedPath) {
29+
return null;
30+
} else {
31+
return resolutionResult.getResolvedPart().resolve(resolutionResult.getUnresolvedPart());
32+
}
33+
}
34+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package org.infinity.util.io;
2+
3+
import java.io.IOException;
4+
import java.nio.file.DirectoryStream;
5+
import java.nio.file.Files;
6+
import java.nio.file.NoSuchFileException;
7+
import java.nio.file.Path;
8+
import java.util.Map;
9+
import java.util.Objects;
10+
import java.util.concurrent.ConcurrentHashMap;
11+
12+
public class CaseAwareSingleDirectoryPathResolver {
13+
14+
public static class ResolutionResult {
15+
private Path resolvedPart;
16+
private Path unresolvedPart;
17+
18+
public ResolutionResult(Path resolvedPart, Path unresolvedPart) {
19+
super();
20+
this.resolvedPart = resolvedPart;
21+
this.unresolvedPart = unresolvedPart;
22+
}
23+
24+
public Path getResolvedPart() {
25+
return resolvedPart;
26+
}
27+
28+
public Path getUnresolvedPart() {
29+
return unresolvedPart;
30+
}
31+
}
32+
33+
private final Map<Path, CaseAwareSingleDirectoryPathResolver> subdirectoryResolvers = new ConcurrentHashMap<>();
34+
private final Path directory;
35+
36+
private boolean caseSensitivityIsFiguredOut = false;
37+
private boolean caseSensitive = false;
38+
39+
public CaseAwareSingleDirectoryPathResolver(Path resolveFrom) {
40+
this.directory = resolveFrom;
41+
}
42+
43+
public ResolutionResult resolve(Path relativePath) {
44+
Path unresolvedPart = relativePath;
45+
Path currentLocation = directory;
46+
Path nextPathElement = unresolvedPart.subpath(0, 1);
47+
Path actualNextLocation = resolveSinglePathElementToActualLocation(currentLocation, nextPathElement);
48+
if (actualNextLocation == null) {
49+
return new ResolutionResult(currentLocation, unresolvedPart);
50+
}
51+
52+
if (unresolvedPart.getNameCount() == 1) { // this was the last path element, nothing left to resolve
53+
return new ResolutionResult(currentLocation.resolve(actualNextLocation), null);
54+
} else {
55+
unresolvedPart = unresolvedPart.subpath(1, unresolvedPart.getNameCount());
56+
return subdirectoryResolvers
57+
.computeIfAbsent(actualNextLocation, (key) -> new CaseAwareSingleDirectoryPathResolver(key))
58+
.resolve(unresolvedPart);
59+
}
60+
61+
}
62+
63+
private Path resolveSinglePathElementToActualLocation(Path resolveFrom, Path elementToResolve) {
64+
Path directResolution = resolveFrom.resolve(elementToResolve);
65+
if (Files.exists(directResolution)) {
66+
return directResolution;
67+
}
68+
69+
if (currentDirIsKnownToBeCaseInsensitive()) {
70+
return null;
71+
}
72+
73+
String searchString = elementToResolve.getFileName().toString();
74+
try (DirectoryStream<Path> ds = Files.newDirectoryStream(resolveFrom)) {
75+
for (final Path dirPath : ds) {
76+
if (!caseSensitivityIsFiguredOut) {
77+
tryFigureOutCaseSensitivityUsingExistingPath(dirPath);
78+
if (currentDirIsKnownToBeCaseInsensitive()) {
79+
break;
80+
}
81+
}
82+
String dirString = dirPath.getFileName().toString();
83+
if (searchString.equalsIgnoreCase(dirString)) {
84+
return resolveFrom.resolve(dirPath);
85+
}
86+
}
87+
} catch (Throwable t) {
88+
throw new RuntimeException(t);
89+
}
90+
return null;
91+
}
92+
93+
private boolean currentDirIsKnownToBeCaseInsensitive() {
94+
return caseSensitivityIsFiguredOut && !caseSensitive;
95+
}
96+
97+
private boolean tryFigureOutCaseSensitivityUsingExistingPath(Path existingPath) throws IOException {
98+
Path baseDir = existingPath.getParent();
99+
String filename = existingPath.getFileName().toString();
100+
String lowerCaseFilename = filename.toLowerCase();
101+
String upperCaseFilename = filename.toUpperCase();
102+
if (Objects.equals(lowerCaseFilename, upperCaseFilename)) {
103+
return false;
104+
}
105+
106+
try {
107+
Path lowercaseFilePath = baseDir.resolve(lowerCaseFilename);
108+
Path uppercaseFilePath = baseDir.resolve(upperCaseFilename);
109+
this.caseSensitive = !Files.isSameFile(lowercaseFilePath, uppercaseFilePath);
110+
this.caseSensitivityIsFiguredOut = true;
111+
} catch (NoSuchFileException e) {
112+
this.caseSensitive = true;
113+
this.caseSensitivityIsFiguredOut = true;
114+
}
115+
return true;
116+
}
117+
118+
}

src/org/infinity/util/io/FileManager.java

Lines changed: 8 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package org.infinity.util.io;
66

77
import java.io.IOException;
8-
import java.nio.file.DirectoryStream;
98
import java.nio.file.FileSystem;
109
import java.nio.file.FileSystems;
1110
import java.nio.file.Files;
@@ -14,9 +13,9 @@
1413
import java.util.ArrayList;
1514
import java.util.Arrays;
1615
import java.util.Collections;
17-
import java.util.Iterator;
1816
import java.util.List;
1917
import java.util.Objects;
18+
import java.util.function.BiFunction;
2019

2120
import org.infinity.util.Logger;
2221
import org.infinity.util.Platform;
@@ -37,6 +36,12 @@ public static boolean isCaseSensitiveMode() {
3736
return caseSensitiveMode;
3837
}
3938

39+
private static final BiFunction<Path, Boolean, Path> pathResolver = makePathResolver();
40+
41+
private static BiFunction<Path, Boolean, Path> makePathResolver() {
42+
return isCaseSensitiveMode() ? new CaseAwarePathResolver()::resolve : (p, f) -> p;
43+
}
44+
4045
/**
4146
* This method can be used to override the default case-sensitive operation mode for filesystems.
4247
*
@@ -363,39 +368,7 @@ private static Path resolvePath(Path path, boolean forced) {
363368
// validating path segments
364369
Path validatedPath = path.getRoot();
365370
if (validatedPath != null && FileEx.create(validatedPath).exists()) {
366-
int idx = 0;
367-
for (; idx < path.getNameCount(); idx++) {
368-
final Path pathItem = path.getName(idx);
369-
final String pathName = pathItem.toString();
370-
371-
final Path resolvedPath = validatedPath.resolve(pathName);
372-
if (Files.exists(resolvedPath)) {
373-
validatedPath = resolvedPath;
374-
} else {
375-
try (final DirectoryStream<Path> ds = Files.newDirectoryStream(validatedPath,
376-
p -> p.getFileName().toString().equalsIgnoreCase(pathName))) {
377-
final Iterator<Path> iter = ds.iterator();
378-
if (iter.hasNext()) {
379-
validatedPath = iter.next();
380-
} else {
381-
break;
382-
}
383-
} catch (IOException e) {
384-
break;
385-
}
386-
}
387-
}
388-
389-
// adding remaining unvalidated path segments (if any)
390-
if (forced && idx < path.getNameCount()) {
391-
validatedPath = null;
392-
} else {
393-
for (; idx < path.getNameCount(); idx++) {
394-
validatedPath = validatedPath.resolve(path.getName(idx));
395-
}
396-
}
397-
398-
retVal = validatedPath;
371+
retVal = pathResolver.apply(path, forced);
399372
}
400373
} else if (forced && !pathEx.exists()) {
401374
retVal = null;

0 commit comments

Comments
 (0)