2828import java .nio .file .WatchKey ;
2929import java .nio .file .WatchService ;
3030import java .time .Duration ;
31+ import java .util .Collections ;
3132import java .util .HashSet ;
3233import java .util .List ;
3334import java .util .Map ;
@@ -85,71 +86,75 @@ void watch(Set<Path> paths, Runnable action) {
8586 this .thread = new WatcherThread ();
8687 this .thread .start ();
8788 }
88- Set <Path > registrationPaths = new HashSet <>();
89- for (Path path : paths ) {
90- registrationPaths .addAll (getRegistrationPaths (path ));
91- }
92- this .thread .register (new Registration (registrationPaths , action ));
89+ this .thread .register (new Registration (getRegistrationPaths (paths ), action ));
9390 }
9491 catch (IOException ex ) {
9592 throw new UncheckedIOException ("Failed to register paths for watching: " + paths , ex );
9693 }
9794 }
9895 }
9996
100- @ Override
101- public void close () throws IOException {
102- synchronized (this .lock ) {
103- if (this .thread != null ) {
104- this .thread .close ();
105- this .thread .interrupt ();
106- try {
107- this .thread .join ();
108- }
109- catch (InterruptedException ex ) {
110- Thread .currentThread ().interrupt ();
111- }
112- this .thread = null ;
113- }
114- }
115- }
116-
11797 /**
11898 * Retrieves all {@link Path Paths} that should be registered for the specified
11999 * {@link Path}. If the path is a symlink, changes to the symlink should be monitored,
120100 * not just the file it points to. For example, for the given {@code keystore.jks}
121101 * path in the following directory structure:<pre>
122- * .
123- * ├── ..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f
124- * │ ├── keystore.jks
125- * ├── ..data -> ..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f
126- * ├── keystore.jks -> ..data/keystore.jks
102+ * +- stores
103+ * | +─ keystore.jks
104+ * +- <em>data</em> -> stores
105+ * +─ <em>keystore.jks</em> -> data/keystore.jks
127106 * </pre> the resulting paths would include:
107+ * <p>
128108 * <ul>
129- * <li><b> keystore.jks</b> </li>
130- * <li><b>.. data/keystore.jks</b> </li>
131- * <li><b>.. data</b> </li>
132- * <li><b>..a72e81ff-f0e1-41d8-a19b-068d3d1d4e2f /keystore.jks</b> </li>
109+ * <li>{@code keystore.jks} </li>
110+ * <li>{@code data/keystore.jks} </li>
111+ * <li>{@code data} </li>
112+ * <li>{@code stores /keystore.jks} </li>
133113 * </ul>
134- * @param path the path
114+ * @param paths the source paths
135115 * @return all possible {@link Path} instances to be registered
136116 * @throws IOException if an I/O error occurs
137117 */
138- private static Set <Path > getRegistrationPaths (Path path ) throws IOException {
139- path = path .toAbsolutePath ();
118+ private Set <Path > getRegistrationPaths (Set <Path > paths ) throws IOException {
140119 Set <Path > result = new HashSet <>();
120+ for (Path path : paths ) {
121+ collectRegistrationPaths (path , result );
122+ }
123+ return Collections .unmodifiableSet (result );
124+ }
125+
126+ private void collectRegistrationPaths (Path path , Set <Path > result ) throws IOException {
127+ path = path .toAbsolutePath ();
141128 result .add (path );
142129 Path parent = path .getParent ();
143130 if (parent != null && Files .isSymbolicLink (parent )) {
144131 result .add (parent );
145- Path target = parent .resolveSibling (Files .readSymbolicLink (parent ));
146- result .addAll (getRegistrationPaths (target .resolve (path .getFileName ())));
132+ collectRegistrationPaths (resolveSiblingSymbolicLink (parent ).resolve (path .getFileName ()), result );
147133 }
148134 else if (Files .isSymbolicLink (path )) {
149- Path target = path .resolveSibling (Files .readSymbolicLink (path ));
150- result .addAll (getRegistrationPaths (target ));
135+ collectRegistrationPaths (resolveSiblingSymbolicLink (path ), result );
136+ }
137+ }
138+
139+ private Path resolveSiblingSymbolicLink (Path path ) throws IOException {
140+ return path .resolveSibling (Files .readSymbolicLink (path ));
141+ }
142+
143+ @ Override
144+ public void close () throws IOException {
145+ synchronized (this .lock ) {
146+ if (this .thread != null ) {
147+ this .thread .close ();
148+ this .thread .interrupt ();
149+ try {
150+ this .thread .join ();
151+ }
152+ catch (InterruptedException ex ) {
153+ Thread .currentThread ().interrupt ();
154+ }
155+ this .thread = null ;
156+ }
151157 }
152- return result ;
153158 }
154159
155160 /**
@@ -254,6 +259,9 @@ public void close() throws IOException {
254259
255260 /**
256261 * An individual watch registration.
262+ *
263+ * @param paths the paths being registered
264+ * @param action the action to take
257265 */
258266 private record Registration (Set <Path > paths , Runnable action ) {
259267
@@ -265,6 +273,7 @@ boolean manages(Path file) {
265273 private boolean isInDirectories (Path file ) {
266274 return this .paths .stream ().filter (Files ::isDirectory ).anyMatch (file ::startsWith );
267275 }
276+
268277 }
269278
270279}
0 commit comments