Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
PojavLauncherTeam
GitHub Repository: PojavLauncherTeam/mobile
Path: blob/master/src/java.base/share/classes/sun/nio/fs/PollingWatchService.java
41159 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
// map of registrations
65
private final Map<Object, PollingWatchKey> map = new HashMap<>();
66
67
// used to execute the periodic tasks that poll for changes
68
private final ScheduledExecutorService scheduledExecutor;
69
70
PollingWatchService() {
71
// TBD: Make the number of threads configurable
72
scheduledExecutor = Executors
73
.newSingleThreadScheduledExecutor(new ThreadFactory() {
74
@Override
75
public Thread newThread(Runnable r) {
76
Thread t = new Thread(null, r, "FileSystemWatcher", 0, false);
77
t.setDaemon(true);
78
return t;
79
}});
80
}
81
82
/**
83
* Register the given file with this watch service
84
*/
85
@SuppressWarnings("removal")
86
@Override
87
WatchKey register(final Path path,
88
WatchEvent.Kind<?>[] events,
89
WatchEvent.Modifier... modifiers)
90
throws IOException
91
{
92
// check events - CCE will be thrown if there are invalid elements
93
final Set<WatchEvent.Kind<?>> eventSet = new HashSet<>(events.length);
94
for (WatchEvent.Kind<?> event: events) {
95
// standard events
96
if (event == StandardWatchEventKinds.ENTRY_CREATE ||
97
event == StandardWatchEventKinds.ENTRY_MODIFY ||
98
event == StandardWatchEventKinds.ENTRY_DELETE)
99
{
100
eventSet.add(event);
101
continue;
102
}
103
104
// OVERFLOW is ignored
105
if (event == StandardWatchEventKinds.OVERFLOW) {
106
continue;
107
}
108
109
// null/unsupported
110
if (event == null)
111
throw new NullPointerException("An element in event set is 'null'");
112
throw new UnsupportedOperationException(event.name());
113
}
114
if (eventSet.isEmpty())
115
throw new IllegalArgumentException("No events to register");
116
117
// Extended modifiers may be used to specify the sensitivity level
118
int sensitivity = 10;
119
if (modifiers.length > 0) {
120
for (WatchEvent.Modifier modifier: modifiers) {
121
if (modifier == null)
122
throw new NullPointerException();
123
124
if (ExtendedOptions.SENSITIVITY_HIGH.matches(modifier)) {
125
sensitivity = ExtendedOptions.SENSITIVITY_HIGH.parameter();
126
} else if (ExtendedOptions.SENSITIVITY_MEDIUM.matches(modifier)) {
127
sensitivity = ExtendedOptions.SENSITIVITY_MEDIUM.parameter();
128
} else if (ExtendedOptions.SENSITIVITY_LOW.matches(modifier)) {
129
sensitivity = ExtendedOptions.SENSITIVITY_LOW.parameter();
130
} else {
131
throw new UnsupportedOperationException("Modifier not supported");
132
}
133
}
134
}
135
136
// check if watch service is closed
137
if (!isOpen())
138
throw new ClosedWatchServiceException();
139
140
// registration is done in privileged block as it requires the
141
// attributes of the entries in the directory.
142
try {
143
int value = sensitivity;
144
return AccessController.doPrivileged(
145
new PrivilegedExceptionAction<PollingWatchKey>() {
146
@Override
147
public PollingWatchKey run() throws IOException {
148
return doPrivilegedRegister(path, eventSet, value);
149
}
150
});
151
} catch (PrivilegedActionException pae) {
152
Throwable cause = pae.getCause();
153
if (cause instanceof IOException ioe)
154
throw ioe;
155
throw new AssertionError(pae);
156
}
157
}
158
159
// registers directory returning a new key if not already registered or
160
// existing key if already registered
161
private PollingWatchKey doPrivilegedRegister(Path path,
162
Set<? extends WatchEvent.Kind<?>> events,
163
int sensitivityInSeconds)
164
throws IOException
165
{
166
// check file is a directory and get its file key if possible
167
BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
168
if (!attrs.isDirectory()) {
169
throw new NotDirectoryException(path.toString());
170
}
171
Object fileKey = attrs.fileKey();
172
if (fileKey == null)
173
throw new AssertionError("File keys must be supported");
174
175
// grab close lock to ensure that watch service cannot be closed
176
synchronized (closeLock()) {
177
if (!isOpen())
178
throw new ClosedWatchServiceException();
179
180
PollingWatchKey watchKey;
181
synchronized (map) {
182
watchKey = map.get(fileKey);
183
if (watchKey == null) {
184
// new registration
185
watchKey = new PollingWatchKey(path, this, fileKey);
186
map.put(fileKey, watchKey);
187
} else {
188
// update to existing registration
189
watchKey.disable();
190
}
191
}
192
watchKey.enable(events, sensitivityInSeconds);
193
return watchKey;
194
}
195
196
}
197
198
@SuppressWarnings("removal")
199
@Override
200
void implClose() throws IOException {
201
synchronized (map) {
202
for (Map.Entry<Object, PollingWatchKey> entry: map.entrySet()) {
203
PollingWatchKey watchKey = entry.getValue();
204
watchKey.disable();
205
watchKey.invalidate();
206
}
207
map.clear();
208
}
209
AccessController.doPrivileged(new PrivilegedAction<Void>() {
210
@Override
211
public Void run() {
212
scheduledExecutor.shutdown();
213
return null;
214
}
215
});
216
}
217
218
/**
219
* Entry in directory cache to record file last-modified-time and tick-count
220
*/
221
private static class CacheEntry {
222
private long lastModified;
223
private int lastTickCount;
224
225
CacheEntry(long lastModified, int lastTickCount) {
226
this.lastModified = lastModified;
227
this.lastTickCount = lastTickCount;
228
}
229
230
int lastTickCount() {
231
return lastTickCount;
232
}
233
234
long lastModified() {
235
return lastModified;
236
}
237
238
void update(long lastModified, int tickCount) {
239
this.lastModified = lastModified;
240
this.lastTickCount = tickCount;
241
}
242
}
243
244
/**
245
* WatchKey implementation that encapsulates a map of the entries of the
246
* entries in the directory. Polling the key causes it to re-scan the
247
* directory and queue keys when entries are added, modified, or deleted.
248
*/
249
private class PollingWatchKey extends AbstractWatchKey {
250
private final Object fileKey;
251
252
// current event set
253
private Set<? extends WatchEvent.Kind<?>> events;
254
255
// the result of the periodic task that causes this key to be polled
256
private ScheduledFuture<?> poller;
257
258
// indicates if the key is valid
259
private volatile boolean valid;
260
261
// used to detect files that have been deleted
262
private int tickCount;
263
264
// map of entries in directory
265
private Map<Path,CacheEntry> entries;
266
267
PollingWatchKey(Path dir, PollingWatchService watcher, Object fileKey)
268
throws IOException
269
{
270
super(dir, watcher);
271
this.fileKey = fileKey;
272
this.valid = true;
273
this.tickCount = 0;
274
this.entries = new HashMap<Path,CacheEntry>();
275
276
// get the initial entries in the directory
277
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
278
for (Path entry: stream) {
279
// don't follow links
280
long lastModified =
281
Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();
282
entries.put(entry.getFileName(), new CacheEntry(lastModified, tickCount));
283
}
284
} catch (DirectoryIteratorException e) {
285
throw e.getCause();
286
}
287
}
288
289
Object fileKey() {
290
return fileKey;
291
}
292
293
@Override
294
public boolean isValid() {
295
return valid;
296
}
297
298
void invalidate() {
299
valid = false;
300
}
301
302
// enables periodic polling
303
void enable(Set<? extends WatchEvent.Kind<?>> events, long period) {
304
synchronized (this) {
305
// update the events
306
this.events = events;
307
308
// create the periodic task
309
Runnable thunk = new Runnable() { public void run() { poll(); }};
310
this.poller = scheduledExecutor
311
.scheduleAtFixedRate(thunk, period, period, TimeUnit.SECONDS);
312
}
313
}
314
315
// disables periodic polling
316
void disable() {
317
synchronized (this) {
318
if (poller != null)
319
poller.cancel(false);
320
}
321
}
322
323
@Override
324
public void cancel() {
325
valid = false;
326
synchronized (map) {
327
map.remove(fileKey());
328
}
329
disable();
330
}
331
332
/**
333
* Polls the directory to detect for new files, modified files, or
334
* deleted files.
335
*/
336
synchronized void poll() {
337
if (!valid) {
338
return;
339
}
340
341
// update tick
342
tickCount++;
343
344
// open directory
345
DirectoryStream<Path> stream = null;
346
try {
347
stream = Files.newDirectoryStream(watchable());
348
} catch (IOException x) {
349
// directory is no longer accessible so cancel key
350
cancel();
351
signal();
352
return;
353
}
354
355
// iterate over all entries in directory
356
try {
357
for (Path entry: stream) {
358
long lastModified = 0L;
359
try {
360
lastModified =
361
Files.getLastModifiedTime(entry, LinkOption.NOFOLLOW_LINKS).toMillis();
362
} catch (IOException x) {
363
// unable to get attributes of entry. If file has just
364
// been deleted then we'll report it as deleted on the
365
// next poll
366
continue;
367
}
368
369
// lookup cache
370
CacheEntry e = entries.get(entry.getFileName());
371
if (e == null) {
372
// new file found
373
entries.put(entry.getFileName(),
374
new CacheEntry(lastModified, tickCount));
375
376
// queue ENTRY_CREATE if event enabled
377
if (events.contains(StandardWatchEventKinds.ENTRY_CREATE)) {
378
signalEvent(StandardWatchEventKinds.ENTRY_CREATE, entry.getFileName());
379
continue;
380
} else {
381
// if ENTRY_CREATE is not enabled and ENTRY_MODIFY is
382
// enabled then queue event to avoid missing out on
383
// modifications to the file immediately after it is
384
// created.
385
if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {
386
signalEvent(StandardWatchEventKinds.ENTRY_MODIFY, entry.getFileName());
387
}
388
}
389
continue;
390
}
391
392
// check if file has changed
393
if (e.lastModified != lastModified) {
394
if (events.contains(StandardWatchEventKinds.ENTRY_MODIFY)) {
395
signalEvent(StandardWatchEventKinds.ENTRY_MODIFY,
396
entry.getFileName());
397
}
398
}
399
// entry in cache so update poll time
400
e.update(lastModified, tickCount);
401
402
}
403
} catch (DirectoryIteratorException e) {
404
// ignore for now; if the directory is no longer accessible
405
// then the key will be cancelled on the next poll
406
} finally {
407
408
// close directory stream
409
try {
410
stream.close();
411
} catch (IOException x) {
412
// ignore
413
}
414
}
415
416
// iterate over cache to detect entries that have been deleted
417
Iterator<Map.Entry<Path,CacheEntry>> i = entries.entrySet().iterator();
418
while (i.hasNext()) {
419
Map.Entry<Path,CacheEntry> mapEntry = i.next();
420
CacheEntry entry = mapEntry.getValue();
421
if (entry.lastTickCount() != tickCount) {
422
Path name = mapEntry.getKey();
423
// remove from map and queue delete event (if enabled)
424
i.remove();
425
if (events.contains(StandardWatchEventKinds.ENTRY_DELETE)) {
426
signalEvent(StandardWatchEventKinds.ENTRY_DELETE, name);
427
}
428
}
429
}
430
}
431
}
432
}
433
434