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