@@ -45,7 +45,6 @@ public class UnionFileSystem extends FileSystem {
4545 static final String SEP_STRING = "/" ;
4646
4747
48-
4948 static {
5049 try {
5150 var hackfield = MethodHandles .Lookup .class .getDeclaredField ("IMPL_LOOKUP" );
@@ -69,8 +68,7 @@ public InputStream buildInputStream(final UnionPath path) {
6968 try {
7069 var bytes = Files .readAllBytes (path );
7170 return new ByteArrayInputStream (bytes );
72- } catch (IOException ioe )
73- {
71+ } catch (IOException ioe ) {
7472 throw new UncheckedIOException (ioe );
7573 }
7674 }
@@ -79,6 +77,7 @@ private static class NoSuchFileException extends java.nio.file.NoSuchFileExcepti
7977 public NoSuchFileException (final String file ) {
8078 super (file );
8179 }
80+
8281 @ Override
8382 public synchronized Throwable fillInStackTrace () {
8483 return this ;
@@ -95,36 +94,40 @@ public synchronized Throwable fillInStackTrace() {
9594 return this ;
9695 }
9796 }
97+
9898 private final UnionPath root = new UnionPath (this , "/" );
9999 private final UnionPath notExistingPath = new UnionPath (this , "/SNOWMAN" );
100100 private final UnionFileSystemProvider provider ;
101101 private final String key ;
102102 private final List <Path > basepaths ;
103+ private final int lastElementIndex ;
103104 private final BiPredicate <String , String > pathFilter ;
104- private final Map <Path ,EmbeddedFileSystemMetadata > embeddedFileSystems ;
105+ private final Map <Path , EmbeddedFileSystemMetadata > embeddedFileSystems ;
105106
106107 public Path getPrimaryPath () {
107- return basepaths .get (basepaths .size ()- 1 );
108+ return basepaths .get (basepaths .size () - 1 );
108109 }
109110
110111 public BiPredicate <String , String > getFilesystemFilter () {
111112 return pathFilter ;
112113 }
113114
114- String getKey () {
115+ String getKey () {
115116 return this .key ;
116117 }
117118
118- private record EmbeddedFileSystemMetadata (Path path , FileSystem fs , SeekableByteChannel fsCh ) {}
119+ private record EmbeddedFileSystemMetadata (Path path , FileSystem fs , SeekableByteChannel fsCh ) {
120+ }
119121
120122 public UnionFileSystem (final UnionFileSystemProvider provider , final BiPredicate <String , String > pathFilter , final String key , final Path ... basepaths ) {
121123 this .pathFilter = pathFilter ;
122124 this .provider = provider ;
123125 this .key = key ;
124126 this .basepaths = IntStream .range (0 , basepaths .length )
125- .mapToObj (i -> basepaths [basepaths .length - i - 1 ])
127+ .mapToObj (i -> basepaths [basepaths .length - i - 1 ])
126128 .filter (Files ::exists )
127129 .toList (); // we flip the list so later elements are first in search order.
130+ lastElementIndex = basepaths .length - 1 ;
128131 this .embeddedFileSystems = this .basepaths .stream ().filter (path -> !Files .isDirectory (path ))
129132 .map (UnionFileSystem ::openFileSystem )
130133 .flatMap (Optional ::stream )
@@ -240,23 +243,36 @@ private Optional<BasicFileAttributes> getFileAttributes(final Path path) {
240243
241244 private Optional <Path > findFirstPathAt (final UnionPath path ) {
242245 return this .basepaths .stream ()
243- .map (p -> toRealPath (p , path ))
244- .filter (p -> p != notExistingPath )
246+ .map (p -> toRealPath (p , path ))
247+ .filter (p -> p != notExistingPath )
245248 .filter (Files ::exists )
246249 .findFirst ();
247250 }
248251
249252 private static boolean zipFsExists (UnionFileSystem ufs , Path path ) {
250253 try {
251- if (Optional .ofNullable (ufs .embeddedFileSystems .get (path .getFileSystem ())).filter (efs ->!efs .fsCh .isOpen ()).isPresent ()) throw new IllegalStateException ("The zip file has closed!" );
254+ if (Optional .ofNullable (ufs .embeddedFileSystems .get (path .getFileSystem ())).filter (efs -> !efs .fsCh .isOpen ()).isPresent ())
255+ throw new IllegalStateException ("The zip file has closed!" );
252256 return (boolean ) ZIPFS_EXISTS .invoke (path );
253257 } catch (Throwable t ) {
254258 throw new IllegalStateException (t );
255259 }
256260 }
257- private Optional <Path > findFirstFiltered (final UnionPath path ) {
258- for (Path p : this .basepaths ) {
259- Path realPath = toRealPath (p , path );
261+
262+ /**
263+ * Finds the first real {@link Path} that matches the {@link UnionPath#toString() path} of the given {@code unionPath}, and the {@link #pathFilter filter}
264+ * of the file system.
265+ *
266+ * @param unionPath the path to find
267+ * @return an optional containing the first real path that {@link Files#exists(Path, LinkOption...) exists},
268+ * or otherwise the last path, if this file system has at least one {@link #basepaths base path}
269+ */
270+ private Optional <Path > findFirstFiltered (final UnionPath unionPath ) {
271+ // Iterate the first base paths to try to find matching existing files
272+ for (int i = 0 ; i < lastElementIndex ; i ++) {
273+ final Path p = this .basepaths .get (i );
274+ final Path realPath = toRealPath (p , unionPath );
275+ // Test if the real path exists and matches the filter of this file system
260276 if (realPath != notExistingPath && testFilter (realPath , p )) {
261277 if (realPath .getFileSystem () == FileSystems .getDefault ()) {
262278 if (realPath .toFile ().exists ()) {
@@ -271,10 +287,21 @@ private Optional<Path> findFirstFiltered(final UnionPath path) {
271287 }
272288 }
273289 }
290+
291+ // Otherwise, if we still haven't found an existing path, return the last possibility without checking its existence
292+ if (lastElementIndex >= 0 ) {
293+ final Path last = basepaths .get (lastElementIndex );
294+ final Path realPath = toRealPath (last , unionPath );
295+ // We still care about the FS filter, but not about the existence of the real path
296+ if (realPath != notExistingPath && testFilter (realPath , last )) {
297+ return Optional .of (realPath );
298+ }
299+ }
300+
274301 return Optional .empty ();
275302 }
276303
277- private <T > Stream <T > streamPathList (final Function <Path ,Optional <T >> function ) {
304+ private <T > Stream <T > streamPathList (final Function <Path , Optional <T >> function ) {
278305 return this .basepaths .stream ()
279306 .map (function )
280307 .flatMap (Optional ::stream );
@@ -289,7 +316,7 @@ public <A extends BasicFileAttributes> A readAttributes(final UnionPath path, fi
289316 Path realPath = toRealPath (base , path );
290317 if (realPath != notExistingPath ) {
291318 Optional <BasicFileAttributes > fileAttributes = this .getFileAttributes (realPath );
292- if (fileAttributes .isPresent () && testFilter (realPath , base )) {
319+ if (fileAttributes .isPresent () && testFilter (realPath , base )) {
293320 return (A ) fileAttributes .get ();
294321 }
295322 }
@@ -302,19 +329,25 @@ public <A extends BasicFileAttributes> A readAttributes(final UnionPath path, fi
302329
303330 public void checkAccess (final UnionPath p , final AccessMode ... modes ) throws IOException {
304331 try {
305- findFirstFiltered (p ).ifPresentOrElse (path -> {
332+ findFirstFiltered (p ).ifPresentOrElse (path -> {
306333 try {
307- if (modes .length == 0 && path .getFileSystem () == FileSystems .getDefault ()) {
308- if (!path .toFile ().exists ()) {
309- throw new UncheckedIOException (new NoSuchFileException (p .toString ()));
334+ if (modes .length == 0 ) {
335+ if (path .getFileSystem () == FileSystems .getDefault ()) {
336+ if (!path .toFile ().exists ()) {
337+ throw new UncheckedIOException (new NoSuchFileException (p .toString ()));
338+ }
339+ } else if (path .getFileSystem ().provider ().getScheme ().equals ("jar" )) {
340+ if (!zipFsExists (this , path )) {
341+ throw new UncheckedIOException (new NoSuchFileException (p .toString ()));
342+ }
310343 }
311344 } else {
312345 path .getFileSystem ().provider ().checkAccess (path , modes );
313346 }
314347 } catch (IOException e ) {
315348 throw new UncheckedIOException (e );
316349 }
317- }, ()-> {
350+ }, () -> {
318351 throw new UncheckedIOException (new NoSuchFileException (p .toString ()));
319352 });
320353 } catch (UncheckedIOException e ) {
@@ -359,15 +392,15 @@ public DirectoryStream<Path> newDirStream(final UnionPath path, final DirectoryS
359392 continue ;
360393 } else if (dir .getFileSystem () == FileSystems .getDefault () && !dir .toFile ().exists ()) {
361394 continue ;
362- } else if (dir .getFileSystem ().provider ().getScheme () == "jar" && !zipFsExists (this , dir )) {
395+ } else if (dir .getFileSystem ().provider ().getScheme (). equals ( "jar" ) && !zipFsExists (this , dir )) {
363396 continue ;
364397 } else if (Files .notExists (dir )) {
365398 continue ;
366399 }
367400 final var isSimple = embeddedFileSystems .containsKey (bp );
368401 try (final var ds = Files .newDirectoryStream (dir , filter )) {
369402 StreamSupport .stream (ds .spliterator (), false )
370- .filter (p -> testFilter (p , bp ))
403+ .filter (p -> testFilter (p , bp ))
371404 .map (other -> StreamSupport .stream (Spliterators .spliteratorUnknownSize ((isSimple ? other : bp .relativize (other )).iterator (), Spliterator .ORDERED ), false )
372405 .map (Path ::getFileName ).map (Path ::toString ).toArray (String []::new ))
373406 .map (this ::fastPath )
0 commit comments