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 private File checkOriginFile() {
146 return checkOrigin().getFile();
147 }
148
149
150
151
152
153
154
155 @Override
156 public FileAlterationObserver get() throws IOException {
157 return new FileAlterationObserver(this);
158 }
159
160
161
162
163
164
165
166 public Builder setFileFilter(final FileFilter fileFilter) {
167 this.fileFilter = fileFilter;
168 return asThis();
169 }
170
171
172
173
174
175
176
177 public Builder setIOCase(final IOCase ioCase) {
178 this.ioCase = ioCase;
179 return asThis();
180 }
181
182
183
184
185
186
187
188 public Builder setRootEntry(final FileEntry rootEntry) {
189 this.rootEntry = rootEntry;
190 return asThis();
191 }
192
193 }
194
195 private static final long serialVersionUID = 1185122225658782848L;
196
197
198
199
200
201
202
203 public static Builder builder() {
204 return new Builder();
205 }
206
207 private static Comparator<File> toComparator(final IOCase ioCase) {
208 switch (IOCase.value(ioCase, IOCase.SYSTEM)) {
209 case SYSTEM:
210 return NameFileComparator.NAME_SYSTEM_COMPARATOR;
211 case INSENSITIVE:
212 return NameFileComparator.NAME_INSENSITIVE_COMPARATOR;
213 default:
214 return NameFileComparator.NAME_COMPARATOR;
215 }
216 }
217
218
219
220
221 private final transient List<FileAlterationListener> listeners = new CopyOnWriteArrayList<>();
222
223
224
225
226 private final FileEntry rootEntry;
227
228
229
230
231 private final transient FileFilter fileFilter;
232
233
234
235
236 private final Comparator<File> comparator;
237
238 private FileAlterationObserver(final Builder builder) {
239 this(builder.rootEntry != null ? builder.rootEntry : new FileEntry(builder.checkOriginFile()), builder.fileFilter, toComparator(builder.ioCase));
240 }
241
242
243
244
245
246
247
248 @Deprecated
249 public FileAlterationObserver(final File directory) {
250 this(directory, null);
251 }
252
253
254
255
256
257
258
259
260 @Deprecated
261 public FileAlterationObserver(final File directory, final FileFilter fileFilter) {
262 this(directory, fileFilter, null);
263 }
264
265
266
267
268
269
270
271
272
273 @Deprecated
274 public FileAlterationObserver(final File directory, final FileFilter fileFilter, final IOCase ioCase) {
275 this(new FileEntry(directory), fileFilter, ioCase);
276 }
277
278
279
280
281
282
283
284
285 private FileAlterationObserver(final FileEntry rootEntry, final FileFilter fileFilter, final Comparator<File> comparator) {
286 Objects.requireNonNull(rootEntry, "rootEntry");
287 Objects.requireNonNull(rootEntry.getFile(), "rootEntry.getFile()");
288 this.rootEntry = rootEntry;
289 this.fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.INSTANCE;
290 this.comparator = Objects.requireNonNull(comparator, "comparator");
291 }
292
293
294
295
296
297
298
299
300 protected FileAlterationObserver(final FileEntry rootEntry, final FileFilter fileFilter, final IOCase ioCase) {
301 this(rootEntry, fileFilter, toComparator(ioCase));
302 }
303
304
305
306
307
308
309
310 @Deprecated
311 public FileAlterationObserver(final String directoryName) {
312 this(new File(directoryName));
313 }
314
315
316
317
318
319
320
321
322 @Deprecated
323 public FileAlterationObserver(final String directoryName, final FileFilter fileFilter) {
324 this(new File(directoryName), fileFilter);
325 }
326
327
328
329
330
331
332
333
334
335 @Deprecated
336 public FileAlterationObserver(final String directoryName, final FileFilter fileFilter, final IOCase ioCase) {
337 this(new File(directoryName), fileFilter, ioCase);
338 }
339
340
341
342
343
344
345 public void addListener(final FileAlterationListener listener) {
346 if (listener != null) {
347 listeners.add(listener);
348 }
349 }
350
351
352
353
354
355
356
357
358 private void checkAndFire(final FileEntry parentEntry, final FileEntry[] previousEntries, final File[] currentEntries) {
359 int c = 0;
360 final FileEntry[] actualEntries = currentEntries.length > 0 ? new FileEntry[currentEntries.length] : FileEntry.EMPTY_FILE_ENTRY_ARRAY;
361 for (final FileEntry previousEntry : previousEntries) {
362 while (c < currentEntries.length && comparator.compare(previousEntry.getFile(), currentEntries[c]) > 0) {
363 actualEntries[c] = createFileEntry(parentEntry, currentEntries[c]);
364 fireOnCreate(actualEntries[c]);
365 c++;
366 }
367 if (c < currentEntries.length && comparator.compare(previousEntry.getFile(), currentEntries[c]) == 0) {
368 fireOnChange(previousEntry, currentEntries[c]);
369 checkAndFire(previousEntry, previousEntry.getChildren(), listFiles(currentEntries[c]));
370 actualEntries[c] = previousEntry;
371 c++;
372 } else {
373 checkAndFire(previousEntry, previousEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
374 fireOnDelete(previousEntry);
375 }
376 }
377 for (; c < currentEntries.length; c++) {
378 actualEntries[c] = createFileEntry(parentEntry, currentEntries[c]);
379 fireOnCreate(actualEntries[c]);
380 }
381 parentEntry.setChildren(actualEntries);
382 }
383
384
385
386
387 public void checkAndNotify() {
388
389
390 listeners.forEach(listener -> listener.onStart(this));
391
392
393 final File rootFile = rootEntry.getFile();
394 if (rootFile.exists()) {
395 checkAndFire(rootEntry, rootEntry.getChildren(), listFiles(rootFile));
396 } else if (rootEntry.isExists()) {
397 checkAndFire(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
398 }
399
400
401
402 listeners.forEach(listener -> listener.onStop(this));
403 }
404
405
406
407
408
409
410
411
412 private FileEntry createFileEntry(final FileEntry parent, final File file) {
413 final FileEntry entry = parent.newChildInstance(file);
414 entry.refresh(file);
415 entry.setChildren(listFileEntries(file, entry));
416 return entry;
417 }
418
419
420
421
422
423
424 @SuppressWarnings("unused")
425 public void destroy() throws Exception {
426
427 }
428
429
430
431
432
433
434
435 private void fireOnChange(final FileEntry entry, final File file) {
436 if (entry.refresh(file)) {
437 listeners.forEach(listener -> {
438 if (entry.isDirectory()) {
439 listener.onDirectoryChange(file);
440 } else {
441 listener.onFileChange(file);
442 }
443 });
444 }
445 }
446
447
448
449
450
451
452 private void fireOnCreate(final FileEntry entry) {
453 listeners.forEach(listener -> {
454 if (entry.isDirectory()) {
455 listener.onDirectoryCreate(entry.getFile());
456 } else {
457 listener.onFileCreate(entry.getFile());
458 }
459 });
460 Stream.of(entry.getChildren()).forEach(this::fireOnCreate);
461 }
462
463
464
465
466
467
468 private void fireOnDelete(final FileEntry entry) {
469 listeners.forEach(listener -> {
470 if (entry.isDirectory()) {
471 listener.onDirectoryDelete(entry.getFile());
472 } else {
473 listener.onFileDelete(entry.getFile());
474 }
475 });
476 }
477
478 Comparator<File> getComparator() {
479 return comparator;
480 }
481
482
483
484
485
486
487 public File getDirectory() {
488 return rootEntry.getFile();
489 }
490
491
492
493
494
495
496
497 public FileFilter getFileFilter() {
498 return fileFilter;
499 }
500
501
502
503
504
505
506 public Iterable<FileAlterationListener> getListeners() {
507 return new ArrayList<>(listeners);
508 }
509
510
511
512
513
514
515 @SuppressWarnings("unused")
516 public void initialize() throws Exception {
517 rootEntry.refresh(rootEntry.getFile());
518 rootEntry.setChildren(listFileEntries(rootEntry.getFile(), rootEntry));
519 }
520
521
522
523
524
525
526
527
528 private FileEntry[] listFileEntries(final File file, final FileEntry entry) {
529 return Stream.of(listFiles(file)).map(f -> createFileEntry(entry, f)).toArray(FileEntry[]::new);
530 }
531
532
533
534
535
536
537
538 private File[] listFiles(final File directory) {
539 return directory.isDirectory() ? sort(directory.listFiles(fileFilter)) : FileUtils.EMPTY_FILE_ARRAY;
540 }
541
542
543
544
545
546
547 public void removeListener(final FileAlterationListener listener) {
548 if (listener != null) {
549 listeners.removeIf(listener::equals);
550 }
551 }
552
553 private File[] sort(final File[] files) {
554 if (files == null) {
555 return FileUtils.EMPTY_FILE_ARRAY;
556 }
557 if (files.length > 1) {
558 Arrays.sort(files, comparator);
559 }
560 return files;
561 }
562
563
564
565
566
567
568 @Override
569 public String toString() {
570 final StringBuilder builder = new StringBuilder();
571 builder.append(getClass().getSimpleName());
572 builder.append("[file='");
573 builder.append(getDirectory().getPath());
574 builder.append('\'');
575 builder.append(", ");
576 builder.append(fileFilter.toString());
577 builder.append(", listeners=");
578 builder.append(listeners.size());
579 builder.append("]");
580 return builder.toString();
581 }
582
583 }