uniol.apt.APT.java Source code

Java tutorial

Introduction

Here is the source code for uniol.apt.APT.java

Source

/*-
 * APT - Analysis of Petri Nets and labeled Transition systems
 * Copyright (C) 2012-2013  Members of the project group APT
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

package uniol.apt;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;

import org.apache.commons.collections4.Trie;
import org.apache.commons.collections4.trie.PatriciaTrie;
import org.apache.commons.io.FileUtils;

import uniol.apt.module.AptModuleRegistry;
import uniol.apt.module.Category;
import uniol.apt.module.Module;
import uniol.apt.module.ModuleExitStatusChecker;
import uniol.apt.module.ModuleOutputSpec;
import uniol.apt.module.ModulePreconditionsChecker;
import uniol.apt.module.ModuleRegistry;
import uniol.apt.module.PropertyModuleExitStatusChecker;
import uniol.apt.module.exception.ModuleException;
import uniol.apt.module.impl.ExitStatus;
import uniol.apt.module.impl.ModuleInvoker;
import uniol.apt.module.impl.ModuleUtils;
import uniol.apt.module.impl.Parameter;
import uniol.apt.module.impl.ReturnValue;
import uniol.apt.module.impl.SimpleModulePreconditionsChecker;
import uniol.apt.ui.AptParameterTransformation;
import uniol.apt.ui.ParameterTransformation;
import uniol.apt.ui.ParametersParser;
import uniol.apt.ui.ParametersTransformer;
import uniol.apt.ui.ReturnValuesTransformer;
import uniol.apt.ui.impl.AptParametersTransformer;
import uniol.apt.ui.impl.AptReturnValuesTransformer;
import uniol.apt.ui.impl.SimpleParametersParser;
import uniol.apt.ui.impl.UIUtils;

/**
 * @author Renke Grunwald
 */
public class APT {
    /**
     * Symbol that signals that a file should be read from the standard input.
     */
    public static final String STANDARD_INPUT_SYMBOL = "-";

    /**
     * The exact APT version that is running as seen by "git describe".
     */
    public static final String GIT_VERSION;

    /**
     * A timestamp for when this version of APT was built.
     */
    public static final String TIMESTAMP;

    /**
     * A human-readable string generated from {@link GIT_VERSION} and {@link TIMESTAMP}.
     */
    public static final String VERSION_STRING;

    static {
        Properties props = new Properties();
        try (InputStream in = APT.class.getResourceAsStream("APT.properties")) {
            if (in != null)
                props.load(in);
        } catch (IOException e) {
            // Ignore the exception and just make sure that the version will be UNKNOWN
            props = new Properties();
        }
        GIT_VERSION = props.getProperty("git-version", "UNKNOWN");
        TIMESTAMP = props.getProperty("timestamp", "UNKNOWN");
        VERSION_STRING = "APT version " + GIT_VERSION + " built on " + TIMESTAMP;
    }

    private static final ParametersParser PARAMETERS_PARSER = new SimpleParametersParser();
    private static final ParametersTransformer PARAMETERS_TRANSFORMER = AptParametersTransformer.INSTANCE;
    private static final ReturnValuesTransformer RETURN_VALUES_TRANSFORMER = AptReturnValuesTransformer.INSTANCE;
    private static final ModuleRegistry REGISTRY = AptModuleRegistry.INSTANCE;
    private static final Trie<String, String> REMOVED_MODULES = new PatriciaTrie<>();

    private static final PrintStream OUT_PRINTER = System.out;
    private static final PrintStream ERR_PRINTER = System.err;

    /**
     * Hidden Constructor.
     */
    private APT() {
    }

