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

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

see #15310 - remove Main.worker

  • Property svn:eol-style set to native
File size: 22.2 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.ArrayList;
12import java.util.Collection;
13import java.util.Collections;
14import java.util.EnumSet;
15import java.util.HashMap;
16import java.util.Iterator;
17import java.util.List;
18import java.util.Map;
19import java.util.Objects;
20import java.util.Set;
21import java.util.concurrent.Callable;
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 service.shutdown();
169 } catch (InterruptedException | ExecutionException ex) {
170 throw new JosmRuntimeException(ex);
171 }
172
173 // Initializes tasks that must be run after parallel tasks
174 runInitializationTasks(afterInitializationTasks());
175 }
176
177 private static void runInitializationTasks(List<InitializationTask> tasks) {
178 for (InitializationTask task : tasks) {
179 try {
180 task.call();
181 } catch (JosmRuntimeException e) {
182 // Can happen if the current projection needs NTV2 grid which is not available
183 // In this case we want the user be able to change his projection
184 BugReport.intercept(e).warn();
185 }
186 }
187 }
188
189 /**
190 * Returns tasks that must be run before parallel tasks.
191 * @return tasks that must be run before parallel tasks
192 * @see #afterInitializationTasks
193 * @see #parallelInitializationTasks
194 */
195 protected List<InitializationTask> beforeInitializationTasks() {
196 return Collections.emptyList();
197 }
198
199 /**
200 * Returns tasks to be executed (in parallel) by a ExecutorService.
201 * @return tasks to be executed (in parallel) by a ExecutorService
202 */
203 protected Collection<InitializationTask> parallelInitializationTasks() {
204 return Collections.emptyList();
205 }
206
207 /**
208 * Returns asynchronous callable initializations to be completed eventually
209 * @return asynchronous callable initializations to be completed eventually
210 */
211 protected List<Callable<?>> asynchronousCallableTasks() {
212 return Collections.emptyList();
213 }
214
215 /**
216 * Returns asynchronous runnable initializations to be completed eventually
217 * @return asynchronous runnable initializations to be completed eventually
218 */
219 protected List<Runnable> asynchronousRunnableTasks() {
220 return Collections.emptyList();
221 }
222
223 /**
224 * Returns tasks that must be run after parallel tasks.
225 * @return tasks that must be run after parallel tasks
226 * @see #beforeInitializationTasks
227 * @see #parallelInitializationTasks
228 */
229 protected List<InitializationTask> afterInitializationTasks() {
230 return Collections.emptyList();
231 }
232
233 protected static final class InitializationTask implements Callable<Void> {
234
235 private final String name;
236 private final Runnable task;
237
238 /**
239 * Constructs a new {@code InitializationTask}.
240 * @param name translated name to be displayed to user
241 * @param task runnable initialization task
242 */
243 public InitializationTask(String name, Runnable task) {
244 this.name = name;
245 this.task = task;
246 }
247
248 @Override
249 public Void call() {
250 Object status = null;
251 if (initListener != null) {
252 status = initListener.updateStatus(name);
253 }
254 task.run();
255 if (initListener != null) {
256 initListener.finish(status);
257 }
258 return null;
259 }
260 }
261
262 /**
263 * Replies the current selected primitives, from a end-user point of view.
264 * It is not always technically the same collection of primitives than {@link DataSet#getSelected()}.
265 * @return The current selected primitives, from a end-user point of view. Can be {@code null}.
266 * @since 6546
267 */
268 public Collection<OsmPrimitive> getInProgressSelection() {
269 return Collections.emptyList();
270 }
271
272 /**
273 * Gets the active edit data set.
274 * @return That data set, <code>null</code>.
275 * @since 12691
276 */
277 public abstract DataSet getEditDataSet();
278
279 /**
280 * Sets the active data set.
281 * @param ds New edit data set, or <code>null</code>
282 * @since 12718
283 */
284 public abstract void setEditDataSet(DataSet ds);
285
286 /**
287 * Determines if the list of data sets managed by JOSM contains {@code ds}.
288 * @param ds the data set to look for
289 * @return {@code true} if the list of data sets managed by JOSM contains {@code ds}
290 * @since 12718
291 */
292 public abstract boolean containsDataSet(DataSet ds);
293
294 ///////////////////////////////////////////////////////////////////////////
295 // Implementation part
296 ///////////////////////////////////////////////////////////////////////////
297
298 /**
299 * Should be called before the main constructor to setup some parameter stuff
300 */
301 public static void preConstructorInit() {
302 // init default coordinate format
303 ICoordinateFormat fmt = CoordinateFormatManager.getCoordinateFormat(Config.getPref().get("coordinates"));
304 if (fmt == null) {
305 fmt = DecimalDegreesCoordinateFormat.INSTANCE;
306 }
307 CoordinateFormatManager.setCoordinateFormat(fmt);
308 }
309
310 /**
311 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
312 * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
313 * @param exitCode The return code
314 * @return {@code true}
315 * @since 12636
316 */
317 public static boolean exitJosm(boolean exit, int exitCode) {
318 if (Main.main != null) {
319 Main.main.shutdown();
320 }
321
322 if (exit) {
323 System.exit(exitCode);
324 }
325 return true;
326 }
327
328 /**
329 * Shutdown JOSM.
330 */
331 protected void shutdown() {
332 if (!GraphicsEnvironment.isHeadless()) {
333 ImageProvider.shutdown(false);
334 }
335 try {
336 pref.saveDefaults();
337 } catch (IOException ex) {
338 Logging.log(Logging.LEVEL_WARN, tr("Failed to save default preferences."), ex);
339 }
340 if (!GraphicsEnvironment.isHeadless()) {
341 ImageProvider.shutdown(true);
342 }
343 }
344
345 /**
346 * Identifies the current operating system family and initializes the platform hook accordingly.
347 * @since 1849
348 */
349 public static void determinePlatformHook() {
350 platform = Platform.determinePlatform().accept(PlatformHook.CONSTRUCT_FROM_PLATFORM);
351 }
352
353 /* ----------------------------------------------------------------------------------------- */
354 /* projection handling - Main is a registry for a single, global projection instance */
355 /* */
356 /* TODO: For historical reasons the registry is implemented by Main. An alternative approach */
357 /* would be a singleton org.openstreetmap.josm.data.projection.ProjectionRegistry class. */
358 /* ----------------------------------------------------------------------------------------- */
359 /**
360 * The projection method used.
361 * Use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
362 * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
363 */
364 private static volatile Projection proj;
365
366 /**
367 * Replies the current projection.
368 *
369 * @return the currently active projection
370 */
371 public static Projection getProjection() {
372 return proj;
373 }
374
375 /**
376 * Sets the current projection
377 *
378 * @param p the projection
379 */
380 public static void setProjection(Projection p) {
381 CheckParameterUtil.ensureParameterNotNull(p);
382 Projection oldValue = proj;
383 Bounds b = main != null ? main.getRealBounds() : null;
384 proj = p;
385 fireProjectionChanged(oldValue, proj, b);
386 }
387
388 /**
389 * Returns the bounds for the current projection. Used for projection events.
390 * @return the bounds for the current projection
391 * @see #restoreOldBounds
392 */
393 protected Bounds getRealBounds() {
394 // To be overriden
395 return null;
396 }
397
398 /**
399 * Restore clean state corresponding to old bounds after a projection change event.
400 * @param oldBounds bounds previously returned by {@link #getRealBounds}, before the change of projection
401 * @see #getRealBounds
402 */
403 protected void restoreOldBounds(Bounds oldBounds) {
404 // To be overriden
405 }
406
407 /*
408 * Keep WeakReferences to the listeners. This relieves clients from the burden of
409 * explicitly removing the listeners and allows us to transparently register every
410 * created dataset as projection change listener.
411 */
412 private static final List<WeakReference<ProjectionChangeListener>> listeners = new ArrayList<>();
413
414 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
415 if ((newValue == null ^ oldValue == null)
416 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
417 synchronized (Main.class) {
418 Iterator<WeakReference<ProjectionChangeListener>> it = listeners.iterator();
419 while (it.hasNext()) {
420 WeakReference<ProjectionChangeListener> wr = it.next();
421 ProjectionChangeListener listener = wr.get();
422 if (listener == null) {
423 it.remove();
424 continue;
425 }
426 listener.projectionChanged(oldValue, newValue);
427 }
428 }
429 if (newValue != null && oldBounds != null && main != null) {
430 main.restoreOldBounds(oldBounds);
431 }
432 /* TODO - remove layers with fixed projection */
433 }
434 }
435
436 /**
437 * Register a projection change listener.
438 * The listener is registered to be weak, so keep a reference of it if you want it to be preserved.
439 *
440 * @param listener the listener. Ignored if <code>null</code>.
441 */
442 public static void addProjectionChangeListener(ProjectionChangeListener listener) {
443 if (listener == null) return;
444 synchronized (Main.class) {
445 for (WeakReference<ProjectionChangeListener> wr : listeners) {
446 // already registered ? => abort
447 if (wr.get() == listener) return;
448 }
449 listeners.add(new WeakReference<>(listener));
450 }
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 synchronized (Main.class) {
461 // remove the listener - and any other listener which got garbage
462 // collected in the meantime
463 listeners.removeIf(wr -> wr.get() == null || wr.get() == listener);
464 }
465 }
466
467 /**
468 * Listener for window switch events.
469 *
470 * These are events, when the user activates a window of another application
471 * or comes back to JOSM. Window switches from one JOSM window to another
472 * are not reported.
473 */
474 public interface WindowSwitchListener {
475 /**
476 * Called when the user activates a window of another application.
477 */
478 void toOtherApplication();
479
480 /**
481 * Called when the user comes from a window of another application back to JOSM.
482 */
483 void fromOtherApplication();
484 }
485
486 /**
487 * Adds a new network error that occur to give a hint about broken Internet connection.
488 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
489 *
490 * @param url The accessed URL that caused the error
491 * @param t The network error
492 * @return The previous error associated to the given resource, if any. Can be {@code null}
493 * @since 6642
494 */
495 public static Throwable addNetworkError(URL url, Throwable t) {
496 if (url != null && t != null) {
497 Throwable old = addNetworkError(url.toExternalForm(), t);
498 if (old != null) {
499 Logging.warn("Already here "+old);
500 }
501 return old;
502 }
503 return null;
504 }
505
506 /**
507 * Adds a new network error that occur to give a hint about broken Internet connection.
508 * Do not use this method for errors known for sure thrown because of a bad proxy configuration.
509 *
510 * @param url The accessed URL that caused the error
511 * @param t The network error
512 * @return The previous error associated to the given resource, if any. Can be {@code null}
513 * @since 6642
514 */
515 public static Throwable addNetworkError(String url, Throwable t) {
516 if (url != null && t != null) {
517 return NETWORK_ERRORS.put(url, t);
518 }
519 return null;
520 }
521
522 /**
523 * Returns the network errors that occured until now.
524 * @return the network errors that occured until now, indexed by URL
525 * @since 6639
526 */
527 public static Map<String, Throwable> getNetworkErrors() {
528 return new HashMap<>(NETWORK_ERRORS);
529 }
530
531 /**
532 * Clears the network errors cache.
533 * @since 12011
534 */
535 public static void clearNetworkErrors() {
536 NETWORK_ERRORS.clear();
537 }
538
539 /**
540 * Returns the JOSM website URL.
541 * @return the josm website URL
542 * @since 6897
543 */
544 public static String getJOSMWebsite() {
545 if (Config.getPref() != null)
546 return Config.getPref().get("josm.url", JOSM_WEBSITE);
547 return JOSM_WEBSITE;
548 }
549
550 /**
551 * Returns the JOSM XML URL.
552 * @return the josm XML URL
553 * @since 6897
554 */
555 public static String getXMLBase() {
556 // Always return HTTP (issues reported with HTTPS)
557 return "http://josm.openstreetmap.de";
558 }
559
560 /**
561 * Returns the OSM website URL.
562 * @return the OSM website URL
563 * @since 6897
564 */
565 public static String getOSMWebsite() {
566 if (Config.getPref() != null)
567 return Config.getPref().get("osm.url", OSM_WEBSITE);
568 return OSM_WEBSITE;
569 }
570
571 /**
572 * Returns the OSM website URL depending on the selected {@link OsmApi}.
573 * @return the OSM website URL depending on the selected {@link OsmApi}
574 */
575 private static String getOSMWebsiteDependingOnSelectedApi() {
576 final String api = OsmApi.getOsmApi().getServerUrl();
577 if (OsmApi.DEFAULT_API_URL.equals(api)) {
578 return getOSMWebsite();
579 } else {
580 return api.replaceAll("/api$", "");
581 }
582 }
583
584 /**
585 * Replies the base URL for browsing information about a primitive.
586 * @return the base URL, i.e. https://www.openstreetmap.org
587 * @since 7678
588 */
589 public static String getBaseBrowseUrl() {
590 if (Config.getPref() != null)
591 return Config.getPref().get("osm-browse.url", getOSMWebsiteDependingOnSelectedApi());
592 return getOSMWebsiteDependingOnSelectedApi();
593 }
594
595 /**
596 * Replies the base URL for browsing information about a user.
597 * @return the base URL, i.e. https://www.openstreetmap.org/user
598 * @since 7678
599 */
600 public static String getBaseUserUrl() {
601 if (Config.getPref() != null)
602 return Config.getPref().get("osm-user.url", getOSMWebsiteDependingOnSelectedApi() + "/user");
603 return getOSMWebsiteDependingOnSelectedApi() + "/user";
604 }
605
606 /**
607 * Determines if we are currently running on OSX.
608 * @return {@code true} if we are currently running on OSX
609 * @since 6957
610 */
611 public static boolean isPlatformOsx() {
612 return Main.platform instanceof PlatformHookOsx;
613 }
614
615 /**
616 * Determines if we are currently running on Windows.
617 * @return {@code true} if we are currently running on Windows
618 * @since 7335
619 */
620 public static boolean isPlatformWindows() {
621 return Main.platform instanceof PlatformHookWindows;
622 }
623
624 /**
625 * Determines if the given online resource is currently offline.
626 * @param r the online resource
627 * @return {@code true} if {@code r} is offline and should not be accessed
628 * @since 7434
629 */
630 public static boolean isOffline(OnlineResource r) {
631 return OFFLINE_RESOURCES.contains(r) || OFFLINE_RESOURCES.contains(OnlineResource.ALL);
632 }
633
634 /**
635 * Sets the given online resource to offline state.
636 * @param r the online resource
637 * @return {@code true} if {@code r} was not already offline
638 * @since 7434
639 */
640 public static boolean setOffline(OnlineResource r) {
641 return OFFLINE_RESOURCES.add(r);
642 }
643
644 /**
645 * Sets the given online resource to online state.
646 * @param r the online resource
647 * @return {@code true} if {@code r} was offline
648 * @since 8506
649 */
650 public static boolean setOnline(OnlineResource r) {
651 return OFFLINE_RESOURCES.remove(r);
652 }
653
654 /**
655 * Replies the set of online resources currently offline.
656 * @return the set of online resources currently offline
657 * @since 7434
658 */
659 public static Set<OnlineResource> getOfflineResources() {
660 return EnumSet.copyOf(OFFLINE_RESOURCES);
661 }
662}
Note: See TracBrowser for help on using the repository browser.