source: josm/trunk/src/org/openstreetmap/josm/Main.java@ 13854

Last change on this file since 13854 was 13838, checked in by Don-vip, 6 years ago

fix #16316 - catch InvalidPathException

  • Property svn:eol-style set to native
File size: 21.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.awt.Component;
7import java.awt.GraphicsEnvironment;
8import java.io.IOException;
9import java.lang.ref.WeakReference;
10import java.net.URL;
11import java.nio.file.InvalidPathException;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.EnumSet;
15import java.util.HashMap;
16import java.util.List;
17import java.util.Map;
18import java.util.Objects;
19import java.util.Set;
20import java.util.concurrent.Callable;
21import java.util.concurrent.CopyOnWriteArrayList;
22import java.util.concurrent.ExecutionException;
23import java.util.concurrent.ExecutorService;
24import java.util.concurrent.Executors;
25import java.util.concurrent.Future;
26
27import org.openstreetmap.josm.data.Bounds;
28import org.openstreetmap.josm.data.Preferences;
29import org.openstreetmap.josm.data.UndoRedoHandler;
30import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
31import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
32import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat;
33import org.openstreetmap.josm.data.osm.DataSet;
34import org.openstreetmap.josm.data.osm.OsmPrimitive;
35import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
36import org.openstreetmap.josm.data.projection.Projection;
37import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
38import org.openstreetmap.josm.io.FileWatcher;
39import org.openstreetmap.josm.io.OnlineResource;
40import org.openstreetmap.josm.io.OsmApi;
41import org.openstreetmap.josm.spi.preferences.Config;
42import org.openstreetmap.josm.tools.CheckParameterUtil;
43import org.openstreetmap.josm.tools.ImageProvider;
44import org.openstreetmap.josm.tools.JosmRuntimeException;
45import org.openstreetmap.josm.tools.Logging;
46import org.openstreetmap.josm.tools.Platform;
47import org.openstreetmap.josm.tools.PlatformHook;
48import org.openstreetmap.josm.tools.PlatformHookOsx;
49import org.openstreetmap.josm.tools.PlatformHookWindows;
50import org.openstreetmap.josm.tools.Utils;
51import org.openstreetmap.josm.tools.bugreport.BugReport;
52
53/**
54 * Abstract class holding various static global variables and methods used in large parts of JOSM application.
55 * @since 98
56 */
57public abstract class Main {
58
59 /**
60 * The JOSM website URL.
61 * @since 6897 (was public from 6143 to 6896)
62 */
63 private static final String JOSM_WEBSITE = "https://josm.openstreetmap.de";
64
65 /**
66 * The OSM website URL.
67 * @since 6897 (was public from 6453 to 6896)
68 */
69 private static final String OSM_WEBSITE = "https://www.openstreetmap.org";
70
71 /**
72 * Global parent component for all dialogs and message boxes
73 */
74 public static Component parent;
75
76 /**
77 * Global application.
78 */
79 public static volatile Main main;
80
81 /**
82 * Global application preferences
83 */
84 public static final Preferences pref = new Preferences(JosmBaseDirectories.getInstance());
85
86 /**
87 * The commands undo/redo handler.
88 */
89 public final UndoRedoHandler undoRedo = new UndoRedoHandler();
90
91 /**
92 * The file watcher service.
93 */
94 public static final FileWatcher fileWatcher = new FileWatcher();
95
96 private static final Map<String, Throwable> NETWORK_ERRORS = new HashMap<>();
97
98 private static final Set<OnlineResource> OFFLINE_RESOURCES = EnumSet.noneOf(OnlineResource.class);
99
100 /**
101 * Platform specific code goes in here.
102 * Plugins may replace it, however, some hooks will be called before any plugins have been loaded.
103 * So if you need to hook into those early ones, split your class and send the one with the early hooks
104 * to the JOSM team for inclusion.
105 */
106 public static volatile PlatformHook platform;
107
108 private static volatile InitStatusListener initListener;
109
110 /**
111 * Initialization task listener.
112 */
113 public interface InitStatusListener {
114
115 /**
116 * Called when an initialization task updates its status.
117 * @param event task name
118 * @return new status
119 */
120 Object updateStatus(String event);
121
122 /**
123 * Called when an initialization task completes.
124 * @param status final status
125 */
126 void finish(Object status);
127 }
128
129 /**
130 * Sets initialization task listener.
131 * @param listener initialization task listener
132 */
133 public static void setInitStatusListener(InitStatusListener listener) {
134 CheckParameterUtil.ensureParameterNotNull(listener);
135 initListener = listener;
136 }
137
138 /**
139 * Constructs new {@code Main} object.
140 * @see #initialize()
141 */
142 protected Main() {
143 setInstance(this);
144 }
145
146 private static void setInstance(Main instance) {
147 main = instance;
148 }
149
150 /**
151 * Initializes the main object. A lot of global variables are initialized here.
152 * @since 10340
153 */
154 public void initialize() {
155 // Initializes tasks that must be run before parallel tasks
156 runInitializationTasks(beforeInitializationTasks());
157
158 // Initializes tasks to be executed (in parallel) by a ExecutorService
159 try {
160 ExecutorService service = Executors.newFixedThreadPool(
161 Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
162 for (Future<Void> i : service.invokeAll(parallelInitializationTasks())) {
163 i.get();
164 }
165 // asynchronous initializations to be completed eventually
166 asynchronousRunnableTasks().forEach(service::submit);
167 asynchronousCallableTasks().forEach(service::submit);
168 try {
169 service.shutdown();
170 } catch (SecurityException e) {
171 Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown executor service", e);
172 }
173 } catch (InterruptedException | ExecutionException ex) {
174 throw new JosmRuntimeException(ex);
175 }
176
177 // Initializes tasks that must be run after parallel tasks
178 runInitializationTasks(afterInitializationTasks());
179 }
180
181 private static void runInitializationTasks(List<InitializationTask> tasks) {
182 for (InitializationTask task : tasks) {
183 try {
184 task.call();
185 } catch (JosmRuntimeException e) {
186 // Can happen if the current projection needs NTV2 grid which is not available
187 // In this case we want the user be able to change his projection
188 BugReport.intercept(e).warn();
189 }
190 }
191 }
192
193 /**
194 * Returns tasks that must be run before parallel tasks.
195 * @return tasks that must be run before parallel tasks
196 * @see #afterInitializationTasks
197 * @see #parallelInitializationTasks
198 */
199 protected List<InitializationTask> beforeInitializationTasks() {
200 return Collections.emptyList();
201 }
202
203 /**
204 * Returns tasks to be executed (in parallel) by a ExecutorService.
205 * @return tasks to be executed (in parallel) by a ExecutorService
206 */
207 protected Collection<InitializationTask> parallelInitializationTasks() {
208 return Collections.emptyList();
209 }
210
211 /**
212 * Returns asynchronous callable initializations to be completed eventually
213 * @return asynchronous callable initializations to be completed eventually
214 */
215 protected List<Callable<?>> asynchronousCallableTasks() {
216 return Collections.emptyList();
217 }
218
219 /**
220 * Returns asynchronous runnable initializations to be completed eventually
221 * @return asynchronous runnable initializations to be completed eventually
222 */
223 protected List<Runnable> asynchronousRunnableTasks() {
224 return Collections.emptyList();
225 }
226
227 /**
228 * Returns tasks that must be run after parallel tasks.
229 * @return tasks that must be run after parallel tasks
230 * @see #beforeInitializationTasks
231 * @see #parallelInitializationTasks
232 */
233 protected List<InitializationTask> afterInitializationTasks() {
234 return Collections.emptyList();
235 }
236
237 protected static final class InitializationTask implements Callable<Void> {
238
239 private final String name;
240 private final Runnable task;
241
242 /**
243 * Constructs a new {@code InitializationTask}.
244 * @param name translated name to be displayed to user
245 * @param task runnable initialization task
246 */
247 public InitializationTask(String name, Runnable task) {
248 this.name = name;
249 this.task = task;
250 }
251
252 @Override
253 public Void call() {
254 Object status = null;
255 if (initListener != null) {
256 status = initListener.updateStatus(name);
257 }
258 task.run();
259 if (initListener != null) {
260 initListener.finish(status);
261 }
262 return null;
263 }
264 }
265
266 /**
267 * Replies the current selected primitives, from a end-user point of view.
268 * It is not always technically the same collection of primitives than {@link DataSet#getSelected()}.
269 * @return The current selected primitives, from a end-user point of view. Can be {@code null}.
270 * @since 6546
271 */
272 public Collection<OsmPrimitive> getInProgressSelection() {
273 return Collections.emptyList();
274 }
275
276 /**
277 * Gets the active edit data set (not read-only).
278 * @return That data set, <code>null</code>.
279 * @see #getActiveDataSet
280 * @since 12691
281 */
282 public abstract DataSet getEditDataSet();
283
284 /**
285 * Gets the active data set (can be read-only).
286 * @return That data set, <code>null</code>.
287 * @see #getEditDataSet
288 * @since 13434
289 */
290 public abstract DataSet getActiveDataSet();
291
292 /**
293 * Sets the active data set (and also edit data set if not read-only).
294 * @param ds New data set, or <code>null</code>
295 * @since 13434
296 */
297 public abstract void setActiveDataSet(DataSet ds);
298
299 /**
300 * Determines if the list of data sets managed by JOSM contains {@code ds}.
301 * @param ds the data set to look for
302 * @return {@code true} if the list of data sets managed by JOSM contains {@code ds}
303 * @since 12718
304 */
305 public abstract boolean containsDataSet(DataSet ds);
306
307 ///////////////////////////////////////////////////////////////////////////
308 // Implementation part
309 ///////////////////////////////////////////////////////////////////////////
310
311 /**
312 * Should be called before the main constructor to setup some parameter stuff
313 */
314 public static void preConstructorInit() {
315 // init default coordinate format
316 ICoordinateFormat fmt = CoordinateFormatManager.getCoordinateFormat(Config.getPref().get("coordinates"));
317 if (fmt == null) {
318 fmt = DecimalDegreesCoordinateFormat.INSTANCE;
319 }
320 CoordinateFormatManager.setCoordinateFormat(fmt);
321 }
322
323 /**
324 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
325 * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
326 * @param exitCode The return code
327 * @return {@code true}
328 * @since 12636
329 */
330 public static boolean exitJosm(boolean exit, int exitCode) {
331 if (Main.main != null) {
332 Main.main.shutdown();
333 }
334
335 if (exit) {
336 System.exit(exitCode);
337 }
338 return true;
339 }
340
341 /**
342 * Shutdown JOSM.
343 */
344 protected void shutdown() {
345 if (!GraphicsEnvironment.isHeadless()) {
346 ImageProvider.shutdown(false);
347 }
348 try {
349 pref.saveDefaults();
350 } catch (IOException | InvalidPathException ex) {
351 Logging.log(Logging.LEVEL_WARN, tr("Failed to save default preferences."), ex);
352 }
353 if (!GraphicsEnvironment.isHeadless()) {
354 ImageProvider.shutdown(true);
355 }
356 }
357
358 /**
359 * Identifies the current operating system family and initializes the platform hook accordingly.
360 * @since 1849
361 */
362 public static void determinePlatformHook() {
363 platform = Platform.determinePlatform().accept(PlatformHook.CONSTRUCT_FROM_PLATFORM);
364 }
365
366 /* ----------------------------------------------------------------------------------------- */
367 /* projection handling - Main is a registry for a single, global projection instance */
368 /* */
369 /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
370 /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
371 /* ----------------------------------------------------------------------------------------- */
372 /**
373 * The projection method used.
374 * Use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
375 * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
376 */
377 private static volatile Projection proj;
378
379 /**
380 * Replies the current projection.
381 *
382 * @return the currently active projection
383 */
384 public static Projection getProjection() {
385 return proj;
386 }
387
388 /**
389 * Sets the current projection
390 *
391 * @param p the projection
392 */
393 public static void setProjection(Projection p) {
394 CheckParameterUtil.ensureParameterNotNull(p);
395 Projection oldValue = proj;
396 Bounds b = main != null ? main.getRealBounds() : null;
397 proj = p;
398 fireProjectionChanged(oldValue, proj, b);
399 }
400
401 /**
402 * Returns the bounds for the current projection. Used for projection events.
403 * @return the bounds for the current projection
404 * @see #restoreOldBounds
405 */
406 protected Bounds getRealBounds() {
407 // To be overriden
408 return null;
409 }
410
411 /**
412 * Restore clean state corresponding to old bounds after a projection change event.
413 * @param oldBounds bounds previously returned by {@link #getRealBounds}, before the change of projection
414 * @see #getRealBounds
415 */
416 protected void restoreOldBounds(Bounds oldBounds) {
417 // To be overriden
418 }
419
420 /*
421 * Keep WeakReferences to the listeners. This relieves clients from the burden of
422 * explicitly removing the listeners and allows us to transparently register every
423 * created dataset as projection change listener.
424 */
425 private static final List<WeakReference<ProjectionChangeListener>> listeners = new CopyOnWriteArrayList<>();
426
427 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
428 if ((newValue == null ^ oldValue == null)
429 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
430 listeners.removeIf(x -> x.get() == null);
431 listeners.stream().map(WeakReference::get).filter(Objects::nonNull).forEach(x -> x.projectionChanged(oldValue, newValue));
432 if (newValue != null && oldBounds != null && main != null) {
433 main.restoreOldBounds(oldBounds);
434 }
435 /* TODO - remove layers with fixed projection */
436 }
437 }
438
439 /**
440 * Register a projection change listener.
441 * The listener is registered to be weak, so keep a reference of it if you want it to be preserved.
442 *
443 * @param listener the listener. Ignored if <code>null</code>.
444 */
445 public static void addProjectionChangeListener(ProjectionChangeListener listener) {
446 if (listener == null) return;
447 for (WeakReference<ProjectionChangeListener> wr : listeners) {
448 // already registered ? => abort
449 if (wr.get() == listener) return;
450 }
451 listeners.add(new WeakReference<>(listener));
452 }
453
454 /**
455 * Removes a projection change listener.
456 *
457 * @param listener the listener. Ignored if <code>null</code>.
458 */
459 public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
460 if (listener == null) return;
461 // remove the listener - and any other listener which got garbage collected in the meantime
462 listeners.removeIf(wr -> wr.get() == null || wr.get() == listener);
463 }
464
465 /**
466 * Remove all projection change listeners. For testing purposes only.
467 * @since 13322
468 */
469 public static void clearProjectionChangeListeners() {
470 listeners.clear();
471 }
472
473 /**
474 * Adds a new network error that occur to give a hint about broken Internet connection.
475 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
476 *
477 * @param url The accessed URL that caused the error
478 * @param t The network error
479 * @return The previous error associated to the given resource, if any. Can be {@code null}
480 * @since 6642
481 */
482 public static Throwable addNetworkError(URL url, Throwable t) {
483 if (url != null && t != null) {
484 Throwable old = addNetworkError(url.toExternalForm(), t);
485 if (old != null) {
486 Logging.warn("Already here "+old);
487 }
488 return old;
489 }
490 return null;
491 }
492
493 /**
494 * Adds a new network error that occur to give a hint about broken Internet connection.
495 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
496 *
497 * @param url The accessed URL that caused the error
498 * @param t The network error
499 * @return The previous error associated to the given resource, if any. Can be {@code null}
500 * @since 6642
501 */
502 public static Throwable addNetworkError(String url, Throwable t) {
503 if (url != null && t != null) {
504 return NETWORK_ERRORS.put(url, t);
505 }
506 return null;
507 }
508
509 /**
510 * Returns the network errors that occured until now.
511 * @return the network errors that occured until now, indexed by URL
512 * @since 6639
513 */
514 public static Map<String, Throwable> getNetworkErrors() {
515 return new HashMap<>(NETWORK_ERRORS);
516 }
517
518 /**
519 * Clears the network errors cache.
520 * @since 12011
521 */
522 public static void clearNetworkErrors() {
523 NETWORK_ERRORS.clear();
524 }
525
526 /**
527 * Returns the JOSM website URL.
528 * @return the josm website URL
529 * @since 6897
530 */
531 public static String getJOSMWebsite() {
532 if (Config.getPref() != null)
533 return Config.getPref().get("josm.url", JOSM_WEBSITE);
534 return JOSM_WEBSITE;
535 }
536
537 /**
538 * Returns the JOSM XML URL.
539 * @return the josm XML URL
540 * @since 6897
541 */
542 public static String getXMLBase() {
543 // Always return HTTP (issues reported with HTTPS)
544 return "http://josm.openstreetmap.de";
545 }
546
547 /**
548 * Returns the OSM website URL.
549 * @return the OSM website URL
550 * @since 6897
551 */
552 public static String getOSMWebsite() {
553 if (Config.getPref() != null)
554 return Config.getPref().get("osm.url", OSM_WEBSITE);
555 return OSM_WEBSITE;
556 }
557
558 /**
559 * Returns the OSM website URL depending on the selected {@link OsmApi}.
560 * @return the OSM website URL depending on the selected {@link OsmApi}
561 */
562 private static String getOSMWebsiteDependingOnSelectedApi() {
563 final String api = OsmApi.getOsmApi().getServerUrl();
564 if (OsmApi.DEFAULT_API_URL.equals(api)) {
565 return getOSMWebsite();
566 } else {
567 return api.replaceAll("/api$", "");
568 }
569 }
570
571 /**
572 * Replies the base URL for browsing information about a primitive.
573 * @return the base URL, i.e. https://www.openstreetmap.org
574 * @since 7678
575 */
576 public static String getBaseBrowseUrl() {
577 if (Config.getPref() != null)
578 return Config.getPref().get("osm-browse.url", getOSMWebsiteDependingOnSelectedApi());
579 return getOSMWebsiteDependingOnSelectedApi();
580 }
581
582 /**
583 * Replies the base URL for browsing information about a user.
584 * @return the base URL, i.e. https://www.openstreetmap.org/user
585 * @since 7678
586 */
587 public static String getBaseUserUrl() {
588 if (Config.getPref() != null)
589 return Config.getPref().get("osm-user.url", getOSMWebsiteDependingOnSelectedApi() + "/user");
590 return getOSMWebsiteDependingOnSelectedApi() + "/user";
591 }
592
593 /**
594 * Determines if we are currently running on OSX.
595 * @return {@code true} if we are currently running on OSX
596 * @since 6957
597 */
598 public static boolean isPlatformOsx() {
599 return Main.platform instanceof PlatformHookOsx;
600 }
601
602 /**
603 * Determines if we are currently running on Windows.
604 * @return {@code true} if we are currently running on Windows
605 * @since 7335
606 */
607 public static boolean isPlatformWindows() {
608 return Main.platform instanceof PlatformHookWindows;
609 }
610
611 /**
612 * Determines if the given online resource is currently offline.
613 * @param r the online resource
614 * @return {@code true} if {@code r} is offline and should not be accessed
615 * @since 7434
616 */
617 public static boolean isOffline(OnlineResource r) {
618 return OFFLINE_RESOURCES.contains(r) || OFFLINE_RESOURCES.contains(OnlineResource.ALL);
619 }
620
621 /**
622 * Sets the given online resource to offline state.
623 * @param r the online resource
624 * @return {@code true} if {@code r} was not already offline
625 * @since 7434
626 */
627 public static boolean setOffline(OnlineResource r) {
628 return OFFLINE_RESOURCES.add(r);
629 }
630
631 /**
632 * Sets the given online resource to online state.
633 * @param r the online resource
634 * @return {@code true} if {@code r} was offline
635 * @since 8506
636 */
637 public static boolean setOnline(OnlineResource r) {
638 return OFFLINE_RESOURCES.remove(r);
639 }
640
641 /**
642 * Replies the set of online resources currently offline.
643 * @return the set of online resources currently offline
644 * @since 7434
645 */
646 public static Set<OnlineResource> getOfflineResources() {
647 return EnumSet.copyOf(OFFLINE_RESOURCES);
648 }
649}
Note: See TracBrowser for help on using the repository browser.