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

com.bnorm.infinite.file.FileStateMachineStructure Maven / Gradle / Ivy

The newest version!
package com.bnorm.infinite.file;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Scanner;

import com.bnorm.infinite.Action;
import com.bnorm.infinite.ActionType;
import com.bnorm.infinite.InternalState;
import com.bnorm.infinite.InternalStateFactory;
import com.bnorm.infinite.StateMachineStructureBase;
import com.bnorm.infinite.TransitionFactory;
import com.bnorm.infinite.TransitionGuard;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * State machine structure read from a text file.  This text file is interpreted by a string state machine reader.
 *
 * @param  the class type of the states.
 * @param  the class type of the events.
 * @param  the class type of the context.
 * @author Brian Norman
 * @since 1.2.0
 */
public class FileStateMachineStructure extends StateMachineStructureBase {

    /** The class logger. */
    private static final Logger log = LoggerFactory.getLogger(FileStateMachineStructure.class);

    /**
     * Constructs a new FileStateMachineStructure with the specified parameters.
     *
     * @param internalStateFactory the factory used to create internal states.
     * @param transitionFactory the factory used to create transitions.
     * @param path the file location of the state machine text.
     * @param stateMachineReader the reader used to load the state machine.
     * @throws IOException if there is a problem reading the state machine file.
     */
    public FileStateMachineStructure(InternalStateFactory internalStateFactory,
                                     TransitionFactory transitionFactory, Path path,
                                     StringStateMachineReader stateMachineReader) throws IOException {
        super(internalStateFactory, transitionFactory);

        try (Scanner scanner = new Scanner(path)) {
            boolean validState = false;
            S state = null;
            while (scanner.hasNextLine()) {
                // Remove comments from the end of lines
                final String fullLine = scanner.nextLine();
                final String[] split = fullLine.split("#", 2);
                String line = split[0];

                if (!line.trim().isEmpty()) {
                    // The line contains state machine structure information
                    if (!line.startsWith(" ") && !line.startsWith("\t")) {
                        // A line that is not indented in anyway is a state
                        log.trace("Parsing line as state.  Line[{}]", fullLine);
                        state = parseState(stateMachineReader, line);
                        validState = true;
                    } else if (validState) {
                        // An indented line, if it comes directly after a state, is treated as a transition
                        log.trace("Parsing line as transition.  Line[{}]", fullLine);
                        parseTransition(state, stateMachineReader, line);
                    } else {
                        // Indented line that is at beginning of the file or did not come directly after a state
                        log.trace("Ignoring line.  Line[{}]", fullLine);
                    }
                }
            }
        }
    }

    /**
     * Parses the specified line string as a state using the given reader.
     *
     * @param stateMachineReader the state machine reader.
     * @param line the state machine text string.
     * @return the resulting state from the string.
     */
    private S parseState(StringStateMachineReader stateMachineReader, String line) {
        final String[] split = line.split(":|/");
        int index = 0;

        final String stateString = split[index].trim();
        final S state = stateMachineReader.readState(stateString);
        final InternalState internalState = this.getState(state);
        index++;
        log.trace("Found state [{}]", stateString);

        if (line.contains(":")) {
            final String parentString = split[index].trim();
            final S parent = stateMachineReader.readState(parentString);

            final InternalState internalParent = this.getState(parent);
            internalParent.addChild(internalState);
            internalState.setParentState(internalParent);

            index++;
            log.trace("Found parent state [{}]", parentString);
        }

        if (line.contains("/")) {
            final String entryActionString = split[index].trim();
            if (!entryActionString.isEmpty()) {
                final Action entryAction = stateMachineReader.readStateAction(state, ActionType.Entrance,
                                                                                       entryActionString);
                internalState.addEntranceAction(entryAction);
                log.trace("Found entry action [{}]", entryActionString);
            }
            index++;
        }

        if (index < split.length) {
            final String exitActionString = split[index].trim();
            final Action exitAction = stateMachineReader.readStateAction(state, ActionType.Exit,
                                                                                  exitActionString);
            internalState.addExitAction(exitAction);
            index++;
            log.trace("Found exit action [{}]", exitActionString);
        }

        return state;
    }

    /**
     * Parses the specified line string as a transition using the given reader and source state.
     *
     * @param state the transition source state.
     * @param stateMachineReader the state machine reader.
     * @param line the state machine text string.
     */
    private void parseTransition(S state, StringStateMachineReader stateMachineReader, String line) {
        final String[] split = line.split("->|\\[|/");
        int index = 0;

        final String eventString = split[index].trim();
        final E event = stateMachineReader.readEvent(eventString);
        index++;
        log.trace("Found transition event [{}]", eventString);

        final S destination;
        if (line.contains("->")) {
            final String stateString = split[index].trim();
            destination = stateMachineReader.readState(stateString);
            index++;
            log.trace("Found transition state [{}]", stateString);
        } else {
            destination = state;
            log.trace("Creating re-entrant transition to [{}]", state);
        }

        final TransitionGuard transitionGuard;
        if (line.contains("[")) {
            String guard = split[index];
            final int guardEnd = guard.lastIndexOf(']');
            if (guardEnd > 0) {
                guard = guard.substring(0, guardEnd);
            } // else - no guard ending - that's fine
            guard = guard.trim();
            transitionGuard = stateMachineReader.readTransitionGuard(state, event, destination, guard);
            index++;
            log.trace("Found transition guard [{}]", guard);
        } else {
            transitionGuard = TransitionGuard.none();
        }

        final Action transitionAction;
        if (line.contains("/")) {
            final String transitionActionString = split[index].trim();
            transitionAction = stateMachineReader.readTransitionAction(state, event, destination,
                                                                       transitionActionString);
            index++;
            log.trace("Found transition action [{}]", transitionActionString);
        } else {
            transitionAction = Action.noAction();
        }

        log.trace("Adding state transition.  Event [{}] State [{}]", event, destination);
        this.addTransition(event,
                           this.getTransitionFactory().create(state, destination, transitionGuard, transitionAction));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy