Command.java

/**
 *
 * This file is part of TRIO.
 *
 * TRIO is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * TRIO 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with TRIO.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 *
 * This file is part of TRIO.
 *
 * TRIO is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option) any
 * later version.
 *
 * TRIO 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 Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with TRIO. If not, see <http://www.gnu.org/licenses/>.
 */
package eu.diversify.trio.ui;

import eu.diversify.trio.Configuration;
import eu.diversify.trio.simulation.RandomFailureSequence;
import eu.diversify.trio.Trio;
import eu.diversify.trio.analysis.Analysis;
import eu.diversify.trio.analysis.Distribution;
import eu.diversify.trio.analysis.Metric;
import eu.diversify.trio.core.System;
import eu.diversify.trio.data.DataSet;
import eu.diversify.trio.filter.All;
import eu.diversify.trio.filter.Filter;
import eu.diversify.trio.filter.TaggedAs;
import eu.diversify.trio.simulation.Scenario;
import java.io.*;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * The options which can be passed to trio
 */
public class Command {

    public static final String DEFAULT_CONTROL = "*";
    public static final String DEFAULT_OBSERVATION = "*";
    public static final String DEFAULT_OUTPUT_FILE = "results.csv";
    public static final int DEFAULT_RUN_COUNT = 1000;

    public static Command from(String[] commandLine) {
        return new Command(commandLine);
    }

    public static Command from(String commandLine) {
        return from(commandLine.split("\\s+"));
    }

    private String observation;
    private String control;
    private String inputFile;
    private String outputFile;
    private int runCount;

    private Command(String[] commandLine) {
        final List<String> parameters = Arrays.asList(commandLine);
        outputFile = DEFAULT_OUTPUT_FILE;
        runCount = DEFAULT_RUN_COUNT;
        observation = DEFAULT_OBSERVATION;
        control = DEFAULT_CONTROL;
        extractOptions(parameters);
    }

    private void extractOptions(List<String> parameters) {
        final Iterator<String> command = parameters.iterator();
        while (command.hasNext()) {
            final String parameter = command.next();
            if (parameter.matches(".*\\.trio")) {
                inputFile = parameter;
                break;

            } else if (parameter.startsWith("--")) {
                parseLongOptions(parameter);

            } else if (parameter.startsWith("-")) {
                parseShortOptions(command, parameter);

            } else {
                final String error = String.format("Unknown argument '%s'. Expecting either a path to a TRIO file or an option.", parameter);
                throw new IllegalArgumentException(error);
            }
        }
        if (inputFile == null) {
            throw new IllegalArgumentException("Missing input TRIO file!");
        }
    }

    private void parseShortOptions(final Iterator<String> command, final String eachParameter) throws IllegalArgumentException {
        if (!command.hasNext()) {
            final String error = String.format("Missing value after '%s'", eachParameter);
            throw new IllegalArgumentException(error);
        }
        if (eachParameter.equals("-o")) {
            observation = command.next();

        } else if (eachParameter.equals("-c")) {
            control = command.next();

        } else if (eachParameter.equals("-t")) {
            outputFile = command.next();

        } else if (eachParameter.equals("-r")) {
            extractRunCountFrom(command.next());

        } else {
            final String error = String.format("Unknown short option '%s'", eachParameter);
            throw new IllegalArgumentException(error);
        }
    }

    private void parseLongOptions(final String eachParameter) throws IllegalArgumentException {
        final String parts[] = eachParameter.split("=");
        if (parts[0].equals("--trace")) {
            outputFile = parts[1];

        } else if (parts[0].equals("--observe")) {
            observation = parts[1];

        } else if (parts[0].equals("--control")) {
            control = parts[1];

        } else if (parts[0].equals("--runs")) {
            extractRunCountFrom(parts[1]);

        } else {
            final String error = String.format("Unknown long option '%s'! Expecting either '--runs' or '--output'", parts[0]);
            throw new IllegalArgumentException(error);
        }
    }

