org.xlrnet.tibaija.processor.ExecutionEnvironment.java Source code

Java tutorial

Introduction

Here is the source code for org.xlrnet.tibaija.processor.ExecutionEnvironment.java

Source

/*
 * Copyright (c) 2015 Jakob Hende
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE
 */

package org.xlrnet.tibaija.processor;

import com.google.common.collect.ImmutableList;
import org.apache.commons.lang3.CharUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xlrnet.tibaija.CodeProvider;
import org.xlrnet.tibaija.DummyCodeProvider;
import org.xlrnet.tibaija.VirtualCalculator;
import org.xlrnet.tibaija.exception.CommandNotFoundException;
import org.xlrnet.tibaija.exception.DuplicateCommandException;
import org.xlrnet.tibaija.exception.TIRuntimeException;
import org.xlrnet.tibaija.graphics.*;
import org.xlrnet.tibaija.io.CalculatorIO;
import org.xlrnet.tibaija.memory.CalculatorMemory;
import org.xlrnet.tibaija.memory.Parameter;
import org.xlrnet.tibaija.memory.ReadOnlyCalculatorMemory;
import org.xlrnet.tibaija.memory.Value;
import org.xlrnet.tibaija.util.ValueFormatUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Stack;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * This class provides the main environment where programs and functions get executed.
 */
public class ExecutionEnvironment {

    private static final Logger LOGGER = LoggerFactory.getLogger(ExecutionEnvironment.class);

    private final HomeScreen homeScreen;

    private final Stack<ExecutableProgram> programStack = new Stack<>();

    private final CalculatorMemory memory;

    private final CalculatorIO calculatorIO;

    private final CodeProvider codeProvider;

    private final FontRegistry fontRegistry;

    Map<String, Command> commandFunctionMap = new HashMap<>();

    Map<String, Command> expressionFunction = new HashMap<>();

    Map<String, Command> commandStatementMap = new HashMap<>();

    private DecimalDisplayMode decimalDisplayMode;

    private NumberDisplayFormat numberDisplayFormat;

    private ExecutionEnvironment(@NotNull CalculatorMemory memory, @NotNull CalculatorIO calculatorIO,
            @NotNull CodeProvider codeProvider, @NotNull HomeScreen homeScreen,
            @NotNull FontRegistry fontRegistry) {
        this.memory = memory;
        this.calculatorIO = calculatorIO;
        this.codeProvider = codeProvider;
        this.homeScreen = homeScreen;
        this.fontRegistry = fontRegistry;
    }

    /**
     * Instantiate a new environment without any preconfigured commands and no font registry.
     *
     * @param memory
     *         The writable memory for the new environment.
     * @param calculatorIO
     *         The I/O device for the new environment.
     * @return A new environment
     */
    @NotNull
    public static ExecutionEnvironment newEnvironment(@NotNull CalculatorMemory memory,
            @NotNull CalculatorIO calculatorIO) {
        return new ExecutionEnvironment(memory, calculatorIO, new DummyCodeProvider(), new NullHomeScreen(),
                new FontRegistry());
    }

    /**
     * Instantiate a new environment without any preconfigured commands, no code provider and no font registry.
     *
     * @param memory
     *         The writable memory for the new environment.
     * @param calculatorIO
     *         The I/O device for the new environment.
     * @param codeProvider
     *         The code provider for the new environment.
     * @return A new environment
     */
    @NotNull
    public static ExecutionEnvironment newEnvironment(@NotNull CalculatorMemory memory,
            @NotNull CalculatorIO calculatorIO, @NotNull CodeProvider codeProvider) {
        return new ExecutionEnvironment(memory, calculatorIO, codeProvider, new NullHomeScreen(),
                new FontRegistry());
    }

    /**
     * Instantiate a new environment without any preconfigured commands, but with an existing code provider, home screen
     * and font registry.
     *
     * @param memory
     *         The writable memory for the new environment.
     * @param calculatorIO
     *         The I/O device for the new environment.
     * @param codeProvider
     *         The code provider for the new environment.
     * @param homeScreen
     *         The home screen on which should be printed.
     * @param fontRegistry
     *         The registry with already configured fonts.
     * @return A new environment
     */
    @NotNull
    public static ExecutionEnvironment newEnvironment(@NotNull CalculatorMemory memory,
            @NotNull CalculatorIO calculatorIO, @NotNull CodeProvider codeProvider, @NotNull HomeScreen homeScreen,
            @NotNull FontRegistry fontRegistry) {
        return new ExecutionEnvironment(memory, calculatorIO, codeProvider, homeScreen, fontRegistry);
    }

