Refactor CSV and Net loading to use InputStream for better flexibility

This commit is contained in:
2025-02-03 12:57:17 +01:00
parent 214705b675
commit bd61042573
6 changed files with 197 additions and 121 deletions

View File

@@ -3,31 +3,83 @@ package net.berack.upo.valpre;
import java.io.File;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.HashMap;
public class Main {
public static void main(String[] args) throws Exception {
public static void main(String[] args) {
if (args.length == 0)
exit();
exit("No program specified!");
var subArgs = Arrays.copyOfRange(args, 1, args.length);
switch (args[0]) {
case "simulation":
var sim = new Simulation(subArgs);
sim.run();
break;
case "plot":
var plot = new Plot(subArgs);
plot.show();
break;
default:
exit();
try {
var program = args[0];
var subArgs = Arrays.copyOfRange(args, 1, args.length);
var param = Main.getParameters(program, subArgs);
switch (program) {
case "simulation" -> {
var net = param.get("net");
var csv = param.get("csv");
var runs = param.getOrDefault("runs", Integer::parseInt, 100);
var seed = param.getOrDefault("seed", Long::parseLong, 2007539552L);
var p = param.getOrDefault("p", Boolean::parseBoolean, false);
var sim = new Simulation(net, seed, runs, p, csv);
sim.run();
}
case "plot" -> {
var csv = param.get("csv");
var plot = new Plot(csv);
plot.show();
}
default -> exit("Invalid program!");
}
} catch (Exception e) {
exit(e.getMessage());
}
}
public static void exit() throws URISyntaxException {
var uri = Main.class.getProtectionDomain().getCodeSource().getLocation().toURI();
var name = new File(uri).getName();
System.out.println("Usage: java -jar " + name + ".jar [simulation|plot] [args]");
System.exit(1);
/**
* 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<String, Boolean>();
arguments.put("p", false);
arguments.put("seed", true);
arguments.put("runs", true);
arguments.put("net", true);
arguments.put("csv", true);
var descriptions = new HashMap<String, String>();
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("net", "The file net to use. Use example1.net or example2.net for the provided ones.");
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.out.println(message);
System.out.println("Usage: java -jar " + name + ".jar [simulation|plot] [args]");
System.exit(1);
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
}

View File

@@ -1,7 +1,11 @@
package net.berack.upo.valpre;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
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.
@@ -9,6 +13,7 @@ import java.util.Map;
public class Parameters {
private final Map<String, Boolean> arguments;
private final String prefix;
private Map<String, String> parameters;
/**
* Constructs a new Parameters object with the specified prefix and arguments.
@@ -27,6 +32,41 @@ public class Parameters {
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> T getOrDefault(String key, Function<String, T> parse, T value) {
var arg = this.get(key);
return arg != null ? parse.apply(arg) : value;
}
/**
* Return a string with the standard <arggument> <description> spaced enough
*
@@ -68,20 +108,21 @@ public class Parameters {
}
/**
* Parse the arguments passed and returns a map of Argument --> Value that can
* 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
* @return a map of the values
* or if there is an unknown argument or there
* are not arguments in the input
*/
public Map<String, String> parse(String[] args) {
var result = new HashMap<String, String>();
public void parse(String[] args) {
if (args == null || args.length == 0)
return result;
throw new IllegalArgumentException("No arguments passed");
var result = new HashMap<String, String>();
for (var i = 0; i < args.length; i += 1) {
var current = args[i];
var next = i + 1 < args.length ? args[i + 1] : null;
@@ -91,7 +132,7 @@ public class Parameters {
i += 1;
}
return result;
this.parameters = result;
}
/**
@@ -121,7 +162,7 @@ public class Parameters {
result.put(current, "");
if (finalSize != result.size())
throw new IllegalArgumentException("Argument unknown");
throw new IllegalArgumentException("Unknown argument [" + current + "]");
return false;
}
@@ -129,6 +170,7 @@ public class Parameters {
* 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
@@ -139,12 +181,13 @@ public class Parameters {
* or if there is an unknown argument
* @return a map of the values
*/
public static Map<String, String> getArgsOrHelper(String[] args, String prefix, Map<String, Boolean> arguments,
public static Parameters getArgsOrHelper(String[] args, String prefix, Map<String, Boolean> arguments,
Map<String, String> descriptions) {
var param = new Parameters(prefix, arguments);
try {
return param.parse(args);
param.parse(args);
return param;
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
System.out.println(param.helper(descriptions));
@@ -157,10 +200,17 @@ public class Parameters {
*
* @param file the file to get
* @return the file or the example file
* @throws FileNotFoundException if the file is not found
*/
public static String getFileOrExample(String file) {
if (file.startsWith("example"))
file = Main.class.getClassLoader().getResource(file).getPath();
return file;
public static InputStream getFileOrExample(String file) throws FileNotFoundException {
if (file == null)
return null;
if (file.startsWith("example")) {
var resource = Parameters.class.getClassLoader().getResourceAsStream(file);
if (resource != null)
return resource;
}
return new FileInputStream(file);
}
}

View File

@@ -3,8 +3,6 @@ package net.berack.upo.valpre;
import java.awt.BorderLayout;
import java.awt.Font;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.Box;
@@ -42,13 +40,13 @@ public class Plot {
* @param args the arguments to create the plot
* @throws IOException if anything happens while reading the file
*/
public Plot(String[] args) throws IOException {
var arguments = Plot.parseParameters(args);
var file = Parameters.getFileOrExample(arguments.get("csv"));
if (file == null)
throw new IllegalArgumentException("CSV file needed! Use -csv <file>");
public Plot(String csv) throws IOException {
var stream = Parameters.getFileOrExample(csv);
if (stream == null)
throw new IllegalArgumentException("CSV file needed!");
var results = CsvResult.loadResults(stream);
stream.close();
var results = new CsvResult(file).loadResults();
this.summary = new ResultSummary(results);
var nodes = this.summary.getNodes().toArray(new String[0]);
@@ -157,22 +155,6 @@ public class Plot {
}
}
/**
* Parse the arguments to get the CSV file.
*
* @param args the arguments to parse
* @return a map with the arguments
*/
private static Map<String, String> parseParameters(String[] args) {
var arguments = new HashMap<String, Boolean>();
arguments.put("csv", true);
var descriptions = new HashMap<String, String>();
descriptions.put("csv", "The filename that contains the previous saved runs.");
return Parameters.getArgsOrHelper(args, "-", arguments, descriptions);
}
/**
* This class is used to create a panel with a name and a value.
* The name is on the left and the value is on the right.

View File

@@ -1,11 +1,8 @@
package net.berack.upo.valpre;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import com.esotericsoftware.kryo.KryoException;
import net.berack.upo.valpre.sim.Net;
@@ -21,24 +18,32 @@ public class Simulation {
public final int runs;
public final long seed;
public final boolean parallel;
public final String file;
public final Net net;
/**
* Create a new simulation with the given arguments.
*
* @param args The arguments for the simulation.
* @throws IOException if the file is has a problem
*/
public Simulation(String[] args) {
// Evantually change the parameters
var arguments = Simulation.parseParameters(args);
this.runs = Simulation.getFromArguments(arguments, "runs", Integer::parseInt, 100);
this.seed = Simulation.getFromArguments(arguments, "seed", Long::parseLong, 2007539552L);
this.csv = arguments.getOrDefault("csv", null);
this.parallel = arguments.containsKey("p");
public Simulation(String netFile, long seed, int runs, boolean parallel, String csv) throws IOException {
if (runs <= 0)
throw new IllegalArgumentException("Runs must be greater than 0!");
this.file = Parameters.getFileOrExample(arguments.get("net"));
if (this.file == null)
throw new IllegalArgumentException("Net file needed! Use -net <file>");
this.runs = runs;
this.seed = seed;
this.csv = csv;
this.parallel = parallel;
try {
var file = Parameters.getFileOrExample(netFile);
this.net = Net.load(file);
file.close();
} catch (FileNotFoundException e) {
throw new IllegalArgumentException("Net file needed!");
} catch (KryoException e) {
throw new IllegalArgumentException("Net file is not valid or corrupted!");
}
}
/**
@@ -51,9 +56,8 @@ public class Simulation {
* @throws IOException If the simulation fails.
*/
public void run() throws InterruptedException, ExecutionException, KryoException, IOException {
var net = Net.load(this.file);
var nano = System.nanoTime();
var sim = new SimulationMultiple(net);
var sim = new SimulationMultiple(this.net);
var summary = this.parallel ? sim.runParallel(this.seed, this.runs) : sim.run(this.seed, this.runs);
nano = System.nanoTime() - nano;
@@ -65,45 +69,4 @@ public class Simulation {
System.out.println("Data saved to " + this.csv);
}
}
/**
* Get the value from the arguments or the default value if it is not present.
*
* @param args The arguments for the simulation.
* @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.
*/
private static <T> T getFromArguments(Map<String, String> args, String key, Function<String, T> parse, T value) {
if (args.containsKey(key))
return parse.apply(args.get(key));
return value;
}
/**
* Parse the arguments for the simulation.
*
* @param args The arguments for the simulation.
* @return The parsed arguments for the simulation.
*/
private static Map<String, String> parseParameters(String[] args) {
var arguments = new HashMap<String, Boolean>();
arguments.put("p", false);
arguments.put("seed", true);
arguments.put("runs", true);
arguments.put("net", true);
arguments.put("csv", true);
var descriptions = new HashMap<String, String>();
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("net",
"The net to use. It should be a file. Use example1.net or example2.net for the provided ones.");
descriptions.put("csv", "The filename for saving every run statistics.");
return Parameters.getArgsOrHelper(args, "-", arguments, descriptions);
}
}

View File

@@ -3,6 +3,8 @@ package net.berack.upo.valpre.sim;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -250,15 +252,29 @@ public final class Net {
*
* @param file the file to load
* @return a new Net object
* @throws KryoException if the file saved is not a net
* @throws FileNotFoundException if the file is not found
* @throws KryoException if the file saved is not a net
* @throws IOException if the file is not found
*/
public static Net load(String file) throws KryoException, FileNotFoundException {
public static Net load(String file) throws KryoException, IOException {
try (var stream = new FileInputStream(file)) {
return Net.load(stream);
}
}
/**
* Load the net from the stream passed as input.
* The net will be the same as the one saved.
*
* @param stream the input stream to read
* @return a new Net object
* @throws KryoException if the file saved is not a net
*/
public static Net load(InputStream stream) throws KryoException {
var kryo = new Kryo();
kryo.setRegistrationRequired(false);
kryo.setInstantiatorStrategy(new StdInstantiatorStrategy());
try (var in = new Input(new FileInputStream(file))) {
try (var in = new Input(stream)) {
return (Net) kryo.readClassAndObject(in);
}
}

View File

@@ -1,8 +1,9 @@
package net.berack.upo.valpre.sim.stats;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
@@ -59,8 +60,20 @@ public class CsvResult {
* @throws IOException if anything happens while reading the file
*/
public Result[] loadResults() throws IOException {
try (var stream = new FileInputStream(this.file)) {
return CsvResult.loadResults(stream);
}
}
/**
* Load the results from the CSV stream.
*
* @param input the input stream to read
* @return the results loaded from the stream
*/
public static Result[] loadResults(InputStream input) {
var results = new ArrayList<Result>();
try (var scan = new Scanner(new File(this.file))) {
try (var scan = new Scanner(input)) {
var _ = scan.nextLine();
var nodes = new HashMap<String, Statistics>();