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