de.pixida.logtest.designer.automaton.AutomatonNode.java Source code

Java tutorial

Introduction

Here is the source code for de.pixida.logtest.designer.automaton.AutomatonNode.java

Source

/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * Copyright (c) 2016 Pixida GmbH
 */

package de.pixida.logtest.designer.automaton;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;

import org.apache.commons.lang3.Validate;
import org.json.JSONObject;

import de.pixida.logtest.automatondefinitions.INodeDefinition;
import de.pixida.logtest.designer.commons.Icons;
import javafx.beans.value.ChangeListener;
import javafx.scene.Node;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.ButtonType;
import javafx.scene.control.CheckBox;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.control.ToggleGroup;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;

class AutomatonNode extends RectangularNode implements INodeDefinition {
    private static final int Z_INDEX_AUTOMATON_NODES = 100;
    private static final int Z_INDEX_AUTOMATON_NODES_PROPERTIES = 120;
    private static final int Z_INDEX_AUTOMATON_NODES_CONNECTORS_TO_PROPERTIES = 90;

    private static final String JSON_KEY_RECT = "rect";

    private String id;
    private String name;
    private String description;
    private Type type;
    private String onEnter;
    private final PropertyNode onEnterPropertyNode;
    private String onLeave;
    private final PropertyNode onLeavePropertyNode;
    private String successCheckExp;
    private final PropertyNode successCheckExpPropertyNode;
    private boolean wait;
    private final PropertyNode descriptionPropertyNode;

    AutomatonNode(final Graph graph) {
        super(graph, ContentDisplayMode.ADJUST_RECT, Z_INDEX_AUTOMATON_NODES);

        this.onEnterPropertyNode = new PropertyNode(graph, this, "On Enter", Z_INDEX_AUTOMATON_NODES_PROPERTIES,
                Z_INDEX_AUTOMATON_NODES_CONNECTORS_TO_PROPERTIES);
        this.onLeavePropertyNode = new PropertyNode(graph, this, "On Leave", Z_INDEX_AUTOMATON_NODES_PROPERTIES,
                Z_INDEX_AUTOMATON_NODES_CONNECTORS_TO_PROPERTIES);
        this.successCheckExpPropertyNode = new PropertyNode(graph, this, "Check Success",
                Z_INDEX_AUTOMATON_NODES_PROPERTIES, Z_INDEX_AUTOMATON_NODES_CONNECTORS_TO_PROPERTIES);
        this.descriptionPropertyNode = new PropertyNode(graph, this, PropertyNode.WITHOUT_TITLE,
                Z_INDEX_AUTOMATON_NODES_PROPERTIES, Z_INDEX_AUTOMATON_NODES_CONNECTORS_TO_PROPERTIES);
        this.descriptionPropertyNode
                .setColor(Color.YELLOW.desaturate().desaturate().desaturate().brighter().brighter());
    }

    private Map<String, PropertyNode> getPropertyNodes() {
        final Map<String, PropertyNode> result = new HashMap<>();
        result.put("onEnter", this.onEnterPropertyNode);
        result.put("onLeave", this.onLeavePropertyNode);
        result.put("successCheckExp", this.successCheckExpPropertyNode);
        result.put("description", this.descriptionPropertyNode);
        return result;
    }

    void loadFromJson(final INodeDefinition node, final JSONObject nodeDesignerConfig) {
        Validate.notNull(node);
        Validate.notNull(nodeDesignerConfig);

        this.setId(node.getId());
        this.setName(node.getName());
        this.setDescription(node.getDescription());
        this.setType(node.getType());
        this.setOnEnter(node.getOnEnter());
        this.setOnLeave(node.getOnLeave());
        this.setSuccessCheckExp(node.getSuccessCheckExp());
        this.setWait(node.getWait());

        JSONObject rectDefinition = nodeDesignerConfig.optJSONObject(JSON_KEY_RECT);
        if (rectDefinition == null) {
            rectDefinition = new JSONObject();
        }
        this.loadDimensionsFromJson(rectDefinition);

        PropertyNode.loadAllPropertyNodesFromJson(this.getPropertyNodes(), nodeDesignerConfig);

        this.updateRect();
    }

