goal.tools.PlatformManager.java Source code

Java tutorial

Introduction

Here is the source code for goal.tools.PlatformManager.java

Source

/**
 * GOAL interpreter that facilitates developing and executing GOAL multi-agent
 * programs. Copyright (C) 2011 K.V. Hindriks, W. Pasman
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package goal.tools;

import goal.core.gamygdala.Agent;
import goal.core.gamygdala.Engine;
import goal.core.gamygdala.Gamygdala;
import goal.tools.errorhandling.Resources;
import goal.tools.errorhandling.WarningStrings;
import goal.tools.errorhandling.exceptions.GOALBug;
import goal.tools.logging.GOALLogger;
import goal.tools.logging.Loggers;
import goal.tools.logging.StringsLogRecord;
import goal.util.Extension;
import goalhub.krTools.KRFactory;

import java.io.File;
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.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import krTools.KRInterface;
import krTools.errors.exceptions.ParserException;
import languageTools.analyzer.agent.AgentValidator;
import languageTools.analyzer.agent.AgentValidatorSecondPass;
import languageTools.analyzer.mas.MASValidator;
import languageTools.analyzer.test.TestValidator;
import languageTools.errors.Message;
import languageTools.exceptions.relationParser.InvalidEmotionConfigFile;
import languageTools.parser.relationParser.EmotionConfig;
import languageTools.parser.relationParser.GamRelation;
import languageTools.parser.relationParser.RelationParser;
import languageTools.program.Program;
import languageTools.program.agent.AgentProgram;
import languageTools.program.mas.MASProgram;
import languageTools.program.test.UnitTest;

import org.apache.commons.io.IOUtils;

/**
 * FIXME: This class should not be needed. {@link MASProgram}s,
 * {@link AgentProgram}s and {@link UnitTest}s should contain internally
 * references to all their relevant files. Removing this class allows a large
 * amount of static state to be removed from the GOAL core.
 *
 * <p>
 * Keeps track of all files and their associated parsed objects by maintaining a
 * static map that contains of file-parsed object pairs. This provides a unique
 * store of file references and their associated parsed objects that can be
 * accessed by other classes that need access to the result of parsing a file.
 * </p>
 * <p>
 * The {@link PlatformManager} is a <i>utility class</i> that provides
 * functionality mainly for and related to <i>parsing</i> file types recognized
 * by the GOAL platform (@see {@link Extension}).
 * </p>
 *
 * @since 23jul09: PlatformManager extends Observable (see TRAC #696). Inspired
 *        by Jade (PlatformController.Listener) the PM sends platformEvent
 *        objects to its listeners.
 * @since 27jul09: PlatformManager merges JADE platform events with
 *        DebugObserver platform events (Agent.SLEEPS, etc).
 * @author W.Pasman modified 20jul09.
 * @modified KH
 * @modified W.Pasman removed rmi host stuff. Use preferences and middleware
 *           init call for that.
 * @modified W.Pasman 28jun2011 made singleton, as there can be only one
 *           platform manager in the system
 * @modified K.Hindriks Now a utility class that provides static access to all
 *           files and their associated parsed objects.
 */
public class PlatformManager {
    /**
     * The current PlatformManager
     */
    private static PlatformManager current;
    /**
     * The associated BreakpointManager (lazily loaded)
     */
    private BreakpointManager breaks;
    /**
     * All files with associated parsed objects.
     */
    private final Map<File, Program> parsedFiles;

    public static PlatformManager getCurrent() {
        if (current == null) {
            return createNew();
        } else {
            return current;
        }
    }

    public static PlatformManager createNew() {
        current = new PlatformManager();
        for (final String language : KRFactory.getSupportedInterfaces()) {
            /*
             * try { FIXME: no longer necessary/possible?!
             * KRFactory.getInterface(language).reset(); } catch (final
             * KRInitFailedException e) { new Warning(String.format(
             * Resources.get(WarningStrings.INTERNAL_PROBLEM),
             * "PlatformManager::createNew()", language), e); }
             */
        }
        return current;
    }

    private PlatformManager() {
        this.parsedFiles = new HashMap<>();
    }

    public BreakpointManager getBreakpointManager() {
        if (this.breaks == null) {
            this.breaks = new BreakpointManager(this);
        }
        return this.breaks;
    }

