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

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

see #15182 - code refactoring to avoid dependence on GUI packages from MapPaintStyles

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