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.Image;
010import java.awt.Toolkit;
011import java.awt.event.WindowAdapter;
012import java.awt.event.WindowEvent;
013import java.io.File;
014import java.net.Authenticator;
015import java.net.ProxySelector;
016import java.net.URL;
017import java.security.AllPermission;
018import java.security.CodeSource;
019import java.security.PermissionCollection;
020import java.security.Permissions;
021import java.security.Policy;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.HashMap;
025import java.util.LinkedList;
026import java.util.List;
027import java.util.Map;
028
029import javax.swing.JFrame;
030import javax.swing.RepaintManager;
031import javax.swing.SwingUtilities;
032
033import org.jdesktop.swinghelper.debug.CheckThreadViolationRepaintManager;
034import org.openstreetmap.josm.Main;
035import org.openstreetmap.josm.data.AutosaveTask;
036import org.openstreetmap.josm.data.CustomConfigurator;
037import org.openstreetmap.josm.data.Preferences;
038import org.openstreetmap.josm.data.Version;
039import org.openstreetmap.josm.gui.download.DownloadDialog;
040import org.openstreetmap.josm.gui.preferences.server.OAuthAccessTokenHolder;
041import org.openstreetmap.josm.gui.progress.ProgressMonitor;
042import org.openstreetmap.josm.gui.util.GuiHelper;
043import org.openstreetmap.josm.io.DefaultProxySelector;
044import org.openstreetmap.josm.io.MessageNotifier;
045import org.openstreetmap.josm.io.auth.CredentialsManager;
046import org.openstreetmap.josm.io.auth.DefaultAuthenticator;
047import org.openstreetmap.josm.io.remotecontrol.RemoteControl;
048import org.openstreetmap.josm.plugins.PluginHandler;
049import org.openstreetmap.josm.plugins.PluginInformation;
050import org.openstreetmap.josm.tools.BugReportExceptionHandler;
051import org.openstreetmap.josm.tools.I18n;
052import org.openstreetmap.josm.tools.ImageProvider;
053import org.openstreetmap.josm.tools.OsmUrlToBounds;
054import org.openstreetmap.josm.tools.Utils;
055
056/**
057 * Main window class application.
058 *
059 * @author imi
060 */
061public class MainApplication extends Main {
062    /**
063     * Allow subclassing (see JOSM.java)
064     */
065    public MainApplication() {}
066
067    /**
068     * Constructs a main frame, ready sized and operating. Does not display the frame.
069     * @param mainFrame The main JFrame of the application
070     */
071    public MainApplication(JFrame mainFrame) {
072        addListener();
073        mainFrame.setContentPane(contentPanePrivate);
074        mainFrame.setJMenuBar(menu);
075        geometry.applySafe(mainFrame);
076        LinkedList<Image> l = new LinkedList<Image>();
077        l.add(ImageProvider.get("logo_16x16x32").getImage());
078        l.add(ImageProvider.get("logo_16x16x8").getImage());
079        l.add(ImageProvider.get("logo_32x32x32").getImage());
080        l.add(ImageProvider.get("logo_32x32x8").getImage());
081        l.add(ImageProvider.get("logo_48x48x32").getImage());
082        l.add(ImageProvider.get("logo_48x48x8").getImage());
083        l.add(ImageProvider.get("logo").getImage());
084        mainFrame.setIconImages(l);
085        mainFrame.addWindowListener(new WindowAdapter(){
086            @Override public void windowClosing(final WindowEvent arg0) {
087                Main.exitJosm(true, 0);
088            }
089        });
090        mainFrame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
091    }
092
093    /**
094     * Displays help on the console
095     * @since 2748
096     */
097    public static void showHelp() {
098        // TODO: put in a platformHook for system that have no console by default
099        System.out.println(tr("Java OpenStreetMap Editor")+" ["
100                +Version.getInstance().getAgentString()+"]\n\n"+
101                tr("usage")+":\n"+
102                "\tjava -jar josm.jar <options>...\n\n"+
103                tr("options")+":\n"+
104                "\t--help|-h                                 "+tr("Show this help")+"\n"+
105                "\t--geometry=widthxheight(+|-)x(+|-)y       "+tr("Standard unix geometry argument")+"\n"+
106                "\t[--download=]minlat,minlon,maxlat,maxlon  "+tr("Download the bounding box")+"\n"+
107                "\t[--download=]<URL>                        "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z)")+"\n"+
108                "\t[--download=]<filename>                   "+tr("Open a file (any file type that can be opened with File/Open)")+"\n"+
109                "\t--downloadgps=minlat,minlon,maxlat,maxlon "+tr("Download the bounding box as raw GPS")+"\n"+
110                "\t--downloadgps=<URL>                       "+tr("Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS")+"\n"+
111                "\t--selection=<searchstring>                "+tr("Select with the given search")+"\n"+
112                "\t--[no-]maximize                           "+tr("Launch in maximized mode")+"\n"+
113                "\t--reset-preferences                       "+tr("Reset the preferences to default")+"\n\n"+
114                "\t--load-preferences=<url-to-xml>           "+tr("Changes preferences according to the XML file")+"\n\n"+
115                "\t--set=<key>=<value>                       "+tr("Set preference key to value")+"\n\n"+
116                "\t--language=<language>                     "+tr("Set the language")+"\n\n"+
117                "\t--version                                 "+tr("Displays the JOSM version and exits")+"\n\n"+
118                tr("options provided as Java system properties")+":\n"+
119                "\t-Djosm.home="+tr("/PATH/TO/JOSM/FOLDER/         ")+tr("Change the folder for all user settings")+"\n\n"+
120                tr("note: For some tasks, JOSM needs a lot of memory. It can be necessary to add the following\n" +
121                        "      Java option to specify the maximum size of allocated memory in megabytes")+":\n"+
122                        "\t-Xmx...m\n\n"+
123                        tr("examples")+":\n"+
124                        "\tjava -jar josm.jar track1.gpx track2.gpx london.osm\n"+
125                        "\tjava -jar josm.jar "+OsmUrlToBounds.getURL(43.2, 11.1, 13)+"\n"+
126                        "\tjava -jar josm.jar london.osm --selection=http://www.ostertag.name/osm/OSM_errors_node-duplicate.xml\n"+
127                        "\tjava -jar josm.jar 43.2,11.1,43.4,11.4\n"+
128                        "\tjava -Djosm.home=/home/user/.josm_dev -jar josm.jar\n"+
129                        "\tjava -Xmx1024m -jar josm.jar\n\n"+
130                        tr("Parameters --download, --downloadgps, and --selection are processed in this order.")+"\n"+
131                        tr("Make sure you load some data if you use --selection.")+"\n"
132                );
133    }
134
135    /**
136     * JOSM command line options.
137     * @see <a href="http://josm.openstreetmap.de/wiki/Help/CommandLineOptions">Help/CommandLineOptions</a>
138     * @since 5279
139     */
140    public enum Option {
141        /** --help|-h                                 Show this help */
142        HELP(false),
143        /** --version                                 Displays the JOSM version and exits */
144        VERSION(false),
145        /** --language=<language>                     Set the language */
146        LANGUAGE(true),
147        /** --reset-preferences                       Reset the preferences to default */
148        RESET_PREFERENCES(false),
149        /** --load-preferences=<url-to-xml>           Changes preferences according to the XML file */
150        LOAD_PREFERENCES(true),
151        /** --set=<key>=<value>                       Set preference key to value */
152        SET(true),
153        /** --geometry=widthxheight(+|-)x(+|-)y       Standard unix geometry argument */
154        GEOMETRY(true),
155        /** --no-maximize                             Do not launch in maximized mode */
156        NO_MAXIMIZE(false),
157        /** --maximize                                Launch in maximized mode */
158        MAXIMIZE(false),
159        /** --download=minlat,minlon,maxlat,maxlon    Download the bounding box <br>
160         *  --download=<URL>                          Download the location at the URL (with lat=x&lon=y&zoom=z) <br>
161         *  --download=<filename>                     Open a file (any file type that can be opened with File/Open) */
162        DOWNLOAD(true),
163        /** --downloadgps=minlat,minlon,maxlat,maxlon Download the bounding box as raw GPS <br>
164         *  --downloadgps=<URL>                       Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS */
165        DOWNLOADGPS(true),
166        /** --selection=<searchstring>                Select with the given search */
167        SELECTION(true);
168
169        private String name;
170        private boolean requiresArgument;
171
172        private Option(boolean requiresArgument) {
173            this.name = name().toLowerCase().replace("_", "-");
174            this.requiresArgument = requiresArgument;
175        }
176
177        /**
178         * Replies the option name
179         * @return The option name, in lowercase
180         */
181        public String getName() {
182            return name;
183        }
184
185        /**
186         * Determines if this option requires an argument.
187         * @return {@code true} if this option requires an argument, {@code false} otherwise
188         */
189        public boolean requiresArgument() {
190            return requiresArgument;
191        }
192
193        public static Map<Option, Collection<String>> fromStringMap(Map<String, Collection<String>> opts) {
194            Map<Option, Collection<String>> res = new HashMap<Option, Collection<String>>();
195            for (Map.Entry<String, Collection<String>> e : opts.entrySet()) {
196                Option o = Option.valueOf(e.getKey().toUpperCase().replace("-", "_"));
197                if (o != null) {
198                    res.put(o, e.getValue());
199                }
200            }
201            return res;
202        }
203    }
204
205    private static Map<Option, Collection<String>> buildCommandLineArgumentMap(String[] args) {
206
207        List<LongOpt> los = new ArrayList<LongOpt>();
208        for (Option o : Option.values()) {
209            los.add(new LongOpt(o.getName(), o.requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0));
210        }
211
212        Getopt g = new Getopt("JOSM", args, "hv", los.toArray(new LongOpt[los.size()]));
213
214        Map<Option, Collection<String>> argMap = new HashMap<Option, Collection<String>>();
215
216        int c;
217        while ((c = g.getopt()) != -1 ) {
218            Option opt = null;
219            switch (c) {
220                case 'h':
221                    opt = Option.HELP;
222                    break;
223                case 'v':
224                    opt = Option.VERSION;
225                    break;
226                case 0:
227                    opt = Option.values()[g.getLongind()];
228                    break;
229            }
230            if (opt != null) {
231                Collection<String> values = argMap.get(opt);
232                if (values == null) {
233                    values = new ArrayList<String>();
234                    argMap.put(opt, values);
235                }
236                values.add(g.getOptarg());
237            } else
238                throw new IllegalArgumentException();
239        }
240        // positional arguments are a shortcut for the --download ... option
241        for (int i = g.getOptind(); i < args.length; ++i) {
242            Collection<String> values = argMap.get(Option.DOWNLOAD);
243            if (values == null) {
244                values = new ArrayList<String>();
245                argMap.put(Option.DOWNLOAD, values);
246            }
247            values.add(args[i]);
248        }
249
250        return argMap;
251    }
252
253    /**
254     * Main application Startup
255     * @param argArray Command-line arguments
256     */
257    public static void main(final String[] argArray) {
258        I18n.init();
259        Main.checkJava6();
260
261        // construct argument table
262        Map<Option, Collection<String>> args = null;
263        try {
264            args = buildCommandLineArgumentMap(argArray);
265        } catch (IllegalArgumentException e) {
266            System.exit(1);
267        }
268        
269        final boolean languageGiven = args.containsKey(Option.LANGUAGE);
270
271        if (languageGiven) {
272            I18n.set(args.get(Option.LANGUAGE).iterator().next());
273        }
274
275        initApplicationPreferences();
276
277        Policy.setPolicy(new Policy() {
278            // Permissions for plug-ins loaded when josm is started via webstart
279            private PermissionCollection pc;
280
281            {
282                pc = new Permissions();
283                pc.add(new AllPermission());
284            }
285
286            @Override
287            public void refresh() { }
288
289            @Override
290            public PermissionCollection getPermissions(CodeSource codesource) {
291                return pc;
292            }
293        });
294
295        Thread.setDefaultUncaughtExceptionHandler(new BugReportExceptionHandler());
296        // http://stuffthathappens.com/blog/2007/10/15/one-more-note-on-uncaught-exception-handlers/
297        System.setProperty("sun.awt.exception.handler", BugReportExceptionHandler.class.getName());
298
299        // initialize the platform hook, and
300        Main.determinePlatformHook();
301        // call the really early hook before we do anything else
302        Main.platform.preStartupHook();
303
304        Main.commandLineArgs = Utils.copyArray(argArray);
305        
306        if (args.containsKey(Option.VERSION)) {
307            System.out.println(Version.getInstance().getAgentString());
308            System.exit(0);
309        }
310
311        Main.pref.init(args.containsKey(Option.RESET_PREFERENCES));
312
313        if (!languageGiven) {
314            I18n.set(Main.pref.get("language", null));
315        }
316        Main.pref.updateSystemProperties();
317
318        final JFrame mainFrame = new JFrame(tr("Java OpenStreetMap Editor"));
319        Main.parent = mainFrame;
320
321        if (args.containsKey(Option.LOAD_PREFERENCES)) {
322            CustomConfigurator.XMLCommandProcessor config = new CustomConfigurator.XMLCommandProcessor(Main.pref);
323            for (String i : args.get(Option.LOAD_PREFERENCES)) {
324                info("Reading preferences from " + i);
325                try {
326                    config.openAndReadXML(Utils.openURL(new URL(i)));
327                } catch (Exception ex) {
328                    throw new RuntimeException(ex);
329                }
330            }
331        }
332
333        if (args.containsKey(Option.SET)) {
334            for (String i : args.get(Option.SET)) {
335                String[] kv = i.split("=", 2);
336                Main.pref.put(kv[0], "null".equals(kv[1]) ? null : kv[1]);
337            }
338        }
339
340        DefaultAuthenticator.createInstance();
341        Authenticator.setDefault(DefaultAuthenticator.getInstance());
342        ProxySelector.setDefault(new DefaultProxySelector(ProxySelector.getDefault()));
343        OAuthAccessTokenHolder.getInstance().init(Main.pref, CredentialsManager.getInstance());
344
345        // asking for help? show help and exit
346        if (args.containsKey(Option.HELP)) {
347            showHelp();
348            System.exit(0);
349        }
350
351        final SplashScreen splash = new SplashScreen();
352        final ProgressMonitor monitor = splash.getProgressMonitor();
353        monitor.beginTask(tr("Initializing"));
354        splash.setVisible(Main.pref.getBoolean("draw.splashscreen", true));
355        Main.setInitStatusListener(new InitStatusListener() {
356
357            @Override
358            public void updateStatus(String event) {
359                monitor.indeterminateSubTask(event);
360            }
361        });
362
363        List<PluginInformation> pluginsToLoad = PluginHandler.buildListOfPluginsToLoad(splash,monitor.createSubTaskMonitor(1, false));
364        if (!pluginsToLoad.isEmpty() && PluginHandler.checkAndConfirmPluginUpdate(splash)) {
365            monitor.subTask(tr("Updating plugins"));
366            pluginsToLoad = PluginHandler.updatePlugins(splash,pluginsToLoad, monitor.createSubTaskMonitor(1, false));
367        }
368
369        monitor.indeterminateSubTask(tr("Installing updated plugins"));
370        PluginHandler.installDownloadedPlugins(true);
371
372        monitor.indeterminateSubTask(tr("Loading early plugins"));
373        PluginHandler.loadEarlyPlugins(splash,pluginsToLoad, monitor.createSubTaskMonitor(1, false));
374
375        monitor.indeterminateSubTask(tr("Setting defaults"));
376        preConstructorInit(args);
377
378        monitor.indeterminateSubTask(tr("Creating main GUI"));
379        final Main main = new MainApplication(mainFrame);
380
381        monitor.indeterminateSubTask(tr("Loading plugins"));
382        PluginHandler.loadLatePlugins(splash,pluginsToLoad,  monitor.createSubTaskMonitor(1, false));
383        toolbar.refreshToolbarControl();
384
385        GuiHelper.runInEDT(new Runnable() {
386            @Override
387            public void run() {
388                splash.setVisible(false);
389                splash.dispose();
390                mainFrame.setVisible(true);
391            }
392        });
393
394        Main.MasterWindowListener.setup();
395
396        boolean maximized = Boolean.parseBoolean(Main.pref.get("gui.maximized"));
397        if ((!args.containsKey(Option.NO_MAXIMIZE) && maximized) || args.containsKey(Option.MAXIMIZE)) {
398            if (Toolkit.getDefaultToolkit().isFrameStateSupported(JFrame.MAXIMIZED_BOTH)) {
399                Main.windowState = JFrame.MAXIMIZED_BOTH;
400                mainFrame.setExtendedState(Main.windowState);
401            } else {
402                Main.debug("Main window: maximizing not supported");
403            }
404        }
405        if (main.menu.fullscreenToggleAction != null) {
406            main.menu.fullscreenToggleAction.initial();
407        }
408
409        final Map<Option, Collection<String>> args_final = args;
410
411        SwingUtilities.invokeLater(new Runnable() {
412            @Override
413            public void run() {
414                if (AutosaveTask.PROP_AUTOSAVE_ENABLED.get()) {
415                    AutosaveTask autosaveTask = new AutosaveTask();
416                    List<File> unsavedLayerFiles = autosaveTask.getUnsavedLayersFiles();
417                    if (!unsavedLayerFiles.isEmpty()) {
418                        ExtendedDialog dialog = new ExtendedDialog(
419                                Main.parent,
420                                tr("Unsaved osm data"),
421                                new String[] {tr("Restore"), tr("Cancel"), tr("Discard")}
422                                );
423                        dialog.setContent(
424                                trn("JOSM found {0} unsaved osm data layer. ",
425                                        "JOSM found {0} unsaved osm data layers. ", unsavedLayerFiles.size(), unsavedLayerFiles.size()) +
426                                        tr("It looks like JOSM crashed last time. Would you like to restore the data?"));
427                        dialog.setButtonIcons(new String[] {"ok", "cancel", "dialogs/delete"});
428                        int selection = dialog.showDialog().getValue();
429                        if (selection == 1) {
430                            autosaveTask.recoverUnsavedLayers();
431                        } else if (selection == 3) {
432                            autosaveTask.dicardUnsavedLayers();
433                        }
434                    }
435                    autosaveTask.schedule();
436                }
437
438                postConstructorProcessCmdLine(args_final);
439
440                DownloadDialog.autostartIfNeeded();
441            }
442        });
443
444        if (RemoteControl.PROP_REMOTECONTROL_ENABLED.get()) {
445            RemoteControl.start();
446        }
447        
448        if (MessageNotifier.PROP_NOTIFIER_ENABLED.get()) {
449            MessageNotifier.start();
450        }
451
452        if (Main.pref.getBoolean("debug.edt-checker.enable", Version.getInstance().isLocalBuild())) {
453            // 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
454            info("Enabled EDT checker, wrongful access to gui from non EDT thread will be printed to console");
455            RepaintManager.setCurrentManager(new CheckThreadViolationRepaintManager());
456        }
457    }
458}