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

Last change on this file since 8461 was 7937, checked in by bastiK, 9 years ago

add subversion property svn:eol=native

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