Skip to content

Commit 420e984

Browse files
authored
Remove the notExistingPath check and use proper lazy streams (#50)
The `!= notExistingPath` doesn't do anything, and the `newDirStream` method would accumulate the results in a list, which is not efficient for dir streams that early-exit. Streams are meant to be lazy.
1 parent dff4f69 commit 420e984

File tree

2 files changed

+84
-39
lines changed

2 files changed

+84
-39
lines changed

src/main/java/cpw/mods/niofs/union/UnionFileSystem.java

Lines changed: 46 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.nio.file.WatchService;
2525
import java.nio.file.attribute.BasicFileAttributes;
2626
import java.nio.file.attribute.UserPrincipalLookupService;
27+
import java.util.ArrayList;
2728
import java.util.Collections;
2829
import java.util.Iterator;
2930
import java.util.LinkedHashSet;
@@ -98,7 +99,6 @@ public synchronized Throwable fillInStackTrace() {
9899
}
99100

100101
private final UnionPath root = new UnionPath(this, "/");
101-
private final UnionPath notExistingPath = new UnionPath(this, "/SNOWMAN");
102102
private final UnionFileSystemProvider provider;
103103
private final String key;
104104
private final List<Path> basepaths;
@@ -245,14 +245,6 @@ private Optional<BasicFileAttributes> getFileAttributes(final Path path) {
245245
}
246246
}
247247