    private void extractRunCountFrom(String text) throws IllegalArgumentException {
        try {
            runCount = Integer.parseInt(text);
            if (runCount < 1) {
                final String error = String.format("Run count shall be at least 1! (found %d)", runCount);
                throw new IllegalArgumentException(error);
            }

        } catch (NumberFormatException ex) {
            final String error = String.format("Invalid number of runs '%s", text);
            throw new IllegalArgumentException(error);

        }
    }

    public void execute(Trio trio, OutputStream output) throws IOException {
        final Configuration config = Configuration.forProduction();

        PrintStream out = null;

        try {
            out = new PrintStream(output, AUTO_FLUSH, UTF_8);

            out.println(config.version() + " -- " + config.description());
            out.println(config.copyright());
            out.println("Licensed under " + config.license());
            out.println();

            final System system = trio.loadSystemFrom(inputFile);
            out.println("SYSTEM: " + system.getName());
            final RandomFailureSequence scenario = new RandomFailureSequence(system, observation(), control());
            out.println("SCENARIO: " + this.format(scenario));

            final DataSet data = trio.run(scenario, runCount);
            trio.saveDataAs(data, outputFile);

            report(trio.analyse(data), out);

            out.println();
            out.println("That's all folks!");
        
        } catch (IOException ex) {
            throw new RuntimeException("Unable to write on the given output stream", ex);

        } finally {
            if (out != null) {
                out.close();
            }
        }
    }
    private static final boolean AUTO_FLUSH = true;
    private static final String UTF_8 = "UTF-8";

    private Filter observation() {
        return parseTag(observation);
    }

    private Filter control() {
        return parseTag(control);
    }

    private Filter parseTag(String tag) {
        if (tag.contains("*")) {
            return All.getInstance();
        }
        return new TaggedAs(tag);
    }

    private void report(Analysis analyzer, PrintStream out) {
        out.println("INDICATORS:");
        out.printf(" + Robustness: %.4f%n", getRobustness(analyzer).distribution().mean());
        out.println(" + Five most sensitive components:");
        int counter = 1;
        for (Map.Entry<String, Double> entry: getLoss(analyzer).distribution().byDecreasingExpectedValue(Distribution.mean).entrySet()) {
            out.printf("   %d: %.4e %s%n", counter++, entry.getValue(), entry.getKey().replaceAll("inactivate", ""));
            if (counter > 5) {
                break;
            }
        }
        reportHarmfulSequences(out, analyzer);
    }

    public void reportHarmfulSequences(PrintStream out, Analysis analyzer) {
        out.println(" + Five most harmful sequences:");
        int counter = 1;
        for (Map.Entry<String, Double> entry: getFragility(analyzer).distribution().byDecreasingExpectedValue(Distribution.mean).entrySet()) {
            out.printf("   %d: %.4e %s%n", counter++, entry.getValue(), entry.getKey().replaceAll("inactivate", ""));
            if (counter > 5) {
                break;
            }
        }
    }

    public static Metric getRobustness(Analysis analyzer) {
        return analyzer.metric("norm. robustness");
    }

    public static Metric getFragility(Analysis analyzer) {
        return analyzer.metric("fragility");
    }

    public static Metric getLoss(Analysis analyzer) {
        return analyzer.metric("Loss");
    }

    private String format(Scenario scenario) {
        String observation = String.format("'%s' layer", scenario.getObservation().toString());
        if (observation.contains("*")) {
            observation = "system";
        }
        String control = String.format("'%s' layer", scenario.getControl().toString());
        if (control.contains("*")) {
            control = "system";
        }
        return String.format("Robustness of the %s to failure in the %s", observation, control);
    }

    public static String usage() {
        final String EOL = java.lang.System.lineSeparator();
        return "Usage: trio [options] input.trio" + EOL
                + "where 'options' are:" + EOL
                + "  -o, --observe=TAG  the tag of the components whose activity shall be observed" + EOL
                + "  -c, --control=TAG  the tag of the components whose activity shall be controlled" + EOL
                + "  -r, --runs=INTEGER the number of sample for statistical evidence" + EOL
                + "  -t, --trace=FILE   the file where the generated data shall be stored" + EOL
                + "Example: trio -o result.csv --run=10000 system.trio";
    }

}