2424import java .nio .file .WatchService ;
2525import java .nio .file .attribute .BasicFileAttributes ;
2626import java .nio .file .attribute .UserPrincipalLookupService ;
27+ import java .util .ArrayList ;
2728import java .util .Collections ;
2829import java .util .Iterator ;
2930import 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 /
0 commit comments