248-
private Optional<Path> findFirstPathAt(final UnionPath path) {
249-
return this.basepaths.stream()
250-
.map(p -> toRealPath(p, path))
251-
.filter(p -> p != notExistingPath)
252-
.filter(Files::exists)
253-
.findFirst();
254-
}
255-
256248
private static boolean zipFsExists(UnionFileSystem ufs, Path path) {
257249
try {
258250
if (Optional.ofNullable(ufs.embeddedFileSystems.get(path.getFileSystem())).filter(efs -> !efs.fsCh.isOpen()).isPresent())
@@ -277,7 +269,7 @@ private Optional<Path> findFirstFiltered(final UnionPath unionPath) {
277269
final Path p = this.basepaths.get(i);
278270
final Path realPath = toRealPath(p, unionPath);
279271
// Test if the real path exists and matches the filter of this file system
280-
if (realPath != notExistingPath && testFilter(realPath, p)) {
272+
if (testFilter(realPath, p)) {
281273
if (realPath.getFileSystem() == FileSystems.getDefault()) {
282274
if (realPath.toFile().exists()) {
283275
return Optional.of(realPath);
@@ -297,32 +289,24 @@ private Optional<Path> findFirstFiltered(final UnionPath unionPath) {
297289
final Path last = basepaths.get(lastElementIndex);
298290
final Path realPath = toRealPath(last, unionPath);
299291
// We still care about the FS filter, but not about the existence of the real path
300-
if (realPath != notExistingPath && testFilter(realPath, last)) {
292+
if (testFilter(realPath, last)) {
301293
return Optional.of(realPath);
302294
}
303295
}
304296

305297
return Optional.empty();
306298
}
307299

308-
private <T> Stream<T> streamPathList(final Function<Path, Optional<T>> function) {
309-
return this.basepaths.stream()
310-
.map(function)
311-
.flatMap(Optional::stream);
312-
}
313-
314300
@SuppressWarnings("unchecked")
315301
public <A extends BasicFileAttributes> A readAttributes(final UnionPath path, final Class<A> type, final LinkOption... options) throws IOException {
316302
if (type == BasicFileAttributes.class) {
317303
// We need to run the test on the actual path,
318304
for (Path base : this.basepaths) {
319305
// We need to know the full path for the filter
320-
Path realPath = toRealPath(base, path);
321-
if (realPath != notExistingPath) {
322-
Optional<BasicFileAttributes> fileAttributes = this.getFileAttributes(realPath);
323-
if (fileAttributes.isPresent() && testFilter(realPath, base)) {
324-
return (A) fileAttributes.get();
325-
}
306+
final Path realPath = toRealPath(base, path);
307+
final Optional<BasicFileAttributes> fileAttributes = this.getFileAttributes(realPath);
308+
if (fileAttributes.isPresent() && testFilter(realPath, base)) {
309+
return (A) fileAttributes.get();
326310
}
327311
}
328312
throw new NoSuchFileException(path.toString());
@@ -389,41 +373,66 @@ private SeekableByteChannel byteChannel(final Path path) {
389373
}
390374

391375
public DirectoryStream<Path> newDirStream(final UnionPath path, final DirectoryStream.Filter<? super Path> filter) throws IOException {
392-
final var allpaths = new LinkedHashSet<Path>();
376+
List<Closeable> closeables = new ArrayList<>(basepaths.size());
377+
Stream<Path> stream = Stream.empty();
393378
for (final var bp : basepaths) {
394379
final var dir = toRealPath(bp, path);
395-
if (dir == notExistingPath) {
396-
continue;
397-
} else if (dir.getFileSystem() == FileSystems.getDefault() && !dir.toFile().exists()) {
380+
if (dir.getFileSystem() == FileSystems.getDefault() && !dir.toFile().exists()) {
398381
continue;
399382
} else if (dir.getFileSystem().provider().getScheme().equals("jar") && !zipFsExists(this, dir)) {
400383
continue;
401384
} else if (Files.notExists(dir)) {
402385
continue;
403386
}
404387
final var isSimple = embeddedFileSystems.containsKey(bp);
405-
try (final var ds = Files.newDirectoryStream(dir, filter)) {
406-
StreamSupport.stream(ds.spliterator(), false)
407-
.filter(p -> testFilter(p, bp))
408-
.map(other -> StreamSupport.stream(Spliterators.spliteratorUnknownSize((isSimple ? other : bp.relativize(other)).iterator(), Spliterator.ORDERED), false)
409-
.map(Path::getFileName).map(Path::toString).toArray(String[]::new))
410-
.map(this::fastPath)
411-
.forEachOrdered(allpaths::add);
412-
}
388+
final var ds = Files.newDirectoryStream(dir, filter);
389+
closeables.add(ds);
390+
final var currentPaths = StreamSupport.stream(ds.spliterator(), false)
391+
.filter(p -> testFilter(p, bp))
392+
.map(other -> fastPath(isSimple ? other : bp.relativize(other)));
393+
stream = Stream.concat(stream, currentPaths);
413394
}
395+
final Stream<Path> realStream = stream.distinct();
414396
return new DirectoryStream<>() {
415397
@Override
416398
public Iterator<Path> iterator() {
417-
return allpaths.iterator();
399+
return realStream.iterator();
418400
}
419401

420402
@Override
421403
public void close() throws IOException {
422-
// noop
404+
List<IOException> exceptions = new ArrayList<>();
405+
406+
for (Closeable closeable : closeables) {
407+
try {
408+
closeable.close();
409+
} catch (IOException e) {
410+
exceptions.add(e);
411+
}
412+
}
413+
414+
if (!exceptions.isEmpty()) {
415+
IOException aggregate = new IOException("Failed to close some streams in UnionFileSystem.newDirStream");
416+
exceptions.forEach(aggregate::addSuppressed);
417+
throw aggregate;
418+
}
423419
}
424420
};
425421
}
426422

423+
/**
424+
* Create a relative UnionPath from the path elements of the given {@link Path}.
425+
*/
426+
private Path fastPath(Path pathToConvert) {
427+
String[] parts = new String[pathToConvert.getNameCount()];
428+
429+
for (int i = 0; i < parts.length; i++) {
430+
parts[i] = pathToConvert.getName(i).toString();
431+
}
432+
433+
return fastPath(parts);
434+
}
435+
427436
/*
428437
* Standardize paths:
429438
* Path separators converted to /

src/test/java/cpw/mods/niofs/union/TestUnionFS.java

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import org.junit.jupiter.api.Test;
44

5+
import java.io.FileNotFoundException;
56
import java.io.IOException;
67
import java.nio.file.FileSystems;
78
import java.nio.file.Files;
@@ -11,13 +12,22 @@
1112
import java.nio.file.attribute.BasicFileAttributes;
1213
import java.nio.file.attribute.FileAttributeView;
1314
import java.nio.file.spi.FileSystemProvider;
15+
import java.util.ArrayList;
1416
import java.util.List;
1517
import java.util.Map;
1618
import java.util.Set;
1719
import java.util.stream.Collectors;
1820
import java.util.stream.StreamSupport;
1921

20-
import static org.junit.jupiter.api.Assertions.*;
22+
import static org.junit.jupiter.api.Assertions.assertAll;
23+
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
24+
import static org.junit.jupiter.api.Assertions.assertEquals;
25+
import static org.junit.jupiter.api.Assertions.assertFalse;
26+
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
27+
import static org.junit.jupiter.api.Assertions.assertNotNull;
28+
import static org.junit.jupiter.api.Assertions.assertNull;
29+
import static org.junit.jupiter.api.Assertions.assertThrows;
30+
import static org.junit.jupiter.api.Assertions.assertTrue;
2131

2232
public class TestUnionFS {
2333
private static final UnionFileSystemProvider UFSP = (UnionFileSystemProvider) FileSystemProvider.installedProviders().stream().filter(fsp->fsp.getScheme().equals("union")).findFirst().orElseThrow(()->new IllegalStateException("Couldn't find UnionFileSystemProvider"));
@@ -249,9 +259,35 @@ public void testDirectoryVisitorJar() throws Exception {
249259
final var fileSystem = UFSP.newFileSystem(jar1, Map.of("additional", List.of(jar2, jar3)));
250260
var root = fileSystem.getPath("/");
251261
try (var dirStream = Files.newDirectoryStream(root)) {
262+
final List<String> foundFiles = new ArrayList<>();
252263
assertAll(
253-
StreamSupport.stream(dirStream.spliterator(), false).map(p->()->Files.exists(p))
264+
StreamSupport.stream(dirStream.spliterator(), false)
265+
.peek(path -> foundFiles.add(path.toString()))
266+
.map(p-> ()-> {
267+
if (!Files.exists(p)) {
268+
throw new FileNotFoundException(p.toString());
269+
}
270+
})
254271
);
272+
assertEquals(foundFiles, List.of(
273+
"log4j2.xml",
274+
"module-info.class",
275+
"cpw",
276+
"META-INF",
277+
"LICENSE.txt",
278+
"CREDITS.txt",
279+
"url.png",
280+
"pack.mcmeta",
281+
"mcplogo.png",
282+
"forge_logo.png",
283+
"forge.srg",
284+
"forge.sas",
285+
"forge.exc",
286+
"data",
287+
"coremods",
288+
"assets",
289+
"net"
290+
));
255291
}
256292
}
257293
@Test

0 commit comments

Comments
 (0)