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

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

fix #16410 - NPE

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