001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui;
003
004import java.util.ArrayList;
005import java.util.Arrays;
006import java.util.Collection;
007import java.util.Collections;
008import java.util.EnumMap;
009import java.util.HashMap;
010import java.util.List;
011import java.util.Locale;
012import java.util.Map;
013import java.util.Optional;
014import java.util.logging.Level;
015import java.util.stream.Stream;
016
017import org.openstreetmap.josm.tools.Logging;
018import org.openstreetmap.josm.tools.OptionParser;
019import org.openstreetmap.josm.tools.OptionParser.OptionCount;
020
021/**
022 * This class holds the arguments passed on to {@link MainApplication#main}.
023 * @author Michael Zangl
024 * @since 10899
025 */
026public class ProgramArguments {
027
028    /**
029     * JOSM command line options.
030     * @see <a href="https://josm.openstreetmap.de/wiki/Help/CommandLineOptions">Help/CommandLineOptions</a>
031     */
032    public enum Option {
033        /** --help|-h                                  Show this help */
034        HELP(false),
035        /** --version                                  Displays the JOSM version and exits */
036        VERSION(false),
037        /** --debug                                    Print debugging messages to console */
038        DEBUG(false),
039        /** --trace                                    Print detailed debugging messages to console */
040        TRACE(false),
041        /** --language=&lt;language&gt;                Set the language */
042        LANGUAGE(true),
043        /** --reset-preferences                        Reset the preferences to default */
044        RESET_PREFERENCES(false),
045        /** --load-preferences=&lt;url-to-xml&gt;      Changes preferences according to the XML file */
046        LOAD_PREFERENCES(true),
047        /** --set=&lt;key&gt;=&lt;value&gt;            Set preference key to value */
048        SET(true),
049        /** --geometry=widthxheight(+|-)x(+|-)y        Standard unix geometry argument */
050        GEOMETRY(true),
051        /** --no-maximize                              Do not launch in maximized mode */
052        NO_MAXIMIZE(false),
053        /** --maximize                                 Launch in maximized mode */
054        MAXIMIZE(false),
055        /** --download=minlat,minlon,maxlat,maxlon     Download the bounding box <br>
056         *  --download=&lt;URL&gt;                     Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) <br>
057         *  --download=&lt;filename&gt;                Open a file (any file type that can be opened with File/Open) */
058        DOWNLOAD(true),
059        /** --downloadgps=minlat,minlon,maxlat,maxlon  Download the bounding box as raw GPS <br>
060         *  --downloadgps=&lt;URL&gt;                  Download the location at the URL (with lat=x&amp;lon=y&amp;zoom=z) as raw GPS */
061        DOWNLOADGPS(true),
062        /** --selection=&lt;searchstring&gt;           Select with the given search */
063        SELECTION(true),
064        /** --offline=&lt;osm_api|josm_website|all&gt; Disable access to the given resource(s), delimited by comma */
065        OFFLINE(true),
066        /** --skip-plugins */
067        SKIP_PLUGINS(false);
068
069        private final String name;
070        private final boolean requiresArg;
071
072        Option(boolean requiresArgument) {
073            this.name = name().toLowerCase(Locale.ENGLISH).replace('_', '-');
074            this.requiresArg = requiresArgument;
075        }
076
077        /**
078         * Replies the option name
079         * @return The option name, in lowercase
080         */
081        public String getName() {
082            return name;
083        }
084
085        /**
086         * Determines if this option requires an argument.
087         * @return {@code true} if this option requires an argument, {@code false} otherwise
088         */
089        public boolean requiresArgument() {
090            return requiresArg;
091        }
092    }
093
094    private final Map<Option, List<String>> argMap = new EnumMap<>(Option.class);
095
096    /**
097     * Construct the program arguments object
098     * @param args The args passed to main.
099     * @since 10936
100     */
101    public ProgramArguments(String... args) {
102        Stream.of(Option.values()).forEach(o -> argMap.put(o, new ArrayList<>()));
103        buildCommandLineArgumentMap(args);
104    }
105
106    /**
107     * Builds the command-line argument map.
108     * @param args command-line arguments array
109     */
110    private void buildCommandLineArgumentMap(String... args) {
111        OptionParser parser = new OptionParser("JOSM");
112        for (Option o : Option.values()) {
113            if (o.requiresArgument()) {
114                parser.addArgumentParameter(o.getName(), OptionCount.MULTIPLE, p -> addOption(o, p));
115            } else {
116                parser.addFlagParameter(o.getName(), () -> addOption(o, ""));
117            }
118        }
119
120        parser.addShortAlias(Option.HELP.getName(), "h");
121        parser.addShortAlias(Option.VERSION.getName(), "v");
122
123        List<String> remaining = parser.parseOptionsOrExit(Arrays.asList(args));
124
125        // positional arguments are a shortcut for the --download ... option
126        for (String arg : remaining) {
127            addOption(Option.DOWNLOAD, arg);
128        }
129    }
130
131    private void addOption(Option opt, String optarg) {
132        argMap.get(opt).add(optarg);
133    }
134
135    /**
136     * Gets a single argument (the first) that was given for the given option.
137     * @param option The option to search
138     * @return The argument as optional value.
139     */
140    public Optional<String> getSingle(Option option) {
141        return get(option).stream().findFirst();
142    }
143
144    /**
145     * Gets all values that are given for a given option
146     * @param option The option
147     * @return The values that were given. May be empty.
148     */
149    public Collection<String> get(Option option) {
150        return Collections.unmodifiableList(argMap.get(option));
151    }
152
153    /**
154     * Test if a given option was used by the user.
155     * @param option The option to test for
156     * @return <code>true</code> if the user used it.
157     */
158    public boolean hasOption(Option option) {
159        return !get(option).isEmpty();
160    }
161
162    /**
163     * Helper method to indicate if version should be displayed.
164     * @return <code>true</code> to display version
165     */
166    public boolean showVersion() {
167        return hasOption(Option.VERSION);
168    }
169
170    /**
171     * Helper method to indicate if help should be displayed.
172     * @return <code>true</code> to display version
173     */
174    public boolean showHelp() {
175        return !get(Option.HELP).isEmpty();
176    }
177
178    /**
179     * Get the log level the user wants us to use.
180     * @return The log level.
181     */
182    public Level getLogLevel() {
183        if (hasOption(Option.TRACE)) {
184            return Logging.LEVEL_TRACE;
185        } else if (hasOption(Option.DEBUG)) {
186            return Logging.LEVEL_DEBUG;
187        } else {
188            return Logging.LEVEL_INFO;
189        }
190    }
191
192    /**
193     * Gets a map of all preferences the user wants to set.
194     * @return The preferences to set. It contains null values for preferences to unset
195     */
196    public Map<String, String> getPreferencesToSet() {
197        HashMap<String, String> map = new HashMap<>();
198        get(Option.SET).stream().map(i -> i.split("=", 2)).forEach(kv -> map.put(kv[0], getValue(kv)));
199        return map;
200    }
201
202    private static String getValue(String... kv) {
203        if (kv.length < 2) {
204            return "";
205        } else if ("null".equals(kv[1])) {
206            return null;
207        } else {
208            return kv[1];
209        }
210    }
211}