All Downloads are FREE. Search and download functionalities are using the official Maven repository.

de.tlc4b.TLC4B Maven / Gradle / Ivy

The newest version!
package de.tlc4b;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import de.be4.classicalb.core.parser.exceptions.BCompoundException;
import de.tlc4b.exceptions.TLC4BException;
import de.tlc4b.exceptions.TLC4BIOException;
import de.tlc4b.exceptions.TranslationException;
import de.tlc4b.tlc.TLCOutputInfo;
import de.tlc4b.tlc.TLCResults;
import de.tlc4b.util.StopWatch;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

import static de.tlc4b.TLC4BOption.*;
import static de.tlc4b.util.StopWatch.Watches.*;
import static de.tlc4b.MP.*;

public class TLC4B {
	private static final String CSV_DELIMITER = ";";

	private File mainfile, traceFile;
	private String machineFileNameWithoutFileExtension;
	// e.g. Test of file foo/bar/Test.mch
	private File logFile;

	private File buildDir;

	private String tlaModule;
	private String config;
	private Translator translator;
	private TLCOutputInfo tlcOutputInfo;
	private String ltlFormula;
	private String constantsSetup;

	public static void main(String[] args) {
		try {
			run(args);
		} catch (BCompoundException e) {
			printlnErr("***** Parsing Error *****");
			printlnErr(e.getMessage());
			System.exit(-1);
		} catch (TLC4BException e) {
			printlnErr(e.getMessage());
			println("Result: " + e.getError());
			System.exit(-1);
		} catch (IOException e) {
			printlnErr(e.getMessage());
			println("Result: " + "I/O Error");
			System.exit(-1);
		}
		System.exit(0);
	}

	/**
	 * API method to call TLC4B directly in Java
	 * @param args same arguments as for the CLI version
	 * @return results of TLC model check
	 */
	public static TLCResults run(String[] args) throws TLC4BException, IOException, BCompoundException {
		System.setProperty("apple.awt.UIElement", "true"); // avoiding pop up window

		TLC4B tlc4b = new TLC4B();
		tlc4b.process(args);

		TLCResults results = null;
		if (TLC4BGlobals.isRunTLC()) {
			try {
				TLCRunner.runTLC(tlc4b.machineFileNameWithoutFileExtension, tlc4b.buildDir);
				results = new TLCResults(tlc4b.tlcOutputInfo);
				results.evalResults();
				tlc4b.printResults(results);
				tlc4b.createLogFile(results);
			} catch (NoClassDefFoundError e) {
				printlnErr("Can not find TLC. The tlatools.jar must be included in the classpath.");
			}
		}
		return results;
	}

	/**
	 * Check whether TLC4B is applicable to the provided machine.
	 * This method has no return value - if it returns without throwing an exception, then TLC4B is applicable.
	 * Be aware that this method may take a long time to run for large/complex machines.
	 *
	 * @param path path to B machine file
	 * @throws BCompoundException if the machine file could not be parsed
	 * @throws TLC4BException if translation fails for any other reason
	 */
	public static void checkTLC4BIsApplicable(String path) throws BCompoundException {
		TLC4B tlc4B = new TLC4B();
		tlc4B.processArgs(new String[]{path, SILENT.cliArg()});
		tlc4B.translate();
		// tlc4B.createFiles() is intentionally not called here!
	}

