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

Last change on this file since 12620 was 12620, checked in by Don-vip, 7 years ago

see #15182 - deprecate all Main logging methods and introduce suitable replacements in Logging for most of them

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