001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005import static org.openstreetmap.josm.tools.I18n.trn;
006import gnu.getopt.Getopt;
007import gnu.getopt.LongOpt;
008
009import java.awt.Dimension;
010import java.awt.Image;
011import java.awt.Toolkit;
012import java.awt.event.WindowAdapter;
013import java.awt.event.WindowEvent;
014import java.io.File;
015import java.io.IOException;
016import java.io.InputStream;
017import java.net.Authenticator;
018import java.net.ProxySelector;
019import java.net.URL;
020import java.security.AllPermission;
021import java.security.CodeSource;
022import java.security.KeyStoreException;
023import java.security.NoSuchAlgorithmException;
024import java.security.PermissionCollection;
025import java.security.Permissions;
026import java.security.Policy;
027import java.security.cert.CertificateException;
028import java.util.ArrayList;
029import java.util.Arrays;
030import java.util.Collection;
031import java.util.HashMap;
032import java.util.LinkedList;
033import java.util.List;
034import java.util.Map;
035import java.util.Set;
036import java.util.TreeSet;
037
038import javax.swing.JFrame;
039import javax.swing.JOptionPane;
040import javax.swing.RepaintManager;
041import javax.swing.SwingUtilities;
042
043import org.jdesktop.swinghelper.debug.CheckThreadViolationRepaintManager;
044import org.openstreetmap.josm.Main;
045import org.openstreetmap.josm.actions.PreferencesAction;
046import org.openstreetmap.josm.data.AutosaveTask;
047import org.openstreetmap.josm.data.CustomConfigurator;
048import org.openstreetmap.josm.data.Preferences;
049import org.openstreetmap.josm.data.Version;
050import org.openstreetmap.josm.gui.download.DownloadDialog;
051import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
052import org.openstreetmap.josm.gui.preferences.server.ProxyPreference;
053import org.openstreetmap.josm.gui.progress.ProgressMonitor;
054import org.openstreetmap.josm.gui.util.GuiHelper;
055import org.openstreetmap.josm.io.DefaultProxySelector;
056import org.openstreetmap.josm.io.MessageNotifier;
057import org.openstreetmap.josm.io.OnlineResource;
058import org.openstreetmap.josm.io.auth.CredentialsManager;
059import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
060import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
061import org.openstreetmap.josm.plugins.PluginHandler;
062import org.openstreetmap.josm.plugins.PluginInformation;
063import org.openstreetmap.josm.tools.BugReportExceptionHandler;
064import org.openstreetmap.josm.tools.FontsManager;
065import org.openstreetmap.josm.tools.I18n;
066import org.openstreetmap.josm.tools.ImageProvider;
067import org.openstreetmap.josm.tools.OsmUrlToBounds;
068import org.openstreetmap.josm.tools.PlatformHookWindows;
069import org.openstreetmap.josm.tools.Utils;
070
071/**
072 * Main window class application.
073 *
074 * @author imi
075 */
076public class MainApplication extends Main {
077    /**
078     * Allow subclassing (see JOSM.java)
079     */
080    public MainApplication() {}
081
082    /**
083     * Constructs a main frame, ready sized and operating. Does not display the frame.
084     * @param mainFrame The main JFrame of the application
085     */
086    public MainApplication(JFrame mainFrame) {
087        addListener();
088        mainFrame.setContentPane(contentPanePrivate);
089        mainFrame.setJMenuBar(menu);
090        geometry.applySafe(mainFrame);
091        List<Image> l = new LinkedList<>();
092        l.add(ImageProvider.get("logo_16x16x32").getImage());
093        l.add(ImageProvider.get("logo_16x16x8").getImage());
094        l.add(ImageProvider.get("logo_32x32x32").getImage());
095        l.add(ImageProvider.get("logo_32x32x8").getImage());
096        l.add(ImageProvider.get("logo_48x48x32").getImage());
097        l.add(ImageProvider.get("logo_48x48x8").getImage());
098        l.add(ImageProvider.get("logo").getImage());
099        mainFrame.setIconImages(l);
100        mainFrame.addWindowListener(new WindowAdapter(){
101            @Override
102            public void windowClosing(final WindowEvent arg0) {
103                Main.exitJosm(true, 0);
104            }
105        });
106        mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
107    }
108
109    /**
110     * Displays help on the console
111     * @since 2748
112     */
113    public static void showHelp() {
114        // TODO: put in a platformHook for system that have no console by default
115        System.out.println(tr("Java OpenStreetMap Editor")+" ["
116                +Version.getInstance().getAgentString()+"]\n\n"+
117                tr("usage")+":\n"+
118                "\tjava -jar josm.jar <options>...\n\n"+
119                tr("options")+":\n"+
120                "\t--help|-h                                 "+tr("Show this help")+"\n"+
121                "\t--geometry=widthxheight(+|-)x(+|-)y       "+tr("Standard unix geometry argument")+"\n"+
122                "\t[--download=]minlat,minlon,maxlat,maxlon  "+tr("Download the bounding box")+"\n"+
123                "\t[--download=]<URL>                        "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z)")+"\n"+
124                "\t[--download=]<filename>                   "+tr("Open a file (any file type that can be opened with File/Open)")+"\n"+
125                "\t--downloadgps=minlat,minlon,maxlat,maxlon "+tr("Download the bounding box as raw GPS")+"\n"+
126                "\t--downloadgps=<URL>                       "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS")+"\n"+
127                "\t--selection=<searchstring>                "+tr("Select with the given search")+"\n"+
128                "\t--[no-]maximize                           "+tr("Launch in maximized mode")+"\n"+
129                "\t--reset-preferences                       "+tr("Reset the preferences to default")+"\n\n"+
130                "\t--load-preferences=<url-to-xml>           "+tr("Changes preferences according to the XML file")+"\n\n"+
131                "\t--set=<key>=<value>                       "+tr("Set preference key to value")+"\n\n"+
132                "\t--language=<language>                     "+tr("Set the language")+"\n\n"+
133                "\t--version                                 "+tr("Displays the JOSM version and exits")+"\n\n"+
134                "\t--debug                                   "+tr("Print debugging messages to console")+"\n\n"+
135                "\t--offline=<osm_api|josm_website|all>      "+tr("Disable access to the given resource(s), separated by comma")+"\n\n"+
136                tr("options provided as Java system properties")+":\n"+
137                "\t-Djosm.home="+tr("/PATH/TO/JOSM/FOLDER/         ")+tr("Change the folder for all user settings")+"\n\n"+
138                tr("note: For some tasks, JOSM needs a lot of memory. It can be necessary to add the following\n" +
139                        "      Java option to specify the maximum size of allocated memory in megabytes")+":\n"+
140                        "\t-Xmx...m\n\n"+
141                        tr("examples")+":\n"+
142                        "\tjava -jar josm.jar track1.gpx track2.gpx london.osm\n"+
143                        "\tjava -jar josm.jar "+OsmUrlToBounds.getURL(43.2, 11.1, 13)+"\n"+
144                        "\tjava -jar josm.jar london.osm --selection=http://www.ostertag.name/osm/OSM_errors_node-duplicate.xml\n"+
145                        "\tjava -jar josm.jar 43.2,11.1,43.4,11.4\n"+
146                        "\tjava -Djosm.home=/home/user/.josm_dev -jar josm.jar\n"+
147                        "\tjava -Xmx1024m -jar josm.jar\n\n"+
148                        tr("Parameters --download, --downloadgps, and --selection are processed in this order.")+"\n"+
149                        tr("Make sure you load some data if you use --selection.")+"\n"
150                );
151    }
152
153    /**
154     * JOSM command line options.
155     * @see <a href="https://josm.openstreetmap.de/wiki/Help/CommandLineOptions">Help/CommandLineOptions</a>
156     * @since 5279
157     */
158    public enum Option {
159        /** --help|-h                                  Show this help */
160        HELP(false),
161        /** --version                                  Displays the JOSM version and exits */
162        VERSION(false),
163        /** --debug                                    Print debugging messages to console */
164        DEBUG(false),
165        /** --trace                                    Print detailed debugging messages to console */
166        TRACE(false),
167        /** --language=&lt;language&gt;                Set the language */
168        LANGUAGE(true),
169        /** --reset-preferences                        Reset the preferences to default */
170        RESET_PREFERENCES(false),
171        /** --load-preferences=&lt;url-to-xml&gt;      Changes preferences according to the XML file */
172        LOAD_PREFERENCES(true),
173        /** --set=&lt;key&gt;=&lt;value&gt;            Set preference key to value */
174        SET(true),
175        /** --geometry=widthxheight(+|-)x(+|-)y        Standard unix geometry argument */
176        GEOMETRY(true),
177        /** --no-maximize                              Do not launch in maximized mode */
178        NO_MAXIMIZE(false),
179        /** --maximize                                 Launch in maximized mode */
180        MAXIMIZE(false),
181        /** --download=minlat,minlon,maxlat,maxlon     Download the bounding box <br>
182         *  --download=&lt;URL&gt;                     Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) <br>
183         *  --download=&lt;filename&gt;                Open a file (any file type that can be opened with File/Open) */
184        DOWNLOAD(true),
185        /** --downloadgps=minlat,minlon,maxlat,maxlon  Download the bounding box as raw GPS <br>
186         *  --downloadgps=&lt;URL&gt;                  Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) as raw GPS */
187        DOWNLOADGPS(true),
188        /** --selection=&lt;searchstring&gt;           Select with the given search */
189        SELECTION(true),
190        /** --offline=&lt;osm_api|josm_website|all&gt; Disable access to the given resource(s), delimited by comma */
191        OFFLINE(true);
192
193        private String name;
194        private boolean requiresArgument;
195
196        private Option(boolean requiresArgument) {
197            this.name = name().toLowerCase().replace("_", "-");
198            this.requiresArgument = requiresArgument;
199        }
200
201        /**
202         * Replies the option name
203         * @return The option name, in lowercase
204         */
205        public String getName() {
206            return name;
207        }
208
209        /**
210         * Determines if this option requires an argument.
211         * @return {@code true} if this option requires an argument, {@code false} otherwise
212         */
213        public boolean requiresArgument() {
214            return requiresArgument;
215        }
216
217        public static Map<Option, Collection<String>> fromStringMap(Map<String, Collection<String>> opts) {
218            Map<Option, Collection<String>> res = new HashMap<>();
219            for (Map.Entry<String, Collection<String>> e : opts.entrySet()) {
220                Option o = Option.valueOf(e.getKey().toUpperCase().replace("-", "_"));
221                if (o != null) {
222                    res.put(o, e.getValue());
223                }
224            }
225            return res;
226        }
227    }
228
229    private static Map<Option, Collection<String>> buildCommandLineArgumentMap(String[] args) {
230
231        List<LongOpt> los = new ArrayList<>();
232        for (Option o : Option.values()) {
233            los.add(new LongOpt(o.getName(), o.requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0));
234        }
235
236        Getopt g = new Getopt("JOSM", args, "hv", los.toArray(new LongOpt[los.size()]));
237
238        Map<Option, Collection<String>> argMap = new HashMap<>();
239
240        int c;
241        while ((c = g.getopt()) != -1 ) {
242            Option opt = null;
243            switch (c) {
244                case 'h':
245                    opt = Option.HELP;
246                    break;
247                case 'v':
248                    opt = Option.VERSION;
249                    break;
250                case 0:
251                    opt = Option.values()[g.getLongind()];
252                    break;
253            }
254            if (opt != null) {
255                Collection<String> values = argMap.get(opt);
256                if (values == null) {
257                    values = new ArrayList<>();
258                    argMap.put(opt, values);
259                }
260                values.add(g.getOptarg());
261            } else
262                throw new IllegalArgumentException();
263        }
264        // positional arguments are a shortcut for the --download ... option
265        for (int i = g.getOptind(); i < args.length; ++i) {
266            Collection<String> values = argMap.get(Option.DOWNLOAD);
267            if (values == null) {
268                values = new ArrayList<>();
269                argMap.put(Option.DOWNLOAD, values);
270            }
271            values.add(args[i]);
272        }
273
274        return argMap;
275    }
276
277    /**
278     * Main application Startup
279     * @param argArray Command-line arguments
280     */
281    public static void main(final String[] argArray) {
282        I18n.init();
283        Main.checkJavaVersion();
284
285        // construct argument table
286        Map<Option, Collection<String>> args = null;
287        try {
288            args = buildCommandLineArgumentMap(argArray);
289        } catch (IllegalArgumentException e) {
290            System.exit(1);
291            return;
292        }
293
294        final boolean languageGiven = args.containsKey(Option.LANGUAGE);
295
296        if (languageGiven) {
297            I18n.set(args.get(Option.LANGUAGE).iterator().next());
298        }
299
300        initApplicationPreferences();
301
302        Policy.setPolicy(new Policy() {
303            // Permissions for plug-ins loaded when josm is started via webstart
304            private PermissionCollection pc;
305
306            {
307                pc = new Permissions();
308                pc.add(new AllPermission());
309            }
310
311            @Override
312            public void refresh() { }
313
314            @Override
315            public PermissionCollection getPermissions(CodeSource codesource) {
316                return pc;
317            }
318        });
319
320        Thread.setDefaultUncaughtExceptionHandler(new BugReportExceptionHandler());
321
322        // initialize the platform hook, and
323        Main.determinePlatformHook();
324        // call the really early hook before we do anything else
325        Main.platform.preStartupHook();
326
327        Main.commandLineArgs = Utils.copyArray(argArray);
328
329        if (args.containsKey(Option.VERSION)) {
330            System.out.println(Version.getInstance().getAgentString());
331            System.exit(0);
332        }
333
334        if (args.containsKey(Option.DEBUG) || args.containsKey(Option.TRACE)) {
335            // Enable JOSM debug level
336            logLevel = 4;
337            Main.info(tr("Printing debugging messages to console"));
338        }
339
340        if (args.containsKey(Option.TRACE)) {
341            // Enable JOSM debug level
342            logLevel = 5;
343            // Enable debug in OAuth signpost via system preference, but only at trace level
344            Preferences.updateSystemProperty("debug", "true");
345            Main.info(tr("Enabled detailed debug level (trace)"));
346        }
347
348        Main.pref.init(args.containsKey(Option.RESET_PREFERENCES));
349
350        if (!languageGiven) {
351            I18n.set(Main.pref.get("language", null));
352        }
353        Main.pref.updateSystemProperties();
354
355        processOffline(args);
356
357        FontsManager.initialize();
358
359        final JFrame mainFrame = new JFrame(tr("Java OpenStreetMap Editor"));
360        Main.parent = mainFrame;
361
362        if (args.containsKey(Option.LOAD_PREFERENCES)) {
363            CustomConfigurator.XMLCommandProcessor config = new CustomConfigurator.XMLCommandProcessor(Main.pref);
364            for (String i : args.get(Option.LOAD_PREFERENCES)) {
365                info("Reading preferences from " + i);
366                try (InputStream is = Utils.openURL(new URL(i))) {
367                    config.openAndReadXML(is);
368                } catch (Exception ex) {
369                    throw new RuntimeException(ex);
370                }
371            }
372        }
373
374        if (args.containsKey(Option.SET)) {
375            for (String i : args.get(Option.SET)) {
376                String[] kv = i.split("=", 2);
377                Main.pref.put(kv[0], "null".equals(kv[1]) ? null : kv[1]);
378            }
379        }
380
381        DefaultAuthenticator.createInstance();
382        Authenticator.setDefault(DefaultAuthenticator.getInstance());
383        DefaultProxySelector proxySelector = new DefaultProxySelector(ProxySelector.getDefault());
384        ProxySelector.setDefault(proxySelector);
385        OAuthAccessTokenHolder.getInstance().init(Main.pref, CredentialsManager.getInstance());
386
387        // asking for help? show help and exit
388        if (args.containsKey(Option.HELP)) {
389            showHelp();
390            System.exit(0);
391        }
392
393        final SplashScreen splash = new SplashScreen();
394        final ProgressMonitor monitor = splash.getProgressMonitor();
395        monitor.beginTask(tr("Initializing"));
396        splash.setVisible(Main.pref.getBoolean("draw.splashscreen", true));
397        Main.setInitStatusListener(new InitStatusListener() {
398
399            @Override
400            public void updateStatus(String event) {
401                monitor.indeterminateSubTask(event);
402            }
403        });
404
405        Collection<PluginInformation> pluginsToLoad = PluginHandler.buildListOfPluginsToLoad(splash,monitor.createSubTaskMonitor(1, false));
406        if (!pluginsToLoad.isEmpty() && PluginHandler.checkAndConfirmPluginUpdate(splash)) {
407            monitor.subTask(tr("Updating plugins"));
408            pluginsToLoad = PluginHandler.updatePlugins(splash, null, monitor.createSubTaskMonitor(1, false), false);
409        }
410
411        monitor.indeterminateSubTask(tr("Installing updated plugins"));
412        PluginHandler.installDownloadedPlugins(true);
413
414        monitor.indeterminateSubTask(tr("Loading early plugins"));
415        PluginHandler.loadEarlyPlugins(splash,pluginsToLoad, monitor.createSubTaskMonitor(1, false));
416
417        monitor.indeterminateSubTask(tr("Setting defaults"));
418        preConstructorInit(args);
419
420        monitor.indeterminateSubTask(tr("Creating main GUI"));
421        final Main main = new MainApplication(mainFrame);
422
423        monitor.indeterminateSubTask(tr("Loading plugins"));
424        PluginHandler.loadLatePlugins(splash,pluginsToLoad,  monitor.createSubTaskMonitor(1, false));
425        toolbar.refreshToolbarControl();
426
427        // Wait for splash disappearance (fix #9714)
428        GuiHelper.runInEDTAndWait(new Runnable() {
429            @Override
430            public void run() {
431                splash.setVisible(false);
432                splash.dispose();
433                mainFrame.setVisible(true);
434            }
435        });
436
437        Main.MasterWindowListener.setup();
438
439        boolean maximized = Main.pref.getBoolean("gui.maximized", false);
440        if ((!args.containsKey(Option.NO_MAXIMIZE) && maximized) || args.containsKey(Option.MAXIMIZE)) {
441            if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) {
442                Main.windowState = JFrame.MAXIMIZED_BOTH;
443                mainFrame.setExtendedState(Main.windowState);
444            } else {
445                Main.debug("Main window: maximizing not supported");
446            }
447        }
448        if (main.menu.fullscreenToggleAction != null) {
449            main.menu.fullscreenToggleAction.initial();
450        }
451
452        SwingUtilities.invokeLater(new GuiFinalizationWorker(args, proxySelector));
453
454        if (Main.isPlatformWindows()) {
455            try {
456                // Check for insecure certificates to remove.
457                // This is Windows-dependant code but it can't go to preStartupHook (need i18n) neither startupHook (need to be called before remote control)
458                PlatformHookWindows.removeInsecureCertificates();
459            } catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | IOException e) {
460                error(e);
461            }
462        }
463
464        if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) {
465            RemoteControl.start();
466        }
467
468        if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) {
469            MessageNotifier.start();
470        }
471
472        if (Main.pref.getBoolean("debug.edt-checker.enable", Version.getInstance().isLocalBuild())) {
473            // Repaint manager is registered so late for a reason - there is lots of violation during startup process but they don't seem to break anything and are difficult to fix
474            info("Enabled EDT checker, wrongful access to gui from non EDT thread will be printed to console");
475            RepaintManager.setCurrentManager(new CheckThreadViolationRepaintManager());
476        }
477    }
478
479    private static void processOffline(Map<Option, Collection<String>> args) {
480        if (args.containsKey(Option.OFFLINE)) {
481            for (String s : args.get(Option.OFFLINE).iterator().next().split(",")) {
482                try {
483                    Main.setOffline(OnlineResource.valueOf(s.toUpperCase()));
484                } catch (IllegalArgumentException e) {
485                    Main.error(tr("''{0}'' is not a valid value for argument ''{1}''. Possible values are {2}, possibly delimited by commas.",
486                            s.toUpperCase(), Option.OFFLINE.getName(), Arrays.toString(OnlineResource.values())));
487                    System.exit(1);
488                    return;
489                }
490            }
491            Set<OnlineResource> offline = Main.getOfflineResources();
492            if (!offline.isEmpty()) {
493                Main.warn(trn("JOSM is running in offline mode. This resource will not be available: {0}",
494                        "JOSM is running in offline mode. These resources will not be available: {0}",
495                        offline.size(), offline.size() == 1 ? offline.iterator().next() : Arrays.toString(offline.toArray())));
496            }
497        }
498    }
499
500    private static class GuiFinalizationWorker implements Runnable {
501
502        private final Map<Option, Collection<String>> args;
503        private final DefaultProxySelector proxySelector;
504
505        public GuiFinalizationWorker(Map<Option, Collection<String>> args, DefaultProxySelector proxySelector) {
506            this.args = args;
507            this.proxySelector = proxySelector;
508        }
509
510        @Override
511        public void run() {
512
513            // Handle proxy/network errors early to inform user he should change settings to be able to use JOSM correctly
514            if (!handleProxyErrors()) {
515                handleNetworkErrors();
516            }
517
518            // Restore autosave layers after crash and start autosave thread
519            handleAutosave();
520
521            // Handle command line instructions
522            postConstructorProcessCmdLine(args);
523
524            // Show download dialog if autostart is enabled
525            DownloadDialog.autostartIfNeeded();
526        }
527
528        private void handleAutosave() {
529            if (AutosaveTask.PROP_AUTOSAVE_ENABLED.get()) {
530                AutosaveTask autosaveTask = new AutosaveTask();
531                List<File> unsavedLayerFiles = autosaveTask.getUnsavedLayersFiles();
532                if (!unsavedLayerFiles.isEmpty()) {
533                    ExtendedDialog dialog = new ExtendedDialog(
534                            Main.parent,
535                            tr("Unsaved osm data"),
536                            new String[] {tr("Restore"), tr("Cancel"), tr("Discard")}
537                            );
538                    dialog.setContent(
539                            trn("JOSM found {0} unsaved osm data layer. ",
540                                    "JOSM found {0} unsaved osm data layers. ", unsavedLayerFiles.size(), unsavedLayerFiles.size()) +
541                                    tr("It looks like JOSM crashed last time. Would you like to restore the data?"));
542                    dialog.setButtonIcons(new String[] {"ok", "cancel", "dialogs/delete"});
543                    int selection = dialog.showDialog().getValue();
544                    if (selection == 1) {
545                        autosaveTask.recoverUnsavedLayers();
546                    } else if (selection == 3) {
547                        autosaveTask.discardUnsavedLayers();
548                    }
549                }
550                autosaveTask.schedule();
551            }
552        }
553
554        private boolean handleNetworkOrProxyErrors(boolean hasErrors, String title, String message) {
555            if (hasErrors) {
556                ExtendedDialog ed = new ExtendedDialog(
557                        Main.parent, title,
558                        new String[]{tr("Change proxy settings"), tr("Cancel")});
559                ed.setButtonIcons(new String[]{"dialogs/settings.png", "cancel.png"}).setCancelButton(2);
560                ed.setMinimumSize(new Dimension(460, 260));
561                ed.setIcon(JOptionPane.WARNING_MESSAGE);
562                ed.setContent(message);
563
564                if (ed.showDialog().getValue() == 1) {
565                    PreferencesAction.forPreferenceSubTab(null, null, ProxyPreference.class).run();
566                }
567            }
568            return hasErrors;
569        }
570
571        private boolean handleProxyErrors() {
572            return handleNetworkOrProxyErrors(proxySelector.hasErrors(), tr("Proxy errors occurred"),
573                    tr("JOSM tried to access the following resources:<br>" +
574                            "{0}" +
575                            "but <b>failed</b> to do so, because of the following proxy errors:<br>" +
576                            "{1}" +
577                            "Would you like to change your proxy settings now?",
578                            Utils.joinAsHtmlUnorderedList(proxySelector.getErrorResources()),
579                            Utils.joinAsHtmlUnorderedList(proxySelector.getErrorMessages())
580                    ));
581        }
582
583        private boolean handleNetworkErrors() {
584            boolean condition = !NETWORK_ERRORS.isEmpty();
585            if (condition) {
586                Set<String> errors = new TreeSet<>();
587                for (Throwable t : NETWORK_ERRORS.values()) {
588                    errors.add(t.toString());
589                }
590                return handleNetworkOrProxyErrors(condition, tr("Network errors occurred"),
591                        tr("JOSM tried to access the following resources:<br>" +
592                                "{0}" +
593                                "but <b>failed</b> to do so, because of the following network errors:<br>" +
594                                "{1}" +
595                                "It may be due to a missing proxy configuration.<br>" +
596                                "Would you like to change your proxy settings now?",
597                                Utils.joinAsHtmlUnorderedList(NETWORK_ERRORS.keySet()),
598                                Utils.joinAsHtmlUnorderedList(errors)
599                        ));
600            }
601            return false;
602        }
603    }
604}