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

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

see #16204 - Allow to start and close JOSM in WebStart sandbox mode (where every external access is denied). This was very useful to reproduce some very tricky bugs that occured in real life but were almost impossible to diagnose.

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