[6380] | 1 | // License: GPL. For details, see LICENSE file.
|
---|
[626] | 2 | package org.openstreetmap.josm;
|
---|
[5670] | 3 |
|
---|
[301] | 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
---|
| 5 |
|
---|
| 6 | import java.awt.Component;
|
---|
[11106] | 7 | import java.awt.GraphicsEnvironment;
|
---|
[9821] | 8 | import java.io.IOException;
|
---|
[4126] | 9 | import java.lang.ref.WeakReference;
|
---|
[6471] | 10 | import java.net.URL;
|
---|
[13838] | 11 | import java.nio.file.InvalidPathException;
|
---|
[301] | 12 | import java.util.Collection;
|
---|
[11986] | 13 | import java.util.Collections;
|
---|
[10044] | 14 | import java.util.EnumSet;
|
---|
[6642] | 15 | import java.util.HashMap;
|
---|
[2025] | 16 | import java.util.List;
|
---|
[301] | 17 | import java.util.Map;
|
---|
[7083] | 18 | import java.util.Objects;
|
---|
[7434] | 19 | import java.util.Set;
|
---|
[5134] | 20 | import java.util.concurrent.Callable;
|
---|
[13316] | 21 | import java.util.concurrent.CopyOnWriteArrayList;
|
---|
[10212] | 22 | import java.util.concurrent.ExecutionException;
|
---|
[1465] | 23 | import java.util.concurrent.ExecutorService;
|
---|
[5134] | 24 | import java.util.concurrent.Executors;
|
---|
[2322] | 25 | import java.util.concurrent.Future;
|
---|
[301] | 26 |
|
---|
| 27 | import org.openstreetmap.josm.data.Bounds;
|
---|
| 28 | import org.openstreetmap.josm.data.Preferences;
|
---|
| 29 | import org.openstreetmap.josm.data.UndoRedoHandler;
|
---|
[12735] | 30 | import org.openstreetmap.josm.data.coor.conversion.CoordinateFormatManager;
|
---|
| 31 | import org.openstreetmap.josm.data.coor.conversion.DecimalDegreesCoordinateFormat;
|
---|
| 32 | import org.openstreetmap.josm.data.coor.conversion.ICoordinateFormat;
|
---|
[301] | 33 | import org.openstreetmap.josm.data.osm.DataSet;
|
---|
[13926] | 34 | import org.openstreetmap.josm.data.osm.IPrimitive;
|
---|
| 35 | import org.openstreetmap.josm.data.osm.OsmData;
|
---|
[6546] | 36 | import org.openstreetmap.josm.data.osm.OsmPrimitive;
|
---|
[13023] | 37 | import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
|
---|
[301] | 38 | import org.openstreetmap.josm.data.projection.Projection;
|
---|
[4126] | 39 | import org.openstreetmap.josm.data.projection.ProjectionChangeListener;
|
---|
[7185] | 40 | import org.openstreetmap.josm.io.FileWatcher;
|
---|
[7434] | 41 | import org.openstreetmap.josm.io.OnlineResource;
|
---|
[3934] | 42 | import org.openstreetmap.josm.io.OsmApi;
|
---|
[12846] | 43 | import org.openstreetmap.josm.spi.preferences.Config;
|
---|
[4126] | 44 | import org.openstreetmap.josm.tools.CheckParameterUtil;
|
---|
[301] | 45 | import org.openstreetmap.josm.tools.ImageProvider;
|
---|
[11374] | 46 | import org.openstreetmap.josm.tools.JosmRuntimeException;
|
---|
[10899] | 47 | import org.openstreetmap.josm.tools.Logging;
|
---|
[12776] | 48 | import org.openstreetmap.josm.tools.Platform;
|
---|
[1023] | 49 | import org.openstreetmap.josm.tools.PlatformHook;
|
---|
[1465] | 50 | import org.openstreetmap.josm.tools.PlatformHookOsx;
|
---|
[1023] | 51 | import org.openstreetmap.josm.tools.PlatformHookWindows;
|
---|
[4288] | 52 | import org.openstreetmap.josm.tools.Utils;
|
---|
[11642] | 53 | import 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] | 59 | public 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 | }
|
---|