/* * $Id$ * * Copyright (c) 2008-2010 by Joel Uckelman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License (LGPL) as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Library General Public License for more details. * * You should have received a copy of the GNU Library General Public * License along with this library; if not, copies are available * at http://www.opensource.org. */ package VASSAL.launch; import gnu.getopt.Getopt; import gnu.getopt.LongOpt; import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import VASSAL.Info; import VASSAL.build.module.metadata.AbstractMetaData; import VASSAL.build.module.metadata.ExtensionMetaData; import VASSAL.build.module.metadata.MetaDataFactory; import VASSAL.build.module.metadata.ModuleMetaData; import VASSAL.build.module.metadata.SaveMetaData; import VASSAL.i18n.Resources; /** * Encapsulates and parses command-line arguments. * <code>args</code> and <code>LaunchRequest.parseArgs(args).toArgs()</code> * are equivalent (though perhaps not equal) argument lists. * * @author Joel Uckelman * @since 3.1.0 */ public class LaunchRequest implements Serializable { private static final long serialVersionUID = 1L; enum Mode { MANAGE { public String toString() { return "manage"; } }, LOAD { public String toString() { return "load"; } }, EDIT { public String toString() { return "edit"; } }, IMPORT { public String toString() { return "import"; } }, NEW { public String toString() { return "new"; } }, EDIT_EXT { public String toString() { return "edit-extension"; } }, NEW_EXT { public String toString() { return "new-extension"; } }, TRANSLATE { public String toString() { return "translate"; } } } public Mode mode; public File module; public File game; public File extension; public File importFile; public boolean standalone = false; public boolean builtInModule; public List<String> autoext; public int port = -1; public long key; public LaunchRequest() { this(null, null); } public LaunchRequest(Mode mode) { this(mode, null, null); } public LaunchRequest(Mode mode, File module) { this(mode, module, null); } public LaunchRequest(Mode mode, File module, File other) { this.mode = mode; this.module = module; if (mode == Mode.EDIT_EXT) extension = other; else game = other; } public LaunchRequest(LaunchRequest lr) { this.mode = lr.mode; this.module = lr.module; this.game = lr.game; this.extension = lr.extension; this.importFile = lr.importFile; this.standalone = lr.standalone; this.builtInModule = lr.builtInModule; if (lr.autoext != null) this.autoext = new ArrayList<String>(lr.autoext); } /** * Create an argument array equivalent to this <code>LaunchRequest</code>. * * @return an array which would be parsed to this <code>LaunchRequest</code> */ public String[] toArgs() { final ArrayList<String> args = new ArrayList<String>(); args.add("--" + mode.toString()); if (builtInModule) args.add("--auto"); if (port >= 0) args.add("--port=" + port); if (autoext != null) { final StringBuilder sb = new StringBuilder("--auto-extensions="); final Iterator<String> i = autoext.iterator(); sb.append(i.next()); while (i.hasNext()) sb.append(',').append(i.next()); args.add(sb.toString().replace(' ','_')); } args.add("--"); if (module != null) { args.add(module.getPath()); if (game != null) { args.add(game.getPath()); } else if (extension != null) { args.add(extension.getPath()); } } else if (importFile != null) { args.add(importFile.getPath()); } return args.toArray(new String[args.size()]); } // FIXME: translate this somehow? private static final String help = "Usage:\n" + " VASSAL -e [option]... module\n" + " VASSAL -i [option]... module\n" + " VASSAL -l [option]... module|save|log...\n" + " VASSAL -n [option]...\n" + " VASSAL -m\n" + " VASSAL -h\n" + " VASSAL --edit-extension [option]... module|extension...\n" + " VASSAL --new-extension [option]...\n" + "\n" + "Options:\n" + " -a, --auto TODO\n" + " -e, --edit Edit a module\n" + " -h, --help Display this help and exit\n" + " -i, --import Import a non-VASSAL module\n" + " -l, --load Load a module and saved game or log\n" + " -m, --manage Use the module manager\n" + " -n, --new Create a new module\n" + " -s, --standalone Run in standalone mode\n" + " --auto-extensions TODO\n" + " --edit-extension Edit a module extension\n" + " --new-extension Create a new module extension\n" + " --port Set port for manager to listen on\n" + " --version Display version information and exit\n" + " -- Terminate the list of options\n" + "\n" + "VASSAL defaults to '-m' if no options are given.\n" + "\n"; /** * Parse an argument array to a <code>LaunchRequest</code>. * * @param args an array of command-line arguments * @return a <code>LaunchRequest</code> equivalent to <code>args</code> * @throws LaunchRequestException when parsing fails */ public static LaunchRequest parseArgs(String[] args) throws LaunchRequestException { final LaunchRequest lr = new LaunchRequest(); final int AUTO_EXT = 2; final int EDIT_EXT = 3; final int NEW_EXT = 4; final int PORT = 5; final int VERSION = 6; final int TRANSLATE = 7; final LongOpt[] longOpts = new LongOpt[]{ new LongOpt("auto", LongOpt.NO_ARGUMENT, null, 'a'), new LongOpt("edit", LongOpt.NO_ARGUMENT, null, 'e'), new LongOpt("help", LongOpt.NO_ARGUMENT, null, 'h'), new LongOpt("import", LongOpt.NO_ARGUMENT, null, 'i'), new LongOpt("load", LongOpt.NO_ARGUMENT, null, 'l'), new LongOpt("manage", LongOpt.NO_ARGUMENT, null, 'm'), new LongOpt("new", LongOpt.NO_ARGUMENT, null, 'n'), new LongOpt("standalone", LongOpt.NO_ARGUMENT, null, 's'), new LongOpt("auto-extensions", LongOpt.REQUIRED_ARGUMENT, null, AUTO_EXT), new LongOpt("edit-extension", LongOpt.NO_ARGUMENT, null, EDIT_EXT), new LongOpt("new-extension", LongOpt.NO_ARGUMENT, null, NEW_EXT), new LongOpt("port", LongOpt.REQUIRED_ARGUMENT, null, PORT), new LongOpt("version", LongOpt.NO_ARGUMENT, null, VERSION), new LongOpt("translate", LongOpt.NO_ARGUMENT, null, TRANSLATE) }; final Getopt g = new Getopt("VASSAL", args, ":aehilmn", longOpts); g.setOpterr(false); int c; while ((c = g.getopt()) != -1) { switch (c) { case AUTO_EXT: if (lr.autoext == null) lr.autoext = new ArrayList<String>(); for (String ext : g.getOptarg().split(",")) { lr.autoext.add(ext.replace("_"," ")); } break; case EDIT_EXT: setMode(lr, Mode.EDIT_EXT); break; case NEW_EXT: setMode(lr, Mode.NEW_EXT); break; case PORT: try { lr.port = Integer.parseInt(g.getOptarg()); } catch (NumberFormatException e) { die("LaunchRequest.bad_port", g.getOptarg()); } if (lr.port < 49152 || lr.port > 65535) { die("LaunchRequest.bad_port", g.getOptarg()); } break; case VERSION: System.err.println("VASSAL " + Info.getVersion()); System.exit(0); break; case TRANSLATE: setMode(lr, Mode.TRANSLATE); break; case 'a': lr.builtInModule = true; break; case 'e': setMode(lr, Mode.EDIT); break; case 'h': System.err.print(help); System.exit(0); break; case 'i': setMode(lr, Mode.IMPORT); break; case 'l': setMode(lr, Mode.LOAD); break; case 'm': setMode(lr, Mode.MANAGE); break; case 'n': setMode(lr, Mode.NEW); break; case 's': lr.standalone = true; break; case ':': die("LaunchRequest.missing_argument", args[g.getOptind()-1]); break; case '?': // NB: getOptind() is not advanced if the unrecognized option // is short and bundled, so we must handle unrecognized long // options separately from unrecognized short options. die( "LaunchRequest.unrecognized_option", g.getOptopt() == 0 ? args[g.getOptind()-1] : '-' + String.valueOf((char) g.getOptopt()) ); break; default: // should never happen throw new IllegalStateException(); } } int i = g.getOptind(); // load by default if a non-option argument is given; otherwise, manage if (lr.mode == null) { lr.mode = i < args.length ? Mode.LOAD : Mode.MANAGE; } // get the module and game, if specified switch (lr.mode) { case MANAGE: break; case LOAD: while (i < args.length) { final File file = new File(args[i++]); final AbstractMetaData data = MetaDataFactory.buildMetaData(file); if (data instanceof ModuleMetaData) { if (lr.module != null) die("LaunchRequest.only_one", "module"); lr.module = file; } else if (data instanceof ExtensionMetaData) { if (lr.extension != null) die(""); lr.extension = file; } else if (data instanceof SaveMetaData) { if (lr.game != null) die("LaunchRequest.only_one", "saved game or log"); lr.game = file; } else { die("LaunchRequest.unknown_file_type", file.toString()); } } if (!lr.builtInModule && lr.module == null && lr.game == null) { die("LaunchRequest.missing_module"); } break; case IMPORT: if (i < args.length) { lr.importFile = new File(args[i++]); } else { die("LaunchRequest.missing_module"); } break; case EDIT: case NEW_EXT: if (i < args.length) { final File file = new File(args[i++]); final AbstractMetaData data = MetaDataFactory.buildMetaData(file); if (data instanceof ModuleMetaData) { lr.module = file; } else if (data instanceof ExtensionMetaData) { } else if (data instanceof SaveMetaData) { } else { die("LaunchRequest.unknown_file_type", file.toString()); } } else { die("LaunchRequest.missing_module"); } break; case EDIT_EXT: while (i < args.length) { final File file = new File(args[i++]); final AbstractMetaData data = MetaDataFactory.buildMetaData(file); if (data instanceof ModuleMetaData) { if (lr.module != null) die("LaunchRequest.only_one", "module"); lr.module = file; } else if (data instanceof ExtensionMetaData) { if (lr.extension != null) die(""); lr.extension = file; } else if (data instanceof SaveMetaData) { } else { die("LaunchRequest.unknown_file_type", file.toString()); } } if (lr.module == null) { die("LaunchRequest.missing_module"); } if (lr.extension == null) { die("LaunchRequest.missing_extension"); } break; case NEW: case TRANSLATE: break; } if (i < args.length) { die("LaunchRequest.excess_args", args[i]); } // other consistency checks if (lr.builtInModule) { if (lr.mode != Mode.LOAD) { die("LaunchRequest.only_in_mode", "--auto", Mode.LOAD.toString()); } if (lr.module != null) { die("LaunchRequest.excess_args", args[i]); } } if (lr.autoext != null) { if (lr.mode != Mode.LOAD) { die("LaunchRequest.only_in_mode", "--auto-extensions", Mode.LOAD.toString()); } if (lr.module != null) { die("LaunchRequest.excess_args", args[i]); } } return lr; } protected static void setMode(LaunchRequest lr, Mode mode) throws LaunchRequestException { if (lr.mode != null) die("LaunchRequest.only_one", "mode"); lr.mode = mode; } /** * Throws a {@link LaunchRequestException}. * * @param key {@link Resources} key * @param vals {@link Resources} arguments * @throws LaunchRequestException always */ protected static void die(String key, String... vals) throws LaunchRequestException { throw new LaunchRequestException(key, vals); } }