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

Last change on this file since 13963 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
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;
[13838]11import java.nio.file.InvalidPathException;
[301]12import java.util.Collection;
[11986]13import java.util.Collections;
[10044]14import java.util.EnumSet;
[6642]15import java.util.HashMap;
[2025]16import java.util.List;
[301]17import java.util.Map;
[7083]18import java.util.Objects;
[7434]19import java.util.Set;
[5134]20import java.util.concurrent.Callable;
[13316]21import java.util.concurrent.CopyOnWriteArrayList;
[10212]22import java.util.concurrent.ExecutionException;
[1465]23import java.util.concurrent.ExecutorService;
[5134]24import java.util.concurrent.Executors;
[2322]25import java.util.concurrent.Future;
[301]26
27import org.openstreetmap.josm.data.Bounds;
28import org.openstreetmap.josm.data.Preferences;
29import org.openstreetmap.josm.data.UndoRedoHandler;
[12735]30import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
31import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
32import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat;
[301]33import org.openstreetmap.josm.data.osm.DataSet;
[13926]34import org.openstreetmap.josm.data.osm.IPrimitive;
35import org.openstreetmap.josm.data.osm.OsmData;
[6546]36import org.openstreetmap.josm.data.osm.OsmPrimitive;
[13023]37import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
[301]38import org.openstreetmap.josm.data.projection.Projection;
[4126]39import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
[7185]40import org.openstreetmap.josm.io.FileWatcher;
[7434]41import org.openstreetmap.josm.io.OnlineResource;
[3934]42import org.openstreetmap.josm.io.OsmApi;
[12846]43import org.openstreetmap.josm.spi.preferences.Config;
[4126]44import org.openstreetmap.josm.tools.CheckParameterUtil;
[301]45import org.openstreetmap.josm.tools.ImageProvider;
[11374]46import org.openstreetmap.josm.tools.JosmRuntimeException;
[10899]47import org.openstreetmap.josm.tools.Logging;
[12776]48import org.openstreetmap.josm.tools.Platform;
[1023]49import org.openstreetmap.josm.tools.PlatformHook;
[1465]50import org.openstreetmap.josm.tools.PlatformHookOsx;
[1023]51import org.openstreetmap.josm.tools.PlatformHookWindows;
[4288]52import org.openstreetmap.josm.tools.Utils;
[11642]53import org.openstreetmap.josm.tools.bugreport.BugReport;
[626]54
[5829]55/**
56 * Abstract class holding various static global variables and methods used in large parts of JOSM application.
57 * @since 98
58 */
[6883]59public abstract class Main {
[2485]60
[1104]61 /**
[6143]62 * The JOSM website URL.
[6897]63 * @since 6897 (was public from 6143 to 6896)
[6143]64 */
[6920]65 private static final String JOSM_WEBSITE = "https://josm.openstreetmap.de";
[6453]66
[6143]67 /**
[6453]68 * The OSM website URL.
[6897]69 * @since 6897 (was public from 6453 to 6896)
[6453]70 */
[6920]71 private static final String OSM_WEBSITE = "https://www.openstreetmap.org";
[6453]72
73 /**
[1104]74 * Global parent component for all dialogs and message boxes
75 */
76 public static Component parent;
[6070]77
[1104]78 /**
79 * Global application.
80 */
[8126]81 public static volatile Main main;
[6070]82
[1104]83 /**
84 * Global application preferences
85 */
[13023]86 public static final Preferences pref = new Preferences(JosmBaseDirectories.getInstance());
[1814]87
[1104]88 /**
[5829]89 * The commands undo/redo handler.
90 */
[12718]91 public final UndoRedoHandler undoRedo = new UndoRedoHandler();
[626]92
[5831]93 /**
[7185]94 * The file watcher service.
95 */
96 public static final FileWatcher fileWatcher = new FileWatcher();
97
[11650]98 private static final Map<String, Throwable> NETWORK_ERRORS = new HashMap<>();
[6642]99
[10044]100 private static final Set<OnlineResource> OFFLINE_RESOURCES = EnumSet.noneOf(OnlineResource.class);
[7434]101
[1356]102 /**
[1104]103 * Platform specific code goes in here.
[12641]104 * Plugins may replace it, however, some hooks will be called before any plugins have been loaded.
[1104]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 */
[8126]108 public static volatile PlatformHook platform;
[1023]109
[8840]110 private static volatile InitStatusListener initListener;
[4681]111
[11352]112 /**
113 * Initialization task listener.
114 */
[8512]115 public interface InitStatusListener {
[4681]116
[11352]117 /**
118 * Called when an initialization task updates its status.
119 * @param event task name
120 * @return new status
121 */
[8497]122 Object updateStatus(String event);
[8510]123
[11352]124 /**
125 * Called when an initialization task completes.
126 * @param status final status
127 */
[8497]128 void finish(Object status);
[4681]129 }
130
[11352]131 /**
132 * Sets initialization task listener.
133 * @param listener initialization task listener
134 */
[4681]135 public static void setInitStatusListener(InitStatusListener listener) {
[8497]136 CheckParameterUtil.ensureParameterNotNull(listener);
[4681]137 initListener = listener;
138 }
139
[6523]140 /**
[10340]141 * Constructs new {@code Main} object.
142 * @see #initialize()
[6523]143 */
[11656]144 protected Main() {
145 setInstance(this);
[10340]146 }
147
[11656]148 private static void setInstance(Main instance) {
149 main = instance;
150 }
151
[10340]152 /**
153 * Initializes the main object. A lot of global variables are initialized here.
154 * @since 10340
155 */
156 public void initialize() {
[12629]157 // Initializes tasks that must be run before parallel tasks
158 runInitializationTasks(beforeInitializationTasks());
[4681]159
[12629]160 // Initializes tasks to be executed (in parallel) by a ExecutorService
[11642]161 try {
[10899]162 ExecutorService service = Executors.newFixedThreadPool(
[8734]163 Runtime.getRuntime().availableProcessors(), Utils.newThreadFactory("main-init-%d", Thread.NORM_PRIORITY));
[12629]164 for (Future<Void> i : service.invokeAll(parallelInitializationTasks())) {
[5195]165 i.get();
166 }
[11092]167 // asynchronous initializations to be completed eventually
[12629]168 asynchronousRunnableTasks().forEach(service::submit);
169 asynchronousCallableTasks().forEach(service::submit);
[13647]170 try {
171 service.shutdown();
172 } catch (SecurityException e) {
173 Logging.log(Logging.LEVEL_ERROR, "Unable to shutdown executor service", e);
174 }
[10212]175 } catch (InterruptedException | ExecutionException ex) {
[11374]176 throw new JosmRuntimeException(ex);
[4718]177 }
[3669]178
[12629]179 // Initializes tasks that must be run after parallel tasks
180 runInitializationTasks(afterInitializationTasks());
181 }
[4489]182
[12629]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 }
[1104]193 }
[626]194
[10340]195 /**
[12629]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> {
[6779]240
241 private final String name;
[10936]242 private final Runnable task;
[6779]243
[12629]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) {
[6779]250 this.name = name;
[10899]251 this.task = task;
[6779]252 }
253
254 @Override
[8497]255 public Void call() {
256 Object status = null;
[6779]257 if (initListener != null) {
[8497]258 status = initListener.updateStatus(name);
[6779]259 }
[10899]260 task.run();
[8497]261 if (initListener != null) {
262 initListener.finish(status);
[6852]263 }
[6779]264 return null;
265 }
266 }
267
[1104]268 /**
[13926]269 * Replies the current selected OSM primitives, from a end-user point of view.
[6546]270 * It is not always technically the same collection of primitives than {@link DataSet#getSelected()}.
[13926]271 * @return The current selected OSM primitives, from a end-user point of view. Can be {@code null}.
[6546]272 * @since 6546
273 */
274 public Collection<OsmPrimitive> getInProgressSelection() {
[12636]275 return Collections.emptyList();
[6546]276 }
[1750]277
[11352]278 /**
[13926]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 /**
[13434]289 * Gets the active edit data set (not read-only).
[12718]290 * @return That data set, <code>null</code>.
[13434]291 * @see #getActiveDataSet
[12691]292 * @since 12691
293 */
[12718]294 public abstract DataSet getEditDataSet();
[12691]295
296 /**
[13434]297 * Gets the active data set (can be read-only).
298 * @return That data set, <code>null</code>.
299 * @see #getEditDataSet
300 * @since 13434
[12718]301 */
[13434]302 public abstract DataSet getActiveDataSet();
[12718]303
304 /**
[13434]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 /**
[12718]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
[1104]319 ///////////////////////////////////////////////////////////////////////////
320 // Implementation part
321 ///////////////////////////////////////////////////////////////////////////
[626]322
[5829]323 /**
[1104]324 * Should be called before the main constructor to setup some parameter stuff
325 */
[10962]326 public static void preConstructorInit() {
[1990]327 // init default coordinate format
[12846]328 ICoordinateFormat fmt = CoordinateFormatManager.getCoordinateFormat(Config.getPref().get("coordinates"));
[12735]329 if (fmt == null) {
330 fmt = DecimalDegreesCoordinateFormat.INSTANCE;
[1990]331 }
[12735]332 CoordinateFormatManager.setCoordinateFormat(fmt);
[1104]333 }
[626]334
[11986]335 /**
[8412]336 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
[6342]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
[12636]339 * @return {@code true}
340 * @since 12636
[5829]341 */
[12636]342 public static boolean exitJosm(boolean exit, int exitCode) {
343 if (Main.main != null) {
344 Main.main.shutdown();
345 }
[8412]346
[12636]347 if (exit) {
348 System.exit(exitCode);
[5829]349 }
[12636]350 return true;
[3378]351 }
352
[11986]353 /**
354 * Shutdown JOSM.
355 */
[10340]356 protected void shutdown() {
[11106]357 if (!GraphicsEnvironment.isHeadless()) {
358 ImageProvider.shutdown(false);
359 }
[10340]360 try {
361 pref.saveDefaults();
[13838]362 } catch (IOException | InvalidPathException ex) {
[12620]363 Logging.log(Logging.LEVEL_WARN, tr("Failed to save default preferences."), ex);
[10340]364 }
[11106]365 if (!GraphicsEnvironment.isHeadless()) {
366 ImageProvider.shutdown(true);
367 }
[10340]368 }
369
[2798]370 /**
[5829]371 * Identifies the current operating system family and initializes the platform hook accordingly.
372 * @since 1849
373 */
[1849]374 public static void determinePlatformHook() {
[12776]375 platform = Platform.determinePlatform().accept(PlatformHook.CONSTRUCT_FROM_PLATFORM);
[1104]376 }
[1023]377
[4126]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.
[12631]386 * Use {@link #getProjection()} and {@link #setProjection(Projection)} for access.
[4897]387 * Use {@link #setProjection(Projection)} in order to trigger a projection change event.
[4126]388 */
[8126]389 private static volatile Projection proj;
[4126]390
391 /**
392 * Replies the current projection.
[4489]393 *
[5357]394 * @return the currently active projection
[4126]395 */
396 public static Projection getProjection() {
397 return proj;
398 }
399
400 /**
401 * Sets the current projection
[4489]402 *
[4126]403 * @param p the projection
404 */
405 public static void setProjection(Projection p) {
406 CheckParameterUtil.ensureParameterNotNull(p);
407 Projection oldValue = proj;
[12631]408 Bounds b = main != null ? main.getRealBounds() : null;
[4126]409 proj = p;
[5234]410 fireProjectionChanged(oldValue, proj, b);
[4126]411 }
412
[12631]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
[4126]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 */
[13316]437 private static final List<WeakReference<ProjectionChangeListener>> listeners = new CopyOnWriteArrayList<>();
[4126]438
[5234]439 private static void fireProjectionChanged(Projection oldValue, Projection newValue, Bounds oldBounds) {
[10655]440 if ((newValue == null ^ oldValue == null)
[7083]441 || (newValue != null && oldValue != null && !Objects.equals(newValue.toCode(), oldValue.toCode()))) {
[13319]442 listeners.removeIf(x -> x.get() == null);
[13326]443 listeners.stream().map(WeakReference::get).filter(Objects::nonNull).forEach(x -> x.projectionChanged(oldValue, newValue));
[12631]444 if (newValue != null && oldBounds != null && main != null) {
445 main.restoreOldBounds(oldBounds);
[4126]446 }
447 /* TODO - remove layers with fixed projection */
448 }
449 }
450
451 /**
[5538]452 * Register a projection change listener.
[12119]453 * The listener is registered to be weak, so keep a reference of it if you want it to be preserved.
[4489]454 *
[5357]455 * @param listener the listener. Ignored if <code>null</code>.
[4126]456 */
457 public static void addProjectionChangeListener(ProjectionChangeListener listener) {
458 if (listener == null) return;
[13322]459 for (WeakReference<ProjectionChangeListener> wr : listeners) {
460 // already registered ? => abort
461 if (wr.get() == listener) return;
[4126]462 }
[13322]463 listeners.add(new WeakReference<>(listener));
[4126]464 }
465
466 /**
[5538]467 * Removes a projection change listener.
[4489]468 *
[5357]469 * @param listener the listener. Ignored if <code>null</code>.
[4126]470 */
471 public static void removeProjectionChangeListener(ProjectionChangeListener listener) {
472 if (listener == null) return;
[13322]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);
[4126]475 }
[5538]476
477 /**
[13322]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 /**
[6642]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.
[6852]488 *
[6642]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}
[6643]492 * @since 6642
[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) {
[12620]498 Logging.warn("Already here "+old);
[6642]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.
[6852]508 *
[6642]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}
[6643]512 * @since 6642
[6642]513 */
514 public static Throwable addNetworkError(String url, Throwable t) {
515 if (url != null && t != null) {
[6806]516 return NETWORK_ERRORS.put(url, t);
[6642]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() {
[7005]527 return new HashMap<>(NETWORK_ERRORS);
[6642]528 }
[6897]529
530 /**
[12011]531 * Clears the network errors cache.
532 * @since 12011
533 */
534 public static void clearNetworkErrors() {
535 NETWORK_ERRORS.clear();
536 }
537
538 /**
[6897]539 * Returns the JOSM website URL.
540 * @return the josm website URL
541 * @since 6897
542 */
543 public static String getJOSMWebsite() {
[12846]544 if (Config.getPref() != null)
545 return Config.getPref().get("josm.url", JOSM_WEBSITE);
[6897]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() {
[6920]555 // Always return HTTP (issues reported with HTTPS)
[6900]556 return "http://josm.openstreetmap.de";
[6897]557 }
558
559 /**
560 * Returns the OSM website URL.
561 * @return the OSM website URL
562 * @since 6897
563 */
564 public static String getOSMWebsite() {
[12846]565 if (Config.getPref() != null)
566 return Config.getPref().get("osm.url", OSM_WEBSITE);
[6897]567 return OSM_WEBSITE;
568 }
[6957]569
570 /**
[11262]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 /**
[7678]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() {
[12846]589 if (Config.getPref() != null)
590 return Config.getPref().get("osm-browse.url", getOSMWebsiteDependingOnSelectedApi());
[11262]591 return getOSMWebsiteDependingOnSelectedApi();
[7678]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() {
[12846]600 if (Config.getPref() != null)
601 return Config.getPref().get("osm-user.url", getOSMWebsiteDependingOnSelectedApi() + "/user");
[11262]602 return getOSMWebsiteDependingOnSelectedApi() + "/user";
[7678]603 }
604
605 /**
[6957]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 }
[7335]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 }
[7434]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 /**
[8506]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 /**
[7434]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() {
[10044]659 return EnumSet.copyOf(OFFLINE_RESOURCES);
[7434]660 }
[626]661}
Note: See TracBrowser for help on using the repository browser.