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

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

see #15310 - remove most of deprecated APIs

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