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

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

findbugs - NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE

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