package org.scribble.cli;
import java.io.File;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.scribble.cli.CommandLine.ArgFlag;
// String[] -> Map<CommandLine.Arg, String[]> -- Map array values are the arguments associated to each CommandLine.Arg
public class CommandLineArgParser
{
// Unique flags
public static final String JUNIT_FLAG = "-junit"; // For internal use (JUnit test harness)
public static final String VERBOSE_FLAG = "-V";
public static final String IMPORT_PATH_FLAG = "-ip";
public static final String API_OUTPUT_DIR_FLAG = "-d";
public static final String STATECHAN_SUBTYPES_FLAG = "-subtypes";
public static final String OLD_WF_FLAG = "-oldwf";
public static final String NO_LIVENESS_FLAG = "-nolive";
public static final String LTSCONVERT_MIN_FLAG = "-minlts";
public static final String FAIR_FLAG = "-fair";
public static final String NO_LOCAL_CHOICE_SUBJECT_CHECK = "-nolocalchoicecheck";
public static final String NO_ACCEPT_CORRELATION_CHECK = "-nocorrelation";
public static final String DOT_FLAG = "-dot";
public static final String AUT_FLAG = "-aut";
public static final String NO_VALIDATION_FLAG = "-novalid";
public static final String INLINE_MAIN_MOD_FLAG = "-inline";
public static final String F17_FLAG = "-f17";
// Non-unique flags
public static final String PROJECT_FLAG = "-project";
public static final String EFSM_FLAG = "-fsm";
public static final String VALIDATION_EFSM_FLAG = "-vfsm";
public static final String UNFAIR_EFSM_FLAG = "-ufsm";
public static final String EFSM_PNG_FLAG = "-fsmpng";
public static final String VALIDATION_EFSM_PNG_FLAG = "-vfsmpng";
public static final String UNFAIR_EFSM_PNG_FLAG = "-ufsmpng";
public static final String SGRAPH_FLAG = "-model";
public static final String UNFAIR_SGRAPH_FLAG = "-umodel";
public static final String SGRAPH_PNG_FLAG = "-modelpng";
public static final String UNFAIR_SGRAPH_PNG_FLAG = "-umodelpng";
public static final String API_GEN_FLAG = "-api";
public static final String SESSION_API_GEN_FLAG = "-sessapi";
public static final String STATECHAN_API_GEN_FLAG = "-chanapi";
private static final Map<String, CommandLine.ArgFlag> UNIQUE_FLAGS = new HashMap<>();
{
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.JUNIT_FLAG, CommandLine.ArgFlag.JUNIT);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.VERBOSE_FLAG, CommandLine.ArgFlag.VERBOSE);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.IMPORT_PATH_FLAG, CommandLine.ArgFlag.IMPORT_PATH);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.API_OUTPUT_DIR_FLAG, CommandLine.ArgFlag.API_OUTPUT);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.STATECHAN_SUBTYPES_FLAG, CommandLine.ArgFlag.SCHAN_API_SUBTYPES);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.OLD_WF_FLAG, CommandLine.ArgFlag.OLD_WF);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.LTSCONVERT_MIN_FLAG, CommandLine.ArgFlag.LTSCONVERT_MIN);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.FAIR_FLAG, CommandLine.ArgFlag.FAIR);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.NO_LOCAL_CHOICE_SUBJECT_CHECK, CommandLine.ArgFlag.NO_LOCAL_CHOICE_SUBJECT_CHECK);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.NO_ACCEPT_CORRELATION_CHECK, CommandLine.ArgFlag.NO_ACCEPT_CORRELATION_CHECK);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.DOT_FLAG, CommandLine.ArgFlag.DOT);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.AUT_FLAG, CommandLine.ArgFlag.AUT);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.NO_VALIDATION_FLAG, CommandLine.ArgFlag.NO_VALIDATION);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.INLINE_MAIN_MOD_FLAG, CommandLine.ArgFlag.INLINE_MAIN_MOD);
CommandLineArgParser.UNIQUE_FLAGS.put(CommandLineArgParser.F17_FLAG, CommandLine.ArgFlag.F17);
}
private static final Map<String, CommandLine.ArgFlag> NON_UNIQUE_FLAGS = new HashMap<>();
{
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.PROJECT_FLAG, CommandLine.ArgFlag.PROJECT);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.EFSM_FLAG, CommandLine.ArgFlag.EFSM);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.VALIDATION_EFSM_FLAG, CommandLine.ArgFlag.VALIDATION_EFSM);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.UNFAIR_EFSM_FLAG, CommandLine.ArgFlag.UNFAIR_EFSM);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.EFSM_PNG_FLAG, CommandLine.ArgFlag.EFSM_PNG);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.VALIDATION_EFSM_PNG_FLAG, CommandLine.ArgFlag.VALIDATION_EFSM_PNG);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.UNFAIR_EFSM_PNG_FLAG, CommandLine.ArgFlag.UNFAIR_EFSM_PNG);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.SGRAPH_FLAG, CommandLine.ArgFlag.SGRAPH);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.UNFAIR_SGRAPH_FLAG, CommandLine.ArgFlag.UNFAIR_SGRAPH);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.SGRAPH_PNG_FLAG, CommandLine.ArgFlag.SGRAPH_PNG);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.UNFAIR_SGRAPH_PNG_FLAG, CommandLine.ArgFlag.UNFAIR_SGRAPH_PNG);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.API_GEN_FLAG, CommandLine.ArgFlag.API_GEN);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.SESSION_API_GEN_FLAG, CommandLine.ArgFlag.SESS_API_GEN);
CommandLineArgParser.NON_UNIQUE_FLAGS.put(CommandLineArgParser.STATECHAN_API_GEN_FLAG, CommandLine.ArgFlag.SCHAN_API_GEN);
}
private static final Map<String, CommandLine.ArgFlag> FLAGS = new HashMap<>();
{
CommandLineArgParser.FLAGS.putAll(CommandLineArgParser.UNIQUE_FLAGS);
CommandLineArgParser.FLAGS.putAll(CommandLineArgParser.NON_UNIQUE_FLAGS);
}
private final String[] args;
private final Map<CommandLine.ArgFlag, String[]> parsed = new HashMap<>();
public CommandLineArgParser(String[] args) throws CommandLineException
{
this.args = args;
parseArgs();
}
public Map<CommandLine.ArgFlag, String[]> getArgs()
{
return this.parsed;
}
private void parseArgs() throws CommandLineException
{
for (int i = 0; i < this.args.length; i++)
{
String arg = this.args[i];
if (CommandLineArgParser.FLAGS.containsKey(arg))
{
i = this.parseFlag(i);
}
else
{
if (isMainModuleParsed())
{
if (arg.startsWith("-"))
{
throw new CommandLineException("Unknown flag or bad main module arg: " + arg);
}
// May actually be the second bad argument -- we didn't validate the value of the main arg
throw new CommandLineException("Bad/multiple main module arg: " + arg);
}
parseMain(i);
}
}
}
private boolean isMainModuleParsed()
{
return this.parsed.containsKey(CommandLine.ArgFlag.MAIN_MOD) || this.parsed.containsKey(CommandLine.ArgFlag.INLINE_MAIN_MOD);
}
// Pre: i is the index of the current flag to parse
// Post: i is the index of the last argument parsed -- parseArgs does the index increment to the next current flag
// Currently allows repeat flag decls: next overrides previous
private int parseFlag(int i) throws CommandLineException
{
String flag = this.args[i];
switch (flag)
{
// Unique flags
case CommandLineArgParser.IMPORT_PATH_FLAG:
{
return parseImportPath(i);
}
case CommandLineArgParser.INLINE_MAIN_MOD_FLAG:
{
if (isMainModuleParsed())
{
throw new CommandLineException("Multiple main modules given.");
}
return parseInlineMainModule(i);
}
case CommandLineArgParser.F17_FLAG:
{
return parseF17(i);
}
case CommandLineArgParser.JUNIT_FLAG:
case CommandLineArgParser.VERBOSE_FLAG:
case CommandLineArgParser.STATECHAN_SUBTYPES_FLAG:
case CommandLineArgParser.OLD_WF_FLAG:
case CommandLineArgParser.NO_LIVENESS_FLAG:
case CommandLineArgParser.LTSCONVERT_MIN_FLAG:
case CommandLineArgParser.FAIR_FLAG:
case CommandLineArgParser.NO_LOCAL_CHOICE_SUBJECT_CHECK:
case CommandLineArgParser.NO_ACCEPT_CORRELATION_CHECK:
case CommandLineArgParser.NO_VALIDATION_FLAG:
{
checkAndAddNoArgUniqueFlag(flag, new String[0]);
return i;
}
case CommandLineArgParser.API_OUTPUT_DIR_FLAG:
{
return parseOutput(i);
}
case CommandLineArgParser.DOT_FLAG:
{
if (this.parsed.containsKey(CommandLineArgParser.UNIQUE_FLAGS.get(AUT_FLAG)))
{
throw new CommandLineException("Incompatible flags: " + DOT_FLAG + " and " + AUT_FLAG);
}
checkAndAddNoArgUniqueFlag(flag, new String[0]);
return i;
}
case CommandLineArgParser.AUT_FLAG:
{
if (this.parsed.containsKey(CommandLineArgParser.UNIQUE_FLAGS.get(DOT_FLAG)))
{
throw new CommandLineException("Incompatible flags: " + DOT_FLAG + " and " + AUT_FLAG);
}
checkAndAddNoArgUniqueFlag(flag, new String[0]);
return i;
}
// Non-unique flags
case CommandLineArgParser.PROJECT_FLAG:
{
return parseProject(i);
}
case CommandLineArgParser.EFSM_FLAG:
case CommandLineArgParser.VALIDATION_EFSM_FLAG:
case CommandLineArgParser.UNFAIR_EFSM_FLAG:
case CommandLineArgParser.API_GEN_FLAG:
case CommandLineArgParser.STATECHAN_API_GEN_FLAG:
{
return parseProtoAndRoleArgs(flag, i);
}
case CommandLineArgParser.EFSM_PNG_FLAG:
case CommandLineArgParser.VALIDATION_EFSM_PNG_FLAG:
case CommandLineArgParser.UNFAIR_EFSM_PNG_FLAG:
{
return parseProtoRoleAndFileArgs(flag, i);
}
case CommandLineArgParser.SGRAPH_FLAG:
case CommandLineArgParser.UNFAIR_SGRAPH_FLAG:
case CommandLineArgParser.SESSION_API_GEN_FLAG:
{
return parseProtoArg(flag, i);
}
case CommandLineArgParser.SGRAPH_PNG_FLAG:
case CommandLineArgParser.UNFAIR_SGRAPH_PNG_FLAG:
{
return parseProtoAndFileArgs(flag, i);
}
default:
{
throw new RuntimeException("[TODO] Unknown flag: " + flag);
}
}
}
private void checkAndAddNoArgUniqueFlag(String flag, String[] args) throws CommandLineException
{
ArgFlag argFlag = CommandLineArgParser.UNIQUE_FLAGS.get(flag);
if (this.parsed.containsKey(argFlag))
{
throw new CommandLineException("Duplicate flag: " + flag);
}
this.parsed.put(argFlag, args);
}
private int parseOutput(int i) throws CommandLineException
{
if ((i + 1) >= this.args.length)
{
throw new CommandLineException("Missing directory argument");
}
String dir = this.args[++i];
this.parsed.put(CommandLineArgParser.UNIQUE_FLAGS.get(CommandLineArgParser.API_OUTPUT_DIR_FLAG), new String[] { dir } );
return i;
}
private void parseMain(int i) throws CommandLineException
{
String main = args[i];
if (!CommandLineArgParser.validateModuleArg(main))
{
throw new CommandLineException("Bad module arg: " + main);
}
this.parsed.put(CommandLine.ArgFlag.MAIN_MOD, new String[] { main } );
}
private int parseImportPath(int i) throws CommandLineException
{
if ((i + 1) >= this.args.length)
{
throw new CommandLineException("Missing path argument");
}
String path = this.args[++i];
if (!validatePaths(path))
{
throw new CommandLineException("Scribble module import path '"+ path +"' is not valid\r\n");
}
//this.parsed.put(CommandLineArgParser.FLAGS.get(CommandLineArgParser.PATH_FLAG), new String[] { path });
checkAndAddNoArgUniqueFlag(CommandLineArgParser.IMPORT_PATH_FLAG, new String[] { path });
return i;
}
private int parseInlineMainModule(int i) throws CommandLineException
{
if ((i + 1) >= this.args.length)
{
throw new CommandLineException("Missing module definition");
}
String inline = this.args[++i];
checkAndAddNoArgUniqueFlag(CommandLineArgParser.INLINE_MAIN_MOD_FLAG, new String[] { inline });
return i;
}
private int parseF17(int i) throws CommandLineException
{
if ((i + 1) >= this.args.length)
{
throw new CommandLineException("Missing simple global protocol name argument");
}
String proto = this.args[++i];
checkAndAddNoArgUniqueFlag(CommandLineArgParser.F17_FLAG, new String[] { proto });
return i;
}
private int parseProject(int i) throws CommandLineException // Similar to parseProtoAndRoleArgs
{
if ((i + 2) >= this.args.length)
{
throw new CommandLineException("Missing protocol/role arguments");
}
String proto = this.args[++i];
String role = this.args[++i];
/*if (!validateProtocolName(proto)) // TODO
{
throw new RuntimeException("Protocol name '"+ proto +"' is not valid\r\n");
}*/
concatArgs(CommandLineArgParser.NON_UNIQUE_FLAGS.get(CommandLineArgParser.PROJECT_FLAG), proto, role);
return i;
}
private int parseProtoAndRoleArgs(String f, int i) throws CommandLineException
{
ArgFlag flag = CommandLineArgParser.NON_UNIQUE_FLAGS.get(f);
if ((i + 2) >= this.args.length)
{
throw new CommandLineException("Missing protocol/role arguments");
}
String proto = this.args[++i];
String role = this.args[++i];
concatArgs(flag, proto, role);
return i;
}
private int parseProtoRoleAndFileArgs(String f, int i) throws CommandLineException
{
ArgFlag flag = CommandLineArgParser.NON_UNIQUE_FLAGS.get(f);
if ((i + 3) >= this.args.length)
{
throw new CommandLineException("Missing protocol/role/file arguments");
}
String proto = this.args[++i];
String role = this.args[++i];
String png = this.args[++i];
concatArgs(flag, proto, role, png);
return i;
}
private int parseProtoArg(String f, int i) throws CommandLineException
{
ArgFlag flag = CommandLineArgParser.NON_UNIQUE_FLAGS.get(f);
if ((i + 1) >= this.args.length)
{
throw new CommandLineException("Missing protocol argument");
}
String proto = this.args[++i];
concatArgs(flag, proto);
return i;
}
private int parseProtoAndFileArgs(String f, int i) throws CommandLineException
{
ArgFlag flag = CommandLineArgParser.NON_UNIQUE_FLAGS.get(f);
if ((i + 2) >= this.args.length)
{
throw new CommandLineException("Missing protocol/file arguments");
}
String proto = this.args[++i];
String png = this.args[++i];
concatArgs(flag, proto, png);
return i;
}
private void concatArgs(ArgFlag flag, String... toAdd)
{
String[] args = this.parsed.get(flag);
if (args == null)
{
args = Arrays.copyOf(toAdd, toAdd.length);
}
else
{
String[] tmp = new String[args.length + toAdd.length];
System.arraycopy(args, 0, tmp, 0, args.length);
System.arraycopy(toAdd, 0, tmp, args.length, toAdd.length);
args = tmp;
}
this.parsed.put(flag, args);
}
// Used to guard subsequent file open attempt?
private static boolean validateModuleArg(String arg)
{
return arg.chars().noneMatch((i) ->
!Character.isLetterOrDigit(i) && i != '.' && i != File.separatorChar && i != ':' && i != '-' && i != '_'
&& i != '/'); // Hack? (cygwin)
}
private static boolean validatePaths(String paths)
{
for (String path : paths.split(File.pathSeparator))
{
if (!new File(path).isDirectory())
{
return false;
}
}
return true;
}
}