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.Logging;
017
018import gnu.getopt.Getopt;
019import gnu.getopt.LongOpt;
020
021/**
022 * This class holds the arguments passed on to 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        LongOpt toLongOpt() {
094            return new LongOpt(getName(), requiresArgument() ? LongOpt.REQUIRED_ARGUMENT : LongOpt.NO_ARGUMENT, null, 0);
095        }
096    }
097
098    private final Map<Option, List<String>> argMap = new EnumMap<>(Option.class);
099
100    /**
101     * Construct the program arguments object
102     * @param args The args passed to main.
103     * @since 10936
104     */
105    public ProgramArguments(String ... args) {
106        Stream.of(Option.values()).forEach(o -> argMap.put(o, new ArrayList<>()));
107        buildCommandLineArgumentMap(args);
108    }
109
110    /**
111     * Builds the command-line argument map.
112     * @param args command-line arguments array
113     */
114    private void buildCommandLineArgumentMap(String ... args) {
115        LongOpt[] los = Stream.of(Option.values()).map(Option::toLongOpt).toArray(i -> new LongOpt[i]);
116
117        Getopt g = new Getopt("JOSM", args, "hv", los);
118
119        int c;
120        while ((c = g.getopt()) != -1) {
121            Option opt;
122            switch (c) {
123            case 'h':
124                opt = Option.HELP;
125                break;
126            case 'v':
127                opt = Option.VERSION;
128                break;
129            case 0:
130                opt = Option.values()[g.getLongind()];
131                break;
132            default:
133                opt = null;
134            }
135            if (opt != null) {
136                addOption(opt, g.getOptarg());
137            } else
138                throw new IllegalArgumentException("Invalid option: "+c);
139        }
140        // positional arguments are a shortcut for the --download ... option
141        for (int i = g.getOptind(); i < args.length; ++i) {
142            addOption(Option.DOWNLOAD, args[i]);
143        }
144    }
145
146    private void addOption(Option opt, String optarg) {
147        argMap.get(opt).add(optarg);
148    }
149
150    /**
151     * Gets a single argument (the first) that was given for the given option.
152     * @param option The option to search
153     * @return The argument as optional value.
154     */
155    public Optional<String> getSingle(Option option) {
156        return get(option).stream().findFirst();
157    }
158
159    /**
160     * Gets all values that are given for a given option
161     * @param option The option
162     * @return The values that were given. May be empty.
163     */
164    public Collection<String> get(Option option) {
165        return Collections.unmodifiableList(argMap.get(option));
166    }
167
168    /**
169     * Test if a given option was used by the user.
170     * @param option The option to test for
171     * @return <code>true</code> if the user used it.
172     */
173    public boolean hasOption(Option option) {
174        return !get(option).isEmpty();
175    }
176
177    /**
178     * Helper method to indicate if version should be displayed.
179     * @return <code>true</code> to display version
180     */
181    public boolean showVersion() {
182        return hasOption(Option.VERSION);
183    }
184
185    /**
186     * Helper method to indicate if help should be displayed.
187     * @return <code>true</code> to display version
188     */
189    public boolean showHelp() {
190        return !get(Option.HELP).isEmpty();
191    }
192
193    /**
194     * Get the log level the user wants us to use.
195     * @return The log level.
196     */
197    public Level getLogLevel() {
198        if (hasOption(Option.TRACE)) {
199            return Logging.LEVEL_TRACE;
200        } else if (hasOption(Option.DEBUG)) {
201            return Logging.LEVEL_DEBUG;
202        } else {
203            return Logging.LEVEL_INFO;
204        }
205    }
206
207    /**
208     * Gets a map of all preferences the user wants to set.
209     * @return The preferences to set. It contains null values for preferences to unset
210     */
211    public Map<String, String> getPreferencesToSet() {
212        HashMap<String, String> map = new HashMap<>();
213        get(Option.SET).stream().map(i -> i.split("=", 2)).forEach(kv -> map.put(kv[0], getValue(kv)));
214        return map;
215    }
216
217    private static String getValue(String ... kv) {
218        if (kv.length < 2) {
219            return "";
220        } else if ("null".equals(kv[1])) {
221            return null;
222        } else {
223            return kv[1];
224        }
225    }
226}