diff --git a/.vscode/launch.json b/.vscode/launch.json index fabc344..af5db31 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -30,7 +30,7 @@ "name": "Run Incremental", "request": "launch", "mainClass": "net.berack.upo.valpre.Main", - "args": "simulation -net src/test/resources/example2.net -runs 1000 -i \"[Service1:throughput=0.98:0.01],[Service2:utilization=0.98:0.01],[Service2:unavailable=0.98:0.01]\"" + "args": "simulation -net src/test/resources/example2.net -runs 1000 -indices \"[Service1:throughput=0.98:0.01],[Service2:utilization=0.98:0.01],[Service2:unavailable=0.98:0.01]\"" }, { "type": "java", diff --git a/pom.xml b/pom.xml index b0a0f94..3682dd2 100644 --- a/pom.xml +++ b/pom.xml @@ -41,5 +41,10 @@ jfreechart 1.5.5 + + net.sourceforge.argparse4j + argparse4j + 0.9.0 + \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/InteractiveConsole.java b/src/main/java/net/berack/upo/valpre/InteractiveConsole.java index adb323d..a013ec5 100644 --- a/src/main/java/net/berack/upo/valpre/InteractiveConsole.java +++ b/src/main/java/net/berack/upo/valpre/InteractiveConsole.java @@ -143,8 +143,7 @@ public class InteractiveConsole { switch (choice) { case 1 -> new SimulationBuilder(net).setSeed(seed).setMaxRuns(100).setParallel(true).setCsv(csv).run(); case 2 -> new SimulationBuilder(net).setSeed(seed).setMaxRuns(1000).setParallel(true).setCsv(csv).run(); - case 3 -> new SimulationBuilder(net).setSeed(seed).setMaxRuns(1000).setParallel(true).setCsv(csv).run(); - case 4 -> { + case 3 -> { var indices = ask("Confidence indices with format [node:stat=confidence:relativeError];[..]\n"); new SimulationBuilder(net).setSeed(seed).setMaxRuns(10000).parseConfidenceIndices(indices).setCsv(csv) .run(); diff --git a/src/main/java/net/berack/upo/valpre/Main.java b/src/main/java/net/berack/upo/valpre/Main.java index 8135468..f5833db 100644 --- a/src/main/java/net/berack/upo/valpre/Main.java +++ b/src/main/java/net/berack/upo/valpre/Main.java @@ -2,95 +2,93 @@ package net.berack.upo.valpre; import java.io.File; import java.net.URISyntaxException; -import java.util.Arrays; -import java.util.HashMap; +import net.sourceforge.argparse4j.ArgumentParsers; +import net.sourceforge.argparse4j.impl.Arguments; +import net.sourceforge.argparse4j.inf.Namespace; public class Main { - public static void main(String[] args) { - if (args.length == 0) - exit("No program specified!"); + private final static String NAME; + /** + * The name of the program, used for the help message. + */ + static { + var name = "valpre"; try { - var program = args[0]; - var subArgs = Arrays.copyOfRange(args, 1, args.length); - switch (program) { + var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI(); + name = new File(uri).getName(); + } catch (URISyntaxException e) { + } + NAME = name; + } + + /** + * The main method of the program. It parses the arguments and runs the + * simulation or the plotter. + * + * @param args the arguments to parse + */ + public static void main(String[] args) { + try { + var param = Main.getParameters(args); + var command = param.getString("command"); + + switch (command) { case "simulation" -> { - var param = Main.getParameters(program, subArgs); - new SimulationBuilder(param.get("net")) - .setCsv(param.get("csv")) - .setMaxRuns(param.getOrDefault("runs", Integer::parseInt, 100)) - .setSeed(param.getOrDefault("seed", Long::parseLong, 0L)) - .setParallel(param.get("p") != null) - .parseEndCriteria(param.get("end")) - .parseConfidenceIndices(param.get("i")) + new SimulationBuilder(param.getString("net")) + .setCsv(param.getString("csv")) + .setMaxRuns(param.getInt("runs")) + .setSeed(param.getLong("seed")) + .setParallel(param.getBoolean("p")) + .parseEndCriteria(param.getString("end")) + .parseConfidenceIndices(param.getString("indices")) .run(); } case "plot" -> { - var param = Main.getParameters(program, subArgs); - var csv = param.get("csv"); + var csv = param.getString("csv"); var plot = new Plot(csv); plot.show(); } case "interactive" -> new InteractiveConsole().run(); - default -> exit("Invalid program!"); + default -> throw new RuntimeException("Invalid program!"); // Should never happen } } catch (Exception e) { - exit(e.getMessage()); - } - } - - /** - * Get the parameters from the arguments. - * - * @param program the program to run - * @param args the arguments to parse - * @return the parameters - */ - private static Parameters getParameters(String program, String[] args) { - var arguments = new HashMap(); - arguments.put("p", false); - arguments.put("seed", true); - arguments.put("runs", true); - arguments.put("net", true); - arguments.put("end", true); - arguments.put("csv", true); - arguments.put("i", true); - - var descriptions = new HashMap(); - descriptions.put("p", "Add this if you want the simulation to use threads (one each run)."); - descriptions.put("seed", "The seed of the simulation."); - descriptions.put("runs", "How many runs the simulator should run."); - descriptions.put("end", "When the simulation should end. Format is \"[ClassName:param1,..,paramN];[..]\""); - descriptions.put("net", "The file net to use. Use example1.net or example2.net for the provided ones."); - descriptions.put("i", "The confidence indices to use for the simulation. If active then p is ignored." - + " Format is \"[node:stat=confidence:relativeError];[..]\""); - - var csvDesc = switch (program) { - case "simulation" -> "The filename for saving every run statistics."; - case "plot" -> "The filename that contains the previous saved runs."; - default -> ""; - }; - descriptions.put("csv", csvDesc); - - return Parameters.getArgsOrHelper(args, "-", arguments, descriptions); - } - - /** - * Exit the program with an error message. - */ - public static void exit(String message) { - try { - var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI(); - var name = new File(uri).getName(); - System.err.println(message); - System.out.println("Usage: java -jar " + name + ".jar [simulation|plot|interactive] [args]"); - System.out.println("simulation args: -net [-csv ] [-runs ] [-seed ]" - + "[-p] [-end ] [-i ]"); - System.out.println("plot args: -csv "); - System.out.println("interactive: no args needed"); + System.err.println(e.getMessage()); System.exit(1); - } catch (URISyntaxException e) { - e.printStackTrace(); } } + + /** + * Parses the arguments of the program. It uses the argparse4j library to parse + * the arguments and return a Namespace object with the parsed arguments. + * + * @param args the arguments to parse + * @return a Namespace object with the parsed arguments + */ + private static Namespace getParameters(String[] args) { + var parser = ArgumentParsers.newFor(NAME).build() + .defaultHelp(true) + .description("Build a network simulation and/or plot the results of a simulation."); + var subparser = parser.addSubparsers().title("commands").description("valid commands").help("subcommand help"); + + var sim = subparser.addParser("simulation").help("Run a simulation of the network."); + sim.addArgument("-net").help("The file net to use.").required(true); + sim.addArgument("-csv").help("The filename for saving every run statistics."); + sim.addArgument("-runs").type(Integer.class).help("How many runs the simulator should run.").setDefault(100); + sim.addArgument("-seed").type(Long.class).help("The seed of the simulation.").setDefault(0L); + sim.addArgument("-p").action(Arguments.storeTrue()).help("Parallel (one thread each run).").setDefault(false); + sim.addArgument("-end").help("When the simulation should end. Format:\n\"[ClassName:param1,..,paramN];[..]\""); + sim.addArgument("-indices").help("The confidence indices to use for the simulation. If active -p is ignored." + + " Format:\n\"[node:stat=confidence:relativeError];[..]\""); + + var plot = subparser.addParser("plot").help("Plot the results of a simulation."); + plot.addArgument("-csv").help("The filename for the csv file to plot.").required(true); + + var _ = subparser.addParser("interactive").help("Run the interactive console."); + // Interactive console does not need any arguments + + var namespace = parser.parseArgsOrFail(args); + namespace.getAttrs().put("command", args[0]); + return namespace; + } } \ No newline at end of file diff --git a/src/main/java/net/berack/upo/valpre/NetExamples.java b/src/main/java/net/berack/upo/valpre/NetExamples.java index 768daa5..d5bc27c 100644 --- a/src/main/java/net/berack/upo/valpre/NetExamples.java +++ b/src/main/java/net/berack/upo/valpre/NetExamples.java @@ -67,9 +67,6 @@ public final class NetExamples { new double[] { 1 / (avg * 0.5), 1 / (avg * 1.5) }, new double[] { 0.5f, 0.5f }); - System.out.println("Normal: " + normal.mean); - System.out.println("Uniform: " + uniform.min + " - " + uniform.max); - for (var spawn : spawnTotals) { System.out.println("Spawn: " + spawn); var nets = new Net[] { diff --git a/src/main/java/net/berack/upo/valpre/Parameters.java b/src/main/java/net/berack/upo/valpre/Parameters.java deleted file mode 100644 index 9c5eff4..0000000 --- a/src/main/java/net/berack/upo/valpre/Parameters.java +++ /dev/null @@ -1,194 +0,0 @@ -package net.berack.upo.valpre; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; - -/** - * Class that helps with parsing the parameters passed as input in the console. - */ -public class Parameters { - private final Map arguments; - private final String prefix; - private Map parameters; - - /** - * Constructs a new Parameters object with the specified prefix and arguments. - * The arguments can be with value, in that case in the map the boolean should - * be true, otherwise it is only an argument that is a flag - * - * @param prefix the prefix to be used - * @param arguments a map of arguments where the key is a string and if the - * boolean is true then the argument expect a value - * @throws IllegalArgumentException if the arguments map is null or empty - */ - public Parameters(String prefix, Map arguments) { - if (arguments == null || arguments.size() == 0) - throw new IllegalArgumentException(); - this.arguments = arguments; - this.prefix = prefix; - } - - /** - * Get the size of the parameters. - * - * @return the size of the parameters - */ - public int size() { - return this.parameters.size(); - } - - /** - * Get the value of the argument passed as input. - * - * @param key the key of the argument - * @return the value of the argument - */ - public String get(String key) { - if (this.parameters == null) - return null; - return this.parameters.get(key); - } - - /** - * Get the value from the arguments or the default value if it is not present. - * - * @param key The key to get the value from. - * @param parse The function to parse the value. - * @param value The default value if the key is not present. - * @return The value from the arguments or the default value if it is not - * present. - */ - public T getOrDefault(String key, Function parse, T value) { - var arg = this.get(key); - return arg != null ? parse.apply(arg) : value; - } - - /** - * Return a string with the standard spaced enough - * - * @param eventualDescription the description for the argument, if not present - * the argument will be shown anyway/ - * @return a string of arguments - */ - public String helper(Map eventualDescription) { - var size = 0; - var parameters = new HashMap(); - - for (var param : this.arguments.entrySet()) { - var string = this.prefix + param.getKey(); - if (param.getValue()) - string += " "; - - parameters.put(param.getKey(), string); - size = Math.max(size, string.length()); - } - size += 2; // spacing - - var builder = new StringBuilder(); - for (var param : parameters.entrySet()) { - var key = param.getKey(); - var args = param.getValue(); - - builder.append(" "); - builder.append(args); - - var desc = eventualDescription.get(key); - if (desc != null) { - builder.append(" ".repeat(size - args.length())); - builder.append(desc); - } - builder.append("\n"); - } - - return builder.toString(); - } - - /** - * Parse the arguments passed and build a map of Argument --> Value that can - * be used to retrieve the information. In the case that the arguments are not - * in the correct format then an exception is thrown. - * To get the arguments use the {@link #get(String)} method. - * - * @param args the arguments in input - * @throws IllegalArgumentException if the arguments are not formatted correctly - * or if there is an unknown argument or there - * are not arguments in the input - */ - public void parse(String[] args) { - if (args == null || args.length == 0) - throw new IllegalArgumentException("No arguments passed"); - - var result = new HashMap(); - for (var i = 0; i < args.length; i += 1) { - var current = args[i]; - var next = i + 1 < args.length ? args[i + 1] : null; - - var updateI = this.parseSingle(current, next, result); - if (updateI) - i += 1; - } - - this.parameters = result; - } - - /** - * Parse one single argument and put it into the map. - * - * @param current the current argument - * @param next the next argument if present - * @param result the map where to insert the value - * @throws IllegalArgumentException if the arguments are not formatted correctly - * or if there is an unknown argument - * @return true if the next argument is used - */ - private boolean parseSingle(String current, String next, Map result) { - if (!current.startsWith(this.prefix)) - throw new IllegalArgumentException("Missing prefix [" + current + "]"); - current = current.substring(this.prefix.length()); - - var value = this.arguments.get(current); - if (value != null) { - result.put(current, value ? next : ""); - return value; - } - - var finalSize = result.size() + current.length(); - for (var letter : current.split("")) - if (this.arguments.get(letter) != null) - result.put(current, ""); - - if (finalSize != result.size()) - throw new IllegalArgumentException("Unknown argument [" + current + "]"); - return false; - } - - /** - * Parse the arguments passed and returns a map of Argument --> Value that can - * be used to retrieve the information. In the case that the arguments are not - * in the correct format then an exception is thrown and the helper is printed. - * If the arguments passed are 0 then the helper is printed. - * - * @param args the arguments in input - * @param prefix the prefix to be used - * @param arguments a map of arguments where the key is a string and if the - * boolean is true then the argument expect a value - * @param descriptions a map of descriptions for the arguments - * @throws IllegalArgumentException if the arguments are not formatted correctly - * or if there is an unknown argument - * @return a map of the values - */ - public static Parameters getArgsOrHelper(String[] args, String prefix, Map arguments, - Map descriptions) { - - var param = new Parameters(prefix, arguments); - try { - param.parse(args); - return param; - } catch (IllegalArgumentException e) { - System.out.println(e.getMessage()); - System.out.println(param.helper(descriptions)); - throw new IllegalArgumentException("Invalid arguments"); - } - } -}