    /**
     * Instantiate a new environment without any preconfigured commands, but with a code provider and a home screen.
     *
     * @param memory
     *         The writable memory for the new environment.
     * @param calculatorIO
     *         The I/O device for the new environment.
     * @param codeProvider
     *         The code provider for the new environment.
     * @param homeScreen
     *         The home screen on which should be printed.
     * @return A new environment
     */
    @NotNull
    public static ExecutionEnvironment newEnvironment(@NotNull CalculatorMemory memory,
            @NotNull CalculatorIO calculatorIO, @NotNull CodeProvider codeProvider,
            @NotNull HomeScreen homeScreen) {
        return new ExecutionEnvironment(memory, calculatorIO, codeProvider, homeScreen, new FontRegistry());
    }

    /**
     * Instantiate a new environment without any preconfigured commands.
     *
     * @param virtualCalculator
     *         The virtual calculator with a configured I/O device and memory.
     * @return A new environment
     */
    @NotNull
    public static ExecutionEnvironment newEnvironment(@NotNull VirtualCalculator virtualCalculator) {
        return ExecutionEnvironment.newEnvironment(virtualCalculator.getMemory(), virtualCalculator.getIODevice());
    }

    /**
     * Formats a given {@link Value} object according to the currently configured {@link DecimalDisplayMode}. The
     * current configuration can be changed through {@link #setDecimalDisplayMode(DecimalDisplayMode)}
     *
     * @param value
     *         The value to format.
     * @return The formatted value.
     */
    public String formatValue(Value value) {
        return ValueFormatUtils.formatValue(value, numberDisplayFormat, decimalDisplayMode);
    }

    /**
     * Get access to the I/O device of the environment.
     *
     * @return Reference to the I/O device of the environment.
     */
    @NotNull
    public CalculatorIO getCalculatorIO() {
        return calculatorIO;
    }

    public CodeProvider getCodeProvider() {
        return codeProvider;
    }

    public DecimalDisplayMode getDecimalDisplayMode() {
        return decimalDisplayMode;
    }

    /**
     * Set the current mode of how decimals should be displayed. Setting this value will affect all formatting through
     * {@link #formatValue(Value)}.
     *
     * @param decimalDisplayMode
     *         The display mode to set.
     */
    public void setDecimalDisplayMode(@NotNull DecimalDisplayMode decimalDisplayMode) {
        this.decimalDisplayMode = decimalDisplayMode;
    }

    public FontRegistry getFontRegistry() {
        return fontRegistry;
    }

    /**
     * Returns the currently registered {@link HomeScreen} implementation for this environment. The home screen should
     * be used for printing out basic texts and data without any graphical components.
     *
     * @return the currently registered home screen for this environment.
     */
    @NotNull
    public HomeScreen getHomeScreen() {
        return homeScreen;
    }

    /**
     * Get readable access to the calculator memory.
     *
     * @return Reference to the readable memory of the calculator.
     */
    @NotNull
    public ReadOnlyCalculatorMemory getMemory() {
        return memory;
    }

    @NotNull
    public NumberDisplayFormat getNumberDisplayFormat() {
        return numberDisplayFormat;
    }

    public void setNumberDisplayFormat(NumberDisplayFormat numberDisplayFormat) {
        this.numberDisplayFormat = numberDisplayFormat;
    }

    /**
     * Get the stack of current program hierarchy. Whenever a new subprogram is executed, it must be placed on this
     * stack to provide a context for accessing the program's labels. When the execution of a program has finished, it
     * must be removed from this stack.
     *
     * @return the stack of current program hierarchy.
     */
    @NotNull
    public Stack<ExecutableProgram> getProgramStack() {
        return programStack;
    }