    void saveToJson(final JSONObject nodeDesignerConfig) {
        Validate.notNull(nodeDesignerConfig);

        final JSONObject rectConfig = new JSONObject();
        this.saveDimensionsToJson(rectConfig);
        nodeDesignerConfig.put(JSON_KEY_RECT, rectConfig);

        PropertyNode.saveAllPropertyNodesToJson(this.getPropertyNodes(), nodeDesignerConfig);
    }

    @Override
    ContextMenu createContextMenu() {
        final ContextMenu cm = new ContextMenu();

        MenuItem mi = new MenuItem("Create edge from here");
        mi.setGraphic(Icons.getIconGraphics("bullet_go"));
        mi.setOnAction(event -> {
            final AutomatonEdgeBuilder newEdge = new AutomatonEdgeBuilder(this.getGraph());
            this.getGraph().startDrawingConnector(this, newEdge);
        });
        cm.getItems().add(mi);

        mi = new MenuItem("Delete state");
        mi.setGraphic(Icons.getIconGraphics("delete"));
        mi.setStyle("-fx-text-fill:#FF3030");
        mi.setOnAction(event -> {
            final Alert alert = new Alert(AlertType.CONFIRMATION);
            alert.setTitle("Confirm");
            alert.setHeaderText("You are about to delete the state.");
            alert.setContentText("Do you want to continue?");
            alert.showAndWait().filter(response -> response == ButtonType.OK)
                    .ifPresent(response -> this.removeNodeAndEdges());
        });
        cm.getItems().add(mi);

        return cm;
    }

    @Override
    public Type getType() {
        return this.type;
    }

    void setType(final Type value) {
        this.type = value;
        this.showHideSuccessCheckExpDependingOnNodeType();
        this.updateRect();
    }

    private void showHideSuccessCheckExpDependingOnNodeType() {
        if (this.type != Type.SUCCESS) {
            this.successCheckExpPropertyNode.apply("");
        } else {
            this.successCheckExpPropertyNode.apply(this.successCheckExp);
        }
    }

    @Override
    public String getOnEnter() {
        return this.onEnter;
    }

    private void setOnEnter(final String value) {
        this.onEnter = this.onEnterPropertyNode.apply(value);
    }

    @Override
    public String getOnLeave() {
        return this.onLeave;
    }

    private void setOnLeave(final String value) {
        this.onLeave = this.onLeavePropertyNode.apply(value);
    }

    @Override
    public String getSuccessCheckExp() {
        return this.successCheckExp;
    }

    private void setSuccessCheckExp(final String value) {
        this.successCheckExp = this.successCheckExpPropertyNode.apply(value);
        this.showHideSuccessCheckExpDependingOnNodeType();
    }

    @Override
    public boolean getWait() {
        return this.wait;
    }

    @Override
    public String getId() {
        return this.id;
    }

    void setId(final String value) {
        this.id = value;
    }

    @Override
    public String getDescription() {
        return this.description;
    }

    private void setDescription(final String value) {
        this.description = this.descriptionPropertyNode.apply(value);
    }

    @Override
    public String getName() {
        return this.name;
    }

    void setName(final String value) {
        this.name = value;
        this.setContent(this.name);
        this.updateRect();
    }