    private static void addRemovedModules() {
        REMOVED_MODULES.put("apt2baggins", "Use pn_convert / lts_convert apt baggins instead.");
        REMOVED_MODULES.put("apt2lola", "Use pn_convert / lts_convert apt lola instead.");
        REMOVED_MODULES.put("apt2petrify", "Use pn_convert / lts_convert apt petrify instead.");
        REMOVED_MODULES.put("apt2pnml", "Use pn_convert / lts_convert apt pnml instead.");
        REMOVED_MODULES.put("apt2synet", "Use pn_convert / lts_convert apt synet instead.");
        REMOVED_MODULES.put("petrify2apt", "Use pn_convert / lts_convert petrify apt instead.");
        REMOVED_MODULES.put("pnml2apt", "Use pn_convert / lts_convert pnml apt instead.");
        REMOVED_MODULES.put("synet2apt", "Use pn_convert / lts_convert synet apt instead.");
        REMOVED_MODULES.put("info", "Use examine_pn instead.");
        REMOVED_MODULES.put("backwards_persistent", "Use backward_persistent instead.");
    }

    /**
     * Program entry point.
     *
     * @param args
     *                command line arguments
     */
    public static void main(String[] args) {
        addRemovedModules();

        PARAMETERS_PARSER.parse(args);

        String[] moduleNames = PARAMETERS_PARSER.getModuleNames();

        if (moduleNames.length == 0) {
            printUsageAndExit();
        }

        String moduleName = moduleNames[0]; // Only use a single module for now
        Collection<Module> foundModules = REGISTRY.findModulesByPrefix(moduleName);

        Module module = null;

        if (foundModules.isEmpty()) {
            printNoSuchModuleAndExit(moduleName);
        } else if (foundModules.size() == 1) {
            // These is only one right module and we just found it
            module = foundModules.iterator().next();
        } else {
            // Ambiguous, but maybe the name isn't a partial name after all
            module = REGISTRY.findModule(moduleName);

            if (module == null) {
                printAmbiguousModuleNameAndExit(moduleName, foundModules);
            }
        }

        List<Parameter> parameters = ModuleUtils.getParameters(module);
        List<Parameter> allParameters = ModuleUtils.getAllParameters(module);

        List<ReturnValue> returnValues = ModuleUtils.getReturnValues(module);
        List<ReturnValue> fileReturnValues = ModuleUtils.getFileReturnValues(module);

        String[] moduleArgs = PARAMETERS_PARSER.getModuleArguments(moduleName);

        try {
            if (moduleArgs.length < parameters.size()) {
                printTooFewArgumentsAndExit(module);
            }

            if (moduleArgs.length > allParameters.size() + fileReturnValues.size()) {
                printTooManyArgumentsAndExit(module);
            }

            // Number of parameters for which values are provided
            int numberOfUsedParameters;

            if (moduleArgs.length > allParameters.size()) {
                numberOfUsedParameters = allParameters.size();
            } else {
                numberOfUsedParameters = moduleArgs.length;
            }

            // First check if multiple parameters are signaled to be read from the standard input
            checkNoTwoStdinParameters(moduleArgs, numberOfUsedParameters, allParameters);

            Object[] transformedArgs = new Object[numberOfUsedParameters];
            for (int i = 0; i < numberOfUsedParameters; i++) {
                transformedArgs[i] = PARAMETERS_TRANSFORMER.transform(moduleArgs[i],
                        allParameters.get(i).getKlass());
            }

            ModulePreconditionsChecker checker = new SimpleModulePreconditionsChecker();

            List<Parameter> unmetParameters = checker.check(REGISTRY, module, transformedArgs);

            if (!unmetParameters.isEmpty()) {
                printPreconditionsUnmetAndExit(unmetParameters);
            }

            ModuleInvoker invoker = new ModuleInvoker();
            List<Object> values = invoker.invoke(module, transformedArgs);

            String[] fileArgs = Arrays.copyOfRange(moduleArgs, numberOfUsedParameters, moduleArgs.length);
            printModuleOutput(fileArgs, returnValues, values);

            ModuleExitStatusChecker statusChecker = new PropertyModuleExitStatusChecker();
            ExitStatus status = statusChecker.check(module, values);

            OUT_PRINTER.flush();
            System.exit(status.getValue());
        } catch (ModuleException e) {
            ERR_PRINTER.println(
                    String.format("Error while invoking module '%s':%n  %s", module.getName(), e.getMessage()));
            ERR_PRINTER.flush();
            System.exit(ExitStatus.ERROR.getValue());
        }
    }

