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=<language> Set the language */ 042 LANGUAGE(true), 043 /** --reset-preferences Reset the preferences to default */ 044 RESET_PREFERENCES(false), 045 /** --load-preferences=<url-to-xml> Changes preferences according to the XML file */ 046 LOAD_PREFERENCES(true), 047 /** --set=<key>=<value> 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=<URL> Download the location at the URL (with lat=x&lon=y&zoom=z) <br> 057 * --download=<filename> 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=<URL> Download the location at the URL (with lat=x&lon=y&zoom=z) as raw GPS */ 061 DOWNLOADGPS(true), 062 /** --selection=<searchstring> Select with the given search */ 063 SELECTION(true), 064 /** --offline=<osm_api|josm_website|all> 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}