    @Override
    public Node getConfigFrame() {
        // TODO: Remove code redundancies; element creating methods have been created in class AutomatonEdge and should be centralized!
        final ConfigFrame cf = new ConfigFrame("State properties");

        final int nameInputLines = 1;
        final TextArea nameInput = new TextArea(this.name);
        nameInput.setPrefRowCount(nameInputLines);
        nameInput.setWrapText(true);
        nameInput.textProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
            this.setName(newValue);
            this.getGraph().handleChange();
        });
        cf.addOption("Name", nameInput);

        final int descriptionInputLines = 3;
        this.createTextAreaInput(descriptionInputLines, cf, "Description", this.getDescription(),
                newValue -> this.setDescription(newValue));

        final VBox typeAttributes = new VBox();
        final ToggleGroup flagToggleGroup = new ToggleGroup();
        typeAttributes.getChildren()
                .add(this.createRadioButtonForType(null, "None (intermediate node)", flagToggleGroup));
        typeAttributes.getChildren().add(this.createRadioButtonForType(Type.INITIAL, "Initial", flagToggleGroup));
        final RadioButton successOption = this.createRadioButtonForType(Type.SUCCESS, "Success", flagToggleGroup);
        typeAttributes.getChildren().add(successOption);
        typeAttributes.getChildren().add(this.createRadioButtonForType(Type.FAILURE, "Failure", flagToggleGroup));
        cf.addOption("Type", typeAttributes);

        final CheckBox waitCheckBox = new CheckBox("Active");
        waitCheckBox.setSelected(this.wait);
        waitCheckBox.setOnAction(event -> {
            this.setWait(waitCheckBox.isSelected());
            this.getGraph().handleChange();
        });
        cf.addOption("Wait", waitCheckBox);

        final int expressionInputLines = 1;
        final TextArea successCheckExpInput = new TextArea(this.successCheckExp);
        successCheckExpInput.setStyle("-fx-font-family: monospace");
        successCheckExpInput.setPrefRowCount(expressionInputLines);
        successCheckExpInput.setWrapText(false);
        successCheckExpInput.textProperty()
                .addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
                    this.setSuccessCheckExp(newValue);
                    this.getGraph().handleChange();
                });
        successCheckExpInput.disableProperty().bind(successOption.selectedProperty().not());
        cf.addOption("Script expression to verify if node is successful", successCheckExpInput);

        this.createEnterAndLeaveScriptConfig(cf);

        return cf;
    }

    public void createEnterAndLeaveScriptConfig(final ConfigFrame cf) {
        final int scriptInputLines = 3;
        final TextArea onEnterScriptInput = new TextArea(this.onEnter);
        onEnterScriptInput.setStyle("-fx-font-family: monospace");
        onEnterScriptInput.setPrefRowCount(scriptInputLines);
        onEnterScriptInput.setWrapText(false);
        onEnterScriptInput.textProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
            this.setOnEnter(newValue);
            this.getGraph().handleChange();
        });
        cf.addOption("When entering state, run script", onEnterScriptInput);

        final TextArea onLeaveScriptInput = new TextArea(this.onLeave);
        onLeaveScriptInput.setStyle("-fx-font-family: monospace");
        onLeaveScriptInput.setPrefRowCount(scriptInputLines);
        onLeaveScriptInput.setWrapText(false);
        onLeaveScriptInput.textProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
            this.setOnLeave(newValue);
            this.getGraph().handleChange();
        });
        cf.addOption("When leaving state, run script", onLeaveScriptInput);
    }

    protected void createTextAreaInput(final int numLines, final ConfigFrame cf, final String propertyName,
            final String initialValue, final Consumer<String> applyValue) {
        final TextArea inputField = new TextArea(initialValue);
        inputField.setPrefRowCount(numLines);
        inputField.setWrapText(true);
        inputField.textProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
            applyValue.accept(newValue);
            this.getGraph().handleChange();
        });
        cf.addOption(propertyName, inputField);
    }

    private void setWait(final boolean value) {
        this.wait = value;
        this.updateRect();
    }

    private RadioButton createRadioButtonForType(final Type aType, final String title,
            final ToggleGroup toggleGroup) {
        final RadioButton result = new RadioButton(title);
        result.setToggleGroup(toggleGroup);
        result.setSelected(this.type == aType);
        result.setOnAction(event -> {
            this.setType(aType);
            this.getGraph().handleChange();
        });
        return result;
    }

    private void updateRect() {
        String title = "";
        if (this.type == Type.INITIAL) {
            this.setColor(Color.BLUE.desaturate().desaturate().desaturate().desaturate().brighter().brighter());
            title += "INITIAL ";
        } else if (this.type == Type.SUCCESS) {
            this.setColor(Color.GREEN.desaturate().desaturate().desaturate().desaturate().brighter().brighter());
            title += "SUCCESS ";
        } else if (this.type == Type.FAILURE) {
            this.setColor(Color.RED.desaturate().desaturate().desaturate().desaturate().brighter().brighter());
            title += "FAILURE ";
        } else {
            this.setColor(Color.LIGHTGREY);
        }
        title += "State";
        if (this.wait) {
            title += " | wait!";
        }
        this.setTitle(title);
    }
}