Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/jdk17u
Path: blob/master/src/java.base/share/classes/sun/nio/fs/PollingWatchService.java
67773 views
1
/*
2
* Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved.
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
*
5
* This code is free software; you can redistribute it and/or modify it
6
* under the terms of the GNU General Public License version 2 only, as
7
* published by the Free Software Foundation. Oracle designates this
8
* particular file as subject to the "Classpath" exception as provided
9
* by Oracle in the LICENSE file that accompanied this code.
10
*
11
* This code is distributed in the hope that it will be useful, but WITHOUT
12
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14
* version 2 for more details (a copy is included in the LICENSE file that
15
* accompanied this code).
16
*
17
* You should have received a copy of the GNU General Public License version
18
* 2 along with this work; if not, write to the Free Software Foundation,
19
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20
*
21
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22
* or visit www.oracle.com if you need additional information or have any
23
* questions.
24
*/
25
26
package sun.nio.fs;
27
28
import java.nio.file.ClosedWatchServiceException;
29
import java.nio.file.DirectoryIteratorException;
30
import java.nio.file.DirectoryStream;
31
import java.nio.file.Files;
32
import java.nio.file.LinkOption;
33
import java.nio.file.NotDirectoryException;
34
import java.nio.file.Path;
35
import java.nio.file.StandardWatchEventKinds;
36
import java.nio.file.WatchEvent;
37
import java.nio.file.WatchKey;
38
import java.nio.file.attribute.BasicFileAttributes;
39
import java.security.AccessController;
40
import java.security.PrivilegedAction;
41
import java.security.PrivilegedExceptionAction;
42
import java.security.PrivilegedActionException;
43
import java.io.IOException;
44
import java.util.HashMap;
45
import java.util.HashSet;
46
import java.util.Iterator;
47
import java.util.Map;
48
import java.util.Set;
49
import java.util.concurrent.Executors;
50
import java.util.concurrent.ScheduledExecutorService;
51
import java.util.concurrent.ScheduledFuture;
52
import java.util.concurrent.ThreadFactory;
53
import java.util.concurrent.TimeUnit;
54
55
/**
56
* Simple WatchService implementation that uses periodic tasks to poll
57
* registered directories for changes. This implementation is for use on
58
* operating systems that do not have native file change notification support.
59
*/
60
61
class PollingWatchService
62
extends AbstractWatchService
63
{
64
// Wait between polling thread creation and first poll (seconds)
65
private static final int POLLING_INIT_DELAY = 1;
66
// Default time between polls (seconds)
67
private static final int DEFAULT_POLLING_INTERVAL = 2;
68
69
// map of registrations
70
private final Map<Object, PollingWatchKey> map = new HashMap<>();
71
72
// used to execute the periodic tasks that poll for changes
73
private final ScheduledExecutorService scheduledExecutor;
74
75
PollingWatchService() {
76
// TBD: Make the number of threads configurable
77
scheduledExecutor = Executors
78
.newSingleThreadScheduledExecutor(new ThreadFactory() {
79
@Override
80
public Thread newThread(Runnable r) {
81
Thread t = new Thread(null, r, "FileSystemWatcher", 0, false);
82
t.setDaemon(true);
83
return t;
84
}});
85
}
86
87
/**
88
* Register the given file with this watch service
89
*/
90
@SuppressWarnings("removal")
91
@Override
92
WatchKey register(final Path path,
93
WatchEvent.Kind<?>[] events,
94
WatchEvent.Modifier... modifiers)
95
throws IOException
96
{
97
// check events - CCE will be thrown if there are invalid elements
98
final Set<WatchEvent.Kind<?>> eventSet = new HashSet<>(events.length);
99
for (WatchEvent.Kind<?> event: events) {
100
// standard events
101
if (event == StandardWatchEventKinds.ENTRY_CREATE ||
102
event == StandardWatchEventKinds.ENTRY_MODIFY ||
103
event == StandardWatchEventKinds.ENTRY_DELETE)
104
{
105
eventSet.add(event);
106
continue;
107
}
108
109
// OVERFLOW is ignored
110
if (event == StandardWatchEventKinds.OVERFLOW) {
111
continue;
112
}
113
114
// null/unsupported
115
if (event == null)
116
throw new NullPointerException("An element in event set is 'null'");
117
throw new UnsupportedOperationException(event.name());
118
}
119
if (eventSet.isEmpty())
120
throw new IllegalArgumentException("No events to register");
121
122
// Extended modifiers may be used to specify the sensitivity level
123
int sensitivity = DEFAULT_POLLING_INTERVAL;
124
if (modifiers.length > 0) {
125
for (WatchEvent.Modifier modifier: modifiers) {
126
if (modifier == null)
127
throw new NullPointerException();
128
129
if (ExtendedOptions.SENSITIVITY_HIGH.matches(modifier)) {
130
sensitivity = ExtendedOptions.SENSITIVITY_HIGH.parameter();
131
} else if (ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier)) {
132
sensitivity = ExtendedOptions.SENSITIVITY_MEDIUM.parameter();
133
} else if (ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) {
134
sensitivity = ExtendedOptions.SENSITIVITY_LOW.parameter();
135
} else {
136
throw new UnsupportedOperationException("Modifier not supported");
137
}
138
}
139
}
140
141
// check if watch service is closed
142
if (!isOpen())
143
throw new ClosedWatchServiceException();
144
145
// registration is done in privileged block as it requires the
146
// attributes of the entries in the directory.
147
try {
148
int value = sensitivity;
149
return AccessController.doPrivileged(
150
new PrivilegedExceptionAction<PollingWatchKey>() {
151
@Override
152
public PollingWatchKey run() throws IOException {
153
return doPrivilegedRegister(path, eventSet, value);
154
}
155
});
156
} catch (PrivilegedActionException pae) {
157
Throwable cause = pae.getCause();
158
if (cause instanceof IOException ioe)
159
throw ioe;
160
throw new AssertionError(pae);
161
}
162
}
163
164
// registers directory returning a new key if not already registered or
165
// existing key if already registered
166
private PollingWatchKey doPrivilegedRegister(Path path,
167
Set<? extends WatchEvent.Kind<?>> events,
168
int sensitivityInSeconds)
169
throws IOException
170
{
171
// check file is a directory and get its file key if possible
172
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
173
if (!attrs.isDirectory()) {
174
throw new NotDirectoryException(path.toString());
175
}
176
Object fileKey = attrs.fileKey();
177
if (fileKey == null)
178
throw new AssertionError("File keys must be supported");
179
180
// grab close lock to ensure that watch service cannot be closed
181
synchronized (closeLock()) {
182
if (!isOpen())
183
throw new ClosedWatchServiceException();
184
185
PollingWatchKey watchKey;
186
synchronized (map) {
187
watchKey = map.get(fileKey);
188
if (watchKey == null) {
189
// new registration
190
watchKey = new PollingWatchKey(path, this, fileKey);
191
map.put(fileKey, watchKey);
192
} else {
193
// update to existing registration
194
watchKey.disable();
195
}
196
}
197
watchKey.enable(events, sensitivityInSeconds);
198
return watchKey;
199
}
200
201
}
202
203
@SuppressWarnings("removal")
204
@Override
205
void implClose() throws IOException {
206
synchronized (map) {
207
for (Map.Entry<Object, PollingWatchKey> entry: map.entrySet()) {
208
PollingWatchKey watchKey = entry.getValue();
209
watchKey.disable();
210
watchKey.invalidate();
211
}
212
map.clear();
213
}
214
AccessController.doPrivileged(new PrivilegedAction<Void>() {
215
@Override
216
public Void run() {
217
scheduledExecutor.shutdown();
218
return null;
219
}
220
});
221
}
222
223
/**
224
* Entry in directory cache to record file last-modified-time and tick-count
225
*/
226
private static class CacheEntry {
227
private long lastModified;
228
private int lastTickCount;
229
230
CacheEntry(long lastModified, int lastTickCount) {
231
this.lastModified = lastModified;
232
this.lastTickCount = lastTickCount;
233
}
234
235
int lastTickCount() {
236
return lastTickCount;
237
}
238
239
long lastModified() {
240
return lastModified;
241
}
242
243
void update(long lastModified, int tickCount) {
244
this.lastModified = lastModified;
245
this.lastTickCount = tickCount;
246
}
247
}
248
249
/**
250
* WatchKey implementation that encapsulates a map of the entries of the
251
* entries in the directory. Polling the key causes it to re-scan the
252
* directory and queue keys when entries are added, modified, or deleted.
253
*/
254
private class PollingWatchKey extends AbstractWatchKey {
255
256
private final Object fileKey;
257
258
// current event set
259
private Set<? extends WatchEvent.Kind<?>> events;
260
261
// the result of the periodic task that causes this key to be polled
262
private ScheduledFuture<?> poller;
263
264
// indicates if the key is valid
265
private volatile boolean valid;
266
267
// used to detect files that have been deleted
268
private int tickCount;
269
270
// map of entries in directory
271
private Map<Path,CacheEntry> entries;
272
273
PollingWatchKey(Path dir, PollingWatchService watcher, Object fileKey)
274
throws IOException
275
{
276
super(dir, watcher);
277
this.fileKey = fileKey;
278
this.valid = true;
279
this.tickCount = 0;
280
this.entries = new HashMap<Path,CacheEntry>();
281
282
// get the initial entries in the directory
283
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
284
for (Path entry: stream) {
285
// don't follow links
286
long lastModified =
287
Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();
288
entries.put(entry.getFileName(), new CacheEntry(lastModified, tickCount));
289
}
290
} catch (DirectoryIteratorException e) {
291
throw e.getCause();
292
}
293
}
294
295
Object fileKey() {
296
return fileKey;
297
}
298
299
@Override
300
public boolean isValid() {
301
return valid;
302
}
303
304
void invalidate() {
305
valid = false;
306
}
307
308
// enables periodic polling
309
void enable(Set<? extends WatchEvent.Kind<?>> events, long period) {
310
synchronized (this) {
311
// update the events
312
this.events = events;
313
314
// create the periodic task with initialDelay set to the specified constant
315
Runnable thunk = new Runnable() { public void run() { poll(); }};
316
this.poller = scheduledExecutor
317
.scheduleAtFixedRate(thunk, POLLING_INIT_DELAY, period, TimeUnit.SECONDS);
318
}
319
}
320
321
// disables periodic polling
322
void disable() {
323
synchronized (this) {
324
if (poller != null)
325
poller.cancel(false);
326
}
327
}
328
329
@Override
330
public void cancel() {
331
valid = false;
332
synchronized (map) {
333
map.remove(fileKey());
334
}
335
disable();
336
}
337
338
/**
339
* Polls the directory to detect for new files, modified files, or
340
* deleted files.
341
*/
342
synchronized void poll() {
343
if (!valid) {
344
return;
345
}
346
347
// update tick
348
tickCount++;
349
350
// open directory
351
DirectoryStream<Path> stream = null;
352
try {
353
stream = Files.newDirectoryStream(watchable());
354
} catch (IOException x) {
355
// directory is no longer accessible so cancel key
356
cancel();
357
signal();
358
return;
359
}
360
361
// iterate over all entries in directory
362
try {
363
for (Path entry: stream) {
364
long lastModified = 0L;
365
try {
366
lastModified =
367
Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();
368
} catch (IOException x) {
369
// unable to get attributes of entry. If file has just
370
// been deleted then we'll report it as deleted on the
371
// next poll
372
continue;
373
}
374
375
// lookup cache
376
CacheEntry e = entries.get(entry.getFileName());
377
if (e == null) {
378
// new file found
379
entries.put(entry.getFileName(),
380
new CacheEntry(lastModified, tickCount));
381
382
// queue ENTRY_CREATE if event enabled
383
if (events.contains(StandardWatchEventKinds.ENTRY_CREATE)) {
384
signalEvent(StandardWatchEventKinds.ENTRY_CREATE, entry.getFileName());
385
continue;
386
} else {
387
// if ENTRY_CREATE is not enabled and ENTRY_MODIFY is
388
// enabled then queue event to avoid missing out on
389
// modifications to the file immediately after it is
390
// created.
391
if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {
392
signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, entry.getFileName());
393
}
394
}
395
continue;
396
}
397
398
// check if file has changed
399
if (e.lastModified != lastModified) {
400
if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {
401
signalEvent(StandardWatchEventKinds.ENTRY_MODIFY,
402
entry.getFileName());
403
}
404
}
405
// entry in cache so update poll time
406
e.update(lastModified, tickCount);
407
408
}
409
} catch (DirectoryIteratorException e) {
410
// ignore for now; if the directory is no longer accessible
411
// then the key will be cancelled on the next poll
412
} finally {
413
414
// close directory stream
415
try {
416
stream.close();
417
} catch (IOException x) {
418
// ignore
419
}
420
}
421
422
// iterate over cache to detect entries that have been deleted
423
Iterator<Map.Entry<Path,CacheEntry>> i = entries.entrySet().iterator();
424
while (i.hasNext()) {
425
Map.Entry<Path,CacheEntry> mapEntry = i.next();
426
CacheEntry entry = mapEntry.getValue();
427
if (entry.lastTickCount() != tickCount) {
428
Path name = mapEntry.getKey();
429
// remove from map and queue delete event (if enabled)
430
i.remove();
431
if (events.contains(StandardWatchEventKinds.ENTRY_DELETE)) {
432
signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);
433
}
434
}
435
}
436
}
437
}
438
}
439
440