Statistics
| Revision:

gvsig-scripting / org.gvsig.scripting / trunk / org.gvsig.scripting / org.gvsig.scripting.swing / org.gvsig.scripting.swing.impl / src / main / java / org / gvsig / scripting / swing / impl / FileChangesObserver.java @ 1267

History | View | Annotate | Download (12.6 KB)

1
package org.gvsig.scripting.swing.impl;
2

    
3
import java.io.File;
4
import java.io.IOException;
5
import java.lang.ref.WeakReference;
6
import java.nio.file.FileSystem;
7
import java.nio.file.FileSystems;
8
import java.nio.file.Path;
9
import static java.nio.file.StandardWatchEventKinds.ENTRY_CREATE;
10
import static java.nio.file.StandardWatchEventKinds.ENTRY_DELETE;
11
import static java.nio.file.StandardWatchEventKinds.ENTRY_MODIFY;
12
import static java.nio.file.StandardWatchEventKinds.OVERFLOW;
13
import java.nio.file.WatchEvent;
14
import java.nio.file.WatchKey;
15
import java.nio.file.WatchService;
16
import java.util.ArrayList;
17
import java.util.HashMap;
18
import java.util.List;
19
import java.util.Map;
20
import java.util.Objects;
21
import org.gvsig.tools.observer.Observable;
22
import org.gvsig.tools.observer.Observer;
23
import org.slf4j.Logger;
24
import org.slf4j.LoggerFactory;
25

    
26
/**
27
 *
28
 * @author gvSIG Team
29
 */