	private void printResults(TLCResults results) throws IOException {
		printOperationsCount(results);
		// options
		printlnSilent("Used Options");
		if (TLC4BGlobals.getDfidInitialDepth() > 0) // -1 if disabled
			printlnSilent("| Use DFS with initial depth: " + TLC4BGlobals.getDfidInitialDepth());
		printlnSilent("| Number of workers: " + TLC4BGlobals.getWorkers());
		printlnSilent("| Invariants check: " + TLC4BGlobals.isInvariant());
		printlnSilent("| Deadlock check: " + TLC4BGlobals.isDeadlockCheck());
		printlnSilent("| Assertion check: " + TLC4BGlobals.isAssertion());
		printlnSilent("| Find Goal check: " + TLC4BGlobals.isGOAL());
		printlnSilent("| LTL formulas check: " + TLC4BGlobals.isCheckLTL());
		printlnSilent("| Partial invariant evaluation: " + TLC4BGlobals.isPartialInvariantEvaluation());
		printlnSilent("| Lazy constants setup: " + !TLC4BGlobals.isForceTLCToEvalConstants());
		printlnSilent("| Aggressive well-definedness check: " + TLC4BGlobals.checkWelldefinedness());
		printlnSilent("| ProB constant setup: " + TLC4BGlobals.isProBconstantsSetup());
		printlnSilent("| Symmetry reduction: " + TLC4BGlobals.useSymmetry());
		printlnSilent("| MIN Int: " + TLC4BGlobals.getMIN_INT());
		printlnSilent("| MAX Int: " + TLC4BGlobals.getMAX_INT());
		printlnSilent("| Standard deferred set size: " + TLC4BGlobals.getDEFERRED_SET_SIZE());
		printlnSilent("--------------------------------");
		if (TLC4BGlobals.isTranslate()) {
			printlnSilent("Parsing time: " + StopWatch.getRunTime(PARSING_TIME) + " ms");
			printlnSilent("Translation time: " + StopWatch.getRunTime(TRANSLATION_TIME) + " ms");
		}
		if (TLC4BGlobals.isRunTLC()) {
			println("Model checking time: " + results.getModelCheckingTime() + " sec");
			// MP.printMessage("Number of workers: " +
			// TLCGlobals.getNumWorkers());
			if (!results.getViolatedAssertions().isEmpty()) {
				println("Violated assertions: " + results.getViolatedAssertions());
			}
			println("States analysed: " + results.getNumberOfDistinctStates());
			println("Transitions fired: " + results.getNumberOfTransitions());
			println("Result: " + results.getResultString());
			String violatedDefinition = results.getViolatedDefinition();
			if (violatedDefinition != null) {
				println("Violated Definition: " + violatedDefinition);
			}

			if (results.hasTrace() && TLC4BGlobals.isCreateTraceFile()) {
				String trace = results.getTrace();
				String tracefileName = machineFileNameWithoutFileExtension + ".tla.trace";
				traceFile = createFile(buildDir, tracefileName, trace, TLC4BGlobals.isDeleteOnExit());
				results.addTraceFilePath(traceFile.getAbsolutePath());
				println("Trace file '" + traceFile.getAbsolutePath() + "' created.");
			}
		}

	}

	private void printOperationsCount(TLCResults results) {
		Map operationCount = results.getOperationCount();
		if (TLC4BGlobals.isPrintCoverage() && operationCount != null) {
			printlnSilent("---------- Coverage statistics ----------");
			for (Entry entry : operationCount.entrySet()) {
				printlnSilent(entry.getKey() + ": " + entry.getValue().toString());
			}
			printlnSilent("---------- End of coverage statistics ----------");
		}
	}

	public static void test(String[] args, boolean deleteFiles) throws Exception {
		System.setProperty("apple.awt.UIElement", "true"); // avoiding pop up windows
		TLC4BGlobals.resetGlobals();
		TLC4BGlobals.setDeleteOnExit(deleteFiles);
		TLC4B tlc4b = new TLC4B();
		try {
			tlc4b.process(args);
		} catch (Exception e) {
			e.printStackTrace();
			printlnErr(e.getMessage());
			throw e;
		}
		if (TLC4BGlobals.isRunTLC()) {
			MP.TLCOutputStream.changeOutputStream();
			TLCRunner.runTLC(tlc4b.machineFileNameWithoutFileExtension, tlc4b.buildDir);
			MP.TLCOutputStream.resetOutputStream();
			TLCResults results = new TLCResults(tlc4b.tlcOutputInfo);
			results.evalResults();
			tlc4b.printResults(results);
			tlc4b.createLogFile(results);

			System.exit(0);
		}
	}

	public static void testString(String machineString, boolean deleteFiles) throws Exception {
		System.setProperty("apple.awt.UIElement", "true"); // avoiding pop up windows
		TLC4BGlobals.resetGlobals();
		TLC4BGlobals.setDeleteOnExit(deleteFiles);
		TLC4B tlc4b = new TLC4B();
		tlc4b.buildDir = new File("temp/");

		tlc4b.machineFileNameWithoutFileExtension = "Test";

		StopWatch.start(PARSING_TIME);
		printSilent("Parsing... ");
		tlc4b.translator = new Translator(machineString);
		StopWatch.stop(PARSING_TIME);
		printlnSilent("(" + StopWatch.getRunTimeAsString(PARSING_TIME) + "ms)");

		StopWatch.start(TRANSLATION_TIME);
		printSilent("Translating... ");
		tlc4b.translator.translate();
		tlc4b.tlaModule = tlc4b.translator.getModuleString();
		tlc4b.config = tlc4b.translator.getConfigString();
		tlc4b.tlcOutputInfo = tlc4b.translator.getTLCOutputInfo();
		StopWatch.stop(TRANSLATION_TIME);
		printlnSilent("(" + StopWatch.getRunTimeAsString(TRANSLATION_TIME) + "ms)");
		tlc4b.createFiles();

		if (TLC4BGlobals.isRunTLC()) {
			MP.TLCOutputStream.changeOutputStream();
			TLCRunner.runTLC(tlc4b.machineFileNameWithoutFileExtension, tlc4b.buildDir);
			MP.TLCOutputStream.resetOutputStream();
			TLCResults results = new TLCResults(tlc4b.tlcOutputInfo);
			results.evalResults();
			tlc4b.printResults(results);
			System.exit(0);
		}
	}
	
