source: josm/trunk/src/org/openstreetmap/josm/io/FileWatcher.java@ 9461

Last change on this file since 9461 was 9205, checked in by Don-vip, 8 years ago

fix some Findbugs warnings

  • Property svn:eol-style set to native
File size: 6.5 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.io;
3
4import java.io.File;
5import java.io.IOException;
6import java.nio.file.FileSystems;
7import java.nio.file.Path;
8import java.nio.file.StandardWatchEventKinds;
9import java.nio.file.WatchEvent;
10import java.nio.file.WatchEvent.Kind;
11import java.nio.file.WatchKey;
12import java.nio.file.WatchService;
13import java.util.Collections;
14import java.util.HashMap;
15import java.util.Map;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.validation.OsmValidator;
19import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
20import org.openstreetmap.josm.gui.mappaint.MapPaintStyles.MapPaintStyleLoader;
21import org.openstreetmap.josm.gui.mappaint.StyleSource;
22import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
23import org.openstreetmap.josm.gui.preferences.SourceEntry;
24import org.openstreetmap.josm.tools.CheckParameterUtil;
25
26/**
27 * Background thread that monitors certain files and perform relevant actions when they change.
28 * @since 7185
29 */
30public class FileWatcher {
31
32 private WatchService watcher;
33 private Thread thread;
34
35 private final Map<Path, StyleSource> styleMap = new HashMap<>();
36 private final Map<Path, SourceEntry> ruleMap = new HashMap<>();
37
38 /**
39 * Constructs a new {@code FileWatcher}.
40 */
41 public FileWatcher() {
42 try {
43 watcher = FileSystems.getDefault().newWatchService();
44 thread = new Thread(new Runnable() {
45 @Override
46 public void run() {
47 processEvents();
48 }
49 }, "File Watcher");
50 } catch (IOException e) {
51 Main.error(e);
52 }
53 }
54
55 /**
56 * Starts the File Watcher thread.
57 */
58 public final void start() {
59 if (!thread.isAlive()) {
60 thread.start();
61 }
62 }
63
64 /**
65 * Registers a map paint style for local file changes, allowing dynamic reloading.
66 * @param style The style to watch
67 * @throws IllegalArgumentException if {@code style} is null or if it does not provide a local file
68 * @throws IllegalStateException if the watcher service failed to start
69 * @throws IOException if an I/O error occurs
70 */
71 public void registerStyleSource(StyleSource style) throws IOException {
72 register(style, styleMap);
73 }
74
75 /**
76 * Registers a validator rule for local file changes, allowing dynamic reloading.
77 * @param rule The rule to watch
78 * @throws IllegalArgumentException if {@code rule} is null or if it does not provide a local file
79 * @throws IllegalStateException if the watcher service failed to start
80 * @throws IOException if an I/O error occurs
81 * @since 7276
82 */
83 public void registerValidatorRule(SourceEntry rule) throws IOException {
84 register(rule, ruleMap);
85 }
86
87 private <T extends SourceEntry> void register(T obj, Map<Path, T> map) throws IOException {
88 CheckParameterUtil.ensureParameterNotNull(obj, "obj");
89 if (watcher == null) {
90 throw new IllegalStateException("File watcher is not available");
91 }
92 // Get local file, as this method is only called for local style sources
93 File file = new File(obj.url);
94 // Get parent directory as WatchService allows only to monitor directories, not single files
95 File dir = file.getParentFile();
96 if (dir == null) {
97 throw new IllegalArgumentException("Resource "+obj+" does not have a parent directory");
98 }
99 synchronized (this) {
100 // Register directory. Can be called several times for a same directory without problem
101 // (it returns the same key so it should not send events several times)
102 dir.toPath().register(watcher, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE);
103 map.put(file.toPath(), obj);
104 }
105 }
106
107 /**
108 * Process all events for the key queued to the watcher.
109 */
110 private void processEvents() {
111 if (Main.isDebugEnabled()) {
112 Main.debug("File watcher thread started");
113 }
114 while (true) {
115
116 // wait for key to be signaled
117 WatchKey key;
118 try {
119 key = watcher.take();
120 } catch (InterruptedException x) {
121 return;
122 }
123
124 for (WatchEvent<?> event: key.pollEvents()) {
125 Kind<?> kind = event.kind();
126
127 if (StandardWatchEventKinds.OVERFLOW.equals(kind)) {
128 continue;
129 }
130
131 // The filename is the context of the event.
132 @SuppressWarnings("unchecked")
133 WatchEvent<Path> ev = (WatchEvent<Path>) event;
134 Path filename = ev.context();
135 if (filename == null) {
136 continue;
137 }
138
139 // Only way to get full path (http://stackoverflow.com/a/7802029/2257172)
140 Path fullPath = ((Path) key.watchable()).resolve(filename);
141
142 synchronized (this) {
143 StyleSource style = styleMap.get(fullPath);
144 SourceEntry rule = ruleMap.get(fullPath);
145 if (style != null) {
146 Main.info("Map style "+style.getDisplayString()+" has been modified. Reloading style...");
147 Main.worker.submit(new MapPaintStyleLoader(Collections.singleton(style)));
148 } else if (rule != null) {
149 Main.info("Validator rule "+rule.getDisplayString()+" has been modified. Reloading rule...");
150 MapCSSTagChecker tagChecker = OsmValidator.getTest(MapCSSTagChecker.class);
151 if (tagChecker != null) {
152 try {
153 tagChecker.addMapCSS(rule.url);
154 } catch (IOException | ParseException e) {
155 Main.warn(e);
156 }
157 }
158 } else if (Main.isDebugEnabled()) {
159 Main.debug("Received "+kind.name()+" event for unregistered file: "+fullPath);
160 }
161 }
162 }
163
164 // Reset the key -- this step is critical to receive
165 // further watch events. If the key is no longer valid, the directory
166 // is inaccessible so exit the loop.
167 if (!key.reset()) {
168 break;
169 }
170 }
171 }
172}
Note: See TracBrowser for help on using the repository browser.