    private static void checkNoTwoStdinParameters(String[] moduleArgs, int numberOfUsedParameters,
            List<Parameter> allParameters) {
        boolean hasStdInParameter = false;

        // First check if multiple parameters are signaled to be read from the standard input
        for (int i = 0; i < numberOfUsedParameters; i++) {
            if (moduleArgs[i].equals(STANDARD_INPUT_SYMBOL) && isFileSourceParameter(allParameters.get(i))) {
                if (hasStdInParameter) {
                    printCanOnlyReadOneParameterFromStdInAndExit();
                }
                hasStdInParameter = true;
            }
        }
    }

    private static boolean isFileSourceParameter(Parameter parameter) {
        Class<?> klass = parameter.getKlass();
        ParameterTransformation<?> transformation = PARAMETERS_TRANSFORMER.getTransformation(klass);
        if (transformation == null)
            return false;
        AptParameterTransformation annotation = transformation.getClass()
                .getAnnotation(AptParameterTransformation.class);
        if (annotation == null)
            return false;
        return annotation.fileSource();
    }

    private static void printModuleOutput(String[] fileArgs, List<ReturnValue> returnValues, List<Object> values)
            throws ModuleException {
        boolean hasStandardOutputFileReturnValue = false;

        for (int i = 0; i < fileArgs.length; i++) {
            if (fileArgs[i].equals(STANDARD_INPUT_SYMBOL)) {
                hasStandardOutputFileReturnValue = true;
            }
        }

        // Handle module output
        try (CloseableCollection<PrintStream> outputs = new CloseableCollection<>()) {
            boolean outputWithName[] = new boolean[values.size()];

            // Figure out where the values which the module produced should be printed to
            int usedFileArgsCount = 0;
            for (int i = 0; i < values.size(); i++) {

                if (values.get(i) == null) {
                    outputWithName[i] = false;
                    outputs.add(null, false);
                    continue;
                }

                String returnValueName = returnValues.get(i).getName();

                boolean isRawReturnValue = returnValues.get(i).hasProperty(ModuleOutputSpec.PROPERTY_RAW);

                boolean isFileReturnValue = returnValues.get(i).hasProperty(ModuleOutputSpec.PROPERTY_FILE);

                // Print the return value to file without its name
                if (isFileReturnValue) {
                    // Check if the user supplied a file name for this return value
                    if (fileArgs.length > usedFileArgsCount) {
                        String filename = fileArgs[usedFileArgsCount];

                        if (filename.equals(STANDARD_INPUT_SYMBOL)) {
                            outputs.add(OUT_PRINTER, false);
                        } else {
                            outputs.add(openOutput(filename), true);
                        }
                        outputWithName[i] = false;

                        usedFileArgsCount++;
                        continue;
                    }
                }

                // Only print the file return value when requested; skip the other return values
                if (hasStandardOutputFileReturnValue) {
                    outputWithName[i] = false;
                    outputs.add(null, false);
                    continue;
                }

                // Print this ordinary return value, possibly with its name
                outputs.add(OUT_PRINTER, false);
                outputWithName[i] = !isRawReturnValue;
            }

            // Print all return values for which the module produced values
            for (int i = 0; i < values.size(); i++) {
                PrintStream out = outputs.get(i);
                if (out == null)
                    continue;

                if (outputWithName[i])
                    out.print(returnValues.get(i).getName() + ": ");

                OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8");
                RETURN_VALUES_TRANSFORMER.transform(writer, values.get(i), returnValues.get(i).getKlass());
                writer.flush();
                out.println();
            }
        } catch (IOException e) {
            ERR_PRINTER.println("Error writing to file: " + e.getMessage());
            ERR_PRINTER.flush();
            System.exit(ExitStatus.ERROR.getValue());
        }
    }

    private static void printPreconditionsUnmetAndExit(List<Parameter> unmetParameters) {
        ERR_PRINTER.println("Some preconditions are unmet:");

        for (Parameter parameter : unmetParameters) {
            ERR_PRINTER.print("Parameter \"" + parameter.getName() + "\" is not");

            for (String property : parameter.getProperties()) {
                ERR_PRINTER.print(" " + property);
            }

            ERR_PRINTER.println();
        }

        ERR_PRINTER.flush();
        System.exit(ExitStatus.ERROR.getValue());
    }