	private static Options getCommandlineOptions() {
		Options options = new Options();
		for (TLC4BOption option : TLC4BOption.values()) {
			options.addOption(option.arg(), option.expectsArg() != null, option.desc());
		}
		return options;
	}

	private void handleParameter(String[] args) {
		DefaultParser parser = new DefaultParser();
		Options options = getCommandlineOptions();
		try {
			CommandLine line = parser.parse(options, args);

			String[] remainingArgs = line.getArgs();
			if (remainingArgs.length != 1) {
				throw new TLC4BIOException("Main machine required!");
			} else {
				mainfile = new File(remainingArgs[0]);
			}

			// reset all parameters to default, then apply current args
			TLC4BGlobals.resetGlobals();
			TLC4BGlobals.setVerbose(line.hasOption(VERBOSE.arg()));
			TLC4BGlobals.setSilent(line.hasOption(SILENT.arg()));
			TLC4BGlobals.setDeadlockCheck(!line.hasOption(NODEAD.arg()));
			TLC4BGlobals.setRunTLC(!line.hasOption(NOTLC.arg()));
			TLC4BGlobals.setTranslate(!line.hasOption(NOTRANSLATION.arg()));
			TLC4BGlobals.setGOAL(!line.hasOption(NOGOAL.arg()));
			TLC4BGlobals.setInvariant(!line.hasOption(NOINV.arg()));
			TLC4BGlobals.setAssertionCheck(!line.hasOption(NOASS.arg()));
			TLC4BGlobals.setWelldefinednessCheck(line.hasOption(WDCHECK.arg()));
			TLC4BGlobals.setSymmetryUse(line.hasOption(SYMMETRY.arg()));
			TLC4BGlobals.setCheckltl(!line.hasOption(NOLTL.arg()));
			TLC4BGlobals.setForceTLCToEvalConstants(!line.hasOption(LAZYCONSTANTS.arg()));
			TLC4BGlobals.setCreateTraceFile(!line.hasOption(NOTRACE.arg()));
			TLC4BGlobals.setDeleteOnExit(line.hasOption(DEL.arg()));
			TLC4BGlobals.setPartialInvariantEvaluation(line.hasOption(PARINVEVAL.arg()));
			TLC4BGlobals.setPrintCoverage(line.hasOption(COVERAGE.arg()));

			if (line.hasOption(TMP.arg())) {
				buildDir = new File(System.getProperty("java.io.tmpdir"));
			}
			if (line.hasOption(LOG.arg())) {
				String logFileString = line.getOptionValue(LOG.arg());
				if (logFileString == null) {
					throw new TLC4BIOException("Error: File required after option '-log'.");
				}
				logFile = new File(logFileString);
			}
			if (line.hasOption(MAXINT.arg())) {
				String maxint = line.getOptionValue(MAXINT.arg());
				if (maxint == null) {
					throw new TLC4BIOException("Error: Number required after option '-maxint'.");
				}
				TLC4BGlobals.setMAX_INT(Integer.parseInt(maxint));
			}
			if (line.hasOption(DEFAULT_SETSIZE.arg())) {
				String deferredSetSize = line.getOptionValue(DEFAULT_SETSIZE.arg());
				if (deferredSetSize == null) {
					throw new TLC4BIOException("Error: Number required after option '-default_setsize'.");
				}
				TLC4BGlobals.setDEFERRED_SET_SIZE(Integer.parseInt(deferredSetSize));
			} 
			if (line.hasOption(MININT.arg())) {
				String minint = line.getOptionValue(MININT.arg());
				if (minint == null) {
					throw new TLC4BIOException("Error: Number required after option '-minint'.");
				}
				TLC4BGlobals.setMIN_INT(Integer.parseInt(minint));
			}
			if (line.hasOption(WORKERS.arg())) {
				String workers = line.getOptionValue(WORKERS.arg());
				if (workers == null) {
					throw new TLC4BIOException("Error: Number required after option '-workers'.");
				}
				TLC4BGlobals.setWorkers(Integer.parseInt(workers));
			}
			if (line.hasOption(DFID.arg())) {
				String dfid_initial_depth = line.getOptionValue(DFID.arg());
				if (dfid_initial_depth == null) {
					throw new TLC4BIOException("Error: Number required after option '-dfid'.");
				}
				TLC4BGlobals.setDfidInitialDepth(Integer.parseInt(dfid_initial_depth));
			}
			if (line.hasOption(CONSTANTSSETUP.arg())) {
				TLC4BGlobals.setProBconstantsSetup(true);
				constantsSetup = line.getOptionValue(CONSTANTSSETUP.arg());
				if (constantsSetup == null) {
					throw new TLC4BIOException("Error: String required after option '-constantssetup'.");
				}
			}
			if (line.hasOption(LTLFORMULA.arg())) {
				ltlFormula = line.getOptionValue(LTLFORMULA.arg());
				if (ltlFormula == null) {
					throw new TLC4BIOException("Error: LTL formula required after option '-ltlformula'.");
				}
			}
			if (line.hasOption(OUTPUT.arg())) {
				buildDir = new File(line.getOptionValue(OUTPUT.arg()));
			}
		} catch (ParseException e) {
			HelpFormatter formatter = new HelpFormatter();
			formatter.printHelp("[file]", options);
			throw new TLC4BIOException(e);
		}
	}

