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.Serializable;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.Comparator;
25 import java.util.List;
26 import java.util.Objects;
27 import java.util.concurrent.CopyOnWriteArrayList;
28 import java.util.stream.Stream;
29
30 import org.apache.commons.io.FileUtils;
31 import org.apache.commons.io.IOCase;
32 import org.apache.commons.io.comparator.NameFileComparator;
33 import org.apache.commons.io.filefilter.TrueFileFilter;
34
35
36
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 public class FileAlterationObserver implements Serializable {
121
122 private static final long serialVersionUID = 1185122225658782848L;
123
124 private static Comparator<File> toComparator(final IOCase ioCase) {
125 switch (IOCase.value(ioCase, IOCase.SYSTEM)) {
126 case SYSTEM:
127 return NameFileComparator.NAME_SYSTEM_COMPARATOR;
128 case INSENSITIVE:
129 return NameFileComparator.NAME_INSENSITIVE_COMPARATOR;
130 default:
131 return NameFileComparator.NAME_COMPARATOR;
132 }
133 }
134
135
136
137
138 private transient final List<FileAlterationListener> listeners = new CopyOnWriteArrayList<>();
139
140
141
142
143 private final FileEntry rootEntry;
144
145
146
147
148 private transient final FileFilter fileFilter;
149
150
151
152
153 private final Comparator<File> comparator;
154
155
156
157
158
159
160 public FileAlterationObserver(final File directory) {
161 this(directory, null);
162 }
163
164
165
166
167
168
169
170 public FileAlterationObserver(final File directory, final FileFilter fileFilter) {
171 this(directory, fileFilter, null);
172 }
173
174
175
176
177
178
179
180
181 public FileAlterationObserver(final File directory, final FileFilter fileFilter, final IOCase ioCase) {
182 this(new FileEntry(directory), fileFilter, ioCase);
183 }
184
185
186
187
188
189
190
191
192 private FileAlterationObserver(final FileEntry rootEntry, final FileFilter fileFilter, final Comparator<File> comparator) {
193 Objects.requireNonNull(rootEntry, "rootEntry");
194 Objects.requireNonNull(rootEntry.getFile(), "rootEntry.getFile()");
195 this.rootEntry = rootEntry;
196 this.fileFilter = fileFilter != null ? fileFilter : TrueFileFilter.INSTANCE;
197 this.comparator = Objects.requireNonNull(comparator, "comparator");
198 }
199
200
201
202
203
204
205
206
207 protected FileAlterationObserver(final FileEntry rootEntry, final FileFilter fileFilter, final IOCase ioCase) {
208 this(rootEntry, fileFilter, toComparator(ioCase));
209 }
210
211
212
213
214
215
216 public FileAlterationObserver(final String directoryName) {
217 this(new File(directoryName));
218 }
219
220
221
222
223
224
225
226 public FileAlterationObserver(final String directoryName, final FileFilter fileFilter) {
227 this(new File(directoryName), fileFilter);
228 }
229
230
231
232
233
234
235
236
237 public FileAlterationObserver(final String directoryName, final FileFilter fileFilter, final IOCase ioCase) {
238 this(new File(directoryName), fileFilter, ioCase);
239 }
240
241
242
243
244
245
246 public void addListener(final FileAlterationListener listener) {
247 if (listener != null) {
248 listeners.add(listener);
249 }
250 }
251
252
253
254
255
256
257
258
259 private void checkAndFire(final FileEntry parentEntry, final FileEntry[] previousEntries, final File[] currentEntries) {
260 int c = 0;
261 final FileEntry[] actualEntries = currentEntries.length > 0 ? new FileEntry[currentEntries.length] : FileEntry.EMPTY_FILE_ENTRY_ARRAY;
262 for (final FileEntry previousEntry : previousEntries) {
263 while (c < currentEntries.length && comparator.compare(previousEntry.getFile(), currentEntries[c]) > 0) {
264 actualEntries[c] = createFileEntry(parentEntry, currentEntries[c]);
265 fireOnCreate(actualEntries[c]);
266 c++;
267 }
268 if (c < currentEntries.length && comparator.compare(previousEntry.getFile(), currentEntries[c]) == 0) {
269 fireOnChange(previousEntry, currentEntries[c]);
270 checkAndFire(previousEntry, previousEntry.getChildren(), listFiles(currentEntries[c]));
271 actualEntries[c] = previousEntry;
272 c++;
273 } else {
274 checkAndFire(previousEntry, previousEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
275 fireOnDelete(previousEntry);
276 }
277 }
278 for (; c < currentEntries.length; c++) {
279 actualEntries[c] = createFileEntry(parentEntry, currentEntries[c]);
280 fireOnCreate(actualEntries[c]);
281 }
282 parentEntry.setChildren(actualEntries);
283 }
284
285
286
287
288 public void checkAndNotify() {
289
290
291 listeners.forEach(listener -> listener.onStart(this));
292
293
294 final File rootFile = rootEntry.getFile();
295 if (rootFile.exists()) {
296 checkAndFire(rootEntry, rootEntry.getChildren(), listFiles(rootFile));
297 } else if (rootEntry.isExists()) {
298 checkAndFire(rootEntry, rootEntry.getChildren(), FileUtils.EMPTY_FILE_ARRAY);
299 }
300
301
302
303 listeners.forEach(listener -> listener.onStop(this));
304 }
305
306
307
308
309
310
311
312
313 private FileEntry createFileEntry(final FileEntry parent, final File file) {
314 final FileEntry entry = parent.newChildInstance(file);
315 entry.refresh(file);
316 entry.setChildren(listFileEntries(file, entry));
317 return entry;
318 }
319
320
321
322
323
324
325 @SuppressWarnings("unused")
326 public void destroy() throws Exception {
327
328 }
329
330
331
332
333
334
335
336 private void fireOnChange(final FileEntry entry, final File file) {
337 if (entry.refresh(file)) {
338 listeners.forEach(listener -> {
339 if (entry.isDirectory()) {
340 listener.onDirectoryChange(file);
341 } else {
342 listener.onFileChange(file);
343 }
344 });
345 }
346 }
347
348
349
350
351
352
353 private void fireOnCreate(final FileEntry entry) {
354 listeners.forEach(listener -> {
355 if (entry.isDirectory()) {
356 listener.onDirectoryCreate(entry.getFile());
357 } else {
358 listener.onFileCreate(entry.getFile());
359 }
360 });
361 Stream.of(entry.getChildren()).forEach(this::fireOnCreate);
362 }
363
364
365
366
367
368
369 private void fireOnDelete(final FileEntry entry) {
370 listeners.forEach(listener -> {
371 if (entry.isDirectory()) {
372 listener.onDirectoryDelete(entry.getFile());
373 } else {
374 listener.onFileDelete(entry.getFile());
375 }
376 });
377 }
378
379
380
381
382
383
384 public File getDirectory() {
385 return rootEntry.getFile();
386 }
387
388
389
390
391
392
393
394 public FileFilter getFileFilter() {
395 return fileFilter;
396 }
397
398
399
400
401
402
403 public Iterable<FileAlterationListener> getListeners() {
404 return new ArrayList<>(listeners);
405 }
406
407
408
409
410
411
412 @SuppressWarnings("unused")
413 public void initialize() throws Exception {
414 rootEntry.refresh(rootEntry.getFile());
415 rootEntry.setChildren(listFileEntries(rootEntry.getFile(), rootEntry));
416 }
417
418
419
420
421
422
423
424
425 private FileEntry[] listFileEntries(final File file, final FileEntry entry) {
426 return Stream.of(listFiles(file)).map(f -> createFileEntry(entry, f)).toArray(FileEntry[]::new);
427 }
428
429
430
431
432
433
434
435 private File[] listFiles(final File directory) {
436 return directory.isDirectory() ? sort(directory.listFiles(fileFilter)) : FileUtils.EMPTY_FILE_ARRAY;
437 }
438
439
440
441
442
443
444 public void removeListener(final FileAlterationListener listener) {
445 if (listener != null) {
446 listeners.removeIf(listener::equals);
447 }
448 }
449
450 private File[] sort(final File[] files) {
451 if (files == null) {
452 return FileUtils.EMPTY_FILE_ARRAY;
453 }
454 if (files.length > 1) {
455 Arrays.sort(files, comparator);
456 }
457 return files;
458 }
459
460
461
462
463
464
465 @Override
466 public String toString() {
467 final StringBuilder builder = new StringBuilder();
468 builder.append(getClass().getSimpleName());
469 builder.append("[file='");
470 builder.append(getDirectory().getPath());
471 builder.append('\'');
472 builder.append(", ");
473 builder.append(fileFilter.toString());
474 builder.append(", listeners=");
475 builder.append(listeners.size());
476 builder.append("]");
477 return builder.toString();
478 }
479
480 }