source: josm/trunk/src/org/openstreetmap/josm/gui/MainApplication.java@ 18985

Last change on this file since 18985 was 18985, checked in by taylor.smock, 4 months ago

Fix #23355: Sanity check JVM arguments on startup

See #17858: JOSM will no longer continue running if the user is on an unsupported
Java version (for this commit, older than Java 11; message indicates Java 17).
This does update the link for Azul from Java 17 to Java 21 as well.

In order to (hopefully) reduce confusion, the webstart and Java update nags will
also be reset in the event that JOSM will exit due to old Java versions. This is
mostly so that users will get the messages to update to OpenWebstart or the
appropriate Java link for their platform and architecture.

Additionally, this will (hopefully) reduce the number of tickets we have to close
due to missing JVM arguments by informing users of the missing arguments at startup.

  • Property svn:eol-style set to native
File size: 71.6 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6import static org.openstreetmap.josm.tools.Utils.getSystemProperty;
7
8import java.awt.AWTError;
9import java.awt.Color;
10import java.awt.Container;
11import java.awt.Dimension;
12import java.awt.Font;
13import java.awt.GraphicsEnvironment;
14import java.awt.GridBagLayout;
15import java.awt.RenderingHints;
16import java.awt.Toolkit;
17import java.io.File;
18import java.io.IOException;
19import java.io.InputStream;
20import java.lang.reflect.Field;
21import java.net.Authenticator;
22import java.net.Inet6Address;
23import java.net.InetAddress;
24import java.net.ProxySelector;
25import java.net.URL;
26import java.nio.file.InvalidPathException;
27import java.nio.file.Paths;
28import java.security.AllPermission;
29import java.security.CodeSource;
30import java.security.GeneralSecurityException;
31import java.security.PermissionCollection;
32import java.security.Permissions;
33import java.security.Policy;
34import java.util.ArrayList;
35import java.util.Arrays;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.List;
39import java.util.Locale;
40import java.util.Map;
41import java.util.Objects;
42import java.util.Optional;
43import java.util.ResourceBundle;
44import java.util.Set;
45import java.util.TreeSet;
46import java.util.concurrent.ExecutorService;
47import java.util.concurrent.Executors;
48import java.util.concurrent.Future;
49import java.util.logging.Level;
50import java.util.stream.Collectors;
51import java.util.stream.Stream;
52
53import javax.net.ssl.SSLSocketFactory;
54import javax.swing.Action;
55import javax.swing.InputMap;
56import javax.swing.JComponent;
57import javax.swing.JLabel;
58import javax.swing.JOptionPane;
59import javax.swing.JPanel;
60import javax.swing.JTextPane;
61import javax.swing.KeyStroke;
62import javax.swing.LookAndFeel;
63import javax.swing.RepaintManager;
64import javax.swing.SwingUtilities;
65import javax.swing.UIManager;
66import javax.swing.UnsupportedLookAndFeelException;
67import javax.swing.plaf.FontUIResource;
68
69import org.openstreetmap.josm.actions.DeleteAction;
70import org.openstreetmap.josm.actions.JosmAction;
71import org.openstreetmap.josm.actions.OpenFileAction;
72import org.openstreetmap.josm.actions.OpenFileAction.OpenFileTask;
73import org.openstreetmap.josm.actions.PreferencesAction;
74import org.openstreetmap.josm.actions.RestartAction;
75import org.openstreetmap.josm.actions.ShowStatusReportAction;
76import org.openstreetmap.josm.actions.downloadtasks.DownloadGpsTask;
77import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask;
78import org.openstreetmap.josm.actions.downloadtasks.DownloadParams;
79import org.openstreetmap.josm.actions.downloadtasks.DownloadTask;
80import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler;
81import org.openstreetmap.josm.actions.search.SearchAction;
82import org.openstreetmap.josm.cli.CLIModule;
83import org.openstreetmap.josm.command.DeleteCommand;
84import org.openstreetmap.josm.command.SplitWayCommand;
85import org.openstreetmap.josm.data.Bounds;
86import org.openstreetmap.josm.data.Preferences;
87import org.openstreetmap.josm.data.UndoRedoHandler;
88import org.openstreetmap.josm.data.UndoRedoHandler.CommandQueueListener;
89import org.openstreetmap.josm.data.Version;
90import org.openstreetmap.josm.data.oauth.OAuthAccessTokenHolder;
91import org.openstreetmap.josm.data.osm.UserInfo;
92import org.openstreetmap.josm.data.osm.search.SearchMode;
93import org.openstreetmap.josm.data.preferences.JosmBaseDirectories;
94import org.openstreetmap.josm.data.preferences.JosmUrls;
95import org.openstreetmap.josm.data.preferences.sources.SourceType;
96import org.openstreetmap.josm.data.projection.ProjectionBoundsProvider;
97import org.openstreetmap.josm.data.projection.ProjectionCLI;
98import org.openstreetmap.josm.data.projection.ProjectionRegistry;
99import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileSource;
100import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
101import org.openstreetmap.josm.data.projection.datum.NTV2Proj4DirGridShiftFileSource;
102import org.openstreetmap.josm.data.validation.ValidatorCLI;
103import org.openstreetmap.josm.data.validation.tests.MapCSSTagChecker;
104import org.openstreetmap.josm.gui.ProgramArguments.Option;
105import org.openstreetmap.josm.gui.SplashScreen.SplashProgressMonitor;
106import org.openstreetmap.josm.gui.bugreport.BugReportDialog;
107import org.openstreetmap.josm.gui.bugreport.DefaultBugReportSendingHandler;
108import org.openstreetmap.josm.gui.download.DownloadDialog;
109import org.openstreetmap.josm.gui.io.CredentialDialog;
110import org.openstreetmap.josm.gui.io.CustomConfigurator.XMLCommandProcessor;
111import org.openstreetmap.josm.gui.io.SaveLayersDialog;
112import org.openstreetmap.josm.gui.io.importexport.Options;
113import org.openstreetmap.josm.gui.layer.AutosaveTask;
114import org.openstreetmap.josm.gui.layer.Layer;
115import org.openstreetmap.josm.gui.layer.LayerManager.LayerAddEvent;
116import org.openstreetmap.josm.gui.layer.LayerManager.LayerChangeListener;
117import org.openstreetmap.josm.gui.layer.LayerManager.LayerOrderChangeEvent;
118import org.openstreetmap.josm.gui.layer.LayerManager.LayerRemoveEvent;
119import org.openstreetmap.josm.gui.layer.MainLayerManager;
120import org.openstreetmap.josm.gui.layer.OsmDataLayer;
121import org.openstreetmap.josm.gui.mappaint.RenderingCLI;
122import org.openstreetmap.josm.gui.mappaint.loader.MapPaintStyleLoader;
123import org.openstreetmap.josm.gui.oauth.OAuthAuthorizationWizard;
124import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
125import org.openstreetmap.josm.gui.preferences.display.LafPreference;
126import org.openstreetmap.josm.gui.preferences.projection.ProjectionPreference;
127import org.openstreetmap.josm.gui.preferences.server.ProxyPreference;
128import org.openstreetmap.josm.gui.progress.swing.ProgressMonitorExecutor;
129import org.openstreetmap.josm.gui.util.CheckThreadViolationRepaintManager;
130import org.openstreetmap.josm.gui.util.GuiHelper;
131import org.openstreetmap.josm.gui.util.RedirectInputMap;
132import org.openstreetmap.josm.gui.util.WindowGeometry;
133import org.openstreetmap.josm.gui.widgets.TextContextualPopupMenu;
134import org.openstreetmap.josm.gui.widgets.UrlLabel;
135import org.openstreetmap.josm.io.CachedFile;
136import org.openstreetmap.josm.io.CertificateAmendment;
137import org.openstreetmap.josm.io.ChangesetUpdater;
138import org.openstreetmap.josm.io.DefaultProxySelector;
139import org.openstreetmap.josm.io.FileWatcher;
140import org.openstreetmap.josm.io.MessageNotifier;
141import org.openstreetmap.josm.io.NetworkManager;
142import org.openstreetmap.josm.io.OnlineResource;
143import org.openstreetmap.josm.io.OsmConnection;
144import org.openstreetmap.josm.io.OsmTransferException;
145import org.openstreetmap.josm.io.auth.AbstractCredentialsAgent;
146import org.openstreetmap.josm.io.auth.CredentialsManager;
147import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
148import org.openstreetmap.josm.io.protocols.data.Handler;
149import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
150import org.openstreetmap.josm.plugins.PluginHandler;
151import org.openstreetmap.josm.plugins.PluginInformation;
152import org.openstreetmap.josm.spi.lifecycle.InitStatusListener;
153import org.openstreetmap.josm.spi.lifecycle.Lifecycle;
154import org.openstreetmap.josm.spi.preferences.Config;
155import org.openstreetmap.josm.tools.FontsManager;
156import org.openstreetmap.josm.tools.GBC;
157import org.openstreetmap.josm.tools.Http1Client;
158import org.openstreetmap.josm.tools.HttpClient;
159import org.openstreetmap.josm.tools.I18n;
160import org.openstreetmap.josm.tools.ImageProvider;
161import org.openstreetmap.josm.tools.JosmRuntimeException;
162import org.openstreetmap.josm.tools.Logging;
163import org.openstreetmap.josm.tools.OsmUrlToBounds;
164import org.openstreetmap.josm.tools.PlatformHook.NativeOsCallback;
165import org.openstreetmap.josm.tools.PlatformHookWindows;
166import org.openstreetmap.josm.tools.PlatformManager;
167import org.openstreetmap.josm.tools.ReflectionUtils;
168import org.openstreetmap.josm.tools.Shortcut;
169import org.openstreetmap.josm.tools.Utils;
170import org.openstreetmap.josm.tools.bugreport.BugReportExceptionHandler;
171import org.openstreetmap.josm.tools.bugreport.BugReportQueue;
172import org.openstreetmap.josm.tools.bugreport.BugReportSender;
173import org.xml.sax.SAXException;
174
175/**
176 * Main window class application.
177 *
178 * @author imi
179 */
180public class MainApplication {
181
182 /**
183 * Command-line arguments used to run the application.
184 */
185 private static volatile List<String> commandLineArgs;
186
187 /**
188 * The main menu bar at top of screen.
189 */
190 static MainMenu menu;
191
192 /**
193 * The main panel, required to be static for {@link MapFrameListener} handling.
194 */
195 static MainPanel mainPanel;
196
197 /**
198 * The private content pane of {@link MainFrame}, required to be static for shortcut handling.
199 */
200 static JComponent contentPanePrivate;
201
202 /**
203 * The MapFrame.
204 */
205 static MapFrame map;
206
207 /**
208 * The toolbar preference control to register new actions.
209 */
210 static volatile ToolbarPreferences toolbar;
211
212 private static MainFrame mainFrame;
213
214 /**
215 * The worker thread slave. This is for executing all long and intensive
216 * calculations. The executed runnables are guaranteed to be executed separately and sequential.
217 * @since 12634 (as a replacement to {@code Main.worker})
218 */
219 public static final ExecutorService worker = new ProgressMonitorExecutor("main-worker-%d", Thread.NORM_PRIORITY);
220
221 /**
222 * Provides access to the layers displayed in the main view.
223 */
224 private static final MainLayerManager layerManager = new MainLayerManager();
225
226 private static final LayerChangeListener undoRedoCleaner = new LayerChangeListener() {
227 @Override
228 public void layerRemoving(LayerRemoveEvent e) {
229 Layer layer = e.getRemovedLayer();
230 if (layer instanceof OsmDataLayer) {
231 UndoRedoHandler.getInstance().clean(((OsmDataLayer) layer).getDataSet());
232 }
233 }
234
235 @Override
236 public void layerOrderChanged(LayerOrderChangeEvent e) {
237 // Do nothing
238 }
239
240 @Override
241 public void layerAdded(LayerAddEvent e) {
242 // Do nothing
243 }
244 };
245
246 private static final ProjectionBoundsProvider mainBoundsProvider = new ProjectionBoundsProvider() {
247 @Override
248 public Bounds getRealBounds() {
249 return isDisplayingMapView() ? map.mapView.getRealBounds() : null;
250 }
251
252 @Override
253 public void restoreOldBounds(Bounds oldBounds) {
254 if (isDisplayingMapView()) {
255 map.mapView.zoomTo(oldBounds);
256 }
257 }
258 };
259
260 private static final List<CLIModule> cliModules = new ArrayList<>();
261
262 /**
263 * Default JOSM command line interface.
264 * <p>
265 * Runs JOSM and performs some action, depending on the options and positional
266 * arguments.
267 */
268 public static final CLIModule JOSM_CLI_MODULE = new CLIModule() {
269 @Override
270 public String getActionKeyword() {
271 return "runjosm";
272 }
273
274 @Override
275 public void processArguments(String[] argArray) {
276 try {
277 // construct argument table
278 ProgramArguments args = new ProgramArguments(argArray);
279 mainJOSM(args);
280 } catch (IllegalArgumentException e) {
281 System.err.println(e.getMessage());
282 Lifecycle.exitJosm(true, 1);
283 }
284 }
285 };
286
287 /**
288 * Listener that sets the enabled state of undo/redo menu entries.
289 */
290 final CommandQueueListener redoUndoListener = (queueSize, redoSize) -> {
291 menu.undo.setEnabled(queueSize > 0);
292 menu.redo.setEnabled(redoSize > 0);
293 };
294
295 /**
296 * Source of NTV2 shift files: Download from JOSM website.
297 * @since 12777
298 */
299 public static final NTV2GridShiftFileSource JOSM_WEBSITE_NTV2_SOURCE = gridFileName -> {
300 String location = Config.getUrls().getJOSMWebsite() + "/proj/" + gridFileName;
301 // Try to load grid file
302 @SuppressWarnings("resource")
303 CachedFile cf = new CachedFile(location);
304 try {
305 return cf.getInputStream();
306 } catch (IOException ex) {
307 Logging.warn(ex);
308 return null;
309 }
310 };
311
312 static {
313 registerCLIModule(JOSM_CLI_MODULE);
314 registerCLIModule(ProjectionCLI.INSTANCE);
315 registerCLIModule(RenderingCLI.INSTANCE);
316 registerCLIModule(ValidatorCLI.INSTANCE);
317 }
318
319 /**
320 * Register a command line interface module.
321 * @param module the module
322 * @since 12886
323 */
324 public static void registerCLIModule(CLIModule module) {
325 cliModules.add(module);
326 }
327
328 /**
329 * Constructs a new {@code MainApplication} without a window.
330 */
331 public MainApplication() {
332 this(null);
333 }
334
335 /**
336 * Constructs a main frame, ready sized and operating. Does not display the frame.
337 * @param mainFrame The main JFrame of the application
338 * @since 10340
339 */
340 @SuppressWarnings("StaticAssignmentInConstructor")
341 public MainApplication(MainFrame mainFrame) {
342 MainApplication.mainFrame = mainFrame;
343 getLayerManager().addLayerChangeListener(undoRedoCleaner);
344 ProjectionRegistry.setboundsProvider(mainBoundsProvider);
345 Lifecycle.setShutdownSequence(new MainTermination());
346 }
347
348 private static void askUpdate(String title, String update, String property, String icon, StringBuilder content, String url) {
349 ExtendedDialog ed = new ExtendedDialog(mainFrame, title, tr("OK"), update, tr("Cancel"));
350 // Check if the dialog has not already been permanently hidden by user
351 if (!ed.toggleEnable(property).toggleCheckState()) {
352 ed.setButtonIcons("ok", icon, "cancel").setCancelButton(3);
353 ed.setMinimumSize(new Dimension(480, 300));
354 ed.setIcon(JOptionPane.WARNING_MESSAGE);
355 ed.setContent(content.toString());
356
357 if (ed.showDialog().getValue() == 2) {
358 try {
359 PlatformManager.getPlatform().openUrl(url);
360 } catch (IOException e) {
361 Logging.warn(e);
362 }
363 }
364 }
365 }
366
367 /**
368 * Asks user to update its version of Java.
369 * @param updVersion target update version
370 * @param url download URL
371 * @param major true for a migration towards a major version of Java (8:11), false otherwise
372 * @param eolDate the EOL/expiration date
373 * @since 12270
374 */
375 public static void askUpdateJava(String updVersion, String url, String eolDate, boolean major) {
376 StringBuilder content = new StringBuilder(256);
377 content.append(tr("You are running version {0} of Java.",
378 "<b>"+getSystemProperty("java.version")+"</b>")).append("<br><br>");
379 if ("Sun Microsystems Inc.".equals(getSystemProperty("java.vendor")) && !PlatformManager.getPlatform().isOpenJDK()) {
380 content.append("<b>").append(tr("This version is no longer supported by {0} since {1} and is not recommended for use.",
381 "Oracle", eolDate)).append("</b><br><br>");
382 }
383 content.append("<b>")
384 .append(major ?
385 tr("JOSM will soon stop working with this version; we highly recommend you to update to Java {0}.", updVersion) :
386 tr("You may face critical Java bugs; we highly recommend you to update to Java {0}.", updVersion))
387 .append("</b><br><br>")
388 .append(tr("Would you like to update now ?"));
389 askUpdate(tr("Outdated Java version"), tr("Update Java"), "askUpdateJava"+updVersion, /* ICON */"java", content, url);
390 }
391
392 /**
393 * Asks user to migrate to OpenWebStart
394 * @param url download URL
395 * @since 17679
396 */
397 public static void askMigrateWebStart(String url) {
398 // CHECKSTYLE.OFF: LineLength
399 StringBuilder content = new StringBuilder(tr("You are running an <b>Oracle</b> implementation of Java WebStart."))
400 .append("<br><br>")
401 .append(tr("It was for years the recommended way to use JOSM. Oracle removed WebStart from Java 11,<br>but the open source community reimplemented the Java Web Start technology as a new product: <b>OpenWebStart</b>"))
402 .append("<br><br>")
403 .append(tr("OpenWebStart is now considered mature enough by JOSM developers to ask everyone to move away from an Oracle implementation,<br>allowing you to benefit from a recent version of Java, and allowing JOSM developers to move forward by planning the Java {0} migration.", "11"))
404 .append("<br><br>")
405 .append(tr("Would you like to <b>download OpenWebStart now</b>? (Please do!)"));
406 askUpdate(tr("Outdated Java WebStart version"), tr("Update to OpenWebStart"), "askUpdateWebStart", /* ICON */"presets/transport/rocket", content, url);
407 // CHECKSTYLE.ON: LineLength
408 }
409
410 /**
411 * Tells the user that a sanity check failed
412 * @param title The title of the message to show
413 * @param canContinue {@code true} if the failed sanity check(s) will not instantly kill JOSM when the user edits
414 * @param message The message parts to show the user (as a list)
415 */
416 public static void sanityCheckFailed(String title, boolean canContinue, String... message) {
417 final ExtendedDialog ed;
418 if (canContinue) {
419 ed = new ExtendedDialog(mainFrame, title, tr("Stop"), tr("Continue"));
420 ed.setButtonIcons("cancel", "ok");
421 } else {
422 ed = new ExtendedDialog(mainFrame, title, tr("Stop"));
423 ed.setButtonIcons("cancel");
424 }
425 ed.setDefaultButton(1).setCancelButton(1);
426 // Check if the dialog has not already been permanently hidden by user
427 if (!ed.toggleEnable("sanityCheckFailed").toggleCheckState() || !canContinue) {
428 final String content = Arrays.stream(message).collect(Collectors.joining("</li><li>",
429 "<html><body><ul><li>", "</li></ul></body></html>"));
430 final JTextPane textField = new JTextPane();
431 textField.setContentType("text/html");
432 textField.setText(content);
433 TextContextualPopupMenu.enableMenuFor(textField, true);
434 ed.setMinimumSize(new Dimension(480, 300));
435 ed.setIcon(JOptionPane.WARNING_MESSAGE);
436 ed.setContent(textField);
437 ed.showDialog();
438 }
439 if (!canContinue || ed.getValue() <= 1) { // 0 == cancel (we want to stop) and 1 == stop
440 Lifecycle.exitJosm(true, -1);
441 }
442 }
443
444 /**
445 * Called once at startup to initialize the main window content.
446 * Should set {@link #menu} and {@link #mainPanel}
447 */
448 protected void initializeMainWindow() {
449 if (mainFrame != null) {
450 mainPanel = mainFrame.getPanel();
451 mainFrame.initialize();
452 menu = mainFrame.getMenu();
453 } else {
454 // required for running some tests.
455 mainPanel = new MainPanel(layerManager);
456 menu = new MainMenu();
457 }
458 mainPanel.addMapFrameListener((o, n) -> redoUndoListener.commandChanged(0, 0));
459 mainPanel.reAddListeners();
460 }
461
462 /**
463 * Returns the JOSM main frame.
464 * @return the JOSM main frame
465 * @since 14140
466 */
467 public static MainFrame getMainFrame() {
468 return mainFrame;
469 }
470
471 /**
472 * Returns the command-line arguments used to run the application.
473 * @return the command-line arguments used to run the application
474 * @since 11650
475 */
476 public static List<String> getCommandLineArgs() {
477 return commandLineArgs == null
478 ? Collections.emptyList()
479 : Collections.unmodifiableList(commandLineArgs);
480 }
481
482 /**
483 * Returns the main layer manager that is used by the map view.
484 * @return The layer manager. The value returned will never change.
485 * @since 12636 (as a replacement to {@code Main.getLayerManager()})
486 */
487 public static MainLayerManager getLayerManager() {
488 return layerManager;
489 }
490
491 /**
492 * Returns the MapFrame.
493 * <p>
494 * There should be no need to access this to access any map data. Use {@link #layerManager} instead.
495 * @return the MapFrame
496 * @see MainPanel
497 * @since 12630
498 */
499 public static MapFrame getMap() {
500 return map;
501 }
502
503 /**
504 * Returns the main panel.
505 * @return the main panel
506 * @since 12642
507 */
508 public static MainPanel getMainPanel() {
509 return mainPanel;
510 }
511
512 /**
513 * Returns the main menu, at top of screen.
514 * @return the main menu
515 * @since 12643 (as a replacement to {@code MainApplication.getMenu()})
516 */
517 public static MainMenu getMenu() {
518 return menu;
519 }
520
521 /**
522 * Returns the toolbar preference control to register new actions.
523 * @return the toolbar preference control
524 * @since 12637
525 */
526 public static ToolbarPreferences getToolbar() {
527 return toolbar;
528 }
529
530 /**
531 * Replies true if JOSM currently displays a map view. False, if it doesn't, i.e. if
532 * it only shows the MOTD panel.
533 * <p>
534 * You do not need this when accessing the layer manager. The layer manager will be empty if no map view is shown.
535 *
536 * @return <code>true</code> if JOSM currently displays a map view
537 * @since 12630 (as a replacement to {@code Main.isDisplayingMapView()})
538 */
539 public static boolean isDisplayingMapView() {
540 return map != null && map.mapView != null;
541 }
542
543 /**
544 * Closes JOSM and optionally terminates the Java Virtual Machine (JVM).
545 * If there are some unsaved data layers, asks first for user confirmation.
546 * @param exit If {@code true}, the JVM is terminated by running {@link System#exit} with a given return code.
547 * @param exitCode The return code
548 * @param reason the reason for exiting
549 * @return {@code true} if JOSM has been closed, {@code false} if the user has cancelled the operation.
550 * @since 12636 (specialized version of {@link Lifecycle#exitJosm})
551 */
552 public static boolean exitJosm(boolean exit, int exitCode, SaveLayersDialog.Reason reason) {
553 final boolean proceed = layerManager.getLayers().isEmpty() ||
554 Boolean.TRUE.equals(GuiHelper.runInEDTAndWaitAndReturn(() ->
555 SaveLayersDialog.saveUnsavedModifications(layerManager.getLayers(),
556 reason != null ? reason : SaveLayersDialog.Reason.EXIT)));
557 if (proceed) {
558 return Lifecycle.exitJosm(exit, exitCode);
559 }
560 return false;
561 }
562
563 /**
564 * Redirects the key inputs from {@code source} to main content pane.
565 * @param source source component from which key inputs are redirected
566 */
567 public static void redirectToMainContentPane(JComponent source) {
568 RedirectInputMap.redirect(source, contentPanePrivate);
569 }
570
571 /**
572 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes.
573 * <p>
574 * It will fire an initial mapFrameInitialized event when the MapFrame is present.
575 * Otherwise will only fire when the MapFrame is created or destroyed.
576 * @param listener The MapFrameListener
577 * @return {@code true} if the listeners collection changed as a result of the call
578 * @see #addMapFrameListener
579 * @since 12639 (as a replacement to {@code Main.addAndFireMapFrameListener})
580 */
581 public static boolean addAndFireMapFrameListener(MapFrameListener listener) {
582 return mainPanel != null && mainPanel.addAndFireMapFrameListener(listener);
583 }
584
585 /**
586 * Registers a new {@code MapFrameListener} that will be notified of MapFrame changes
587 * @param listener The MapFrameListener
588 * @return {@code true} if the listeners collection changed as a result of the call
589 * @see #addAndFireMapFrameListener
590 * @since 12639 (as a replacement to {@code Main.addMapFrameListener})
591 */
592 public static boolean addMapFrameListener(MapFrameListener listener) {
593 return mainPanel != null && mainPanel.addMapFrameListener(listener);
594 }
595
596 /**
597 * Unregisters the given {@code MapFrameListener} from MapFrame changes
598 * @param listener The MapFrameListener
599 * @return {@code true} if the listeners collection changed as a result of the call
600 * @since 12639 (as a replacement to {@code Main.removeMapFrameListener})
601 */
602 public static boolean removeMapFrameListener(MapFrameListener listener) {
603 return mainPanel != null && mainPanel.removeMapFrameListener(listener);
604 }
605
606 /**
607 * Registers a {@code JosmAction} and its shortcut.
608 * @param action action defining its own shortcut
609 * @since 12639 (as a replacement to {@code Main.registerActionShortcut})
610 */
611 public static void registerActionShortcut(JosmAction action) {
612 registerActionShortcut(action, action.getShortcut());
613 }
614
615 /**
616 * Registers an action and its shortcut.
617 * @param action action to register
618 * @param shortcut shortcut to associate to {@code action}
619 * @since 12639 (as a replacement to {@code Main.registerActionShortcut})
620 */
621 public static void registerActionShortcut(Action action, Shortcut shortcut) {
622 KeyStroke keyStroke = shortcut.getKeyStroke();
623 if (keyStroke == null)
624 return;
625
626 InputMap inputMap = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
627 Object existing = inputMap.get(keyStroke);
628 if (existing != null && !existing.equals(action)) {
629 Logging.info(String.format("Keystroke %s is already assigned to %s, will be overridden by %s", keyStroke, existing, action));
630 }
631 inputMap.put(keyStroke, action);
632
633 contentPanePrivate.getActionMap().put(action, action);
634 }
635
636 /**
637 * Unregisters a shortcut.
638 * @param shortcut shortcut to unregister
639 * @since 12639 (as a replacement to {@code Main.unregisterShortcut})
640 */
641 public static void unregisterShortcut(Shortcut shortcut) {
642 contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).remove(shortcut.getKeyStroke());
643 }
644
645 /**
646 * Unregisters a {@code JosmAction} and its shortcut.
647 * @param action action to unregister
648 * @since 12639 (as a replacement to {@code Main.unregisterActionShortcut})
649 */
650 public static void unregisterActionShortcut(JosmAction action) {
651 unregisterActionShortcut(action, action.getShortcut());
652 }
653
654 /**
655 * Unregisters an action and its shortcut.
656 * @param action action to unregister
657 * @param shortcut shortcut to unregister
658 * @since 12639 (as a replacement to {@code Main.unregisterActionShortcut})
659 */
660 public static void unregisterActionShortcut(Action action, Shortcut shortcut) {
661 unregisterShortcut(shortcut);
662 contentPanePrivate.getActionMap().remove(action);
663 }
664
665 /**
666 * Replies the registered action for the given shortcut
667 * @param shortcut The shortcut to look for
668 * @return the registered action for the given shortcut
669 * @since 12639 (as a replacement to {@code Main.getRegisteredActionShortcut})
670 */
671 public static Action getRegisteredActionShortcut(Shortcut shortcut) {
672 KeyStroke keyStroke = shortcut.getKeyStroke();
673 if (keyStroke == null)
674 return null;
675 Object action = contentPanePrivate.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).get(keyStroke);
676 if (action instanceof Action)
677 return (Action) action;
678 return null;
679 }
680
681 /**
682 * Displays help on the console
683 * @since 2748
684 */
685 public static void showHelp() {
686 // TODO: put in a platformHook for system that have no console by default
687 System.out.println(getHelp());
688 }
689
690 static String getHelp() {
691 // IMPORTANT: when changing the help texts, also update:
692 // - native/linux/tested/usr/share/man/man1/josm.1
693 // - native/linux/latest/usr/share/man/man1/josm-latest.1
694 return tr("Java OpenStreetMap Editor")+" ["
695 +Version.getInstance().getAgentString()+"]\n\n"+
696 tr("usage")+":\n"+
697 "\tjava -jar josm.jar [<command>] <options>...\n\n"+
698 tr("commands")+":\n"+
699 "\trunjosm "+tr("launch JOSM (default, performed when no command is specified)")+'\n'+
700 "\trender "+tr("render data and save the result to an image file")+'\n'+
701 "\tproject " + tr("convert coordinates from one coordinate reference system to another")+ '\n' +
702 "\tvalidate " + tr("validate data") + "\n\n" +
703 tr("For details on the {0} and {1} commands, run them with the {2} option.", "render", "project", "--help")+'\n'+
704 tr("The remainder of this help page documents the {0} command.", "runjosm")+"\n\n"+
705 tr("options")+":\n"+
706 "\t--help|-h "+tr("Show this help")+'\n'+
707 "\t--geometry=widthxheight(+|-)x(+|-)y "+tr("Standard unix geometry argument")+'\n'+
708 "\t[--download=]minlat,minlon,maxlat,maxlon "+tr("Download the bounding box")+'\n'+
709 "\t[--download=]<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z)")+'\n'+
710 "\t[--download=]<filename> "+tr("Open a file (any file type that can be opened with File/Open)")+'\n'+
711 "\t--downloadgps=minlat,minlon,maxlat,maxlon "+tr("Download the bounding box as raw GPS")+'\n'+
712 "\t--downloadgps=<URL> "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS")+'\n'+
713 "\t--selection=<searchstring> "+tr("Select with the given search")+'\n'+
714 "\t--[no-]maximize "+tr("Launch in maximized mode")+'\n'+
715 "\t--reset-preferences "+tr("Reset the preferences to default")+"\n\n"+
716 "\t--load-preferences=<url-to-xml> "+tr("Changes preferences according to the XML file")+"\n\n"+
717 "\t--set=<key>=<value> "+tr("Set preference key to value")+"\n\n"+
718 "\t--language=<language> "+tr("Set the language")+"\n\n"+
719 "\t--version "+tr("Displays the JOSM version and exits")+"\n\n"+
720 "\t--status-report "+ShowStatusReportAction.ACTION_DESCRIPTION+"\n\n"+
721 "\t--debug "+tr("Print debugging messages to console")+"\n\n"+
722 "\t--skip-plugins "+tr("Skip loading plugins")+"\n\n"+
723 "\t--offline=" + Arrays.stream(OnlineResource.values()).map(OnlineResource::name).collect(
724 Collectors.joining("|", "<", ">")) + "\n" +
725 "\t "+tr("Disable access to the given resource(s), separated by comma") + "\n" +
726 "\t "+Arrays.stream(OnlineResource.values()).map(OnlineResource::getLocName).collect(
727 Collectors.joining("|", "<", ">")) + "\n\n" +
728 tr("options provided as Java system properties")+":\n"+
729 align("\t-Djosm.dir.name=JOSM") + tr("Change the JOSM directory name") + "\n\n" +
730 align("\t-Djosm.pref=" + tr("/PATH/TO/JOSM/PREF ")) + tr("Set the preferences directory") + "\n" +
731 align("\t") + tr("Default: {0}", PlatformManager.getPlatform().getDefaultPrefDirectory()) + "\n\n" +
732 align("\t-Djosm.userdata=" + tr("/PATH/TO/JOSM/USERDATA")) + tr("Set the user data directory") + "\n" +
733 align("\t") + tr("Default: {0}", PlatformManager.getPlatform().getDefaultUserDataDirectory()) + "\n\n" +
734 align("\t-Djosm.cache=" + tr("/PATH/TO/JOSM/CACHE ")) + tr("Set the cache directory") + "\n" +
735 align("\t") + tr("Default: {0}", PlatformManager.getPlatform().getDefaultCacheDirectory()) + "\n\n" +
736 align("\t-Djosm.home=" + tr("/PATH/TO/JOSM/HOMEDIR ")) +
737 tr("Set the preferences+data+cache directory (cache directory will be josm.home/cache)")+"\n\n"+
738 tr("-Djosm.home has lower precedence, i.e. the specific setting overrides the general one")+"\n\n"+
739 tr("note: For some tasks, JOSM needs a lot of memory. It can be necessary to add the following\n" +
740 " Java option to specify the maximum size of allocated memory in megabytes")+":\n"+
741 "\t-Xmx...m\n\n"+
742 tr("examples")+":\n"+
743 "\tjava -jar josm.jar track1.gpx track2.gpx london.osm\n"+
744 "\tjava -jar josm.jar "+OsmUrlToBounds.getURL(43.2, 11.1, 13)+'\n'+
745 "\tjava -jar josm.jar london.osm --selection=http://www.ostertag.name/osm/OSM_errors_node-duplicate.xml\n"+
746 "\tjava -jar josm.jar 43.2,11.1,43.4,11.4\n"+
747 "\tjava -Djosm.pref=$XDG_CONFIG_HOME -Djosm.userdata=$XDG_DATA_HOME -Djosm.cache=$XDG_CACHE_HOME -jar josm.jar\n"+
748 "\tjava -Djosm.dir.name=josm_dev -jar josm.jar\n"+
749 "\tjava -Djosm.home=/home/user/.josm_dev -jar josm.jar\n"+
750 "\tjava -Xmx1024m -jar josm.jar\n\n"+
751 tr("Parameters --download, --downloadgps, and --selection are processed in this order.")+'\n'+
752 tr("Make sure you load some data if you use --selection.")+'\n';
753 }
754
755 private static String align(String str) {
756 return str + Stream.generate(() -> " ").limit(Math.max(0, 43 - str.length())).collect(Collectors.joining(""));
757 }
758
759 /**
760 * Main application Startup
761 * @param argArray Command-line arguments
762 */
763 public static void main(final String[] argArray) {
764 I18n.init();
765 commandLineArgs = Arrays.asList(Arrays.copyOf(argArray, argArray.length));
766
767 if (argArray.length > 0) {
768 String moduleStr = argArray[0];
769 for (CLIModule module : cliModules) {
770 if (Objects.equals(moduleStr, module.getActionKeyword())) {
771 String[] argArrayCdr = Arrays.copyOfRange(argArray, 1, argArray.length);
772 module.processArguments(argArrayCdr);
773 return;
774 }
775 }
776 }
777 // no module specified, use default (josm)
778 JOSM_CLI_MODULE.processArguments(argArray);
779 }
780
781 /**
782 * Main method to run the JOSM GUI.
783 * @param args program arguments
784 */
785 public static void mainJOSM(ProgramArguments args) {
786
787 if (!GraphicsEnvironment.isHeadless()) {
788 BugReportQueue.getInstance().setBugReportHandler(BugReportDialog::showFor);
789 BugReportSender.setBugReportSendingHandler(new DefaultBugReportSendingHandler());
790 }
791
792 Level logLevel = args.getLogLevel();
793 Logging.setLogLevel(logLevel);
794 if (!args.hasOption(Option.VERSION) && !args.hasOption(Option.STATUS_REPORT) && !args.showHelp()) {
795 Logging.info(tr("Log level is at {0} ({1}, {2})", logLevel.getLocalizedName(), logLevel.getName(), logLevel.intValue()));
796 }
797
798 Optional<String> language = args.getSingle(Option.LANGUAGE);
799 I18n.set(language.orElse(null));
800
801 try {
802 Policy.setPolicy(new Policy() {
803 // Permissions for plug-ins loaded when josm is started via webstart
804 private final PermissionCollection pc;
805
806 {
807 pc = new Permissions();
808 pc.add(new AllPermission());
809 }
810
811 @Override
812 public PermissionCollection getPermissions(CodeSource codesource) {
813 return pc;
814 }
815 });
816 } catch (SecurityException e) {
817 Logging.log(Logging.LEVEL_ERROR, "Unable to set permissions", e);
818 }
819
820 try {
821 Thread.setDefaultUncaughtExceptionHandler(new BugReportExceptionHandler());
822 } catch (SecurityException e) {
823 Logging.log(Logging.LEVEL_ERROR, "Unable to set uncaught exception handler", e);
824 }
825
826 // initialize the platform hook, and
827 PlatformManager.getPlatform().setNativeOsCallback(new DefaultNativeOsCallback());
828 // call the really early hook before we do anything else
829 PlatformManager.getPlatform().preStartupHook();
830
831 Preferences prefs = Preferences.main();
832 Config.setPreferencesInstance(prefs);
833 Config.setBaseDirectoriesProvider(JosmBaseDirectories.getInstance());
834 Config.setUrlsProvider(JosmUrls.getInstance());
835
836 if (args.hasOption(Option.VERSION)) {
837 System.out.println(Version.getInstance().getAgentString());
838 return;
839 } else if (args.hasOption(Option.STATUS_REPORT)) {
840 Preferences.main().enableSaveOnPut(false);
841 Preferences.main().init(false);
842 System.out.println(ShowStatusReportAction.getReportHeader());
843 return;
844 } else if (args.showHelp()) {
845 showHelp();
846 return;
847 }
848
849 boolean skipLoadingPlugins = args.hasOption(Option.SKIP_PLUGINS);
850 if (skipLoadingPlugins) {
851 Logging.info(tr("Plugin loading skipped"));
852 }
853
854 if (Logging.isLoggingEnabled(Logging.LEVEL_TRACE)) {
855 // Enable debug in OAuth signpost via system preference, but only at trace level
856 Utils.updateSystemProperty("debug", "true");
857 Logging.info(tr("Enabled detailed debug level (trace)"));
858 }
859
860 try {
861 Preferences.main().init(args.hasOption(Option.RESET_PREFERENCES));
862 } catch (SecurityException e) {
863 Logging.log(Logging.LEVEL_ERROR, "Unable to initialize preferences", e);
864 }
865
866 args.getPreferencesToSet().forEach(prefs::put);
867
868 if (!language.isPresent()) {
869 I18n.set(Config.getPref().get("language", null));
870 }
871 updateSystemProperties();
872 Preferences.main().addPreferenceChangeListener(e -> updateSystemProperties());
873
874 checkIPv6();
875
876 processOffline(args);
877
878 PlatformManager.getPlatform().afterPrefStartupHook();
879
880 FontsManager.initialize();
881
882 GuiHelper.setupLanguageFonts();
883
884 Handler.install();
885
886 WindowGeometry geometry = WindowGeometry.mainWindow(WindowGeometry.PREF_KEY_GUI_GEOMETRY,
887 args.getSingle(Option.GEOMETRY).orElse(null),
888 !args.hasOption(Option.NO_MAXIMIZE) && Config.getPref().getBoolean("gui.maximized", false));
889 final MainFrame mainFrame = createMainFrame(geometry);
890 final Container contentPane = mainFrame.getContentPane();
891 if (contentPane instanceof JComponent) {
892 contentPanePrivate = (JComponent) contentPane;
893 }
894 // This should never happen, but it does. See #22183.
895 // Hopefully this code block will be temporary until we figure out what is actually going on.
896 if (!GraphicsEnvironment.isHeadless() && contentPanePrivate == null) {
897 throw new JosmRuntimeException("MainFrame contentPane is " + (contentPane == null ? "null" : contentPane.getClass().getName()));
898 }
899 mainPanel = mainFrame.getPanel();
900
901 if (args.hasOption(Option.LOAD_PREFERENCES)) {
902 XMLCommandProcessor config = new XMLCommandProcessor(prefs);
903 for (String i : args.get(Option.LOAD_PREFERENCES)) {
904 try {
905 URL url = i.contains(":/") ? new URL(i) : Paths.get(i).toUri().toURL();
906 Logging.info("Reading preferences from " + url);
907 try (InputStream is = Utils.openStream(url)) {
908 config.openAndReadXML(is);
909 }
910 } catch (IOException | InvalidPathException ex) {
911 Logging.error(ex);
912 return;
913 }
914 }
915 }
916
917 try {
918 CertificateAmendment.addMissingCertificates();
919 } catch (IOException | GeneralSecurityException | SecurityException | ExceptionInInitializerError ex) {
920 Logging.warn(ex);
921 Logging.warn(Logging.getErrorMessage(Utils.getRootCause(ex)));
922 }
923 try {
924 Authenticator.setDefault(DefaultAuthenticator.getInstance());
925 } catch (SecurityException e) {
926 Logging.log(Logging.LEVEL_ERROR, "Unable to set default authenticator", e);
927 }
928 DefaultProxySelector proxySelector = null;
929 try {
930 proxySelector = new DefaultProxySelector(ProxySelector.getDefault());
931 } catch (SecurityException e) {
932 Logging.log(Logging.LEVEL_ERROR, "Unable to get default proxy selector", e);
933 }
934 try {
935 if (proxySelector != null) {
936 ProxySelector.setDefault(proxySelector);
937 }
938 } catch (SecurityException e) {
939 Logging.log(Logging.LEVEL_ERROR, "Unable to set default proxy selector", e);
940 }
941 OAuthAccessTokenHolder.getInstance().init(CredentialsManager.getInstance());
942
943 setupCallbacks();
944
945 if (!skipLoadingPlugins) {
946 PluginHandler.loadVeryEarlyPlugins();
947 }
948 // Configure Look and feel before showing SplashScreen (#19290)
949 setupUIManager();
950 // Then apply LaF workarounds
951 applyLaFWorkarounds();
952 // MainFrame created before setting look and feel and not updated (#20771)
953 SwingUtilities.updateComponentTreeUI(mainFrame);
954
955 final SplashScreen splash = GuiHelper.runInEDTAndWaitAndReturn(SplashScreen::new);
956 // splash can be null sometimes on Linux, in this case try to load JOSM silently
957 final SplashProgressMonitor monitor = splash != null ? splash.getProgressMonitor() : new SplashProgressMonitor(null, e -> {
958 if (e != null) {
959 Logging.debug(e.toString());
960 }
961 });
962 monitor.beginTask(tr("Initializing"));
963 if (splash != null) {
964 GuiHelper.runInEDT(() -> splash.setVisible(Config.getPref().getBoolean("draw.splashscreen", true)));
965 }
966 Lifecycle.setInitStatusListener(new InitStatusListener() {
967
968 @Override
969 public Object updateStatus(String event) {
970 monitor.beginTask(event);
971 return event;
972 }
973
974 @Override
975 public void finish(Object status) {
976 if (status instanceof String) {
977 monitor.finishTask((String) status);
978 }
979 }
980 });
981
982 Collection<PluginInformation> pluginsToLoad = null;
983
984 if (!skipLoadingPlugins) {
985 pluginsToLoad = updateAndLoadEarlyPlugins(splash, monitor);
986 }
987
988 monitor.indeterminateSubTask(tr("Setting defaults"));
989 toolbar = new ToolbarPreferences();
990 ProjectionPreference.setProjection();
991 setupNadGridSources();
992 GuiHelper.translateJavaInternalMessages();
993
994 monitor.indeterminateSubTask(tr("Creating main GUI"));
995 Lifecycle.initialize(new MainInitialization(new MainApplication(mainFrame)));
996
997 if (!skipLoadingPlugins) {
998 loadLatePlugins(splash, monitor, pluginsToLoad);
999 }
1000
1001 // Wait for splash disappearance (fix #9714)
1002 GuiHelper.runInEDTAndWait(() -> {
1003 if (splash != null) {
1004 splash.setVisible(false);
1005 splash.dispose();
1006 }
1007 mainFrame.setVisible(true);
1008 });
1009
1010 boolean maximized = Config.getPref().getBoolean("gui.maximized", false);
1011 if ((!args.hasOption(Option.NO_MAXIMIZE) && maximized) || args.hasOption(Option.MAXIMIZE)) {
1012 mainFrame.setMaximized(true);
1013 }
1014 if (menu.fullscreenToggleAction != null) {
1015 menu.fullscreenToggleAction.initial();
1016 }
1017
1018 SwingUtilities.invokeLater(new GuiFinalizationWorker(args, proxySelector));
1019
1020 if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) {
1021 RemoteControl.start();
1022 }
1023
1024 if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) {
1025 MessageNotifier.start();
1026 }
1027
1028 ChangesetUpdater.start();
1029
1030 if (Config.getPref().getBoolean("debug.edt-checker.enable", Version.getInstance().isLocalBuild())) {
1031 // Repaint manager is registered so late for a reason - there are lots of violations during startup process
1032 // but they don't seem to break anything and are difficult to fix
1033 Logging.info("Enabled EDT checker, wrongful access to gui from non EDT thread will be printed to console");
1034 RepaintManager.setCurrentManager(new CheckThreadViolationRepaintManager());
1035 }
1036 }
1037
1038 private static MainFrame createMainFrame(WindowGeometry geometry) {
1039 try {
1040 return new MainFrame(geometry);
1041 } catch (AWTError e) {
1042 // #12022 #16666 On Debian, Ubuntu and Linux Mint the first AWT toolkit access can fail because of ATK wrapper
1043 // Good news: the error happens after the toolkit initialization so we can just try again and it will work
1044 Logging.error(e);
1045 return new MainFrame(geometry);
1046 }
1047 }
1048
1049 /**
1050 * Updates system properties with the current values in the preferences.
1051 */
1052 private static void updateSystemProperties() {
1053 if ("true".equals(Config.getPref().get("prefer.ipv6", "auto"))
1054 && !"true".equals(Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true"))) {
1055 // never set this to false, only true!
1056 Logging.info(tr("Try enabling IPv6 network, preferring IPv6 over IPv4 (only works on early startup)."));
1057 }
1058 Utils.updateSystemProperty("http.agent", Version.getInstance().getAgentString());
1059 Utils.updateSystemProperty("user.language", Config.getPref().get("language"));
1060 // Workaround to fix a Java bug. This ugly hack comes from Sun bug database: https://bugs.openjdk.java.net/browse/JDK-6292739
1061 // Force AWT toolkit to update its internal preferences (fix #6345).
1062 // Does not work anymore with Java 9, to remove with Java 9 migration
1063 if (Utils.getJavaVersion() < 9 && !GraphicsEnvironment.isHeadless()) {
1064 try {
1065 Field field = Toolkit.class.getDeclaredField("resources");
1066 ReflectionUtils.setObjectsAccessible(field);
1067 field.set(null, ResourceBundle.getBundle("sun.awt.resources.awt"));
1068 } catch (ReflectiveOperationException | RuntimeException e) { // NOPMD
1069 // Catch RuntimeException in order to catch InaccessibleObjectException, new in Java 9
1070 Logging.log(Logging.LEVEL_WARN, null, e);
1071 }
1072 }
1073 // Possibility to disable SNI (not by default) in case of misconfigured https servers
1074 // See #9875 + http://stackoverflow.com/a/14884941/2257172
1075 // then https://josm.openstreetmap.de/ticket/12152#comment:5 for details
1076 if (Config.getPref().getBoolean("jdk.tls.disableSNIExtension", false)) {
1077 Utils.updateSystemProperty("jsse.enableSNIExtension", "false");
1078 }
1079 // Disable automatic POST retry after 5 minutes, see #17882 / https://bugs.openjdk.java.net/browse/JDK-6382788
1080 Utils.updateSystemProperty("sun.net.http.retryPost", "false");
1081 if (Utils.getJavaVersion() >= 17) {
1082 // Allow security manager, otherwise it raises a warning in Java 17 and throws an error with Java 18+
1083 // See https://bugs.openjdk.java.net/browse/JDK-8271301 / https://bugs.openjdk.java.net/browse/JDK-8270380
1084 Utils.updateSystemProperty("java.security.manager", "allow");
1085 }
1086 }
1087
1088 /**
1089 * Setup the sources for NTV2 grid shift files for projection support.
1090 * @since 12795
1091 */
1092 public static void setupNadGridSources() {
1093 NTV2GridShiftFileWrapper.registerNTV2GridShiftFileSource(
1094 NTV2GridShiftFileWrapper.NTV2_SOURCE_PRIORITY_LOCAL,
1095 NTV2Proj4DirGridShiftFileSource.getInstance());
1096 NTV2GridShiftFileWrapper.registerNTV2GridShiftFileSource(
1097 NTV2GridShiftFileWrapper.NTV2_SOURCE_PRIORITY_DOWNLOAD,
1098 JOSM_WEBSITE_NTV2_SOURCE);
1099 }
1100
1101 /**
1102 * Apply workarounds for LaF and platform specific issues. This must be called <i>after</i> the
1103 * LaF is set.
1104 */
1105 static void applyLaFWorkarounds() {
1106 final String laf = UIManager.getLookAndFeel().getID();
1107 final int javaVersion = Utils.getJavaVersion();
1108 // Workaround for JDK-8180379: crash on Windows 10 1703 with Windows L&F and java < 8u141 / 9+172
1109 // To remove during Java 9 migration
1110 if (getSystemProperty("os.name").toLowerCase(Locale.ENGLISH).contains("windows 10") &&
1111 PlatformManager.getPlatform().getDefaultStyle().equals(LafPreference.LAF.get())) {
1112 try {
1113 String build = PlatformHookWindows.getCurrentBuild();
1114 if (build != null) {
1115 final int currentBuild = Integer.parseInt(build);
1116 final int javaUpdate = Utils.getJavaUpdate();
1117 final int javaBuild = Utils.getJavaBuild();
1118 // See https://technet.microsoft.com/en-us/windows/release-info.aspx
1119 if (currentBuild >= 15_063 && ((javaVersion == 8 && javaUpdate < 141)
1120 || (javaVersion == 9 && javaUpdate == 0 && javaBuild < 173))) {
1121 // Workaround from https://bugs.openjdk.java.net/browse/JDK-8179014
1122 UIManager.put("FileChooser.useSystemExtensionHiding", Boolean.FALSE);
1123 }
1124 }
1125 } catch (NumberFormatException | ReflectiveOperationException | JosmRuntimeException e) {
1126 Logging.error(e);
1127 } catch (ExceptionInInitializerError e) {
1128 Logging.log(Logging.LEVEL_ERROR, null, e);
1129 }
1130 } else if (PlatformManager.isPlatformOsx() && javaVersion < 17) {
1131 // Workaround for JDK-8251377: JTabPanel active tab is unreadable in Big Sur, see #20075, see #20821
1132 // os.version will return 10.16, or 11.0 depending on environment variable
1133 // https://twitter.com/BriceDutheil/status/1330926649269956612
1134 final String macOSVersion = getSystemProperty("os.version");
1135 if ((laf.contains("Mac") || laf.contains("Aqua"))
1136 && (macOSVersion.startsWith("10.16") || macOSVersion.startsWith("11"))) {
1137 UIManager.put("TabbedPane.foreground", Color.BLACK);
1138 }
1139 }
1140 // Workaround for JDK-8262085
1141 if ("Metal".equals(laf) && javaVersion >= 11 && javaVersion < 17) {
1142 UIManager.put("ToolTipUI", JosmMetalToolTipUI.class.getCanonicalName());
1143 }
1144
1145 // See #20850. The upstream bug (JDK-6396936) is unlikely to ever be fixed due to potential compatibility
1146 // issues. This affects Windows LaF only (includes Windows Classic, a sub-LaF of Windows LaF).
1147 if ("Windows".equals(laf) && "Monospaced".equals(UIManager.getFont("TextArea.font").getFamily())) {
1148 UIManager.put("TextArea.font", UIManager.getFont("TextField.font"));
1149 }
1150 }
1151
1152 static void setupCallbacks() {
1153 HttpClient.setFactory(Http1Client::new);
1154 OsmConnection.setOAuthAccessTokenFetcher(OAuthAuthorizationWizard::obtainAccessToken);
1155 AbstractCredentialsAgent.setCredentialsProvider(CredentialDialog::promptCredentials);
1156 MessageNotifier.setNotifierCallback(MainApplication::notifyNewMessages);
1157 DeleteCommand.setDeletionCallback(DeleteAction.defaultDeletionCallback);
1158 SplitWayCommand.setWarningNotifier(msg -> new Notification(msg).setIcon(JOptionPane.WARNING_MESSAGE).show());
1159 FileWatcher.registerLoader(SourceType.MAP_PAINT_STYLE, MapPaintStyleLoader::reloadStyle);
1160 FileWatcher.registerLoader(SourceType.TAGCHECKER_RULE, MapCSSTagChecker::reloadRule);
1161 OsmUrlToBounds.setMapSizeSupplier(() -> {
1162 if (isDisplayingMapView()) {
1163 MapView mapView = getMap().mapView;
1164 return new Dimension(mapView.getWidth(), mapView.getHeight());
1165 } else {
1166 return GuiHelper.getScreenSize();
1167 }
1168 });
1169 }
1170
1171 /**
1172 * Set up the UI manager
1173 */
1174 // We want to catch all exceptions here to reset LaF to defaults and report it.
1175 @SuppressWarnings("squid:S2221")
1176 static void setupUIManager() {
1177 String defaultlaf = PlatformManager.getPlatform().getDefaultStyle();
1178 String laf = LafPreference.LAF.get();
1179 try {
1180 UIManager.setLookAndFeel(laf);
1181 } catch (final NoClassDefFoundError | ClassNotFoundException e) {
1182 // Try to find look and feel in plugin classloaders
1183 Logging.trace(e);
1184 Class<?> klass = null;
1185 for (ClassLoader cl : PluginHandler.getPluginClassLoaders()) {
1186 try {
1187 klass = cl.loadClass(laf);
1188 break;
1189 } catch (ClassNotFoundException ex) {
1190 Logging.trace(ex);
1191 }
1192 }
1193 if (klass != null && LookAndFeel.class.isAssignableFrom(klass)) {
1194 try {
1195 UIManager.setLookAndFeel((LookAndFeel) klass.getConstructor().newInstance());
1196 } catch (ReflectiveOperationException ex) {
1197 Logging.log(Logging.LEVEL_WARN, "Cannot set Look and Feel: " + laf + ": "+ex.getMessage(), ex);
1198 } catch (UnsupportedLookAndFeelException ex) {
1199 Logging.info("Look and Feel not supported: " + laf);
1200 LafPreference.LAF.put(defaultlaf);
1201 Logging.trace(ex);
1202 } catch (Exception ex) {
1203 // We do not want to silently exit if there is an exception.
1204 // Put the default laf in place so that the user can use JOSM.
1205 LafPreference.LAF.put(defaultlaf);
1206 BugReportExceptionHandler.handleException(ex);
1207 }
1208 } else {
1209 Logging.info("Look and Feel not found: " + laf);
1210 LafPreference.LAF.put(defaultlaf);
1211 }
1212 } catch (UnsupportedLookAndFeelException e) {
1213 Logging.info("Look and Feel not supported: " + laf);
1214 LafPreference.LAF.put(defaultlaf);
1215 Logging.trace(e);
1216 } catch (InstantiationException | IllegalAccessException e) {
1217 Logging.error(e);
1218 } catch (Exception e) {
1219 // We do not want to silently exit if there is an exception.
1220 // Put the default laf in place.
1221 LafPreference.LAF.put(defaultlaf);
1222 BugReportExceptionHandler.handleException(e);
1223 }
1224
1225 UIManager.put("OptionPane.okIcon", ImageProvider.getIfAvailable("ok"));
1226 UIManager.put("OptionPane.yesIcon", UIManager.get("OptionPane.okIcon"));
1227 UIManager.put("OptionPane.cancelIcon", ImageProvider.getIfAvailable("cancel"));
1228 UIManager.put("OptionPane.noIcon", UIManager.get("OptionPane.cancelIcon"));
1229 // Ensures caret color is the same as text foreground color, see #12257
1230 // See https://docs.oracle.com/javase/8/docs/api/javax/swing/plaf/synth/doc-files/componentProperties.html
1231 for (String p : Arrays.asList(
1232 "EditorPane", "FormattedTextField", "PasswordField", "TextArea", "TextField", "TextPane")) {
1233 UIManager.put(p+".caretForeground", UIManager.getColor(p+".foreground"));
1234 }
1235
1236 scaleFonts(Config.getPref().getDouble("gui.scale.menu.font", 1.0),
1237 "Menu.font", "MenuItem.font", "CheckBoxMenuItem.font", "RadioButtonMenuItem.font", "MenuItem.acceleratorFont");
1238 scaleFonts(Config.getPref().getDouble("gui.scale.list.font", 1.0),
1239 "List.font");
1240 // "Table.font" see org.openstreetmap.josm.gui.util.TableHelper.setFont
1241
1242 setupTextAntiAliasing();
1243 }
1244
1245 private static void scaleFonts(double factor, String... fonts) {
1246 if (factor == 1.0) {
1247 return;
1248 }
1249 for (String key : fonts) {
1250 Font font = UIManager.getFont(key);
1251 if (font != null) {
1252 font = font.deriveFont((float) (font.getSize2D() * factor));
1253 UIManager.put(key, new FontUIResource(font));
1254 }
1255 }
1256 }
1257
1258 private static void setupTextAntiAliasing() {
1259 // On Linux and running on Java 9+, enable text anti aliasing
1260 // if not yet enabled and if neither running on Gnome or KDE desktop
1261 if (PlatformManager.isPlatformUnixoid()
1262 && Utils.getJavaVersion() >= 9
1263 && UIManager.getLookAndFeelDefaults().get(RenderingHints.KEY_TEXT_ANTIALIASING) == null
1264 && System.getProperty("awt.useSystemAAFontSettings") == null
1265 && Toolkit.getDefaultToolkit().getDesktopProperty("gnome.Xft/Antialias") == null
1266 && Toolkit.getDefaultToolkit().getDesktopProperty("fontconfig/Antialias") == null) {
1267 UIManager.getLookAndFeelDefaults().put(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
1268 }
1269 }
1270
1271 static Collection<PluginInformation> updateAndLoadEarlyPlugins(SplashScreen splash, SplashProgressMonitor monitor) {
1272 Collection<PluginInformation> pluginsToLoad;
1273 pluginsToLoad = PluginHandler.buildListOfPluginsToLoad(splash, monitor.createSubTaskMonitor(1, false));
1274 if (!pluginsToLoad.isEmpty() && PluginHandler.checkAndConfirmPluginUpdate(splash)) {
1275 monitor.subTask(tr("Updating plugins"));
1276 pluginsToLoad = PluginHandler.updatePlugins(splash, null, monitor.createSubTaskMonitor(1, false), false);
1277 }
1278
1279 monitor.indeterminateSubTask(tr("Installing updated plugins"));
1280 try {
1281 PluginHandler.installDownloadedPlugins(pluginsToLoad, true);
1282 } catch (SecurityException e) {
1283 Logging.log(Logging.LEVEL_ERROR, "Unable to install plugins", e);
1284 }
1285
1286 monitor.indeterminateSubTask(tr("Loading early plugins"));
1287 PluginHandler.loadEarlyPlugins(splash, pluginsToLoad, monitor.createSubTaskMonitor(1, false));
1288 return pluginsToLoad;
1289 }
1290
1291 static void loadLatePlugins(SplashScreen splash, SplashProgressMonitor monitor, Collection<PluginInformation> pluginsToLoad) {
1292 monitor.indeterminateSubTask(tr("Loading plugins"));
1293 PluginHandler.loadLatePlugins(splash, pluginsToLoad, monitor.createSubTaskMonitor(1, false));
1294 GuiHelper.runInEDTAndWait(() -> {
1295 toolbar.enableInfoAboutMissingAction();
1296 toolbar.refreshToolbarControl();
1297 });
1298 }
1299
1300 private static void processOffline(ProgramArguments args) {
1301 for (String offlineNames : args.get(Option.OFFLINE)) {
1302 for (String s : offlineNames.split(",", -1)) {
1303 try {
1304 NetworkManager.setOffline(OnlineResource.valueOf(s.toUpperCase(Locale.ENGLISH)));
1305 } catch (IllegalArgumentException e) {
1306 Logging.log(Logging.LEVEL_ERROR,
1307 tr("''{0}'' is not a valid value for argument ''{1}''. Possible values are {2}, possibly delimited by commas.",
1308 s.toUpperCase(Locale.ENGLISH), Option.OFFLINE.getName(), Arrays.toString(OnlineResource.values())), e);
1309 Lifecycle.exitJosm(true, 1);
1310 return;
1311 }
1312 }
1313 }
1314 Set<OnlineResource> offline = NetworkManager.getOfflineResources();
1315 if (!offline.isEmpty()) {
1316 Logging.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}",
1317 "JOSM is running in offline mode. These resources will not be available: {0}",
1318 offline.size(), offline.stream().map(OnlineResource::getLocName).collect(Collectors.joining(", "))));
1319 }
1320 }
1321
1322 /**
1323 * Check if IPv6 can be safely enabled and do so. Because this cannot be done after network activation,
1324 * disabling or enabling IPV6 may only be done with next start.
1325 */
1326 private static void checkIPv6() {
1327 if ("auto".equals(Config.getPref().get("prefer.ipv6", "auto"))) {
1328 new Thread((Runnable) () -> { /* this may take some time (DNS, Connect) */
1329 boolean hasv6 = false;
1330 boolean wasv6 = Config.getPref().getBoolean("validated.ipv6", false);
1331 try {
1332 /* Use the check result from last run of the software, as after the test, value
1333 changes have no effect anymore */
1334 if (wasv6) {
1335 Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true");
1336 }
1337 for (InetAddress a : InetAddress.getAllByName("josm.openstreetmap.de")) {
1338 if (a instanceof Inet6Address) {
1339 if (a.isReachable(1000)) {
1340 /* be sure it REALLY works */
1341 SSLSocketFactory.getDefault().createSocket(a, 443).close();
1342 hasv6 = true;
1343 /* in case of routing problems to the main openstreetmap domain don't enable IPv6 */
1344 for (InetAddress b : InetAddress.getAllByName("api.openstreetmap.org")) {
1345 if (b instanceof Inet6Address) {
1346 //if (b.isReachable(1000)) {
1347 SSLSocketFactory.getDefault().createSocket(b, 443).close();
1348 //} else {
1349 // hasv6 = false;
1350 //}
1351 break; /* we're done */
1352 }
1353 }
1354 if (hasv6) {
1355 Utils.updateSystemProperty("java.net.preferIPv6Addresses", "true");
1356 if (!wasv6) {
1357 Logging.info(tr("Detected usable IPv6 network, preferring IPv6 over IPv4 after next restart."));
1358 } else {
1359 Logging.info(tr("Detected usable IPv6 network, preferring IPv6 over IPv4."));
1360 }
1361 }
1362 }
1363 break; /* we're done */
1364 }
1365 }
1366 } catch (IOException | SecurityException e) {
1367 Logging.debug("Exception while checking IPv6 connectivity: {0}", e);
1368 hasv6 = false;
1369 Logging.trace(e);
1370 }
1371 Config.getPref().putBoolean("validated.ipv6", hasv6); // be sure it is stored before the restart!
1372 if (wasv6 && !hasv6) {
1373 Logging.info(tr("Detected no usable IPv6 network, preferring IPv4 over IPv6 after next restart."));
1374 RestartAction.restartJOSM();
1375 }
1376 }, "IPv6-checker").start();
1377 }
1378 }
1379
1380 /**
1381 * Download area specified as Bounds value.
1382 * @param rawGps Flag to download raw GPS tracks
1383 * @param b The bounds value
1384 * @return the complete download task (including post-download handler)
1385 */
1386 static List<Future<?>> downloadFromParamBounds(final boolean rawGps, Bounds b) {
1387 DownloadTask task = rawGps ? new DownloadGpsTask() : new DownloadOsmTask();
1388 // asynchronously launch the download task ...
1389 Future<?> future = task.download(new DownloadParams().withNewLayer(true), b, null);
1390 // ... and the continuation when the download is finished (this will wait for the download to finish)
1391 return Collections.singletonList(MainApplication.worker.submit(new PostDownloadHandler(task, future)));
1392 }
1393
1394 /**
1395 * Handle command line instructions after GUI has been initialized.
1396 * @param args program arguments
1397 * @return the list of submitted tasks
1398 */
1399 static List<Future<?>> postConstructorProcessCmdLine(ProgramArguments args) {
1400 List<Future<?>> tasks = new ArrayList<>();
1401 List<File> fileList = new ArrayList<>();
1402 for (String s : args.get(Option.DOWNLOAD)) {
1403 tasks.addAll(DownloadParamType.paramType(s).download(s, fileList));
1404 }
1405 if (!fileList.isEmpty()) {
1406 tasks.add(OpenFileAction.openFiles(fileList, Options.RECORD_HISTORY));
1407 }
1408 for (String s : args.get(Option.DOWNLOADGPS)) {
1409 tasks.addAll(DownloadParamType.paramType(s).downloadGps(s));
1410 }
1411 final Collection<String> selectionArguments = args.get(Option.SELECTION);
1412 if (!selectionArguments.isEmpty()) {
1413 tasks.add(MainApplication.worker.submit(() -> {
1414 for (String s : selectionArguments) {
1415 SearchAction.search(s, SearchMode.add);
1416 }
1417 }));
1418 }
1419 return tasks;
1420 }
1421
1422 private static class GuiFinalizationWorker implements Runnable {
1423
1424 private final ProgramArguments args;
1425 private final DefaultProxySelector proxySelector;
1426
1427 GuiFinalizationWorker(ProgramArguments args, DefaultProxySelector proxySelector) {
1428 this.args = args;
1429 this.proxySelector = proxySelector;
1430 }
1431
1432 @Override
1433 public void run() {
1434
1435 // Handle proxy/network errors early to inform user he should change settings to be able to use JOSM correctly
1436 if (!handleProxyErrors()) {
1437 handleNetworkErrors();
1438 }
1439
1440 // Restore autosave layers after crash and start autosave thread
1441 handleAutosave();
1442
1443 // Handle command line instructions
1444 postConstructorProcessCmdLine(args);
1445
1446 // Show download dialog if autostart is enabled
1447 DownloadDialog.autostartIfNeeded();
1448 }
1449
1450 private static void handleAutosave() {
1451 if (AutosaveTask.PROP_AUTOSAVE_ENABLED.get()) {
1452 AutosaveTask autosaveTask = new AutosaveTask();
1453 List<File> unsavedLayerFiles = autosaveTask.getUnsavedLayersFiles();
1454 if (!unsavedLayerFiles.isEmpty()) {
1455 ExtendedDialog dialog = new ExtendedDialog(
1456 mainFrame,
1457 tr("Unsaved osm data"),
1458 tr("Restore"), tr("Cancel"), tr("Discard")
1459 );
1460 dialog.setContent(
1461 trn("JOSM found {0} unsaved osm data layer. ",
1462 "JOSM found {0} unsaved osm data layers. ", unsavedLayerFiles.size(), unsavedLayerFiles.size()) +
1463 tr("It looks like JOSM crashed last time. Would you like to restore the data?"));
1464 dialog.setButtonIcons("ok", "cancel", "dialogs/delete");
1465 int selection = dialog.showDialog().getValue();
1466 if (selection == 1) {
1467 autosaveTask.recoverUnsavedLayers();
1468 } else if (selection == 3) {
1469 autosaveTask.discardUnsavedLayers();
1470 }
1471 }
1472 try {
1473 autosaveTask.schedule();
1474 } catch (SecurityException e) {
1475 Logging.log(Logging.LEVEL_ERROR, "Unable to schedule autosave!", e);
1476 }
1477 }
1478 }
1479
1480 private static boolean handleNetworkOrProxyErrors(boolean hasErrors, String title, String message) {
1481 if (hasErrors) {
1482 ExtendedDialog ed = new ExtendedDialog(
1483 mainFrame, title,
1484 tr("Change proxy settings"), tr("Cancel"));
1485 ed.setButtonIcons("preference", "cancel").setCancelButton(2);
1486 ed.setMinimumSize(new Dimension(460, 260));
1487 ed.setIcon(JOptionPane.WARNING_MESSAGE);
1488 ed.setContent(message);
1489
1490 if (ed.showDialog().getValue() == 1) {
1491 PreferencesAction.forPreferenceTab(null, null, ProxyPreference.class).run();
1492 }
1493 }
1494 return hasErrors;
1495 }
1496
1497 private boolean handleProxyErrors() {
1498 return proxySelector != null &&
1499 handleNetworkOrProxyErrors(proxySelector.hasErrors(), tr("Proxy errors occurred"),
1500 tr("JOSM tried to access the following resources:<br>" +
1501 "{0}" +
1502 "but <b>failed</b> to do so, because of the following proxy errors:<br>" +
1503 "{1}" +
1504 "Would you like to change your proxy settings now?",
1505 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorResources()),
1506 Utils.joinAsHtmlUnorderedList(proxySelector.getErrorMessages())
1507 ));
1508 }
1509
1510 private static boolean handleNetworkErrors() {
1511 Map<String, Throwable> networkErrors = NetworkManager.getNetworkErrors();
1512 boolean condition = !networkErrors.isEmpty();
1513 if (condition) {
1514 Set<String> errors = networkErrors.values().stream()
1515 .map(Throwable::toString)
1516 .collect(Collectors.toCollection(TreeSet::new));
1517 return handleNetworkOrProxyErrors(condition, tr("Network errors occurred"),
1518 tr("JOSM tried to access the following resources:<br>" +
1519 "{0}" +
1520 "but <b>failed</b> to do so, because of the following network errors:<br>" +
1521 "{1}" +
1522 "It may be due to a missing proxy configuration.<br>" +
1523 "Would you like to change your proxy settings now?",
1524 Utils.joinAsHtmlUnorderedList(networkErrors.keySet()),
1525 Utils.joinAsHtmlUnorderedList(errors)
1526 ));
1527 }
1528 return false;
1529 }
1530 }
1531
1532 private static class DefaultNativeOsCallback implements NativeOsCallback {
1533 @Override
1534 public void openFiles(List<File> files) {
1535 Executors.newSingleThreadExecutor(Utils.newThreadFactory("openFiles-%d", Thread.NORM_PRIORITY)).submit(
1536 new OpenFileTask(files, null) {
1537 @Override
1538 protected void realRun() throws SAXException, IOException, OsmTransferException {
1539 // Wait for JOSM startup is advanced enough to load a file
1540 while (mainFrame == null || !mainFrame.isVisible()) {
1541 try {
1542 Thread.sleep(25);
1543 } catch (InterruptedException e) {
1544 Logging.warn(e);
1545 Thread.currentThread().interrupt();
1546 }
1547 }
1548 super.realRun();
1549 }
1550 });
1551 }
1552
1553 @Override
1554 public boolean handleQuitRequest() {
1555 return MainApplication.exitJosm(false, 0, null);
1556 }
1557
1558 @Override
1559 public void handleAbout() {
1560 MainApplication.getMenu().about.actionPerformed(null);
1561 }
1562
1563 @Override
1564 public void handlePreferences() {
1565 MainApplication.getMenu().preferences.actionPerformed(null);
1566 }
1567 }
1568
1569 static void notifyNewMessages(UserInfo userInfo) {
1570 GuiHelper.runInEDT(() -> {
1571 JPanel panel = new JPanel(new GridBagLayout());
1572 panel.add(new JLabel(trn("You have {0} unread message.", "You have {0} unread messages.",
1573 userInfo.getUnreadMessages(), userInfo.getUnreadMessages())),
1574 GBC.eol());
1575 panel.add(new UrlLabel(Config.getUrls().getBaseUserUrl() + '/' + userInfo.getDisplayName() + "/inbox",
1576 tr("Click here to see your inbox.")), GBC.eol());
1577 panel.setOpaque(false);
1578 new Notification().setContent(panel)
1579 .setIcon(JOptionPane.INFORMATION_MESSAGE)
1580 .setDuration(Notification.TIME_LONG)
1581 .show();
1582 });
1583 }
1584}
Note: See TracBrowser for help on using the repository browser.