	private void processArgs(String[] args) {
		handleParameter(args);
		handleMainFileName();

		MP.printVerbose("Arguments: ");
		for (String string : args) {
			MP.printVerbose(string);
			MP.printVerbose(" ");
		}
		printlnVerbose("");
	}

	private void translate() throws BCompoundException {
		StopWatch.start(PARSING_TIME);
		MP.printSilent("Parsing... ");
		translator = new Translator(machineFileNameWithoutFileExtension,
			mainfile, this.ltlFormula, this.constantsSetup);
		StopWatch.stop(PARSING_TIME);
		printlnSilent("(" + StopWatch.getRunTimeAsString(PARSING_TIME) + "ms)");

		StopWatch.start(TRANSLATION_TIME);
		MP.printSilent("Translating... ");
		translator.translate();
		this.tlaModule = translator.getModuleString();
		this.config = translator.getConfigString();
		this.tlcOutputInfo = translator.getTLCOutputInfo();
		StopWatch.stop(TRANSLATION_TIME);
		printlnSilent("(" + StopWatch.getRunTimeAsString(TRANSLATION_TIME) + "ms)");
	}

	public void process(String[] args) throws IOException, BCompoundException {
		processArgs(args);
		if (TLC4BGlobals.isTranslate()) {
			translate();
			createFiles();
		}
	}

	private void handleMainFileName() {
		if (!mainfile.exists()) {
			throw new TLC4BIOException("The file " + mainfile.getPath() + " does not exist.");
		}
		try {
			mainfile = mainfile.getCanonicalFile();
		} catch (IOException e) {
			throw new TLC4BIOException("The file '" + mainfile.getPath() + "' can not be accessed.", e);
		}

		machineFileNameWithoutFileExtension = mainfile.getName().substring(0,
				mainfile.getName().length() - 4); // deleting .mch

		if (buildDir == null) {
			buildDir = new File(mainfile.getParentFile(), machineFileNameWithoutFileExtension);
		}
	}

