Skip to content

Commit 485cc17

Browse files
authored
Fix errors with map resources being not found in 2.6. (#10504)
* Fix errors with map resources being not found in 2.6. This was caused by some maps having duplicate files (e.g. a polygon.txt file in baseTiles folder). Before this change, it's undefined which of these files will be found first and all other files will be expected to be alongside it. With this change, the search returns files in a deterministic order, sorted by path length. So files closer to the root will be first in the list. This makes the behavior deterministic across platforms and the map to use the file closest to the map root. Renames findAny() to findClosestToRoot() and adds tests.
1 parent aaaaf1d commit 485cc17

File tree

7 files changed

+78
-20
lines changed

7 files changed

+78
-20
lines changed

game-app/game-core/src/main/java/games/strategy/engine/framework/map/file/system/loader/InstalledMap.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ Optional<Path> findContentRoot() {
4545
// a polygons file, the location of the polygons file is the map content root.
4646
final Path mapYamlParentFolder = mapDescriptionYaml.getYamlFileLocation().getParent();
4747
contentRoot =
48-
FileUtils.findAny(mapYamlParentFolder, 3, MapData.POLYGON_FILE)
48+
FileUtils.findClosestToRoot(mapYamlParentFolder, 3, MapData.POLYGON_FILE)
4949
.map(Path::getParent)
5050
.orElse(null);
5151
}

game-app/map-data/src/main/java/org/triplea/map/description/file/MapDescriptionYaml.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ private Optional<String> findFileNameForGame(final String gameName) {
210210
/** Find 'games' folder starting from map.yml parent folder. */
211211
private Optional<Path> findGamesFolder() {
212212
final Path mapFolder = yamlFileLocation.getParent();
213-
final Optional<Path> gamesFolder = FileUtils.findAny(mapFolder, 5, "games");
213+
final Optional<Path> gamesFolder = FileUtils.findClosestToRoot(mapFolder, 5, "games");
214214

215215
if (gamesFolder.isEmpty()) {
216216
log.warn("No 'games' folder found under location: {}", mapFolder.toAbsolutePath());
@@ -220,7 +220,7 @@ private Optional<Path> findGamesFolder() {
220220

221221
/** Search 'games' folder for a game-xml-file. */
222222
private Optional<Path> searchForGameFile(final Path gamesFolder, final String xmlFileName) {
223-
final Optional<Path> gameFile = FileUtils.findAny(gamesFolder, 3, xmlFileName);
223+
final Optional<Path> gameFile = FileUtils.findClosestToRoot(gamesFolder, 3, xmlFileName);
224224
if (gameFile.isEmpty()) {
225225
log.warn(
226226
"Failed to find game file: {}, within directory tree rooted at: {}",

game-app/map-data/src/main/java/org/triplea/map/description/file/MapDescriptionYamlReader.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ private static Optional<Path> findMapYmlFile(final Path mapFolder) {
3737
* 'map.yml' file at depth 2, eg: 'map_folder-master/map_folder/map/map.yml'.
3838
*/
3939
final int maxMapYmlSearchDepth = 2;
40-
return FileUtils.findAny(
40+
return FileUtils.findClosestToRoot(
4141
mapFolder, maxMapYmlSearchDepth, MapDescriptionYaml.MAP_YAML_FILE_NAME);
4242
}
4343

lib/java-extras/src/main/java/org/triplea/io/FileUtils.java

+23-8
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.nio.file.Path;
1515
import java.time.Instant;
1616
import java.util.Collection;
17+
import java.util.Comparator;
1718
import java.util.List;
1819
import java.util.Optional;
1920
import java.util.function.Function;
@@ -74,26 +75,41 @@ public static URL toUrl(final Path file) {
7475

7576
/**
7677
* Searches a file system starting from a given directory looking for a file or directory with a
77-
* matching name.
78+
* matching name. If multiple matching paths are found, returns the one closest to the root (via
79+
* minimum length of the absolute path).
7880
*
79-
* @param searchRoot The directory whose contents we will search (and sub-directories)
81+
* @param searchRoot The directory whose contents we will search (and subdirectories).
8082
* @param maxDepth The maximum number of subdirectories to search. Zero means only search the
8183
* 'searchRoot' directory.
8284
* @param fileName The name of the file to be search for.
8385
* @return A file matching the given name or empty if not found.
8486
*/
85-
public Optional<Path> findAny(final Path searchRoot, final int maxDepth, final String fileName) {
87+
public Optional<Path> findClosestToRoot(
88+
final Path searchRoot, final int maxDepth, final String fileName) {
8689
return find(searchRoot, maxDepth, fileName).stream().findAny();
8790
}
8891

89-
public Collection<Path> find(final Path searchRoot, final int maxDepth, final String fileName) {
92+
/**
93+
* Searches a file system starting from a given directory looking for files or directories with
94+
* matching names. The resulting list will be in ascending order by absolute path length.
95+
*
96+
* @param searchRoot The directory whose contents we will search (and subdirectories).
97+
* @param maxDepth The maximum number of subdirectories to search. Zero means only search the
98+
* 'searchRoot' directory.
99+
* @param fileName The name of the file to be search for.
100+
* @return A list of files matching the given name or an empty list if not.
101+
*/
102+
public List<Path> find(final Path searchRoot, final int maxDepth, final String fileName) {
90103
Preconditions.checkArgument(Files.isDirectory(searchRoot), searchRoot.toAbsolutePath());
91104
Preconditions.checkArgument(Files.exists(searchRoot), searchRoot.toAbsolutePath());
92105
Preconditions.checkArgument(maxDepth > -1);
93106
Preconditions.checkArgument(!fileName.isBlank());
94107
try (Stream<Path> files = Files.walk(searchRoot, maxDepth)) {
95108
return files
96109
.filter(f -> f.getFileName().toString().equals(fileName))
110+
// Sort by path length (shortest to longest), so that the ordering is deterministic and
111+
// paths closer to the root are earlier in the list.
112+
.sorted(Comparator.comparingInt(f -> f.toAbsolutePath().toString().length()))
97113
.collect(Collectors.toList());
98114
} catch (final IOException e) {
99115
log.error(
@@ -243,7 +259,7 @@ public static void deleteDirectory(final Path path) throws IOException {
243259
}
244260

245261
/**
246-
* Does an overwrite of one folder onto another and rolls back if there any errors. The rollback
262+
* Does an overwrite of one folder onto another and rolls back if there were errors. The rollback
247263
* is done by first moving the destination folder to a backup location. If there are any errors
248264
* then we delete whatever we copied and move the backup location back to the destination
249265
* location.
@@ -287,8 +303,7 @@ static boolean replaceFolder(
287303
return true;
288304
}
289305

290-
// otherwise create a backup of the destination folder before we replace it
291-
306+
// otherwise, create a backup of the destination folder before we replace it
292307
final Path backupFolder;
293308
try {
294309
backupFolder = Files.createTempDirectory("temp-dir").resolve(dest.getFileName());
@@ -298,7 +313,7 @@ static boolean replaceFolder(
298313
}
299314

300315
try {
301-
// make a complete backup by moving the dest folder to backup
316+
// make a complete backup by moving the dest folder to back up
302317
fileMoveOperation.move(dest, backupFolder);
303318

304319
// do the folder move

lib/java-extras/src/test/java/org/triplea/io/FileUtilsTest.java

+51-8
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresent;
55
import static com.github.npathai.hamcrestopt.OptionalMatchers.isPresentAndIs;
66
import static org.hamcrest.MatcherAssert.assertThat;
7+
import static org.hamcrest.Matchers.contains;
78
import static org.hamcrest.Matchers.containsInAnyOrder;
89
import static org.hamcrest.Matchers.empty;
910
import static org.hamcrest.Matchers.is;
1011
import static org.mockito.ArgumentMatchers.any;
1112
import static org.mockito.Mockito.doCallRealMethod;
1213

13-
import java.io.File;
1414
import java.io.IOException;
15+
import java.net.URISyntaxException;
1516
import java.nio.file.Files;
1617
import java.nio.file.Path;
1718
import java.time.Instant;
@@ -59,10 +60,7 @@ void shouldReturnEmptyCollectionWhenDirectoryIsEmpty() throws Exception {
5960
@Test
6061
@DisplayName("Verify we can read file contents with ISO-8859-1 encoded characters")
6162
void readContents() {
62-
final File testFile =
63-
new File(
64-
FileUtilsTest.class.getClassLoader().getResource("ISO-8859-1-test-file.txt").getFile());
65-
final Path testFilePath = Path.of(testFile.toURI());
63+
final Path testFilePath = getPathOfResource("ISO-8859-1-test-file.txt");
6664

6765
final Optional<String> contentRead = FileUtils.readContents(testFilePath);
6866

@@ -87,9 +85,7 @@ void fileExists() {
8785
// |- child1/
8886
// |- child2/
8987
// |- touch-file
90-
final File testFolderFile =
91-
new File(FileUtilsTest.class.getClassLoader().getResource("test-folder-path").getFile());
92-
final Path testFolderPath = Path.of(testFolderFile.toURI());
88+
final Path testFolderPath = getPathOfResource("test-folder-path");
9389
final Path child1 = testFolderPath.resolve("child1");
9490
final Path child2 = child1.resolve("child2");
9591

@@ -118,6 +114,45 @@ void fileExists() {
118114
}
119115
}
120116

117+
@Nested
118+
class Find {
119+
// Folder structure:
120+
// |- test-folder-path/
121+
// |- test-file
122+
// |- child1/
123+
// |- child2/
124+
// |- test-file
125+
final Path testFolderPath = getPathOfResource("test-folder-path");
126+
final Path child1 = testFolderPath.resolve("child1");
127+
final Path child2 = child1.resolve("child2");
128+
129+
@Test
130+
void findClosestToRootDepth1() {
131+
assertThat(
132+
FileUtils.findClosestToRoot(child2, 1, "test-file"),
133+
isPresentAndIs(child2.resolve("test-file")));
134+
}
135+
136+
@Test
137+
void findClosestToRootDepth3() {
138+
assertThat(
139+
FileUtils.findClosestToRoot(testFolderPath, 3, "test-file"),
140+
isPresentAndIs(testFolderPath.resolve("test-file")));
141+
}
142+
143+
@Test
144+
void findDepth1() {
145+
assertThat(FileUtils.find(child2, 1, "test-file"), contains(child2.resolve("test-file")));
146+
}
147+
148+
@Test
149+
void findDepth3() {
150+
assertThat(
151+
FileUtils.find(testFolderPath, 3, "test-file"),
152+
contains(testFolderPath.resolve("test-file"), child2.resolve("test-file")));
153+
}
154+
}
155+
121156
/**
122157
* Creates a non-empty directory, run delete, verify directory is deleted. Linux systems will fail
123158
* the delete operation if the directory is not empty, this test verifies that we do a recursive
@@ -251,4 +286,12 @@ void getLastModified() throws IOException {
251286
assertThat(FileUtils.getLastModified(tempFile), isPresent());
252287
assertThat(FileUtils.getLastModified(tempFile).get().isBefore(Instant.now()), is(true));
253288
}
289+
290+
private Path getPathOfResource(String name) {
291+
try {
292+
return Path.of(FileUtilsTest.class.getClassLoader().getResource(name).toURI());
293+
} catch (URISyntaxException e) {
294+
throw new IllegalStateException(e);
295+
}
296+
}
254297
}

lib/java-extras/src/test/resources/test-folder-path/child1/child2/test-file

Whitespace-only changes.

lib/java-extras/src/test/resources/test-folder-path/test-file

Whitespace-only changes.

0 commit comments

Comments
 (0)