    /**
     * Register a command as a function command in the execution environment. All programs and other commands can use
     * the new function once it has been registered. Every command may only be associated with at most one execution
     * environment. If a command is registered as a command function (i.e. through this function) it cannot be used as
     * an expression and won't return any value but will probably modify the system state (e.g. display output).
     *
     * @param commandName
     *         Name of the new command under which it can be accessed.
     * @param command
     *         An instance of the concrete command.
     */
    public void registerCommandFunction(@NotNull String commandName, @NotNull Command command)
            throws TIRuntimeException {
        checkNotNull(commandName);
        checkNotNull(command);

        checkArgument(StringUtils.isNotBlank(commandName), "Function command name may not be blank");
        checkArgument(StringUtils.isAllUpperCase(commandName.substring(0, 1)),
                "Function command name must begin with a uppercase letter");

        if (commandFunctionMap.get(commandName) != null)
            throw new DuplicateCommandException("Command function is already registered: " + commandName);

        if (command.getEnvironment() != null)
            throw new DuplicateCommandException(
                    "New command instance is already registered in another environment");

        command.setEnvironment(this);
        commandFunctionMap.put(commandName, command);

        LOGGER.debug("Registered new command function '{}'", commandName);
    }

    /**
     * Register a command as a statement in the execution environment. All programs and other commands can use the new
     * statement once it has been registered. Every command may only be associated with at most one execution
     * environment. If a command is registered as a command statement (i.e. through this function) it will not be
     * available in expressions and should not be called with parentheses. E.g. "DISP 123". Command statements should be
     * used when the system state must be manipulated (e.g. display output).
     *
     * @param commandName
     *         Name of the new command under which it can be accessed. Must begin with an uppercase letter.
     * @param command
     *         An instance of the concrete command.
     */
    public void registerCommandStatement(@NotNull String commandName, @NotNull Command command)
            throws TIRuntimeException {
        checkNotNull(commandName);
        checkNotNull(command);

        checkArgument(StringUtils.isNotBlank(commandName), "Command statement name may not be blank");
        checkArgument(StringUtils.isAllUpperCase(commandName.substring(0, 1)),
                "Command statement name must begin with a uppercase letter");

        if (commandStatementMap.get(commandName) != null)
            throw new DuplicateCommandException("Command statement is already registered: " + commandName);

        if (command.getEnvironment() != null)
            throw new DuplicateCommandException(
                    "New command instance is already registered in another environment");

        command.setEnvironment(this);
        commandStatementMap.put(commandName, command);

        LOGGER.debug("Registered new command statement '{}'", commandName);
    }

    /**
     * Register a command as an expression function in the execution environment. All programs and other commands can
     * use the new function once it has been registered. Every command may only be associated with at most one execution
     * environment. If a command is registered as a function expression (i.e. through this function) it can be used in
     * or as an expression and must return a value.
     *
     * @param commandName
     *         Name of the new command under which it can be accessed.
     * @param command
     *         An instance of the concrete command.
     */
    public void registerExpressionFunction(@NotNull String commandName, @NotNull Command command)
            throws TIRuntimeException {
        checkNotNull(commandName);
        checkNotNull(command);

        checkArgument(StringUtils.isNotBlank(commandName), "Expression function name may not be blank");
        Character firstChar = commandName.charAt(0);
        checkArgument(CharUtils.isAsciiAlphaLower(firstChar) || !CharUtils.isAsciiAlphanumeric(firstChar),
                "Expression function name must begin with a lowercase letter or be non-alphanumeric");

        if (expressionFunction.get(commandName) != null)
            throw new DuplicateCommandException("Function expression name is already registered: " + commandName);

        if (command.getEnvironment() != null)
            throw new DuplicateCommandException(
                    "New command instance is already registered in another environment");

        command.setEnvironment(this);
        expressionFunction.put(commandName, command);

        LOGGER.debug("Registered new expression function '{}'", commandName);
    }

    /**
     * Run a given {@link org.xlrnet.tibaija.processor.ExecutableProgram} inside this environment. The program will be
     * visited with the given visitor.
     *
     * @param program
     *         The program to run.
     * @param visitor
     *         The visitor implementation that should run this program.
     * @throws TIRuntimeException
     *         Will be thrown on errors while executing the program
     */
    public void run(@NotNull ExecutableProgram program, @NotNull FullTIBasicVisitor visitor)
            throws TIRuntimeException {
        visitor.setEnvironment(this);
        programStack.push(program);
        visitor.visit(program.getMainProgramContext());
        programStack.pop();
    }

