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

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

add AbstractOsmDataLayer, MainLayerManager.getActiveData, Main.getInProgressISelection

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