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

Java tutorial

Introduction

Here is the source code for de.pixida.logtest.designer.automaton.EditorAutomaton.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.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.json.JSONException;
import org.json.JSONObject;

import de.pixida.logtest.automatondefinitions.AutomatonDefinitionToJsonConverter;
import de.pixida.logtest.automatondefinitions.IAutomatonDefinition;
import de.pixida.logtest.automatondefinitions.IEdgeDefinition;
import de.pixida.logtest.automatondefinitions.INodeDefinition;
import de.pixida.logtest.automatondefinitions.JsonAutomatonDefinition;
import de.pixida.logtest.designer.automaton.RectangularNode.ContentDisplayMode;
import de.pixida.logtest.engine.Automaton;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.control.ChoiceBox;
import javafx.scene.control.TextArea;
import javafx.scene.paint.Color;

class EditorAutomaton implements IAutomatonDefinition {
    private static final String JSON_KEY_DESIGNER = "designer";
    private static final String JSON_KEY_NODES = "nodes";
    private static final String JSON_KEY_EDGES = "edges";
    private static final String JSON_KEY_DESCRIPTION = "description";

    private static final int Z_INDEX_AUTOMATON_PROPERTIES = 1;

    private final RectangularNode descriptionNode;
    private String scriptLanguage;

    private final Graph graph;

    private final ConfigFrame configFrame = new ConfigFrame("Automaton properties");
    private final TextArea descriptionInput = new TextArea();
    private final TextArea onLoadInput = new TextArea();

    EditorAutomaton(final Graph aGraph) {
        Validate.notNull(aGraph);
        this.graph = aGraph;

        this.descriptionNode = new RectangularNode(this.graph, ContentDisplayMode.ADJUST_RECT,
                Z_INDEX_AUTOMATON_PROPERTIES);
        this.descriptionNode.setColor(Color.YELLOW.desaturate().desaturate().desaturate().brighter().brighter());
        this.descriptionNode.setTitle("Abstract");
        this.descriptionNode.hide();
        this.graph.addObject(this.descriptionNode);

        this.createConfigFrame();
    }