    /**
     * Run a previously registered command function with the given arguments. The command to run must be registered as a
     * function through {@link #registerCommandFunction(String, Command)}. The return value of the function will be
     * returned and as an {@link Optional}.
     *
     * @param commandName
     *         Internal name of the previously registered function to execute.
     * @param arguments
     *         The arguments with which the command will be called.
     * @return An optional return value.
     * @throws TIRuntimeException
     *         Can be thrown on type errors, internal problems or illegal parameters.
     */
    @NotNull
    public Optional<Value> runRegisteredCommandFunction(@NotNull String commandName,
            @NotNull Parameter... arguments) throws TIRuntimeException {
        Command command = commandFunctionMap.get(commandName);
        if (command == null)
            throw new CommandNotFoundException(-1, -1, commandName);

        return internalExecuteCommand(command, arguments);
    }

    /**
     * Run a previously registered command statement with the given arguments. The command to run must be registered as
     * a statement command through {@link #registerCommandStatement(String, Command)}. The return value of the function
     * will be <i>not</i> returned.
     *
     * @param commandName
     *         Internal name of the previously registered statement to execute.
     * @param arguments
     *         The arguments with which the command will be called.
     * @throws TIRuntimeException
     *         Can be thrown on type errors, internal problems or illegal parameters.
     */
    public void runRegisteredCommandStatement(@NotNull String commandName, @NotNull Parameter... arguments)
            throws TIRuntimeException {
        Command command = commandStatementMap.get(commandName);
        if (command == null)
            throw new CommandNotFoundException(-1, -1, commandName);

        internalExecuteCommand(command, arguments);
    }

    /**
     * Run a previously registered command function with the given arguments. The command to run must be registered as a
     * function through {@link #registerExpressionFunction(String, Command)}. The return value of the function will be
     * returned and as an {@link Optional}.
     *
     * @param commandName
     *         Internal name of the previously registered function to execute.
     * @param arguments
     *         The arguments with which the command will be called.
     * @return An optional return value.
     * @throws TIRuntimeException
     *         Can be thrown on type errors, internal problems or illegal parameters.
     */
    @NotNull
    public Optional<Value> runRegisteredExpressionFunction(@NotNull String commandName,
            @NotNull Parameter... arguments) throws TIRuntimeException {
        Command command = expressionFunction.get(commandName);
        if (command == null)
            throw new CommandNotFoundException(-1, -1, commandName);

        return internalExecuteCommand(command, arguments);
    }

    /**
     * Run a previously registered command function with the given arguments. The command to run must be registered as a
     * function through {@link #registerExpressionFunction(String, Command)}. The return value of the function will be
     * returned and as an {@link Optional}.
     *
     * @param commandName
     *         Internal name of the previously registered function to execute.
     * @param arguments
     *         The arguments with which the command will be called.
     * @return An optional return value.
     * @throws TIRuntimeException
     *         Can be thrown on type errors, internal problems or illegal parameters.
     */
    @NotNull
    public Optional<Value> runRegisteredExpressionFunction(@NotNull String commandName, @NotNull Value... arguments)
            throws TIRuntimeException {
        Parameter[] parameters = new Parameter[arguments.length];

        for (int i = 0, argumentsLength = arguments.length; i < argumentsLength; i++) {
            Value argument = arguments[i];
            parameters[i] = Parameter.value(argument);
        }

        return runRegisteredExpressionFunction(commandName, parameters);
    }

    /**
     * Return a reference to the writable memory of this environment.
     *
     * @return A reference to the writable memory of this environment.
     */
    @NotNull
    protected CalculatorMemory getWritableMemory() {
        return memory;
    }

    @NotNull
    private Optional<Value> internalExecuteCommand(Command command, @NotNull Parameter[] arguments) {
        ImmutableList<Parameter> argumentList = ImmutableList.copyOf(arguments);
        command.checkArguments(argumentList);
        return command.execute(argumentList);
    }
}