    private static PrintStream openOutput(String fileName) throws IOException {
        File file = new File(fileName);

        if (file.exists())
            throw new IOException("File '" + file + "' already exists");
        return new PrintStream(FileUtils.openOutputStream(file), false, "UTF-8");
    }

    private static void printVersion() {
        OUT_PRINTER.println(VERSION_STRING + ".");
        OUT_PRINTER.println();
    }

    private static void printTooManyArgumentsAndExit(Module module) throws ModuleException {
        ERR_PRINTER.println("Too many arguments");
        ERR_PRINTER.println();
        ERR_PRINTER.println(UIUtils.getModuleUsage(module, PARAMETERS_TRANSFORMER));
        ERR_PRINTER.flush();
        System.exit(ExitStatus.ERROR.getValue());
    }

    private static void printTooFewArgumentsAndExit(Module module) throws ModuleException {
        ERR_PRINTER.println("Too few arguments");
        ERR_PRINTER.println();
        ERR_PRINTER.println(UIUtils.getModuleUsage(module, PARAMETERS_TRANSFORMER));
        ERR_PRINTER.flush();
        System.exit(ExitStatus.ERROR.getValue());
    }

    private static void printAmbiguousModuleNameAndExit(String moduleName, Collection<Module> foundModules) {
        ERR_PRINTER.println("Ambiguous module name: " + moduleName);

        ERR_PRINTER.println();

        ERR_PRINTER.println("Available modules (starting with " + moduleName + "):");
        printModuleList(foundModules, ERR_PRINTER);

        ERR_PRINTER.flush();
        System.exit(ExitStatus.ERROR.getValue());
    }

    private static void printNoSuchModuleAndExit(String moduleName) {
        ERR_PRINTER.println("No such module: " + moduleName);

        Map<String, String> messages = REMOVED_MODULES.prefixMap(moduleName);
        if (messages.size() == 1) {
            Map.Entry<String, String> entry = messages.entrySet().iterator().next();
            ERR_PRINTER.println(String.format("The module '%s' was removed. %s", entry.getKey(), entry.getValue()));
        }

        ERR_PRINTER.flush();
        System.exit(ExitStatus.ERROR.getValue());
    }

    private static void printUsageAndExit() {
        printVersion();
        OUT_PRINTER.println("Usage: apt <module> <arguments>");
        OUT_PRINTER.println();

        OUT_PRINTER.println("Available modules:");
        printModuleList(REGISTRY.getModules(), OUT_PRINTER);

        OUT_PRINTER.flush();
        System.exit(ExitStatus.ERROR.getValue());
    }

    private static void printCanOnlyReadOneParameterFromStdInAndExit() {
        ERR_PRINTER.println("Only one parameter can be read from standard input at the same time");
        ERR_PRINTER.flush();
        System.exit(ExitStatus.ERROR.getValue());
    }

    private static void printModuleList(Collection<Module> modules, PrintStream printer) {
        for (Category category : Category.values()) {
            List<Module> modulesByCategory = ModuleUtils.getModulesByCategory(modules, category);

            if (modulesByCategory.isEmpty()) {
                continue;
            }

            printer.println();
            printer.println(category.getName());

            for (int i = 0; i < category.getName().length(); i++) {
                printer.print("=");
            }

            printer.println();

            Set<Module> sortedModules = new TreeSet<>(new Comparator<Module>() {

                @Override
                public int compare(Module o1, Module o2) {
                    return o1.getName().compareTo(o2.getName());
                }
            });

            sortedModules.addAll(modulesByCategory);

            // Figure out the length of the longest module name
            int longestModuleName = 0;
            for (Module module : modules) {
                longestModuleName = Math.max(longestModuleName, module.getName().length());
            }

            String format = "  %-" + Integer.toString(longestModuleName) + "s  %s";
            for (Module module : sortedModules) {
                printer.println(String.format(format, module.getName(), module.getShortDescription()));
            }
        }
    }
}

// vim: ft=java:noet:sw=8:sts=8:ts=8:tw=120