	private String getLogCsvString(TLCResults tlcResults) {
		List fieldNames = new ArrayList<>();
		List fieldValues = new ArrayList<>();

		fieldNames.add("Machine File");
		String machineFile = mainfile.getAbsolutePath();
		fieldValues.add(machineFile);

		fieldNames.add("TLC Model Checking Time (s)");
		double tlcModelCheckingTime = tlcResults.getModelCheckingTime();
		fieldValues.add(String.valueOf(tlcModelCheckingTime));

		fieldNames.add("Parsing Time Of B machine (ms)");
		long parseTime = StopWatch.getRunTime(PARSING_TIME);
		fieldValues.add(String.valueOf(parseTime));

		fieldNames.add("Translation Time (ms)");
		long translationTime = StopWatch.getRunTime(TRANSLATION_TIME);
		fieldValues.add(String.valueOf(translationTime));

		fieldNames.add("Model Checking Time (ms)");
		long modelCheckingTime = StopWatch.getRunTime(MODEL_CHECKING_TIME);
		fieldValues.add(String.valueOf(modelCheckingTime));
		
		fieldNames.add("TLC Result");
		fieldValues.add(tlcResults.getResultString());

		fieldNames.add("States analysed");
		fieldValues.add(Integer.toString(tlcResults.getNumberOfDistinctStates()));

		fieldNames.add("Transitions fired");
		fieldValues.add(Integer.toString(tlcResults.getNumberOfTransitions()));

		fieldNames.add("Violated Definition");
		String violatedDefinition = tlcResults.getViolatedDefinition();
		fieldValues.add(violatedDefinition != null ? violatedDefinition : "");

		fieldNames.add("Violated Assertions");
		List violatedAssertions = tlcResults.getViolatedAssertions();
		fieldValues.add(!violatedAssertions.isEmpty() ? String.join(CSV_DELIMITER, violatedAssertions) : "");

		fieldNames.add("Operation Coverage");
		Map operationCount = tlcResults.getOperationCount();
		List opCountString = new ArrayList<>();
		if (operationCount != null) {
			operationCount.forEach((operation, count) -> opCountString.add(operation + CSV_DELIMITER + count));
		}
		fieldValues.add(!opCountString.isEmpty() ? String.join(CSV_DELIMITER, opCountString) : "");

		fieldNames.add("Trace File");
		fieldValues.add(traceFile != null ? traceFile.getAbsolutePath() : "");

		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < fieldNames.size(); i++) {
			sb.append(fieldNames.get(i)).append(CSV_DELIMITER).append(fieldValues.get(i)).append("\n");
		}
		return sb.toString();
	}

	private void createLogFile(TLCResults results) throws IOException {
		if (logFile != null) {
			String logCsvString = getLogCsvString(results);
			try (FileWriter fw = new FileWriter(logFile, true)) { // the true will append the new data
				fw.write(logCsvString);
			}
			println("Log file: " + logFile.getAbsolutePath());
		}
	}

	private void createFiles() throws IOException {
		boolean dirCreated = buildDir.mkdir();
		if (dirCreated && TLC4BGlobals.isDeleteOnExit()) {
			buildDir.deleteOnExit();
		}

		File moduleFile = createFile(buildDir,
				machineFileNameWithoutFileExtension + ".tla", tlaModule,
				TLC4BGlobals.isDeleteOnExit());
		println("TLA+ module '" + moduleFile.getAbsolutePath() + "' created.");

		File configFile = createFile(buildDir,
				machineFileNameWithoutFileExtension + ".cfg", config,
				TLC4BGlobals.isDeleteOnExit());
		println("Configuration file '" + configFile.getAbsolutePath() + "' created.");

		createStandardModules();
	}

	private void createStandardModules() throws IOException {
		for (String module : translator.getStandardModuleToBeCreated()) {
			createStandardModule(buildDir, module);
		}
	}

	private void createStandardModule(File path, String name) throws IOException {
		// standard modules are copied from the standardModules folder to the current directory

		File file = new File(path, name + ".tla");
		InputStream resourceStream = TLC4B.class.getResourceAsStream("standardModules/" + name + ".tla");
		if (resourceStream == null) {
			// should never happen
			throw new TranslationException("Unable to determine the source of the standard module: " + name);
		}

		try (InputStream is = resourceStream; OutputStream fos = new FileOutputStream(file)) {
			int read;
			byte[] bytes = new byte[1024];

			while ((read = is.read(bytes)) != -1) {
				fos.write(bytes, 0, read);
			}
			println("Standard module '" + file.getName() + "' created.");
		} finally {
			if (TLC4BGlobals.isDeleteOnExit() && file.exists()) {
				file.deleteOnExit();
			}
		}
	}

	private static File createFile(File dir, String fileName, String text, boolean deleteOnExit) throws IOException {
		File file = new File(dir, fileName);
		boolean exists = false;
		try {
			exists = file.createNewFile();
			try (Writer out = new BufferedWriter(new OutputStreamWriter(Files.newOutputStream(file.toPath()), StandardCharsets.UTF_8))) {
				out.write(text);
			}
			return file;
		} finally {
			if (deleteOnExit && exists) {
				file.deleteOnExit();
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy