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

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

checkstyle - enable CatchParameterName rule

  • 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;
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((Runnable) this::processEvents, "File Watcher");
45 } catch (IOException e) {
46 Main.error(e);
47 }
48 }
49
50 /**
51 * Starts the File Watcher thread.
52 */
53 public final void start() {
54 if (thread != null && !thread.isAlive()) {
55 thread.start();
56 }
57 }
58
59 /**
60 * Registers a map paint style for local file changes, allowing dynamic reloading.
61 * @param style The style to watch
62 * @throws IllegalArgumentException if {@code style} is null or if it does not provide a local file
63 * @throws IllegalStateException if the watcher service failed to start
64 * @throws IOException if an I/O error occurs
65 */
66 public void registerStyleSource(StyleSource style) throws IOException {
67 register(style, styleMap);
68 }
69
70 /**
71 * Registers a validator rule for local file changes, allowing dynamic reloading.
72 * @param rule The rule to watch
73 * @throws IllegalArgumentException if {@code rule} is null or if it does not provide a local file
74 * @throws IllegalStateException if the watcher service failed to start
75 * @throws IOException if an I/O error occurs
76 * @since 7276
77 */
78 public void registerValidatorRule(SourceEntry rule) throws IOException {
79 register(rule, ruleMap);
80 }
81
82 private <T extends SourceEntry> void register(T obj, Map<Path, T> map) throws IOException {
83 CheckParameterUtil.ensureParameterNotNull(obj, "obj");
84 if (watcher == null) {
85 throw new IllegalStateException("File watcher is not available");
86 }
87 // Get local file, as this method is only called for local style sources
88 File file = new File(obj.url);
89 // Get parent directory as WatchService allows only to monitor directories, not single files
90 File dir = file.getParentFile();
91 if (dir == null) {
92 throw new IllegalArgumentException("Resource "+obj+" does not have a parent directory");
93 }
94 synchronized (this) {
95 // Register directory. Can be called several times for a same directory without problem
96 // (it returns the same key so it should not send events several times)
97 dir.toPath().register(watcher, StandardWatchEventKinds.ENTRY_MODIFY, StandardWatchEventKinds.ENTRY_CREATE);
98 map.put(file.toPath(), obj);
99 }
100 }
101
102 /**
103 * Process all events for the key queued to the watcher.
104 */
105 private void processEvents() {
106 if (Main.isDebugEnabled()) {
107 Main.debug("File watcher thread started");
108 }
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 Main.info("Map style "+style.getDisplayString()+" has been modified. Reloading style...");
143 Main.worker.submit(new MapPaintStyleLoader(Collections.singleton(style)));
144 } else if (rule != null) {
145 Main.info("Validator rule "+rule.getDisplayString()+" has been modified. Reloading rule...");
146 MapCSSTagChecker tagChecker = OsmValidator.getTest(MapCSSTagChecker.class);
147 if (tagChecker != null) {
148 try {
149 tagChecker.addMapCSS(rule.url);
150 } catch (IOException | ParseException e) {
151 Main.warn(e);
152 }
153 }
154 } else if (Main.isDebugEnabled()) {
155 Main.debug("Received "+kind.name()+" event for unregistered file: "+fullPath);
156 }
157 }
158 }
159
160 // Reset the key -- this step is critical to receive
161 // further watch events. If the key is no longer valid, the directory
162 // is inaccessible so exit the loop.
163 if (!key.reset()) {
164 break;
165 }
166 }
167 }
168}
Note: See TracBrowser for help on using the repository browser.