1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.io.monitor;
18
19 import java.io.File;
20 import java.io.FileFilter;
21 import java.io.IOException;
22 import java.io.Serializable;
23 import java.util.ArrayList;
24 import java.util.Arrays;
25 import java.util.Comparator;
26 import java.util.List;
27 import java.util.Objects;
28 import java.util.concurrent.CopyOnWriteArrayList;
29 import java.util.stream.Stream;
30
31 import org.apache.commons.io.FileUtils;
32 import org.apache.commons.io.IOCase;
33 import org.apache.commons.io.build.AbstractOrigin;
34 import org.apache.commons.io.build.AbstractOriginSupplier;
35 import org.apache.commons.io.comparator.NameFileComparator;
36 import org.apache.commons.io.filefilter.TrueFileFilter;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128 public class FileAlterationObserver implements Serializable {
129
130
131
132
133
134
135 public static final class Builder extends AbstractOriginSupplier<FileAlterationObserver, Builder> {
136
137 private FileEntry rootEntry;
138 private FileFilter fileFilter;
139 private IOCase ioCase;
140
141 private Builder() {
142
143 }
144
145
146
147
148
149
150
151 @Override
152 public FileAlterationObserver get() throws IOException {
153 return new FileAlterationObserver(rootEntry != null ? rootEntry : new FileEntry(checkOrigin().getFile()), fileFilter, toComparator(ioCase));
154 }
155
156
157
158
159
160
161
162 public Builder setFileFilter(final FileFilter fileFilter) {
163 this.fileFilter = fileFilter;
164 return asThis();
165 }
166
167
168
169
170
171
172
173 public Builder setIOCase(final IOCase ioCase) {
174 this.ioCase = ioCase;
175 return asThis();
176 }
177
178
179
180
181
182
183
184 public Builder setRootEntry(final FileEntry rootEntry) {
185 this.rootEntry = rootEntry;
186 return asThis();
187 }
188
189 }
190
191 private static final long serialVersionUID = 1185122225658782848L;
192
193
194
195
196
197
198
199 public static Builder builder() {
200 return new Builder();
201 }
202
203 private static Comparator<File> toComparator(final IOCase ioCase) {
204 switch (IOCase.value(ioCase, IOCase.SYSTEM)) {
205 case SYSTEM:
206 return NameFileComparator.NAME_SYSTEM_COMPARATOR;
207 case INSENSITIVE:
208 return NameFileComparator.NAME_INSENSITIVE_COMPARATOR;
209 default:
210 return NameFileComparator.NAME_COMPARATOR;
211 }
212 }
213
214
215
216
217 private final transient List<FileAlterationListener> listeners = new CopyOnWriteArrayList<>();
218
219
220
221
222 private final FileEntry rootEntry;
223
224
225
226
227 private final transient FileFilter fileFilter;
228
229
230
231
232 private final Comparator<File> comparator;
233
234
235
236
237
238
239
240 @Deprecated
241 public FileAlterationObserver(final File directory) {
242 this(directory, null);
243 }
244
245
246
247
248
249
250
251
252 @Deprecated
253 public FileAlterationObserver(final File directory, final FileFilter fileFilter) {
254 this(directory, fileFilter, null);
255 }
256
257
258
259
260
261
262
263
264
265 @Deprecated
266 public FileAlterationObserver(final File directory, final FileFilter fileFilter, final IOCase ioCase) {
267 this(new FileEntry(directory), fileFilter, ioCase);
268 }
269
270
271
272
273
274
275
276
277 private FileAlterationObserver(final FileEntry rootEntry, final FileFilter fileFilter, final Comparator<File> comparator) {
278 Objects.requireNonNull(rootEntry, "rootEntry");
279 Objects.requireNonNull(rootEntry.getFile(), "rootEntry.getFile()");
280 this.rootEntry = rootEntry;
281 this.fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.INSTANCE;
282 this.comparator = Objects.requireNonNull(comparator, "comparator");
283 }
284
285
286
287
288
289
290
291
292 protected FileAlterationObserver(final FileEntry rootEntry, final FileFilter fileFilter, final IOCase ioCase) {
293 this(rootEntry, fileFilter, toComparator(ioCase));
294 }
295
296
297
298
299
300
301
302 @Deprecated
303 public FileAlterationObserver(final String directoryName) {
304 this(new File(directoryName));
305 }
306
307
308
309
310
311
312
313
314 @Deprecated
315 public FileAlterationObserver(final String directoryName, final FileFilter fileFilter) {
316 this(new File(directoryName), fileFilter);
317 }
318
319
320
321
322
323
324
325
326
327 @Deprecated
328 public FileAlterationObserver(final String directoryName, final FileFilter fileFilter, final IOCase ioCase) {
329 this(new File(directoryName), fileFilter, ioCase);
330 }
331
332
333
334
335
336
337 public void addListener(final FileAlterationListener listener) {
338 if (listener != null) {
339 listeners.add(listener);
340 }
341 }
342
343
344
345
346
347
348
349
350 private void checkAndFire(final FileEntry parentEntry, final FileEntry[] previousEntries, final File[] currentEntries) {
351 int c = 0;
352 final FileEntry[] actualEntries = currentEntries.length > 0 ? new FileEntry[currentEntries.length] : FileEntry.EMPTY_FILE_ENTRY_ARRAY;
353 for (final FileEntry previousEntry : previousEntries) {
354 while (c < currentEntries.length && comparator.compare(previousEntry.getFile(), currentEntries[c]) > 0) {
355 actualEntries[c] = createFileEntry(parentEntry, currentEntries[c]);
356 fireOnCreate(actualEntries[c]);
357 c++;
358 }
359 if (c < currentEntries.length && comparator.compare(previousEntry.getFile(), currentEntries[c]) == 0) {
360 fireOnChange(previousEntry, currentEntries[c]);
361 checkAndFire(previousEntry, previousEntry.getChildren(), listFiles(currentEntries[c]));
362 actualEntries[c] = previousEntry;
363 c++;
364 } else {
365 checkAndFire(previousEntry, previousEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
366 fireOnDelete(previousEntry);
367 }
368 }
369 for (; c < currentEntries.length; c++) {
370 actualEntries[c] = createFileEntry(parentEntry, currentEntries[c]);
371 fireOnCreate(actualEntries[c]);
372 }
373 parentEntry.setChildren(actualEntries);
374 }
375
376
377
378
379 public void checkAndNotify() {
380
381
382 listeners.forEach(listener -> listener.onStart(this));
383
384
385 final File rootFile = rootEntry.getFile();
386 if (rootFile.exists()) {
387 checkAndFire(rootEntry, rootEntry.getChildren(), listFiles(rootFile));
388 } else if (rootEntry.isExists()) {
389 checkAndFire(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
390 }
391
392
393
394 listeners.forEach(listener -> listener.onStop(this));
395 }
396
397
398
399
400
401
402
403
404 private FileEntry createFileEntry(final FileEntry parent, final File file) {
405 final FileEntry entry = parent.newChildInstance(file);
406 entry.refresh(file);
407 entry.setChildren(listFileEntries(file, entry));
408 return entry;
409 }
410
411
412
413
414
415
416 @SuppressWarnings("unused")
417 public void destroy() throws Exception {
418
419 }
420
421
422
423
424
425
426
427 private void fireOnChange(final FileEntry entry, final File file) {
428 if (entry.refresh(file)) {
429 listeners.forEach(listener -> {
430 if (entry.isDirectory()) {
431 listener.onDirectoryChange(file);
432 } else {
433 listener.onFileChange(file);
434 }
435 });
436 }
437 }
438
439
440
441
442
443
444 private void fireOnCreate(final FileEntry entry) {
445 listeners.forEach(listener -> {
446 if (entry.isDirectory()) {
447 listener.onDirectoryCreate(entry.getFile());
448 } else {
449 listener.onFileCreate(entry.getFile());
450 }
451 });
452 Stream.of(entry.getChildren()).forEach(this::fireOnCreate);
453 }
454
455
456
457
458
459
460 private void fireOnDelete(final FileEntry entry) {
461 listeners.forEach(listener -> {
462 if (entry.isDirectory()) {
463 listener.onDirectoryDelete(entry.getFile());
464 } else {
465 listener.onFileDelete(entry.getFile());
466 }
467 });
468 }
469
470 Comparator<File> getComparator() {
471 return comparator;
472 }
473
474
475
476
477
478
479 public File getDirectory() {
480 return rootEntry.getFile();
481 }
482
483
484
485
486
487
488
489 public FileFilter getFileFilter() {
490 return fileFilter;
491 }
492
493
494
495
496
497
498 public Iterable<FileAlterationListener> getListeners() {
499 return new ArrayList<>(listeners);
500 }
501
502
503
504
505
506
507 @SuppressWarnings("unused")
508 public void initialize() throws Exception {
509 rootEntry.refresh(rootEntry.getFile());
510 rootEntry.setChildren(listFileEntries(rootEntry.getFile(), rootEntry));
511 }
512
513
514
515
516
517
518
519
520 private FileEntry[] listFileEntries(final File file, final FileEntry entry) {
521 return Stream.of(listFiles(file)).map(f -> createFileEntry(entry, f)).toArray(FileEntry[]::new);
522 }
523
524
525
526
527
528
529
530 private File[] listFiles(final File directory) {
531 return directory.isDirectory() ? sort(directory.listFiles(fileFilter)) : FileUtils.EMPTY_FILE_ARRAY;
532 }
533
534
535
536
537
538
539 public void removeListener(final FileAlterationListener listener) {
540 if (listener != null) {
541 listeners.removeIf(listener::equals);
542 }
543 }
544
545 private File[] sort(final File[] files) {
546 if (files == null) {
547 return FileUtils.EMPTY_FILE_ARRAY;
548 }
549 if (files.length > 1) {
550 Arrays.sort(files, comparator);
551 }
552 return files;
553 }
554
555
556
557
558
559
560 @Override
561 public String toString() {
562 final StringBuilder builder = new StringBuilder();
563 builder.append(getClass().getSimpleName());
564 builder.append("[file='");
565 builder.append(getDirectory().getPath());
566 builder.append('\'');
567 builder.append(", ");
568 builder.append(fileFilter.toString());
569 builder.append(", listeners=");
570 builder.append(listeners.size());
571 builder.append("]");
572 return builder.toString();
573 }
574
575 }