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