    private void createConfigFrame() {
        final int descriptionInputLines = 8;
        this.descriptionInput.setPrefRowCount(descriptionInputLines);
        this.descriptionInput.setWrapText(true);
        this.descriptionInput.textProperty()
                .addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
                    this.setDescription(newValue);
                    this.graph.handleChange();
                });
        this.configFrame.addOption("Description", this.descriptionInput);

        final ObservableList<String> options = FXCollections.observableArrayList("JavaScript", "python");
        if (!options.contains(Automaton.DEFAULT_SCRIPTING_LANGUAGE)) {
            options.add(0, Automaton.DEFAULT_SCRIPTING_LANGUAGE);
        }
        final ChoiceBox<String> scriptLanguageInput = new ChoiceBox<>(options);
        scriptLanguageInput
                .setValue(StringUtils.isBlank(this.scriptLanguage) || !options.contains(this.scriptLanguage)
                        ? Automaton.DEFAULT_SCRIPTING_LANGUAGE
                        : this.scriptLanguage);
        scriptLanguageInput.getSelectionModel().selectedIndexProperty()
                .addListener((ChangeListener<Number>) (observable, oldValue, newValue) -> {
                    EditorAutomaton.this.scriptLanguage = scriptLanguageInput.getItems().get((Integer) newValue);
                    this.graph.handleChange();
                });
        this.configFrame.addOption("Script language", scriptLanguageInput);

        final int onLoadInputLines = 8;
        this.onLoadInput.setPrefRowCount(onLoadInputLines);
        this.onLoadInput.setWrapText(true);
        this.onLoadInput.setStyle("-fx-font-family: monospace");
        this.onLoadInput.textProperty().addListener((ChangeListener<String>) (observable, oldValue, newValue) -> {
            this.graph.handleChange();
        });
        this.configFrame.addOption("On Load", this.onLoadInput);
    }

    @Override
    public List<? extends INodeDefinition> getNodes() {
        return this.graph.getAllNodesByClass(INodeDefinition.class);
    }

    @Override
    public List<? extends IEdgeDefinition> getEdges() {
        return this.graph.getAllNodesByClass(IEdgeDefinition.class);
    }

    @Override
    public String getOnLoad() {
        return this.onLoadInput.getText();
    }

    @Override
    public String getDisplayName() {
        return "Editor Automaton";
    }

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

    @Override
    public String getScriptLanguage() {
        return this.scriptLanguage;
    }

    Node getConfigFrame() {
        return this.configFrame;
    }

    @Override
    public void load() {
        // Loading is not done using this method, e.g. the automaton must be loaded before this method is called. Otherwise, it will be
        // empty (no nodes, edges, etc.)
    }

    void loadFromFile(final File src) {
        JSONObject rawJson = null;
        try {
            rawJson = new JSONObject(FileUtils.readFileToString(src, JsonAutomatonDefinition.EXPECTED_CHARSET));
        } catch (final JSONException e) {
            throw new RuntimeException("Invalid JSON data - maybe not an automaton definition?\n" + e.getMessage());
        } catch (final IOException e) {
            throw new RuntimeException("Failed to read file: " + src.getAbsolutePath(), e);
        }

        final JsonAutomatonDefinition def = new JsonAutomatonDefinition(src);
        def.load();

        this.setDescription(def.getDescription());
        this.scriptLanguage = def.getScriptLanguage();
        this.onLoadInput.setText(def.getOnLoad());

        final JSONObject designerConfig = rawJson.optJSONObject(JSON_KEY_DESIGNER);

        final JSONObject nodesDesignerConfig = designerConfig == null ? null
                : designerConfig.optJSONObject(JSON_KEY_NODES);
        final Map<String, AutomatonNode> mapIdToNode = this.createNodes(def, nodesDesignerConfig);

        final JSONObject edgesDesignerConfig = designerConfig == null ? null
                : designerConfig.optJSONObject(JSON_KEY_EDGES);
        this.createEdges(def, mapIdToNode, edgesDesignerConfig);

        JSONObject descriptionConfig = designerConfig == null ? null
                : designerConfig.optJSONObject(JSON_KEY_DESCRIPTION);
        if (descriptionConfig == null) {
            descriptionConfig = new JSONObject();
        }
        this.descriptionNode.loadDimensionsFromJson(descriptionConfig);

        // HACK: For some reason, the edges are not aligned correctly at this point. Fix this...
        this.graph.getAllNodesByClass(BaseEdge.class).forEach(connector -> connector.realign());
    }

    void setDescription(final String value) {
        final String oldValue = this.descriptionInput.getText();
        if (StringUtils.isBlank(oldValue) && StringUtils.isNotBlank(value)) {
            final double defaultXY = 10d;
            this.descriptionNode.setPosition(new Point2D(defaultXY, defaultXY));
            this.descriptionNode.show();
        }
        if (StringUtils.isBlank(value)) {
            this.descriptionNode.hide();
        }
        this.descriptionInput.setText(value);
        this.descriptionNode.setContent(this.descriptionInput.getText());
    }

    private Map<String, AutomatonNode> createNodes(final JsonAutomatonDefinition def,
            final JSONObject nodesDesignerConfig) {
        final Map<String, AutomatonNode> mapIdToNode = new HashMap<>();
        for (final INodeDefinition node : def.getNodes()) {
            Validate.notNull(node.getId());
            JSONObject nodeDesignerConfig = nodesDesignerConfig == null ? null
                    : nodesDesignerConfig.optJSONObject(node.getId());
            if (nodeDesignerConfig == null) {
                nodeDesignerConfig = new JSONObject();
            }

            final AutomatonNode newNode = new AutomatonNode(this.graph);
            newNode.loadFromJson(node, nodeDesignerConfig);
            this.graph.addObject(newNode);
            mapIdToNode.put(newNode.getId(), newNode);
        }
        return mapIdToNode;
    }

    private void createEdges(final JsonAutomatonDefinition def, final Map<String, AutomatonNode> mapIdToNode,
            final JSONObject edgesDesignerConfig) {
        for (final IEdgeDefinition edge : def.getEdges()) {
            Validate.notNull(edge.getSource());
            Validate.notNull(edge.getDestination());
            Validate.isTrue(mapIdToNode.containsKey(edge.getSource().getId()));
            Validate.isTrue(mapIdToNode.containsKey(edge.getDestination().getId()));

            Validate.notNull(edge.getId());
            JSONObject edgeDesignerConfig = edgesDesignerConfig == null ? null
                    : edgesDesignerConfig.optJSONObject(edge.getId());
            if (edgeDesignerConfig == null) {
                edgeDesignerConfig = new JSONObject();
            }

            final AutomatonEdgeBuilder edgeBuilder = new AutomatonEdgeBuilder(this.graph);
            final AutomatonNode sourceNode = mapIdToNode.get(edge.getSource().getId());
            final AutomatonNode targetNode = mapIdToNode.get(edge.getDestination().getId());
            edgeBuilder.attach(sourceNode, targetNode);
            final AutomatonEdge newEdge = edgeBuilder.getCreatedEdge();
            newEdge.loadFromJson(edge, edgeDesignerConfig);
        }
    }

    void saveToFile(final File dest) {
        this.assignArbitraryIdsToNodesAndEdges();

        final JSONObject root = AutomatonDefinitionToJsonConverter.convert(this);

        final JSONObject designerConfig = new JSONObject();
        root.put(JSON_KEY_DESIGNER, designerConfig);

        final JSONObject nodesDesignerConfig = new JSONObject();
        designerConfig.put(JSON_KEY_NODES, nodesDesignerConfig);
        for (final AutomatonNode node : this.graph.getAllNodesByClass(AutomatonNode.class)) {
            final JSONObject nodeDesignerConfig = new JSONObject();
            node.saveToJson(nodeDesignerConfig);
            nodesDesignerConfig.put(node.getId(), nodeDesignerConfig);
        }

        final JSONObject edgesDesignerConfig = new JSONObject();
        designerConfig.put(JSON_KEY_EDGES, edgesDesignerConfig);
        for (final AutomatonEdge edge : this.graph.getAllNodesByClass(AutomatonEdge.class)) {
            final JSONObject edgeDesignerConfig = new JSONObject();
            edge.saveToJson(edgeDesignerConfig);
            edgesDesignerConfig.put(edge.getId(), edgeDesignerConfig);
        }

        final JSONObject descriptionConfig = new JSONObject();
        this.descriptionNode.saveDimensionsToJson(descriptionConfig);
        designerConfig.put(JSON_KEY_DESCRIPTION, descriptionConfig);

        final int indentSpaces = 4;
        try {
            FileUtils.write(dest, root.toString(indentSpaces), JsonAutomatonDefinition.EXPECTED_CHARSET);
        } catch (final JSONException e) {
            throw new RuntimeException("JSON format exception while writing file", e);
        } catch (final IOException e) {
            throw new RuntimeException("Error while saving file: " + e.getMessage());
        }
    }

    private void assignArbitraryIdsToNodesAndEdges() {
        final AtomicInteger idCounter = new AtomicInteger();
        this.graph.getAllNodesByClass(AutomatonNode.class)
                .forEach(node -> node.setId(String.valueOf(idCounter.incrementAndGet())));
        this.graph.getAllNodesByClass(AutomatonEdge.class)
                .forEach(edge -> edge.setId(String.valueOf(idCounter.incrementAndGet())));
    }

    void dispose() {
        this.graph.removeAllObjects();
    }
}