30
@SuppressWarnings("UseSpecificCatch")
31
public class FileChangesObserver {
32
    private static final Logger LOGGER = LoggerFactory.getLogger(FileChangesObserver.class);
33

    
34
    private static FileChangesObserver INSTANCE = null;
35

    
36
    public static FileChangesObserver getInstance() {
37
        if (INSTANCE == null) {
38
            INSTANCE = new FileChangesObserver();
39
        }
40
        return INSTANCE;
41
    }
42

    
43
    private class FileObserver implements Observable {
44
        private final Path folder;
45
        private final WeakReference<Observer> observer;
46
        @SuppressWarnings("FieldMayBeFinal")
47
        private File file;
48
        
49
        public FileObserver(File file, Observer observer) {
50
            try {
51
                this.file = file.getCanonicalFile();
52
            } catch (IOException ex) {
53
                this.file = file.getAbsoluteFile();
54
            }
55
            this.observer = new WeakReference<>(observer);
56
            FileSystem fs = FileSystems.getDefault();
57
            if( file.isDirectory() ) {
58
                this.folder = fs.getPath(file.getAbsolutePath());
59
            } else {
60
                this.folder = fs.getPath(file.getParentFile().getAbsolutePath());
61
            }
62
        }
63
        
64
        public Path getFolder() {
65
            return this.folder;
66
        }
67
        
68
        public File getFile() {
69
            return this.file;
70
        }
71
        
72
        public void notifyObserver() {
73
//            LOGGER.info("notifyObserver(): enter, ("+file.toString()+").");
74
            Observer obs = this.observer.get();
75
            if( obs == null ) {
76
//                LOGGER.info("notifyObserver(): exit, obs=null ("+file.toString()+").");
77
                return;
78
            }
79
//            LOGGER.info("notifyObserver(): obs="+obs.hashCode()+" ("+file.toString()+").");
80
            obs.update(this, this.file);
81
//            LOGGER.info("notifyObserver(): exit, obs="+obs.hashCode()+" ("+file.toString()+").");
82
        }
83

    
84
        private boolean is(Observer observer) {
85
            Observer obs = this.observer.get();
86
            if( obs == null ) {
87
                return false;
88
            }
89
            return obs == observer; // Queremos comparar los punteros.
90
        }
91

    
92
        private boolean is(Observer observer, File file) {
93
            Observer obs = this.observer.get();
94
            if( obs == null || obs != observer) { // Queremos comparar los punteros.
95
                return false;
96
            }
97
            return this.file.equals(file);
98
        }
99

    
100
        private boolean is(Path file) {
101
            File f = file.toAbsolutePath().toFile();
102
            try {
103
                f = f.getCanonicalFile();
104
            } catch (IOException ex) {
105
                f = f.getAbsoluteFile();
106
            }
107
            return this.file.equals(f);
108
        }
109
        
110
        private boolean isDead() {
111
            Observer obs = this.observer.get();
112
            return obs == null;
113
        }
114

    
115
        @Override
116
        public void addObserver(Observer obsrvr) {
117
            FileChangesObserver.this.addObserver(obsrvr, this.file);
118
        }
119

    
120
        @Override
121
        public void deleteObserver(Observer obsrvr) {
122
            FileChangesObserver.this.deleteObserver(obsrvr);
123
        }
124

    
125
        @Override
126
        public void deleteObservers() {
127
            FileChangesObserver.this.deleteObservers();
128
        }
129

    
130
    }
131

    
132
    
133
    private class Process extends Thread {
134
        private WatchService watcher;
135
        private final Map<String,WatchKey> folders;
136

    
137
        public Process() {
138
            try {
139
                this.watcher = FileSystems.getDefault().newWatchService();
140
            } catch (Throwable ex) {
141
                LOGGER.warn("Can't create WatchService.", ex);
142
                this.watcher = null;
143
            }
144
            this.folders = new HashMap<>();
145
            this.setName("ScriptingFilesChangesObserver");
146
        }
147
        
148
        @Override
149
        public void run() {
150
            if( this.watcher == null ) {
151
                return;
152
            }
153
            for (;;) {
154
                // wait for key to be signaled
155
                WatchKey key;
156
                try {
157
                    key = watcher.take();
158
                } catch (InterruptedException x) {
159
                    return;
160
                }
161
                if( key == null ) {
162
                    continue;
163
                }
164
                Path folder = (Path) key.watchable();                
165
                if( !key.isValid() ) {
166
                    String pathname = folder.toFile().getAbsolutePath();
167
                    synchronized(folders) {
168
                        this.folders.remove(pathname);
169
                    }
170
                    try {
171
                        key.cancel();
172
                    } catch(Throwable th) {
173
                        LOGGER.debug("Cat cancel watch on '"+pathname+"'.",th);
174
                    }
175
                    continue;
176
                }
177
                try {
178
                    for (WatchEvent<?> event : key.pollEvents()) {
179
                        WatchEvent.Kind<?> kind = event.kind();
180
                        // This key is registered only
181
                        // for ENTRY_CREATE events,
182
                        // but an OVERFLOW event can
183
                        // occur regardless if events
184
                        // are lost or discarded.
185
                        if (kind == OVERFLOW) {
186
                            continue;
187
                        }
188

    
189
                        // The filename is the context of the event.
190
                        WatchEvent<Path> ev = (WatchEvent<Path>) event;
191
                        Path filename = ev.context();
192
                        Path child = folder.resolve(filename);
193

    
194
                        List<FileObserver>toNotify = new ArrayList<>();
195
                        synchronized(fileObservers) {
196
                            for (FileObserver fileObserver : fileObservers) {
197
                                if( fileObserver.is(child) ) {
198
                                    toNotify.add(fileObserver);
199
                                }
200
                            }
201
                        }
202
                        for (FileObserver fileObserver : toNotify) {
203
                            fileObserver.notifyObserver();
204
                        }
205
                    }
206
                } catch(Exception ex) {
207
                    LOGGER.warn("Can't process watch event.", ex);
208
                } finally {
209
                    // Reset the key -- this step is critical if you want to
210
                    // receive further watch events.
211
                    key.reset();
212
                }
213
            }
214
        }
215

    
216
        public void reqisterWatch(Path folder) {
217
//            LOGGER.info("reqisterWatch(): enter, folder="+folder.hashCode()+", "+folder.toString()+".");
218
            synchronized(folders) {
219
                String pathname = folder.toFile().getAbsolutePath();
220
                WatchKey watchkey = this.folders.get(pathname);
221
                if( watchkey != null ) {
222
                    return;
223
                }
224
                try {
225
                    watchkey = folder.register(watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
226
                    this.folders.put(pathname, watchkey);
227
                } catch (IOException x) {
228
                    LOGGER.warn("Can't add watch on path '"+Objects.toString(folder), x);
229
                }
230
            }
231
//            LOGGER.info("reqisterWatch(): exit, folder="+folder.hashCode()+", "+folder.toString()+".");
232
        }
233

    
234
        public void cancelWatch(final Path folder) {
235
//            LOGGER.info("cancelWatch(): enter, folder="+folder.hashCode()+", "+folder.toString()+".");
236
            synchronized(folders) {
237
                String pathname = folder.toFile().getAbsolutePath();
238
                WatchKey watchkey = this.folders.get(pathname);
239
                if( watchkey == null ) {
240
                    return;
241
                }
242
                this.folders.remove(pathname);
243
                try {
244
                    watchkey.cancel();
245
                } catch(Throwable th) {
246
                    LOGGER.debug("Cat cancel watch on '"+pathname+"'.",th);
247
                }
248
            }
249
//            LOGGER.info("cancelWatch(): exit, folder="+folder.hashCode()+", "+folder.toString()+".");
250
        }
251

    
252
    }
253

    
254
    private final List<FileObserver> fileObservers;
255
    private final Process process;
256

    
257
    private FileChangesObserver() {
258
        this.fileObservers = new ArrayList<>();
259
        this.process = new Process();
260
        this.process.start();
261
    }
262

    
263
    private int getWatchCounts(Path folder) {
264
        int count = 0;
265
        for (FileObserver fo : fileObservers) {
266
            if( folder.equals(fo.getFolder()) ) {
267
                count++;
268
            }
269
        }
270
        return count;
271
    }
272
    
273
    public void addObserver(final Observer observer, final File file) {
274
//        LOGGER.info("addObserver(): enter, observer="+observer.hashCode()+", f="+(file==null?"null":file.getAbsolutePath())+".");
275
        synchronized(fileObservers) {
276
            for (FileObserver fileObserver : fileObservers) {
277
                if( fileObserver.is(observer,file) ) {
278
//                    LOGGER.info("addObserver(): exit, already registered, observer="+observer.hashCode()+", f="+(file==null?"null":file.getAbsolutePath())+".");
279
                    return;
280
                }
281
            }
282
            removeDeads();
283
            FileObserver fobserver = new FileObserver(file, observer);        
284
            this.process.reqisterWatch(fobserver.getFolder());
285
            this.fileObservers.add(fobserver);
286
        }
287
//        LOGGER.info("addObserver(): exit, observer="+observer.hashCode()+", f="+(file==null?"null":file.getAbsolutePath())+".");
288
    }
289

    
290
    public void deleteObserver(final Observer observer) {
291
//        LOGGER.info("deleteObserver(): enter, observer="+observer.hashCode());
292
        synchronized(fileObservers) {
293
            for (FileObserver fileObserver : this.fileObservers) {
294
                if( fileObserver.is(observer) ) {
295
                    this.fileObservers.remove(fileObserver);
296
                    if( this.getWatchCounts(fileObserver.getFolder())<1 ) {
297
                        this.process.cancelWatch(fileObserver.getFolder());
298
                    }
299
                    return;
300
                }
301
            }
302
            removeDeads();
303
        }
304
//        LOGGER.info("deleteObserver(): exit, observer="+observer.hashCode());
305
    }
306
    
307
    public synchronized void removeDeads() {
308
//        LOGGER.info("removeDeads(): enter");
309
        synchronized(fileObservers) {
310
            int deads = 0;
311
            for (FileObserver fileObserver : this.fileObservers) {
312
                if( fileObserver.isDead() ) {
313
                    deads++;
314
                }
315
            }
316
            if( deads == 0 ) {
317
                return;
318
            }
319
            if( deads >= this.fileObservers.size() ) {
320
                this.fileObservers.clear();
321
                return;
322
            }
323
            List<FileObserver> lives = new ArrayList<>(this.fileObservers.size()-deads);
324
            for (FileObserver fileObserver : this.fileObservers) {
325
                if( fileObserver.isDead() ) {
326
                    if( this.getWatchCounts(fileObserver.getFolder())<1 ) {
327
                        this.process.cancelWatch(fileObserver.getFolder());
328
                    }
329
                } else {
330
                    lives.add(fileObserver);
331
                }
332
            }
333
            this.fileObservers.clear();
334
            this.fileObservers.addAll(lives);
335
        }
336
//        LOGGER.info("removeDeads(): exit");
337
    }
338

    
339
    public void deleteObservers() {
340
//        LOGGER.info("deleteObservers(): enter");
341
        List<FileObserver> x;
342
        synchronized(fileObservers) {
343
            x = this.fileObservers;
344
            this.fileObservers.clear();
345
        }
346
        for (FileObserver fileObserver : x) {
347
            this.process.cancelWatch(fileObserver.getFolder());
348
        }
349
//        LOGGER.info("deleteObservers(): exit");
350
    }
351

    
352
}