    /**
     * Adds a file and associated parsed object pair to the store of such
     * objects maintained by the {@link PlatformManager}.
     *
     * @param file
     * @param program
     */
    public void addParsedProgram(File file, Program program) {
        this.parsedFiles.put(file, program);
    }

    /**
     * Remove a file from the map of parsed objects {@link #parsedFiles}.
     *
     * @param file
     */
    public void removeParsedProgram(File file) {
        this.parsedFiles.remove(file);
    }

    public Map<File, AgentProgram> getParsedAgentPrograms() {
        Map<File, AgentProgram> returned = new HashMap<>();
        for (File file : this.parsedFiles.keySet()) {
            Program program = this.parsedFiles.get(file);
            if (program instanceof AgentProgram) {
                returned.put(file, (AgentProgram) program);
            }
        }
        return returned;
    }

    /**
     * DOC
     *
     * @param file
     * @return
     */
    public MASProgram getMASProgram(File file) {
        if (isMASFile(file)) {
            return (MASProgram) this.parsedFiles.get(file);
        } else {
            return null;
        }
    }

    /**
     * DOC
     *
     * @param agentFile
     *            The agent file.
     * @return a set of MAS programs that use the given agent file.
     */
    public Set<MASProgram> getMASProgramsThatUseFile(File agentFile) {
        Set<MASProgram> masPrograms = new HashSet<>();
        for (File file : this.parsedFiles.keySet()) {
            if (isMASFile(file)) {
                MASProgram masProgram = (MASProgram) this.parsedFiles.get(file);
                if (masProgram.getAgentFiles().contains(agentFile)) {
                    masPrograms.add(masProgram);
                }
            }
        }
        return masPrograms;
    }

    /**
     * Checks whether file is a MAS file.
     *
     * @param file
     *            The file to check.
     * @return {@code true} if extension of the file is {@link Extension#MAS}.
     */
    public static boolean isMASFile(File file) {
        return Extension.getFileExtension(file) == Extension.MAS;
    }

    /**
     * Checks whether file is a test file.
     *
     * @param file
     *            The file to check.
     * @return {@code true} if extension of the file is {@link Extension#MAS}.
     */
    public static boolean isTestFile(File file) {
        return Extension.getFileExtension(file) == Extension.TEST;
    }

    /**
     * Returns all files that have been parsed and are managed by this
     * {@link PlatformManager}.
     *
     * @return The set of all files that have been parsed.
     *
     *         TODO: remove this method... Only used by
     *         {@link BreakpointManager}.
     */
    public Set<File> getAllGOALFiles() {
        Set<File> agentFiles = new HashSet<>();
        for (File file : this.parsedFiles.keySet()) {
            if (isGOALFile(file)) {
                agentFiles.add(file);
            }
        }
        return agentFiles;
    }

    /**
     * Returns the agent program that is the result of parsing the given file.
     *
     * @param file
     *            The file that is the source for the agent program.
     * @return The agent program, if the file is an agent (.goal) file;
     *         {@code null} otherwise.
     */
    public AgentProgram getAgentProgram(File file) {
        if (isGOALFile(file)) {
            return (AgentProgram) this.parsedFiles.get(file);
        } else {
            return null;
        }
    }

    /**
     * Checks whether file is a GOAL file.
     *
     * @param file
     *            The file to check.
     * @return {@code true} if extension of the file is {@link Extension#GOAL}.
     */
    private static boolean isGOALFile(File file) {
        return Extension.getFileExtension(file) == Extension.GOAL;
    }

    /**
     * If argument is a mas2g file it will be added. If the argument is a folder
     * all mas2g files in it will be added. If <code>recursive</code> is
     * {@code true}, mas2g files from all sub-folders (and sub-sub-folders,
     * etc.) will also be added.
     *
     * @param fileOrFolder
     *            File or folder to load mas2g file(s) from.
     * @param recursive
     *            If {@code true} all mas2g files in subfolders will also be
     *            loaded.
     * @return List of the MAS files that were loaded.
     */
    public static List<File> getMASFiles(File fileOrFolder, boolean recursive) {
        List<File> masFiles = new LinkedList<>();

        if (fileOrFolder.isFile() && isMASFile(fileOrFolder)) {
            masFiles.add(fileOrFolder);
            return masFiles;
        }

        if (fileOrFolder.isDirectory()) {
            for (File file : fileOrFolder.listFiles()) {
                if (file.isFile()) {
                    masFiles.addAll(getMASFiles(file, recursive));
                }

                if (file.isDirectory() && recursive) {
                    masFiles.addAll(getMASFiles(file, recursive));
                }
            }
        }

        return masFiles;
    }

