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

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

see #8039, see #10456 - support read-only data layers

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