    /**
     * If argument is a test2g file it will be added. If the argument is a
     * folder all test2g files in it will be added. If <code>recursive</code> is
     * {@code true}, test2g files from all sub-folders (and sub-sub-folders,
     * etc.) will also be added.
     *
     * @param fileOrFolder
     *            File or folder to load test2g file(s) from.
     * @param recursive
     *            If {@code true} all test2g files in subfolders will also be
     *            loaded.
     * @return List of the test2g files that were loaded.
     */
    public static List<File> getUnitTestFiles(File fileOrFolder, boolean recursive) {
        List<File> files = new LinkedList<>();

        if (fileOrFolder.isFile() && isTestFile(fileOrFolder)) {
            files.add(fileOrFolder);
            return files;
        }

        if (fileOrFolder.isDirectory()) {
            for (File file : fileOrFolder.listFiles()) {
                if (file.isFile()) {
                    files.addAll(getUnitTestFiles(file, recursive));
                }

                if (file.isDirectory() && recursive) {
                    files.addAll(getUnitTestFiles(file, recursive));
                }
            }
        }

        return files;
    }

    public UnitTest parseUnitTestFile(File file) throws ParserException {
        // Logger to report issues found during parsing and validation.
        GOALLogger parserLogger = Loggers.getParserLogger();
        parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing test file " //$NON-NLS-1$
                + file.getPath()));

        TestValidator validator = new TestValidator(file.getPath());
        validator.validate();
        UnitTest test = validator.getProgram();
        if (test == null) {
            throw new ParserException("Invalid Test file"); //$NON-NLS-1$
        }
        addParsedProgram(file, test);

        // Report any errors encountered during parsing.
        boolean hasErrors = false, hasWarnings = false;
        for (Message error : validator.getSyntaxErrors()) {
            hasErrors = true;
            parserLogger.log(new StringsLogRecord(Level.SEVERE, error.toString()));
        }
        for (Message error : validator.getErrors()) {
            hasErrors = true;
            parserLogger.log(new StringsLogRecord(Level.SEVERE, error.toString()));
        }
        for (Message warning : validator.getWarnings()) {
            hasWarnings = true;
            parserLogger.log(new StringsLogRecord(Level.WARNING, warning.toString()));
        }

        if (hasErrors) {
            parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing completed with errors.")); //$NON-NLS-1$
            // Also output warning to IDE console.
            Loggers.getWarningLogger()
                    .logln(String.format(Resources.get(WarningStrings.PARSING_ERRORS), file.getPath()));
            return test;
        } else {
            // Build message for final log report.
            StringBuilder message = new StringBuilder(35);
            message.append(String.format(Resources.get(WarningStrings.PARSING_COMPLETED), file.getPath()));
            if (hasWarnings) {
                message.append(Resources.get(WarningStrings.WITH_WARNINGS));
            }
            // Provide message to logger.
            parserLogger.log(new StringsLogRecord(Level.INFO, message.toString()));
            return test;
        }
    }

    /**
     * Creates a MAS program from a MAS (.mas2g) file. Loads the file, parses
     * its content, and validates the result.
     *
     * @param masFile
     *            The the MAS (.mas2g) file.
     * @throws ParserException
     *             If ANTLR stream cannot be opened. If parsing MAS file
     *             generates errors.
     * @throws IOException
     *             If file could not be closed properly.
     * @return The parsed and validated MAS program.
     * @throws InvalidEmotionConfigFile 
     * @throws FileNotFoundException 
     */
    public MASProgram parseMASFile(File masFile)
            throws ParserException, FileNotFoundException, InvalidEmotionConfigFile {
        // Logger to report issues found during parsing and validation.
        GOALLogger parserLogger = Loggers.getParserLogger();
        parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing mas file " //$NON-NLS-1$
                + masFile.getPath()));

        MASValidator validator = new MASValidator(masFile.getPath());
        validator.validate();
        MASProgram masProgram = validator.getProgram();

        if (masProgram == null) {
            throw new ParserException("Invalid MAS file"); //$NON-NLS-1$
        }
        addParsedProgram(masFile, masProgram);

        // Log messages.
        boolean hasErrors = false, hasWarnings = false;
        for (Message error : validator.getSyntaxErrors()) {
            hasErrors = true;
            parserLogger.log(new StringsLogRecord(Level.SEVERE, error.toString()));
        }
        for (Message error : validator.getErrors()) {
            hasErrors = true;
            parserLogger.log(new StringsLogRecord(Level.SEVERE, error.toString()));
        }
        for (Message warning : validator.getWarnings()) {
            hasWarnings = true;
            parserLogger.log(new StringsLogRecord(Level.WARNING, warning.toString()));
        }

        if (hasErrors) {
            // Output errors to parser tab (via parser logger).
            parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing completed with errors.")); //$NON-NLS-1$
            // Also output warning to IDE console.
            Loggers.getWarningLogger()
                    .logln(String.format(Resources.get(WarningStrings.PARSING_ERRORS), masFile.getPath()));

        } else {
            // Build message for final log report.
            StringBuilder message = new StringBuilder(35);
            message.append(String.format(Resources.get(WarningStrings.PARSING_COMPLETED), masFile.getPath()));
            if (hasWarnings) {
                message.append(Resources.get(WarningStrings.WITH_WARNINGS));
            }
            // Provide message to logger.
            parserLogger.log(new StringsLogRecord(Level.INFO, message.toString()));
        }

        // parse any found agent files anyway
        parseGOALFiles(masProgram);

        // parse emotion config if found
        parseEmotionConfig(masProgram);

        return masProgram;
    }

    /**
     * Parse the given agent files.
     *
     * @param agentFiles
     *            The agent files.
     */
    public void parseGOALFiles(MASProgram mas) {
        for (File agentFile : mas.getAgentFiles()) {
            try {
                parseGOALFile(agentFile, mas.getKRInterface(agentFile));
            } catch (Exception e) { // top level safety catch
                throw new GOALBug("Unexpected failure in parser", e); //$NON-NLS-1$
            }
        }
    }

    /**
     * TODO: duplicates most of the code of {@link parseMASFile}...
     *
     * Returns a GOAL program that is the result of parsing the agent file.
     *
     * @param goalFile
     *            The agent file to load.
     * @param language
     *            The KR language that is used in the agent file.
     * @return GOAL program resulting from parsing the agent file.
     * @throws ParserException
     *             if the file does not exist, is a directory rather than a
     *             regular file, or for some other reason cannot be opened for
     *             reading. Or if an ANTLR stream could not be opened. if the
     *             parser or lexer encountered errors while parsing the given
     *             .goal file. Indicates that the returned value is null or
     *             otherwise invalid.
     */
    public AgentProgram parseGOALFile(File goalFile, KRInterface language) throws ParserException {
        // Logger to report issues found during parsing and validation.
        GOALLogger parserLogger = Loggers.getParserLogger();
        parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing agent file " //$NON-NLS-1$
                + goalFile.getPath()));

        AgentValidator validator = new AgentValidator(goalFile.getPath());
        validator.setKRInterface(language);
        validator.validate();
        AgentProgram program = validator.getProgram();
        if (program == null) {
            throw new ParserException("Invalid GOAL file"); //$NON-NLS-1$
        }
        addParsedProgram(goalFile, program);

        // Report any errors encountered during parsing.
        boolean hasErrors = false, hasWarnings = false;
        for (Message error : validator.getSyntaxErrors()) {
            hasErrors = true;
            parserLogger.log(new StringsLogRecord(Level.SEVERE, error.toString()));
        }
        for (Message error : validator.getErrors()) {
            hasErrors = true;
            parserLogger.log(new StringsLogRecord(Level.SEVERE, error.toString()));
        }
        for (Message warning : validator.getWarnings()) {
            hasWarnings = true;
            parserLogger.log(new StringsLogRecord(Level.WARNING, warning.toString()));
        }

        if (hasErrors) {
            parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing completed with errors.")); //$NON-NLS-1$
            // Also output warning to IDE console.
            Loggers.getWarningLogger()
                    .logln(String.format(Resources.get(WarningStrings.PARSING_ERRORS), goalFile.getPath()));
            return program;
        } else {
            // Build message for final log report.
            StringBuilder message = new StringBuilder(35);
            message.append(String.format(Resources.get(WarningStrings.PARSING_COMPLETED), goalFile.getPath()));
            if (hasWarnings) {
                message.append(Resources.get(WarningStrings.WITH_WARNINGS));
            }
            // Provide message to logger.
            parserLogger.log(new StringsLogRecord(Level.INFO, message.toString()));

            return program;
        }
    }

    /**
     * Parse the emotion config file in the mas program.
     *
     * @param mas
     *            The MAS program to extract the emotionconfig from.
     */
    public void parseEmotionConfig(MASProgram mas) {
        try {
            String emoString = mas.getEmotionFile();
            if (emoString != null) {
                File emotionFile;
                if (new File(emoString).isAbsolute()) {
                    emotionFile = new File(emoString);
                } else {
                    String emoPath = mas.getSourceFile().getParentFile().getAbsolutePath();
                    emotionFile = new File(emoPath, emoString);
                }

                parseEmotionConfig(emotionFile);
            }

        } catch (Exception e) { // top level safety catch
            throw new GOALBug("Unexpected failure in parser", e); //$NON-NLS-1$
        }
    }

    /**
     * Returns a EmotionConfig that is the result of parsing the file.
     *
     * @param emotionFile
     *            The file to load.
     * @return EmotionConfig resulting from parsing the file.
     * @throws ParserException
     *             if the file does not exist, is a directory rather than a
     *             regular file, or for some other reason cannot be opened for
     *             reading.
     */
    public EmotionConfig parseEmotionConfig(File emotionFile) throws ParserException {
        // Logger to report issues found during parsing and validation.
        GOALLogger parserLogger = Loggers.getParserLogger();
        parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing emotion config file " + emotionFile.getPath())); //$NON-NLS-1$

        EmotionConfig config = null;
        try {
            config = RelationParser.parse(emotionFile);
        } catch (FileNotFoundException e) {
            throw new ParserException("Invalid emotion config file");
        } catch (InvalidEmotionConfigFile error) {
            // Report any errors encountered during parsing.
            parserLogger.log(new StringsLogRecord(Level.SEVERE, error.getMessage()));

            parserLogger.log(new StringsLogRecord(Level.INFO, "Parsing completed with errors.")); //$NON-NLS-1$

            // Also print to main console
            Loggers.getWarningLogger()
                    .logln(String.format(Resources.get(WarningStrings.PARSING_ERRORS), emotionFile.getPath()));

            return config;
        }

        // Provide message to logger.
        parserLogger.log(new StringsLogRecord(Level.INFO,
                String.format(Resources.get(WarningStrings.PARSING_COMPLETED), emotionFile.getPath())));

        return config;
    }

    /**
     * Gets the files representing the child files of the given agent file. A
     * file is a child file if the agent file has an #import &lt; child file
     * &gt; command. This returns only DIRECT children of the file, not children
     * of children.
     *
     * @param agentFile
     *            The agent file to get the child files of.
     * @return The list of files that are imported by the given agent file. A
     *         new list is created every time this method is called.
     */
    public List<File> getImportedFiles(File agentFile) {
        final AgentProgram file = getAgentProgram(agentFile);
        if (file != null) {
            return file.getImportedModules();
        } else {
            return new ArrayList<>(0);
        }
    }

    /**
     * Creates a new file and inserts a corresponding template into the file for
     * a given extension. Overwrites already existing file.
     *
     * @param newFile
     *            The file to be created.
     * @param extension
     *            The {@link Extension} the file to be created should have. The
     *            extension is also used to select and apply the corresponding
     *            template to insert initial content in the file.
     *
     * @return The newly created or emptied file (CHECK which is just the same
     *         file as given as first param?).
     * @throws IOException
     *             If an I/O error occurs.
     */
    public static File createfile(File newFile, Extension extension) throws IOException {
        /**
         * do not use f.createNewFile() because that does not overwrite existing
         * files.
         */

        // make sure the folder to write the file to exists.
        newFile.getParentFile().mkdirs();

        // Copy template if we have an extension.
        if (extension != null) {
            try (FileOutputStream outFile = new FileOutputStream(newFile)) {
                String templatename = "template" //$NON-NLS-1$
                        + extension.toString().toLowerCase();
                try (InputStream template = ClassLoader.getSystemClassLoader()
                        .getResourceAsStream("goal/tools/SimpleIDE/files/" + templatename)) { //$NON-NLS-1$
                    if (template != null) {
                        IOUtils.copy(template, outFile);
                    }
                }
            }
        }

        return newFile;
    }
}