diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d6e07de9..5e0594b41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). * Added `KWay{State,Transition}CoverTestsIterator`s to `automata-util` as a new means for conformance testing. * Added `CollectionUtil#allCombintationsIterator` and `CollectionUtil#allPermutationsIterator` for computing k-combinations and k-permutations. * Added `NFAs#canonize` to canonize NFAs via [Brzozowski's algorithm](https://en.wikipedia.org/wiki/DFA_minimization#Brzozowski's_algorithm). +* Added a DOT parser for `ContextFreeModalProcessSystem`s. +* For nondeterministic model types, `DOTParsers` now contains methods that accept an initial node prefix, in case the number of initial nodes is not known beforehand. ### Changed diff --git a/api/src/main/java/net/automatalib/graph/CFMPSGraphView.java b/api/src/main/java/net/automatalib/graph/CFMPSGraphView.java index 91cfb6c86..f5829b426 100644 --- a/api/src/main/java/net/automatalib/graph/CFMPSGraphView.java +++ b/api/src/main/java/net/automatalib/graph/CFMPSGraphView.java @@ -41,11 +41,13 @@ */ public class CFMPSGraphView implements Graph, Pair> { + private final L mainProcedure; private final Map> pmpgs; // cast is fine, because we make sure to only query nodes/edges belonging to the respective procedures @SuppressWarnings("unchecked") - public CFMPSGraphView(Map> pmpgs) { + public CFMPSGraphView(L mainProcedure, Map> pmpgs) { + this.mainProcedure = mainProcedure; this.pmpgs = (Map>) pmpgs; } @@ -91,6 +93,6 @@ public Collection> getNodes() { @Override public VisualizationHelper, Pair> getVisualizationHelper() { - return new CFMPSVisualizationHelper<>(this.pmpgs); + return new CFMPSVisualizationHelper<>(this.mainProcedure, this.pmpgs); } } diff --git a/api/src/main/java/net/automatalib/graph/ContextFreeModalProcessSystem.java b/api/src/main/java/net/automatalib/graph/ContextFreeModalProcessSystem.java index 8b58a1591..fbab40437 100644 --- a/api/src/main/java/net/automatalib/graph/ContextFreeModalProcessSystem.java +++ b/api/src/main/java/net/automatalib/graph/ContextFreeModalProcessSystem.java @@ -50,6 +50,6 @@ default int size() { @Override default Graph graphView() { - return new CFMPSGraphView<>(getPMPGs()); + return new CFMPSGraphView<>(getMainProcess(), getPMPGs()); } } diff --git a/api/src/main/java/net/automatalib/graph/visualization/CFMPSVisualizationHelper.java b/api/src/main/java/net/automatalib/graph/visualization/CFMPSVisualizationHelper.java index d7a7f0f47..5b11e4af6 100644 --- a/api/src/main/java/net/automatalib/graph/visualization/CFMPSVisualizationHelper.java +++ b/api/src/main/java/net/automatalib/graph/visualization/CFMPSVisualizationHelper.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import net.automatalib.common.util.HashUtil; import net.automatalib.common.util.Pair; @@ -30,25 +31,26 @@ public class CFMPSVisualizationHelper extends DefaultVisualizationHelper, Pair> { - private final Map> visualizers; + private final Map> visualizers; private final List> initialNodes; // cast is fine, because we make sure to only query nodes/edges belonging to the respective procedures @SuppressWarnings("unchecked") - public CFMPSVisualizationHelper(Map> pmpgs) { - + public CFMPSVisualizationHelper(L mainProcedure, + Map> pmpgs) { this.visualizers = new HashMap<>(HashUtil.capacity(pmpgs.size())); this.initialNodes = new ArrayList<>(pmpgs.size()); for (Entry> e : pmpgs.entrySet()) { final ProceduralModalProcessGraph value = (ProceduralModalProcessGraph) e.getValue(); + final L key = e.getKey(); final N initialNode = value.getInitialNode(); - this.visualizers.put(e.getKey(), new PMPGVisualizationHelper<>(value)); + this.visualizers.put(key, new PMPGVisualizationHelper<>(key, Objects.equals(key, mainProcedure), value)); if (initialNode != null) { - this.initialNodes.add(Pair.of(e.getKey(), initialNode)); + this.initialNodes.add(Pair.of(key, initialNode)); } } } @@ -63,7 +65,7 @@ public boolean getNodeProperties(Pair node, Map properties final L process = node.getFirst(); @SuppressWarnings("assignment") // we only use identifier for which procedures exist - final @NonNull PMPGVisualizationHelper visualizer = this.visualizers.get(process); + final @NonNull PMPGVisualizationHelper visualizer = this.visualizers.get(process); return visualizer.getNodeProperties(node.getSecond(), properties); } @@ -73,7 +75,7 @@ public boolean getEdgeProperties(Pair src, Pair edge, Pair tgt final L process = edge.getFirst(); @SuppressWarnings("assignment") // we only use identifier for which procedures exist - final @NonNull PMPGVisualizationHelper visualizer = this.visualizers.get(process); + final @NonNull PMPGVisualizationHelper visualizer = this.visualizers.get(process); return visualizer.getEdgeProperties(src.getSecond(), edge.getSecond(), tgt.getSecond(), properties); } diff --git a/api/src/main/java/net/automatalib/graph/visualization/PMPGVisualizationHelper.java b/api/src/main/java/net/automatalib/graph/visualization/PMPGVisualizationHelper.java index 06db67422..000ccb4b7 100644 --- a/api/src/main/java/net/automatalib/graph/visualization/PMPGVisualizationHelper.java +++ b/api/src/main/java/net/automatalib/graph/visualization/PMPGVisualizationHelper.java @@ -25,12 +25,21 @@ import net.automatalib.graph.ProceduralModalProcessGraph; import net.automatalib.ts.modal.transition.ProceduralModalEdgeProperty; import net.automatalib.visualization.DefaultVisualizationHelper; +import org.checkerframework.checker.nullness.qual.Nullable; -public class PMPGVisualizationHelper extends DefaultVisualizationHelper { +public class PMPGVisualizationHelper extends DefaultVisualizationHelper { + private final @Nullable L label; + private final boolean isMain; private final ProceduralModalProcessGraph pmpg; public PMPGVisualizationHelper(ProceduralModalProcessGraph pmpg) { + this(null, false, pmpg); + } + + public PMPGVisualizationHelper(@Nullable L label, boolean isMain, ProceduralModalProcessGraph pmpg) { + this.label = label; + this.isMain = isMain; this.pmpg = pmpg; } @@ -52,17 +61,26 @@ public boolean getNodeProperties(N node, Map properties) { final Set aps = pmpg.getNodeProperty(node); if (aps.isEmpty()) { - properties.put(NodeAttrs.LABEL, ""); + properties.put(PMPGNodeAttrs.LABEL, ""); } else { - properties.put(NodeAttrs.LABEL, aps.toString()); + properties.put(PMPGNodeAttrs.LABEL, aps.toString()); + } + + if (label != null) { + properties.put(PMPGNodeAttrs.PROCESS, label.toString()); + if (isMain) { + properties.put(PMPGNodeAttrs.MAIN, "true"); + } } if (Objects.equals(pmpg.getInitialNode(), node)) { - properties.put(NodeAttrs.SHAPE, NodeShapes.OCTAGON); + properties.put(PMPGNodeAttrs.SHAPE, NodeShapes.OCTAGON); + properties.put(PMPGNodeAttrs.INITIAL, "true"); } else if (Objects.equals(pmpg.getFinalNode(), node)) { - properties.put(NodeAttrs.SHAPE, NodeShapes.BOX); + properties.put(PMPGNodeAttrs.SHAPE, NodeShapes.BOX); + properties.put(PMPGNodeAttrs.FINAL, "true"); } else { - properties.put(NodeAttrs.SHAPE, NodeShapes.CIRCLE); + properties.put(PMPGNodeAttrs.SHAPE, NodeShapes.CIRCLE); } return true; @@ -83,8 +101,10 @@ public boolean getEdgeProperties(N src, E edge, N tgt, Map prope styleJoiner.add(EdgeStyles.BOLD); } - properties.put(EdgeAttrs.LABEL, String.valueOf(pmpg.getEdgeLabel(edge))); - properties.put(EdgeAttrs.STYLE, styleJoiner.toString()); + properties.put(PMPGEdgeAttrs.LABEL, String.valueOf(pmpg.getEdgeLabel(edge))); + properties.put(PMPGEdgeAttrs.MODALITY, prop.getModalType().toString()); + properties.put(PMPGEdgeAttrs.PROCEDURALITY, prop.getProceduralType().toString()); + properties.put(PMPGEdgeAttrs.STYLE, styleJoiner.toString()); return true; } diff --git a/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java b/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java index 238f6c474..8f061006e 100644 --- a/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java +++ b/api/src/main/java/net/automatalib/visualization/VisualizationHelper.java @@ -84,7 +84,7 @@ private CommonAttrs() { } } - sealed class NodeAttrs extends CommonAttrs permits MMLTNodeAttrs { + sealed class NodeAttrs extends CommonAttrs permits MMLTNodeAttrs, PMPGNodeAttrs { public static final String SHAPE = "shape"; public static final String WIDTH = "width"; @@ -107,6 +107,17 @@ private MMLTNodeAttrs() { } } + final class PMPGNodeAttrs extends NodeAttrs { + + public static final String PROCESS = "process"; + public static final String MAIN = "main"; + public static final String FINAL = "final"; + + private PMPGNodeAttrs() { + // prevent instantiation + } + } + sealed class EdgeAttrs extends CommonAttrs permits MTSEdgeAttrs, MMLTEdgeAttrs { public static final String PENWIDTH = "penwidth"; @@ -117,7 +128,7 @@ private EdgeAttrs() { } } - final class MTSEdgeAttrs extends EdgeAttrs { + sealed class MTSEdgeAttrs extends EdgeAttrs permits PMPGEdgeAttrs { public static final String MODALITY = "modality"; @@ -135,6 +146,15 @@ private MMLTEdgeAttrs() { } } + final class PMPGEdgeAttrs extends MTSEdgeAttrs { + + public static final String PROCEDURALITY = "procedurality"; + + private PMPGEdgeAttrs() { + // prevent instantiation + } + } + final class NodeShapes { public static final String NONE = "none"; diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTCFMPSParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTCFMPSParser.java new file mode 100644 index 000000000..bd02f6751 --- /dev/null +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTCFMPSParser.java @@ -0,0 +1,216 @@ +/* Copyright (C) 2013-2026 TU Dortmund University + * This file is part of AutomataLib . + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.automatalib.serialization.dot; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; + +import net.automatalib.common.util.HashUtil; +import net.automatalib.common.util.IOUtil; +import net.automatalib.exception.FormatException; +import net.automatalib.graph.ContextFreeModalProcessSystem; +import net.automatalib.graph.MutableProceduralModalProcessGraph; +import net.automatalib.graph.ProceduralModalProcessGraph; +import net.automatalib.graph.concept.FinalNode; +import net.automatalib.graph.impl.DefaultCFMPS; +import net.automatalib.serialization.ModelDeserializer; +import net.automatalib.ts.modal.transition.ModalEdgeProperty.ModalType; +import net.automatalib.ts.modal.transition.MutableProceduralModalEdgeProperty; +import net.automatalib.ts.modal.transition.ProceduralModalEdgeProperty.ProceduralType; +import net.automatalib.visualization.VisualizationHelper.PMPGEdgeAttrs; +import net.automatalib.visualization.VisualizationHelper.PMPGNodeAttrs; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * Parses a DOT file that defines a {@link ContextFreeModalProcessSystem}. + *

+ * Besides the typical structure of DOT files, this parser expects/supports the following attributes in order to + * correctly parse the semantics of {@link ContextFreeModalProcessSystem}s: + *

    + *
  • Nodes must provide a {@value PMPGNodeAttrs#PROCESS} attribute that denotes the process a node belongs to
  • + *
  • Nodes may provide a {@value PMPGNodeAttrs#MAIN} attribute that denotes whether their respective process is the {@link ContextFreeModalProcessSystem#getMainProcess() main process}. Exactly one process must mark their nodes as main process nodes.
  • + *
  • Nodes may provide a {@value PMPGNodeAttrs#FINAL} attribute to denote their {@link FinalNode final} state. Exactly one node per (sub-) process must be marked as a final node.
  • + *
  • Edges may provide {@value PMPGEdgeAttrs#MODALITY} and {@value PMPGEdgeAttrs#PROCEDURALITY} attributes to denote their {@link ModalType} and {@link ProceduralType}. If missing, the respective parser may provide default values instead.
  • + *
+ * + * @param + * the node type + * @param + * the label type + * @param + * the edge type + * @param + * the atomic proposition type + * @param + * the transition property type + * @param

+ * the type of internal {@link ProceduralModalProcessGraph}s + */ +public class DOTCFMPSParser> + implements ModelDeserializer> { + + private final Function creator; + private final Function, Set> apParser; + private final Function, @Nullable L> labelParser; + private final Function, L> processParser; + private final Function, TP> tpParser; + private final String initialNodePrefix; + private final boolean fakeInitialNodeIds; + + public DOTCFMPSParser(Function creator, + Function, Set> apParser, + Function, @Nullable L> labelParser, + Function, L> processParser, + Function, TP> tpParser, + String initialNodePrefix, + boolean fakeInitialNodeIds) { + this.creator = creator; + this.apParser = apParser; + this.labelParser = labelParser; + this.processParser = processParser; + this.tpParser = tpParser; + this.initialNodePrefix = initialNodePrefix; + this.fakeInitialNodeIds = fakeInitialNodeIds; + } + + @Override + public ContextFreeModalProcessSystem readModel(InputStream is) throws IOException, FormatException { + + try (Reader r = IOUtil.asNonClosingUTF8Reader(is)) { + InternalDOTParser parser = new InternalDOTParser(r); + parser.parse(); + + final Map pmpgs = new HashMap<>(); + final L mainLabel = parseNodesAndEdges(parser, pmpgs); + + return new DefaultCFMPS<>(mainLabel, pmpgs); + } + } + + private L parseNodesAndEdges(InternalDOTParser parser, Map out) throws FormatException { + final Collection nodes = parser.getNodes(); + final Collection edges = parser.getEdges(); + + final Map stateMap = new HashMap<>(HashUtil.capacity(nodes.size())); + final Map labelMap = new HashMap<>(HashUtil.capacity(nodes.size())); + L mainLabel = null; + + for (Node node : nodes) { + if (!fakeInitialNodeIds || !node.id.startsWith(initialNodePrefix)) { + L label = processParser.apply(node.attributes); + P pmpg = out.computeIfAbsent(label, creator); + N n = pmpg.addNode(apParser.apply(node.attributes)); + + if (!fakeInitialNodeIds && node.id.startsWith(initialNodePrefix)) { + pmpg.setInitialNode(n); + } + if (node.attributes.containsKey(PMPGNodeAttrs.FINAL)) { + pmpg.setFinalNode(n); + } + if (node.attributes.containsKey(PMPGNodeAttrs.MAIN)) { + if (mainLabel == null) { + mainLabel = label; + } else if (!Objects.equals(mainLabel, label)) { + throw new FormatException("multiple main labels are not allowed"); + } + } + + stateMap.put(node.id, n); + labelMap.put(node.id, label); + } + } + + for (Edge edge : edges) { + final L srcLabel = labelMap.get(edge.src); + final L tgtLabel = labelMap.get(edge.tgt); + + if (fakeInitialNodeIds && edge.src.startsWith(initialNodePrefix)) { + @SuppressWarnings("nullness") // we iterated over all nodes + final @NonNull P pmpg = out.get(tgtLabel); + @SuppressWarnings("nullness") // we iterated over all nodes + final @NonNull N node = stateMap.get(edge.tgt); + pmpg.setInitialNode(node); + } else { + if (!Objects.equals(srcLabel, tgtLabel)) { + throw new FormatException("edges connect nodes across different processes"); + } + + @SuppressWarnings("nullness") // we iterated over all nodes + final @NonNull P pmpg = out.get(srcLabel); + @SuppressWarnings("nullness") // we iterated over all nodes + final @NonNull N src = stateMap.get(edge.src); + @SuppressWarnings("nullness") // we iterated over all nodes + final @NonNull N tgt = stateMap.get(edge.tgt); + final E e = pmpg.connect(src, tgt, tpParser.apply(edge.attributes)); + final L l = labelParser.apply(edge.attributes); + if (l != null) { + pmpg.setEdgeLabel(e, l); + } + } + } + + if (mainLabel == null) { + throw new FormatException("main label missing"); + } + + return mainLabel; + + } + + /** + * Reads the {@value PMPGNodeAttrs#LABEL} attribute from the given map, splits the string at {@code ,} and applies + * the given parser to each element individually. + * + * @param attr + * the attribute map (of a node) + * @param parser + * the parser of individual elements + * @param + * label type + * + * @return the union of all parsed labels + */ + public static Set parseLabelAsProperties(Map attr, Function parser) { + String aps = attr.get(PMPGNodeAttrs.LABEL); + + if (aps == null || aps.isEmpty()) { + return Collections.emptySet(); + } + + if (aps.startsWith("[") && aps.endsWith("]")) { // toString from collections + aps = aps.substring(1, aps.length() - 1); + } + + final String[] tokens = aps.split(","); + final Set result = new HashSet<>(HashUtil.capacity(tokens.length)); + + for (String t : tokens) { + result.add(parser.apply(t.trim())); + } + + return result; + } +} diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java index ca5d2d3e9..43c853b9a 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMMLTParser.java @@ -115,18 +115,18 @@ public class DOTMMLTParser> implement private final AutomatonCreator creator; private final Function inputParser; private final Function> outputParser; - private final Collection initialNodeIds; + private final String initialNodeId; private final boolean fakeInitialNodeIds; public DOTMMLTParser(AutomatonCreator creator, Function inputParser, Function> outputParser, - Collection initialNodeIds, + String initialNodeId, boolean fakeInitialNodeIds) { this.creator = creator; this.inputParser = inputParser; this.outputParser = outputParser; - this.initialNodeIds = initialNodeIds; + this.initialNodeId = initialNodeId; this.fakeInitialNodeIds = fakeInitialNodeIds; } @@ -142,7 +142,7 @@ public DOTInputModelData readModel(InputStream is) throws IOException, final Set inputs = new HashSet<>(); for (Edge edge : parser.getEdges()) { - if (!fakeInitialNodeIds || !initialNodeIds.contains(edge.src)) { + if (!fakeInitialNodeIds || !initialNodeId.equals(edge.src)) { final String input = tokenizeLabel(edge)[0].trim(); if (!input.startsWith("to[")) { inputs.add(inputParser.apply(input)); @@ -173,9 +173,9 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableM for (Node node : nodes) { final S n; - if (fakeInitialNodeIds && initialNodeIds.contains(node.id)) { + if (fakeInitialNodeIds && initialNodeId.equals(node.id)) { continue; - } else if (!fakeInitialNodeIds && initialNodeIds.contains(node.id)) { + } else if (!fakeInitialNodeIds && initialNodeId.equals(node.id)) { n = result.addInitialState(); } else { n = result.addState(); @@ -227,7 +227,7 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, MutableM // Parse edges: for (Edge edge : edges) { - if (fakeInitialNodeIds && initialNodeIds.contains(edge.src)) { + if (fakeInitialNodeIds && initialNodeId.contains(edge.src)) { result.setInitial(stateMap.get(edge.tgt), true); continue; } diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMutableAutomatonParser.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMutableAutomatonParser.java index d99b4e7b4..23efce0bb 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMutableAutomatonParser.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTMutableAutomatonParser.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; +import java.util.function.Predicate; import net.automatalib.alphabet.Alphabet; import net.automatalib.alphabet.impl.Alphabets; @@ -58,7 +59,7 @@ public class DOTMutableAutomatonParser creator; private final Function, SP> nodeParser; private final Function, Pair> edgeParser; - private final Collection initialNodeIds; + private final Predicate initialPredicate; private final boolean fakeInitialNodeIds; /** @@ -85,10 +86,45 @@ public DOTMutableAutomatonParser(AutomatonCreator creator, Function, Pair> edgeParser, Collection initialNodeIds, boolean fakeInitialNodeIds) { + this(creator, nodeParser, edgeParser, initialNodeIds::contains, fakeInitialNodeIds); + } + + /** + * Parser for arbitrary {@link MutableAutomaton}s with a custom automaton instance, custom node and edge attributes + * and custom labels for the initial nodes. + * + * @param creator + * a creator that is used to instantiate the returned automaton + * @param nodeParser + * a node parser that extracts from a property map of a node the state property + * @param edgeParser + * an edge parser that extracts from a property map of an edge the input symbol and transition property + * @param initialNodeIdPrefix + * the prefix to match the ids of the initial nodes + * @param fakeInitialNodeIds + * a flag indicating whether the {@code initialNodeIds} are artificial or not. If {@code true}, the nodes + * matching the {@code initialNodeIds} will not be added to the automaton. Instead, their direct successors + * will be initial states instead. This may be useful for instances where there are artificial nodes used to + * display in incoming arrow for the actual initial states. If {@code false}, the nodes matching the + * {@code initialNodeIds} will be used as initial nodes. + */ + public DOTMutableAutomatonParser(AutomatonCreator creator, + Function, SP> nodeParser, + Function, Pair> edgeParser, + String initialNodeIdPrefix, + boolean fakeInitialNodeIds) { + this(creator, nodeParser, edgeParser, p -> p.startsWith(initialNodeIdPrefix), fakeInitialNodeIds); + } + + private DOTMutableAutomatonParser(AutomatonCreator creator, + Function, SP> nodeParser, + Function, Pair> edgeParser, + Predicate initialPredicate, + boolean fakeInitialNodeIds) { this.creator = creator; this.nodeParser = nodeParser; this.edgeParser = edgeParser; - this.initialNodeIds = initialNodeIds; + this.initialPredicate = initialPredicate; this.fakeInitialNodeIds = fakeInitialNodeIds; } @@ -104,7 +140,7 @@ public DOTInputModelData readModel(InputStream is) throws IOException, final Set inputs = new HashSet<>(); for (Edge edge : parser.getEdges()) { - if (!fakeInitialNodeIds || !initialNodeIds.contains(edge.src)) { + if (!fakeInitialNodeIds || !initialPredicate.test(edge.src)) { inputs.add(edgeParser.apply(edge.attributes).getFirst()); } } @@ -127,9 +163,9 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, for (Node node : nodes) { final S state; - if (fakeInitialNodeIds && initialNodeIds.contains(node.id)) { + if (fakeInitialNodeIds && initialPredicate.test(node.id)) { continue; - } else if (!fakeInitialNodeIds && initialNodeIds.contains(node.id)) { + } else if (!fakeInitialNodeIds && initialPredicate.test(node.id)) { state = automaton.addInitialState(nodeParser.apply(node.attributes)); } else { state = automaton.addState(nodeParser.apply(node.attributes)); @@ -142,7 +178,7 @@ private Mapping parseNodesAndEdges(InternalDOTParser parser, } for (Edge edge : parser.getEdges()) { - if (fakeInitialNodeIds && initialNodeIds.contains(edge.src)) { + if (fakeInitialNodeIds && initialPredicate.test(edge.src)) { automaton.setInitial(stateMap.get(edge.tgt), true); } else { final Pair property = edgeParser.apply(edge.attributes); diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java index 972f7e478..f84e75284 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/DOTParsers.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.function.Function; import java.util.function.Supplier; @@ -42,8 +43,12 @@ import net.automatalib.automaton.transducer.impl.CompactMealy; import net.automatalib.automaton.transducer.impl.CompactMoore; import net.automatalib.common.util.Pair; +import net.automatalib.graph.ContextFreeModalProcessSystem; import net.automatalib.graph.Graph; import net.automatalib.graph.MutableGraph; +import net.automatalib.graph.MutableProceduralModalProcessGraph; +import net.automatalib.graph.ProceduralModalProcessGraph; +import net.automatalib.graph.impl.CompactPMPG; import net.automatalib.graph.impl.CompactUniversalGraph; import net.automatalib.serialization.InputModelDeserializer; import net.automatalib.serialization.ModelDeserializer; @@ -52,11 +57,16 @@ import net.automatalib.ts.modal.impl.CompactMTS; import net.automatalib.ts.modal.transition.ModalEdgeProperty.ModalType; import net.automatalib.ts.modal.transition.MutableModalEdgeProperty; +import net.automatalib.ts.modal.transition.MutableProceduralModalEdgeProperty; +import net.automatalib.ts.modal.transition.ProceduralModalEdgeProperty.ProceduralType; import net.automatalib.ts.modal.transition.impl.ModalEdgePropertyImpl; +import net.automatalib.ts.modal.transition.impl.ProceduralModalEdgePropertyImpl; import net.automatalib.visualization.VisualizationHelper.EdgeAttrs; import net.automatalib.visualization.VisualizationHelper.MTSEdgeAttrs; import net.automatalib.visualization.VisualizationHelper.NodeAttrs; import net.automatalib.visualization.VisualizationHelper.NodeShapes; +import net.automatalib.visualization.VisualizationHelper.PMPGEdgeAttrs; +import net.automatalib.visualization.VisualizationHelper.PMPGNodeAttrs; import org.checkerframework.checker.nullness.qual.Nullable; /** @@ -65,21 +75,21 @@ public final class DOTParsers { /** - * Node property parser that parses a node's "{@link NodeAttrs#LABEL label}" attribute and returns its {@link - * Object#toString() string} representation. Returns {@code null} if the attribute is not specified. + * Node property parser that parses a node's {@value NodeAttrs#LABEL} attribute and returns its + * {@link Object#toString() string} representation. Returns {@code null} if the attribute is not specified. */ public static final Function, @Nullable String> DEFAULT_NODE_PARSER = attr -> attr.get(NodeAttrs.LABEL); /** - * Node property parser that returns {@code true} if a node's "{@link NodeAttrs#SHAPE shape}" attribute is specified - * and equals "{@link NodeShapes#DOUBLECIRCLE doublecircle}". Returns {@code false} otherwise. + * Node property parser that returns {@code true} if a node's {@value NodeAttrs#SHAPE} attribute is specified and + * equals {@value NodeShapes#DOUBLECIRCLE}. Returns {@code false} otherwise. */ public static final Function, Boolean> DEFAULT_FSA_NODE_PARSER = attr -> NodeShapes.DOUBLECIRCLE.equals(attr.get(NodeAttrs.SHAPE)); /** - * Node property parser that expects a node's "{@link NodeAttrs#LABEL label}" attribute to be of the form {@code + * Node property parser that expects a node's {@value NodeAttrs#LABEL} attribute to be of the form {@code * /}. Returns the string representation of {@code } as-is. Returns {@code null} if the * attribute does not exist or does not match the expected format. */ @@ -99,8 +109,38 @@ public final class DOTParsers { }; /** - * Edge input parser that parses an edge's "{@link EdgeAttrs#LABEL label}" attribute and returns its {@link - * Object#toString() string} representation. Returns {@code null} if the attribute is not specified. + * Parser that extracts the process for a {@link ContextFreeModalProcessSystem} node by reading the value of the + * {@value PMPGNodeAttrs#PROCESS} attribute. + */ + public static final Function, String> DEFAULT_CFMPS_PROCESS_PARSER = + attr -> getAndRequireNotNull(attr, PMPGNodeAttrs.PROCESS); + + /** + * Parser that extracts the atomic properties for a {@link ContextFreeModalProcessSystem} by interpreting the value + * of the {@value PMPGNodeAttrs#LABEL} attribute as a list of comma-separated values. + * + * @see DOTCFMPSParser#parseLabelAsProperties(Map, Function) + */ + public static final Function, Set> DEFAULT_CFMPS_NODE_PROPERTY_PARSER = + attr -> DOTCFMPSParser.parseLabelAsProperties(attr, Function.identity()); + + /** + * Parser that extracts the transition properties of a {@link ContextFreeModalProcessSystem} by reading the values + * of the {@value PMPGEdgeAttrs#MODALITY} and {@value PMPGEdgeAttrs#PROCEDURALITY} attributes. If an attribute is + * not found, assumes an internal / must transition, respectively. + */ + public static final Function, MutableProceduralModalEdgeProperty> + DEFAULT_CFMPS_TRANSITION_PROPERTY_PARSER = attr -> { + final String proc = attr.getOrDefault(PMPGEdgeAttrs.PROCEDURALITY, ProceduralType.INTERNAL.name()); + final String modal = attr.getOrDefault(PMPGEdgeAttrs.MODALITY, ModalType.MUST.name()); + + return new ProceduralModalEdgePropertyImpl(ProceduralType.valueOf(proc.toUpperCase(Locale.ROOT)), + ModalType.valueOf(modal.toUpperCase(Locale.ROOT))); + }; + + /** + * Edge input parser that parses an edge's "{@link EdgeAttrs#LABEL label}" attribute and returns its + * {@link Object#toString() string} representation. Returns {@code null} if the attribute is not specified. */ public static final Function, @Nullable String> DEFAULT_EDGE_PARSER = attr -> attr.get(EdgeAttrs.LABEL); @@ -142,8 +182,8 @@ private DOTParsers() {} /** * Default parser for {@link DFA}s serialized by AutomataLib. *

- * Invokes {@link #dfa(Function, Function)} with {@link #DEFAULT_FSA_NODE_PARSER} as {@code nodeParser} and {@link - * #DEFAULT_EDGE_PARSER} as {@code edgeParser}. + * Invokes {@link #dfa(Function, Function)} with {@link #DEFAULT_FSA_NODE_PARSER} as {@code nodeParser} and + * {@link #DEFAULT_EDGE_PARSER} as {@code edgeParser}. * * @return a {@link DOTInputModelDeserializer} for {@link CompactDFA}s. */ @@ -173,8 +213,8 @@ public static DOTInputModelDeserializer> dfa(Funct /** * Default parser for {@link NFA}s serialized by AutomataLib. *

- * Invokes {@link #nfa(Function, Function)} with {@link #DEFAULT_FSA_NODE_PARSER} as {@code nodeParser} and {@link - * #DEFAULT_EDGE_PARSER} as {@code edgeParser}. + * Invokes {@link #nfa(Function, Function)} with {@link #DEFAULT_FSA_NODE_PARSER} as {@code nodeParser} and + * {@link #DEFAULT_EDGE_PARSER} as {@code edgeParser}. * * @return a {@link DOTInputModelDeserializer} for {@link CompactNFA}s. */ @@ -232,8 +272,8 @@ public static > DOTInputModelDeserializer - * Invokes {@link #fsa(AutomatonCreator, Function, Function, Collection, boolean)} with {@code true} as {@code - * fakeInitialNodeLabels}. + * Invokes {@link #fsa(AutomatonCreator, Function, Function, Collection, boolean)} with {@code true} as + * {@code fakeInitialNodeLabels}. * * @param creator * a creator that is used to instantiate the returned automaton @@ -259,6 +299,37 @@ public static > DOTInputModelDeserializer + * Invokes {@link #fsa(AutomatonCreator, Function, Function, String, boolean)} with {@code true} as + * {@code fakeInitialNodeLabels}. + * + * @param creator + * a creator that is used to instantiate the returned automaton + * @param nodeParser + * a node parser that decides for a property map of a node whether it is accepting or not + * @param edgeParser + * an edge parser that extracts from a property map of an edge the input symbol + * @param initialNodeIdPrefix + * the prefix to match the ids of the initial nodes + * @param + * the state type of the returned automaton + * @param + * the input symbol type + * @param + * the type of the returned automaton + * + * @return a {@link DOTInputModelDeserializer} for {@code A}s. + */ + public static > DOTInputModelDeserializer fsa(AutomatonCreator creator, + Function, Boolean> nodeParser, + Function, I> edgeParser, + String initialNodeIdPrefix) { + return fsa(creator, nodeParser, edgeParser, initialNodeIdPrefix, true); + } + /** * Parser for {@link FiniteStateAcceptor}s with a custom automaton instance, custom node and edge attributes, custom * labels for initial nodes and a flag whether the initial nodes are artificial. @@ -298,6 +369,45 @@ public static > DOTInputModelDeserializer + * the state type of the returned automaton + * @param + * the input symbol type + * @param + * the type of the returned automaton + * + * @return a {@link DOTInputModelDeserializer} for {@code A}s. + */ + public static > DOTInputModelDeserializer fsa(AutomatonCreator creator, + Function, Boolean> nodeParser, + Function, I> edgeParser, + String initialNodeIdPrefix, + boolean fakeInitialNodeIds) { + return new DOTMutableAutomatonParser<>(creator, + nodeParser, + edge -> Pair.of(edgeParser.apply(edge), null), + initialNodeIdPrefix, + fakeInitialNodeIds); + } + /** * Default parser for {@link MealyMachine}s serialized by AutomataLib. *

@@ -330,8 +440,8 @@ public static DOTInputModelDeserializer> m /** * Parser for {@link MealyMachine}s with a custom automaton instance and custom edge attributes. *

- * Invokes {@link #mealy(AutomatonCreator, Function, String)} with AutomataLib's default initial state label "{@code - * __start0}" as {@code initialNodeLabel}. + * Invokes {@link #mealy(AutomatonCreator, Function, String)} with AutomataLib's default initial state label + * "{@code __start0}" as {@code initialNodeLabel}. * * @param creator * a creator that is used to instantiate the returned automaton @@ -358,8 +468,8 @@ public static DOTInputModelDeserializer> m * Parser for {@link MealyMachine}s with a custom automaton instance, custom edge attributes and a custom label for * the initial node. *

- * Invokes {@link #fsa(AutomatonCreator, Function, Function, Collection, boolean)} with {@code true} as {@code - * fakeInitialNodeLabels}. + * Invokes {@link #fsa(AutomatonCreator, Function, Function, Collection, boolean)} with {@code true} as + * {@code fakeInitialNodeLabels}. * * @param creator * a creator that is used to instantiate the returned automaton @@ -439,8 +549,8 @@ public static DOTInputModelDeserializer> m /** * Parser for {@link MooreMachine}s with custom node and edge attributes. *

- * Invokes {@link #moore(AutomatonCreator, Function, Function)} with {@link CompactMoore.Creator} as {@code - * creator}. + * Invokes {@link #moore(AutomatonCreator, Function, Function)} with {@link CompactMoore.Creator} as + * {@code creator}. * * @param nodeParser * a node parser that extracts from a property map of a node the state property @@ -493,8 +603,8 @@ public static DOTInputModelDeserializer> m * Parser for {@link MooreMachine}s with a custom automaton instance, custom node and edge attributes and a custom * label for the initial node. *

- * Invokes {@link #moore(AutomatonCreator, Function, Function, String, boolean)} with {@code true} as {@code - * fakeInitialNodeLabel}. + * Invokes {@link #moore(AutomatonCreator, Function, Function, String, boolean)} with {@code true} as + * {@code fakeInitialNodeLabel}. * * @param creator * a creator that is used to instantiate the returned automaton @@ -568,8 +678,8 @@ public static DOTInputModelDeserializer> m /** * Default parser for (directed) {@link Graph}s serialized by AutomataLib. *

- * Invokes {@link #graph(Function, Function)} with {@link #DEFAULT_NODE_PARSER} as {@code nodeParser} and {@link - * #DEFAULT_EDGE_PARSER} as {@code edgeParser}. + * Invokes {@link #graph(Function, Function)} with {@link #DEFAULT_NODE_PARSER} as {@code nodeParser} and + * {@link #DEFAULT_EDGE_PARSER} as {@code edgeParser}. * * @return a DOT {@link ModelDeserializer} for {@link CompactUniversalGraph}s. */ @@ -626,8 +736,9 @@ public static ModelDeserializer> graph(Fu /** * Default parser for {@link ModalTransitionSystem}s serialized by AutomataLib. *

- * Invokes {@link #mts(AutomatonCreator, Function, Function)} with {@link CompactMTS#CompactMTS(Alphabet)} as {@code creator}, - * {@link #DEFAULT_EDGE_PARSER} as {@code inputParser} and {@link #DEFAULT_EDGE_PARSER} as {@code propertyParser}. + * Invokes {@link #mts(AutomatonCreator, Function, Function)} with {@link CompactMTS#CompactMTS(Alphabet)} as + * {@code creator}, {@link #DEFAULT_EDGE_PARSER} as {@code inputParser} and {@link #DEFAULT_EDGE_PARSER} as + * {@code propertyParser}. * * @return a {@link DOTInputModelDeserializer} for {@link CompactMTS}s. */ @@ -668,7 +779,7 @@ public static ModelDeserializer> graph(Fu /** * Parser for {@link ModalTransitionSystem}s with a custom MTS instance, custom input type and edge attributes - * parsers and custom initial state labels. + * parsers and custom initial node labels. * * @param creator * a creator that is used to instantiate the returned graph @@ -701,6 +812,41 @@ public static ModelDeserializer> graph(Fu true); } + /** + * Parser for {@link ModalTransitionSystem}s with a custom MTS instance, custom input type and edge attributes + * parsers and custom prefix for initial node labels. + * + * @param creator + * a creator that is used to instantiate the returned graph + * @param inputParser + * an edge parser that extracts from a property map of an edge the input symbol + * @param propertyParser + * an edge parser that extracts from a property map of an edge the modal transition property + * @param initialNodeIdPrefix + * the prefix to match the ids of the initial nodes + * @param + * the state type of the returned MTS + * @param + * the input symbol type + * @param + * the modal transition property + * @param + * the type of the returned MTS + * + * @return a DOT {@link ModelDeserializer} for {@code M}s. + */ + public static > DOTInputModelDeserializer mts( + AutomatonCreator creator, + Function, I> inputParser, + Function, TP> propertyParser, + String initialNodeIdPrefix) { + return new DOTMutableAutomatonParser<>(creator, + node -> null, + edge -> Pair.of(inputParser.apply(edge), propertyParser.apply(edge)), + initialNodeIdPrefix, + true); + } + /** * Parser for {@link MMLT}s with {@link String}-based input and output symbols *

@@ -754,8 +900,8 @@ public static DOTInputModelDeserializer> mm /** * Parser for {@link MMLT}s with a custom MMLT instance and custom input and output types. *

- * Invokes {@link #mmlt(AutomatonCreator, Function, Function, Collection, boolean)} with AutomataLib's default - * initial state label "{@code __start0}" as {@code initialNodeLabels} and uses {@code true} for + * Invokes {@link #mmlt(AutomatonCreator, Function, Function, String, boolean)} with AutomataLib's default initial + * state label "{@code __start0}" as {@code initialNodeLabels} and uses {@code true} for * {@code fakeInitialNodeIds}. * * @param creator @@ -780,7 +926,7 @@ public static DOTInputModelDeserializer> mm public static > DOTInputModelDeserializer mmlt(AutomatonCreator creator, Function inputParser, Function> outputParser) { - return mmlt(creator, inputParser, outputParser, Collections.singletonList(GraphDOT.initialLabel(0)), true); + return mmlt(creator, inputParser, outputParser, GraphDOT.initialLabel(0), true); } /** @@ -793,8 +939,8 @@ public static DOTInputModelDeserializer> mm * a parser for transforming input labels to input symbols * @param outputParser * a parser for transforming output labels to output symbols - * @param initialNodeIds - * the ids of the initial nodes + * @param initialNodeId + * the id of the initial node * @param fakeInitialNodeIds * a flag indicating whether the {@code initialNodeIds} are artificial or not. If {@code true}, the nodes * matching the {@code initialNodeIds} will not be added to the automaton. Instead, their direct successors @@ -817,9 +963,159 @@ public static DOTInputModelDeserializer> mm public static > DOTInputModelDeserializer mmlt(AutomatonCreator creator, Function inputParser, Function> outputParser, - Collection initialNodeIds, + String initialNodeId, boolean fakeInitialNodeIds) { - return new DOTMMLTParser<>(creator, inputParser, outputParser, initialNodeIds, fakeInitialNodeIds); + return new DOTMMLTParser<>(creator, inputParser, outputParser, initialNodeId, fakeInitialNodeIds); + } + + /** + * Parser for {@link ContextFreeModalProcessSystem}s. + *

+ * Invokes {@link #cfmps(Object, Function, Function, Function)} using {@code "?"} as default label, + * {@link #DEFAULT_CFMPS_NODE_PROPERTY_PARSER} as {@code apParser}, {@link #DEFAULT_EDGE_PARSER} as + * {@code edgeParser}, and {@link #DEFAULT_CFMPS_PROCESS_PARSER} as {@code processParser}. + * + * @return a DOT {@link ModelDeserializer} for {@link ContextFreeModalProcessSystem}s. + * + * @see DOTCFMPSParser + */ + public static ModelDeserializer> cfmps() { + return cfmps("?", DEFAULT_CFMPS_NODE_PROPERTY_PARSER, DEFAULT_EDGE_PARSER, DEFAULT_CFMPS_PROCESS_PARSER); + } + + /** + * Parser for {@link ContextFreeModalProcessSystem}s with custom label types and custom atomic propositions. + *

+ * Invokes {@link #cfmps(Function, Function, Function, Function, Function)} using {@link CompactPMPG}s to create + * internal processs and {@link #DEFAULT_CFMPS_TRANSITION_PROPERTY_PARSER} to parse transition properties. + * + * @param defaultLabel + * a creator that is used to instantiate the returned MMLT + * @param apParser + * a parser for transforming node attributes to atomic propositions + * @param labelParser + * a parser for transforming transition attributes to edge symbols + * @param processParser + * a parser for extracting the process that a node belongs to + * @param + * the label type + * @param + * the atomic proposition type + * + * @return a DOT {@link ModelDeserializer} for {@link ContextFreeModalProcessSystem}s. + * + * @see DOTCFMPSParser + */ + public static ModelDeserializer> cfmps(L defaultLabel, + Function, Set> apParser, + Function, @Nullable L> labelParser, + Function, L> processParser) { + return cfmps(l -> new CompactPMPG<>(defaultLabel), + apParser, + labelParser, + processParser, + DEFAULT_CFMPS_TRANSITION_PROPERTY_PARSER); + } + + /** + * Parser for {@link ContextFreeModalProcessSystem}s with custom instances for the internal + * {@link ProceduralModalProcessGraph}s, custom label types, and custom atomic propositions. + *

+ * Invokes {@link #cfmps(Function, Function, Function, Function, Function, String, boolean)} with AutomataLib's + * default initial state label {@value GraphDOT#INITIAL_LABEL} as {@code initialNodeIdPrefix} and uses {@code true} + * for {@code fakeInitialNodeIds}. + * + * @param creator + * a creator that is used to instantiate the returned MMLT + * @param apParser + * a parser for transforming node attributes to atomic propositions + * @param labelParser + * a parser for transforming transition attributes to edge symbols + * @param processParser + * a parser for extracting the process that a node belongs to + * @param tpParser + * a parser for transforming node attributes to transition properties + * @param + * the node type + * @param + * the label type + * @param + * the edge type + * @param + * the atomic proposition type + * @param + * the transition property type + * @param

+ * the type of internal {@link ProceduralModalProcessGraph}s + * + * @return a DOT {@link ModelDeserializer} for {@link ContextFreeModalProcessSystem}s. + * + * @see DOTCFMPSParser + */ + public static > ModelDeserializer> cfmps( + Function creator, + Function, Set> apParser, + Function, @Nullable L> labelParser, + Function, L> processParser, + Function, TP> tpParser) { + return cfmps(creator, apParser, labelParser, processParser, tpParser, GraphDOT.INITIAL_LABEL, true); + } + + /** + * Parser for {@link ContextFreeModalProcessSystem}s with custom instances for the internal + * {@link ProceduralModalProcessGraph}s, custom label types, custom atomic propositions, and custom initial state + * labels. + * + * @param creator + * a creator that is used to instantiate the returned MMLT + * @param apParser + * a parser for transforming node attributes to atomic propositions + * @param labelParser + * a parser for transforming transition attributes to edge symbols + * @param processParser + * a parser for extracting the process that a node belongs to + * @param tpParser + * a parser for transforming node attributes to transition properties + * @param initialNodeIdPrefix + * the prefix to match the ids of the initial nodes + * @param fakeInitialNodeIds + * a flag indicating whether the {@code initialNodeIds} are artificial or not. If {@code true}, the nodes + * matching the {@code initialNodeIdPrefix} will not be added to the automaton. Instead, their direct + * successors will be initial states instead. This may be useful for instances where there are artificial + * nodes used to display in incoming arrow for the actual initial states. If {@code false}, the nodes + * matching the {@code initialNodeIds} will be used as initial nodes. + * @param + * the node type + * @param + * the label type + * @param + * the edge type + * @param + * the atomic proposition type + * @param + * the transition property type + * @param

+ * the type of internal {@link ProceduralModalProcessGraph}s + * + * @return a DOT {@link ModelDeserializer} for {@link ContextFreeModalProcessSystem}s. + * + * @see DOTCFMPSParser + */ + public static > ModelDeserializer> cfmps( + Function creator, + Function, Set> apParser, + Function, @Nullable L> labelParser, + Function, L> processParser, + Function, TP> tpParser, + String initialNodeIdPrefix, + boolean fakeInitialNodeIds) { + return new DOTCFMPSParser<>(creator, + apParser, + labelParser, + processParser, + tpParser, + initialNodeIdPrefix, + fakeInitialNodeIds); } private static String getAndRequireNotNull(Map map, String attribute) { diff --git a/serialization/dot/src/main/java/net/automatalib/serialization/dot/GraphDOT.java b/serialization/dot/src/main/java/net/automatalib/serialization/dot/GraphDOT.java index 389743af5..a7599cef1 100644 --- a/serialization/dot/src/main/java/net/automatalib/serialization/dot/GraphDOT.java +++ b/serialization/dot/src/main/java/net/automatalib/serialization/dot/GraphDOT.java @@ -43,7 +43,11 @@ */ public final class GraphDOT { - private static final String INITIAL_LABEL = "__start"; + /** + * The label to prefix artificial initial nodes with. + */ + public static final String INITIAL_LABEL = "__start"; + private static final String HTML_START_TAG = ""; private static final String HTML_END_TAG = ""; diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java index eebff3335..bbe355d89 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTDeserializationTest.java @@ -22,10 +22,12 @@ import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.Queue; import java.util.Set; @@ -46,9 +48,20 @@ import net.automatalib.automaton.transducer.impl.CompactMoore; import net.automatalib.common.util.io.UnclosableInputStream; import net.automatalib.exception.FormatException; +import net.automatalib.graph.ProceduralModalProcessGraph; import net.automatalib.graph.UniversalGraph; +import net.automatalib.graph.concept.NodeIDs; +import net.automatalib.graph.impl.CompactPMPG; +import net.automatalib.graph.impl.DefaultCFMPS; import net.automatalib.serialization.InputModelData; import net.automatalib.ts.modal.impl.CompactMTS; +import net.automatalib.ts.modal.transition.ModalEdgeProperty.ModalType; +import net.automatalib.ts.modal.transition.ProceduralModalEdgeProperty; +import net.automatalib.ts.modal.transition.ProceduralModalEdgeProperty.ProceduralType; +import net.automatalib.ts.modal.transition.impl.ProceduralModalEdgePropertyImpl; +import net.automatalib.visualization.VisualizationHelper.NodeStyles; +import net.automatalib.visualization.VisualizationHelper.PMPGEdgeAttrs; +import net.automatalib.visualization.VisualizationHelper.PMPGNodeAttrs; import net.automatalib.word.Word; import org.testng.Assert; import org.testng.annotations.Test; @@ -86,18 +99,18 @@ public void testRegularNFA2Deserialization() throws IOException, FormatException Arrays.asList("s0", "s1", "s2"), false) .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.NFA2_RESOURCE)).model; + assertNFAProperties(parsed); + } - Assert.assertEquals(parsed.size(), 3); - Assert.assertEquals(parsed.getInitialStates().size(), 3); - Assert.assertFalse(parsed.accepts(Word.fromSymbols("a", "a", "a"))); - Assert.assertFalse(parsed.accepts(Word.fromSymbols("b", "b"))); - Assert.assertFalse(parsed.accepts(Word.fromSymbols("c"))); - Assert.assertEquals(parsed.getStates(Word.fromSymbols("a", "a", "a")).size(), 1); - Assert.assertEquals(parsed.getStates(Word.fromSymbols("b", "b")).size(), 1); - Assert.assertEquals(parsed.getStates(Word.fromLetter("c")).size(), 1); - Assert.assertTrue(parsed.getStates(Word.fromSymbols("a", "b")).isEmpty()); - Assert.assertTrue(parsed.getStates(Word.fromSymbols("c", "a")).isEmpty()); - Assert.assertTrue(parsed.getStates(Word.fromSymbols("b", "c")).isEmpty()); + @Test + public void testRegularNFA3Deserialization() throws IOException, FormatException { + + final CompactNFA parsed = DOTParsers.fsa(new CompactNFA.Creator<>(), + DOTParsers.DEFAULT_FSA_NODE_PARSER, + DOTParsers.DEFAULT_EDGE_PARSER, + GraphDOT.INITIAL_LABEL) + .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.NFA3_RESOURCE)).model; + assertNFAProperties(parsed); } @Test @@ -146,6 +159,23 @@ public void testRegularMTSDeserialization() throws IOException, FormatException checkIsomorphism(mts, parsed, alphabet); } + @Test + public void testInitialPrefixMTSDeserialization() throws IOException, FormatException { + final CompactMTS mts = DOTSerializationUtil.MTS; + + InputModelData> model = DOTParsers.mts(CompactMTS::new, + DOTParsers.DEFAULT_EDGE_PARSER, + DOTParsers.DEFAULT_MTS_EDGE_PARSER, + GraphDOT.INITIAL_LABEL) + .readModel(DOTSerializationUtil.getResource( + DOTSerializationUtil.MTS_RESOURCE)); + + final Alphabet alphabet = model.alphabet; + final CompactMTS parsed = model.model; + + checkIsomorphism(mts, parsed, alphabet); + } + @Test public void testRegularMMLTDeserialization() throws IOException, FormatException { final CompactMMLT mts = DOTSerializationUtil.MMLT; @@ -239,7 +269,7 @@ public void testMMLTValidation() { var parser = DOTParsers.mmlt(alph -> new CompactMMLT<>(alph, "void", StringSymbolCombiner.getInstance()), Function.identity(), s -> StringSymbolCombiner.getInstance().separateSymbols(s), - Collections.singleton("s0"), + "s0", false); for (int i = 1; i <= 11; i++) { @@ -250,6 +280,69 @@ public void testMMLTValidation() { } } + @Test + public void testCFMPSDeserialization() throws IOException, FormatException { + + final DefaultCFMPS cfmps = DOTSerializationUtil.CFMPS; + + Function, Set> apParser = + attr -> DOTCFMPSParser.parseLabelAsProperties(attr, s -> s.charAt(0)); + Function, Character> labelParser = + attr -> attr.getOrDefault(PMPGEdgeAttrs.LABEL, "?").charAt(0); + Function, Character> procedureParser = + attr -> attr.getOrDefault(PMPGNodeAttrs.PROCESS, "?").charAt(0); + + var model = DOTParsers.cfmps('?', apParser, labelParser, procedureParser) + .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.CFMPS_RESOURCE)); + + Assert.assertEquals(model.getMainProcess(), cfmps.getMainProcess()); + Assert.assertEquals(model.getPMPGs().keySet(), cfmps.getPMPGs().keySet()); + + for (var e : model.getPMPGs().entrySet()) { + var actual = e.getValue(); + var expected = cfmps.getPMPGs().get(e.getKey()); + checkGraphEquivalence(expected, actual, (s1, s2) -> 0); + Assert.assertEquals(actual.getInitialNode(), expected.getInitialNode()); + Assert.assertEquals(actual.getFinalNode(), expected.getFinalNode()); + } + } + + @Test + public void testCFMPSCustomDeserialization() throws IOException, FormatException { + + var model = DOTParsers.cfmps(label -> new CompactPMPG<>("?"), + attr -> Collections.singleton(Integer.parseInt(attr.getOrDefault(PMPGNodeAttrs.LABEL, + "") + .split(" ")[1])), + attr -> attr.get(PMPGEdgeAttrs.LABEL), + attr -> attr.getOrDefault(PMPGNodeAttrs.LABEL, "").split(" ")[0], + attr -> new ProceduralModalEdgePropertyImpl(NodeStyles.BOLD.equals(attr.get( + PMPGEdgeAttrs.STYLE)) ? ProceduralType.PROCESS : ProceduralType.INTERNAL, + ModalType.MUST), + "init", + false) + .readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.CFMPS2_RESOURCE)); + + Assert.assertEquals(model.getMainProcess(), "F"); + Assert.assertEquals(model.getPMPGs().keySet(), Set.of("F", "G")); + + assertPMPGForF(model.getPMPGs().get("F")); + assertPMPGForG(model.getPMPGs().get("G")); + } + + @Test + public void testCFMPSValidation() { + + var parser = DOTParsers.cfmps(); + + for (int i = 1; i <= 3; i++) { + final int id = i; + Assert.assertThrows(Integer.toString(id), + FormatException.class, + () -> parser.readModel(DOTSerializationUtil.getResource("/cfmps_error" + id + ".dot"))); + } + } + @Test(expectedExceptions = FormatException.class) public void testFaultyAutomatonDeserialization() throws IOException, FormatException { DOTParsers.dfa().readModel(DOTSerializationUtil.getResource(DOTSerializationUtil.FAULTY_AUTOMATON_RESOURCE)); @@ -267,6 +360,7 @@ public void doNotCloseInputStreamTest() throws IOException, FormatException { InputStream mealy = DOTSerializationUtil.class.getResourceAsStream(DOTSerializationUtil.MEALY_RESOURCE); InputStream moore = DOTSerializationUtil.class.getResourceAsStream(DOTSerializationUtil.MOORE_RESOURCE); InputStream mmlt = DOTSerializationUtil.class.getResourceAsStream(DOTSerializationUtil.MMLT_RESOURCE); + InputStream cfmps = DOTSerializationUtil.class.getResourceAsStream(DOTSerializationUtil.CFMPS_RESOURCE); InputStream mts = DOTSerializationUtil.class.getResourceAsStream(DOTSerializationUtil.MTS_RESOURCE); InputStream nfa = DOTSerializationUtil.class.getResourceAsStream(DOTSerializationUtil.NFA_RESOURCE)) { DOTParsers.dfa().readModel(new UnclosableInputStream(dfa)); @@ -274,6 +368,7 @@ public void doNotCloseInputStreamTest() throws IOException, FormatException { DOTParsers.mealy().readModel(new UnclosableInputStream(mealy)); DOTParsers.moore().readModel(new UnclosableInputStream(moore)); DOTParsers.mmlt("void", StringSymbolCombiner.getInstance()).readModel(new UnclosableInputStream(mmlt)); + DOTParsers.cfmps().readModel(new UnclosableInputStream(cfmps)); DOTParsers.mts().readModel(new UnclosableInputStream(mts)); DOTParsers.nfa().readModel(new UnclosableInputStream(nfa)); } @@ -335,13 +430,13 @@ private static void checkIsomorphism(UniversalAutoma for (I i : alphabet) { final List sourceTransitions = new ArrayList<>(source.getTransitions(sourceState, i)); - final List targetTransistions = new ArrayList<>(target.getTransitions(targetState, i)); + final List targetTransitions = new ArrayList<>(target.getTransitions(targetState, i)); - Assert.assertEquals(sourceTransitions.size(), targetTransistions.size()); + Assert.assertEquals(sourceTransitions.size(), targetTransitions.size()); for (int j = 0; j < sourceTransitions.size(); j++) { final T1 sourceTrans = sourceTransitions.get(j); - final T2 targetTrans = targetTransistions.get(j); + final T2 targetTrans = targetTransitions.get(j); Assert.assertEquals(source.getTransitionProperty(sourceTrans), target.getTransitionProperty(targetTrans)); @@ -364,9 +459,14 @@ private static void checkIsomorphism(UniversalAutoma Assert.assertEquals(sourceQueue.isEmpty(), targetQueue.isEmpty()); } - private static , EP extends Comparable, N2, E2> void checkGraphEquivalence( - UniversalGraph source, - UniversalGraph target) { + private static , EP, N2, E2> void checkGraphEquivalence(UniversalGraph source, + UniversalGraph target) { + checkGraphEquivalence(source, target, Comparator.naturalOrder()); + } + + private static void checkGraphEquivalence(UniversalGraph source, + UniversalGraph target, + Comparator comparator) { Assert.assertEquals(source.size(), target.size()); @@ -389,8 +489,8 @@ private static , EP extends Comparable, N2 Assert.assertEquals(sourceEdges.size(), targetEdges.size()); // since we have unique node properties, these uniquely identify states - sourceEdges.sort(Comparator.comparing(e -> source.getNodeProperty(source.getTarget(e)))); - targetEdges.sort(Comparator.comparing(e -> target.getNodeProperty(target.getTarget(e)))); + sourceEdges.sort(Comparator.comparing(e -> source.getNodeProperty(source.getTarget(e)), comparator)); + targetEdges.sort(Comparator.comparing(e -> target.getNodeProperty(target.getTarget(e)), comparator)); for (int j = 0; j < sourceEdges.size(); j++) { final E1 sourceEdge = sourceEdges.get(j); @@ -426,4 +526,89 @@ private void assertSilentLoop(MMLT model, int st } Assert.assertFalse(model.isLocalReset(state, input)); } + + private static void assertNFAProperties(CompactNFA parsed) { + Assert.assertEquals(parsed.size(), 3); + Assert.assertEquals(parsed.getInitialStates().size(), 3); + Assert.assertFalse(parsed.accepts(Word.fromSymbols("a", "a", "a"))); + Assert.assertFalse(parsed.accepts(Word.fromSymbols("b", "b"))); + Assert.assertFalse(parsed.accepts(Word.fromSymbols("c"))); + Assert.assertEquals(parsed.getStates(Word.fromSymbols("a", "a", "a")).size(), 1); + Assert.assertEquals(parsed.getStates(Word.fromSymbols("b", "b")).size(), 1); + Assert.assertEquals(parsed.getStates(Word.fromLetter("c")).size(), 1); + Assert.assertTrue(parsed.getStates(Word.fromSymbols("a", "b")).isEmpty()); + Assert.assertTrue(parsed.getStates(Word.fromSymbols("c", "a")).isEmpty()); + Assert.assertTrue(parsed.getStates(Word.fromSymbols("b", "c")).isEmpty()); + } + + private static void assertPMPGForF(ProceduralModalProcessGraph f) { + Assert.assertEquals(f.size(), 8); + + final NodeIDs nodeIDs = f.nodeIDs(); + final N initialNode = f.getInitialNode(); + final N finalNode = f.getFinalNode(); + + for (N n : f.getNodes()) { + Assert.assertEquals(Objects.equals(n, initialNode), + f.getNodeProperty(n).equals(Collections.singleton(0)), + Objects.toString(n)); + Assert.assertEquals(Objects.equals(n, finalNode), + f.getNodeProperty(n).equals(Collections.singleton(1)), + Objects.toString(n)); + } + + Assert.assertFalse(f.isConnected(nodeIDs.getNode(0), nodeIDs.getNode(1))); + Assert.assertTrue(f.isConnected(nodeIDs.getNode(0), nodeIDs.getNode(2))); + Assert.assertTrue(f.isConnected(nodeIDs.getNode(2), nodeIDs.getNode(1))); + + Collection succs = f.getOutgoingEdges(nodeIDs.getNode(2)); + Assert.assertEquals(succs.size(), 4); + + for (E e : succs) { + Assert.assertEquals(f.getEdgeLabel(e).equals("G"), + f.getEdgeProperty(e).getProceduralType() == ProceduralType.PROCESS); + Assert.assertEquals(f.getEdgeLabel(e).equals("G"), + f.getNodeProperty(f.getTarget(e)).equals(Collections.singleton(3))); + Assert.assertEquals(f.getEdgeLabel(e).equals("a"), + f.getNodeProperty(f.getTarget(e)).equals(Collections.singleton(4))); + Assert.assertEquals(f.getEdgeLabel(e).equals("b"), + f.getNodeProperty(f.getTarget(e)).equals(Collections.singleton(5))); + Assert.assertEquals(f.getEdgeLabel(e).equals("R"), + f.getNodeProperty(f.getTarget(e)).equals(Collections.singleton(1))); + } + } + + private static void assertPMPGForG(ProceduralModalProcessGraph g) { + Assert.assertEquals(g.size(), 6); + + final NodeIDs nodeIDs = g.nodeIDs(); + final N initialNode = g.getInitialNode(); + final N finalNode = g.getFinalNode(); + + for (N n : g.getNodes()) { + Assert.assertEquals(Objects.equals(n, initialNode), + g.getNodeProperty(n).equals(Collections.singleton(8)), + Objects.toString(n)); + Assert.assertEquals(Objects.equals(n, finalNode), + g.getNodeProperty(n).equals(Collections.singleton(9)), + Objects.toString(n)); + } + + // id = label - 8 + Assert.assertFalse(g.isConnected(nodeIDs.getNode(0), nodeIDs.getNode(1))); + Assert.assertTrue(g.isConnected(nodeIDs.getNode(0), nodeIDs.getNode(2))); + Assert.assertTrue(g.isConnected(nodeIDs.getNode(2), nodeIDs.getNode(3))); + + Collection succs = g.getOutgoingEdges(nodeIDs.getNode(2)); + Assert.assertEquals(succs.size(), 2); + + for (E e : succs) { + Assert.assertEquals(g.getEdgeLabel(e).equals("F"), + g.getEdgeProperty(e).getProceduralType() == ProceduralType.PROCESS); + Assert.assertEquals(g.getEdgeLabel(e).equals("F"), + g.getNodeProperty(g.getTarget(e)).equals(Collections.singleton(11))); + Assert.assertEquals(g.getEdgeLabel(e).equals("?"), + g.getNodeProperty(g.getTarget(e)).equals(Collections.singleton(12))); + } + } } diff --git a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java index 78f7964bc..2c18c2abd 100644 --- a/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java +++ b/serialization/dot/src/test/java/net/automatalib/serialization/dot/DOTSerializationUtil.java @@ -65,6 +65,7 @@ final class DOTSerializationUtil { static final String DFA_RESOURCE = "/dfa.dot"; static final String NFA_RESOURCE = "/nfa.dot"; static final String NFA2_RESOURCE = "/nfa2.dot"; + static final String NFA3_RESOURCE = "/nfa3.dot"; static final String MEALY_RESOURCE = "/mealy.dot"; static final String MOORE_RESOURCE = "/moore.dot"; static final String SST_RESOURCE = "/sst.dot"; @@ -75,6 +76,7 @@ final class DOTSerializationUtil { static final String CLUSTER_RESOURCE = "/cluster.dot"; static final String PMPG_RESOURCE = "/pmpg.dot"; static final String CFMPS_RESOURCE = "/cfmps.dot"; + static final String CFMPS2_RESOURCE = "/cfmps2.dot"; static final String SPA_RESOURCE = "/spa.dot"; static final String SBA_RESOURCE = "/sba.dot"; static final String SPMM_RESOURCE = "/spmm.dot"; diff --git a/serialization/dot/src/test/resources/cfmps.dot b/serialization/dot/src/test/resources/cfmps.dot index 98390de17..52168058a 100644 --- a/serialization/dot/src/test/resources/cfmps.dot +++ b/serialization/dot/src/test/resources/cfmps.dot @@ -1,18 +1,18 @@ digraph g { - s0 [shape="circle" label=""]; - s1 [shape="box" label="[d]"]; - s2 [shape="octagon" label=""]; - s3 [shape="circle" label="[a, b]"]; - s4 [shape="circle" label="[c]"]; - s0 -> s1 [style="" label="?"]; - s0 -> s1 [style="dashed" label="?"]; - s1 -> s1 [style="bold" label="?"]; - s1 -> s0 [style="dashed,bold" label="?"]; - s2 -> s3 [style="" label="1"]; - s2 -> s3 [style="dashed" label="2"]; - s3 -> s4 [style="bold" label="3"]; - s4 -> s2 [style="dashed,bold" label="4"]; + s0 [process="t" shape="circle" label=""]; + s1 [process="t" shape="box" final="true" label="[d]"]; + s2 [process="s" shape="octagon" main="true" label=""]; + s3 [process="s" shape="circle" main="true" label="[a, b]"]; + s4 [process="s" shape="circle" main="true" label="[c]"]; + s0 -> s1 [modality="MUST" style="" label="?" procedurality="INTERNAL"]; + s0 -> s1 [modality="MAY" style="dashed" label="?" procedurality="INTERNAL"]; + s1 -> s1 [modality="MUST" style="bold" label="?" procedurality="PROCESS"]; + s1 -> s0 [modality="MAY" style="dashed,bold" label="?" procedurality="PROCESS"]; + s2 -> s3 [modality="MUST" style="" label="1" procedurality="INTERNAL"]; + s2 -> s3 [modality="MAY" style="dashed" label="2" procedurality="INTERNAL"]; + s3 -> s4 [modality="MUST" style="bold" label="3" procedurality="PROCESS"]; + s4 -> s2 [modality="MAY" style="dashed,bold" label="4" procedurality="PROCESS"]; __start0 [label="" shape="none" width="0" height="0"]; __start0 -> s2; diff --git a/serialization/dot/src/test/resources/cfmps2.dot b/serialization/dot/src/test/resources/cfmps2.dot new file mode 100644 index 000000000..b02e13c3a --- /dev/null +++ b/serialization/dot/src/test/resources/cfmps2.dot @@ -0,0 +1,49 @@ +// this file represents the palindrome SPA as a CFMPS with somewhat reduced and extended markup to test custom parsers + +digraph g { + +subgraph cluster1 { +label="F"; + + init1 [shape="octagon" main="true" label="F 0"]; + s1 [shape="box" final="true" main="true" label="F 1"]; + s2 [shape="circle" main="true" label="F 2"]; + s3 [shape="circle" main="true" label="F 3"]; + s4 [shape="circle" main="true" label="F 4"]; + s5 [shape="circle" main="true" label="F 5"]; + s6 [shape="circle" main="true" label="F 6"]; + s7 [shape="circle" main="true" label="F 7"]; + + init1 -> s2 [label="F"]; + s2 -> s1 [label="R"]; + s2 -> s4 [label="a"]; + s2 -> s5 [label="b"]; + s2 -> s3 [style="bold" label="G"]; + s3 -> s1 [label="R"]; + s4 -> s1 [label="R"]; + s4 -> s6 [style="bold" label="F"]; + s5 -> s1 [label="R"]; + s5 -> s7 [style="bold" label="F"]; + s6 -> s3 [label="a"]; + s7 -> s3 [label="b"]; +} + +subgraph cluster2 { +label="G"; + + init2 [shape="octagon" label="G 8"]; + s9 [shape="box" final="true" label="G 9"]; + s10 [shape="circle" label="G 10"]; + s11 [shape="circle" label="G 11"]; + s12 [shape="circle" label="G 12"]; + s13 [shape="circle" label="G 13"]; + + init2 -> s10 [label="G" procedurality="INTERNAL"]; + s10 -> s12 [procedurality="INTERNAL"]; + s10 -> s11 [style="bold" label="F" procedurality="PROCESS"]; + s11 -> s9 [label="R" procedurality="INTERNAL"]; + s12 -> s9 [label="R" procedurality="INTERNAL"]; + s12 -> s13 [style="bold" label="G" procedurality="PROCESS"]; + s13 -> s11 [label="c" procedurality="INTERNAL"]; +} +} diff --git a/serialization/dot/src/test/resources/cfmps_error1.dot b/serialization/dot/src/test/resources/cfmps_error1.dot new file mode 100644 index 000000000..5ca902149 --- /dev/null +++ b/serialization/dot/src/test/resources/cfmps_error1.dot @@ -0,0 +1,23 @@ +// erroneous CFMPS with multiple main processes + +digraph g { + + s0 [shape="octagon" main="true" process="S"]; + s1 [main="true" process="S"]; + s2 [main="true" process="S"]; + s3 [shape="box"final="true" main="true" process="S"]; + + s0 -> s1 [label="a"]; + s1 -> s2 [label="T" style="bold" procedurality="PROCESS"]; + s2 -> s3 [label="c"]; + + t0 [shape="octagon" main="true" process="T"]; + t1 [shape="box" final="true" main="true" process="T"]; + + t0 -> t1 [label="b"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; +__start1 [label="" shape="none" width="0" height="0"]; +__start1 -> t0; +} diff --git a/serialization/dot/src/test/resources/cfmps_error2.dot b/serialization/dot/src/test/resources/cfmps_error2.dot new file mode 100644 index 000000000..d4ec5863c --- /dev/null +++ b/serialization/dot/src/test/resources/cfmps_error2.dot @@ -0,0 +1,23 @@ +// erroneous CFMPS with referencing nodes corss-procedure + +digraph g { + + s0 [shape="octagon" main="true" process="S"]; + s1 [main="true" process="S"]; + s2 [main="true" process="S"]; + s3 [shape="box"final="true" main="true" process="S"]; + + s0 -> s1 [label="a"]; + s1 -> s2 [label="U" style="bold" procedurality="PROCESS"]; + s2 -> t1 [label="c"]; + + t0 [shape="octagon" process="T"]; + t1 [shape="box" final="true" process="T"]; + + t0 -> s3 [label="b"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; +__start1 [label="" shape="none" width="0" height="0"]; +__start1 -> t0; +} diff --git a/serialization/dot/src/test/resources/cfmps_error3.dot b/serialization/dot/src/test/resources/cfmps_error3.dot new file mode 100644 index 000000000..cd8db443c --- /dev/null +++ b/serialization/dot/src/test/resources/cfmps_error3.dot @@ -0,0 +1,23 @@ +// erroneous CFMPS with missing main process + +digraph g { + + s0 [shape="octagon" process="S"]; + s1 [process="S"]; + s2 [process="S"]; + s3 [shape="box"final="true" process="S"]; + + s0 -> s1 [label="a"]; + s1 -> s2 [label="T" style="bold" procedurality="PROCESS"]; + s2 -> s3 [label="c"]; + + t0 [shape="octagon" process="T"]; + t1 [shape="box" final="true" process="T"]; + + t0 -> t1 [label="b"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; +__start1 [label="" shape="none" width="0" height="0"]; +__start1 -> t0; +} diff --git a/serialization/dot/src/test/resources/nfa3.dot b/serialization/dot/src/test/resources/nfa3.dot new file mode 100644 index 000000000..509142f70 --- /dev/null +++ b/serialization/dot/src/test/resources/nfa3.dot @@ -0,0 +1,19 @@ +/* another test file + * that uses some more syntax + * properties of DOT files + */ +digraph g { + + // just an NFA with 3 initial self loops states + // none of which is accepting + s0 -> s0 [label="a"]; + s1 -> s1 [label="b"]; + s2 -> s2 [label="c"]; + +__start0 [label="" shape="none" width="0" height="0"]; +__start0 -> s0; +__start1 [label="" shape="none" width="0" height="0"]; +__start1 -> s1; +__start2 [label="" shape="none" width="0" height="0"]; +__start2 -> s2; +} diff --git a/serialization/dot/src/test/resources/pmpg.dot b/serialization/dot/src/test/resources/pmpg.dot index 17624a37c..0f16f8139 100644 --- a/serialization/dot/src/test/resources/pmpg.dot +++ b/serialization/dot/src/test/resources/pmpg.dot @@ -3,10 +3,10 @@ digraph g { s0 [shape="octagon" label=""]; s1 [shape="circle" label="[a, b]"]; s2 [shape="circle" label="[c]"]; - s0 -> s1 [style="" label="1"]; - s0 -> s1 [style="dashed" label="2"]; - s1 -> s2 [style="bold" label="3"]; - s2 -> s0 [style="dashed,bold" label="4"]; + s0 -> s1 [modality="MUST" style="" label="1" procedurality="INTERNAL"]; + s0 -> s1 [modality="MAY" style="dashed" label="2" procedurality="INTERNAL"]; + s1 -> s2 [modality="MUST" style="bold" label="3" procedurality="PROCESS"]; + s2 -> s0 [modality="MAY" style="dashed,bold" label="4" procedurality="PROCESS"]; __start0 [label="" shape="none" width="0" height="0"]; __start0 -> s0; diff --git a/util/src/main/java/net/automatalib/util/automaton/procedural/CFMPSViewSPA.java b/util/src/main/java/net/automatalib/util/automaton/procedural/CFMPSViewSPA.java index 023c21dc9..b41f627ae 100644 --- a/util/src/main/java/net/automatalib/util/automaton/procedural/CFMPSViewSPA.java +++ b/util/src/main/java/net/automatalib/util/automaton/procedural/CFMPSViewSPA.java @@ -53,7 +53,7 @@ class CFMPSViewSPA implements ContextFreeModalProcessSystem { this.pmpgs = new HashMap<>(HashUtil.capacity(procedures.size())); for (Entry> e : procedures.entrySet()) { - this.pmpgs.put(e.getKey(), new MPGView<>(spa, e.getKey(), e.getValue())); + this.pmpgs.put(e.getKey(), new PMPGView<>(spa, e.getKey(), e.getValue())); } } @@ -67,7 +67,7 @@ class CFMPSViewSPA implements ContextFreeModalProcessSystem { return this.spa.getInitialProcedure(); } - private static final class MPGView + private static final class PMPGView implements ProceduralModalProcessGraph, Void, ProceduralModalEdgeProperty> { private static final Object INITIAL = new Object(); @@ -84,7 +84,7 @@ private static final class MPGView // we make sure to handle 'init' and 'end' correctly @SuppressWarnings("unchecked") - MPGView(SPA spa, I procedure, DFA dfa) { + PMPGView(SPA spa, I procedure, DFA dfa) { final S dfaInit = dfa.getInitialState(); diff --git a/util/src/test/resources/cfmps/diss.dot b/util/src/test/resources/cfmps/diss.dot index becf65905..5f978732a 100644 --- a/util/src/test/resources/cfmps/diss.dot +++ b/util/src/test/resources/cfmps/diss.dot @@ -1,77 +1,77 @@ digraph g { - s0 [shape="octagon" label=""]; - s1 [shape="box" label=""]; - s2 [shape="circle" label=""]; - s3 [shape="circle" label=""]; - s4 [shape="circle" label=""]; - s5 [shape="octagon" label=""]; - s6 [shape="box" label=""]; - s7 [shape="circle" label=""]; - s8 [shape="circle" label=""]; - s9 [shape="circle" label=""]; - s10 [shape="circle" label=""]; - s11 [shape="octagon" label=""]; - s12 [shape="box" label=""]; - s13 [shape="circle" label=""]; - s14 [shape="circle" label=""]; - s15 [shape="circle" label=""]; - s0 -> s2 [style="" label="c_1"]; - s2 -> s3 [style="" label="a"]; - s2 -> s4 [style="" label="b"]; - s2 -> s4 [style="bold" label="main"]; - s2 -> s4 [style="bold" label="c_1"]; - s2 -> s4 [style="bold" label="c_2"]; - s3 -> s1 [style="" label="r"]; - s3 -> s4 [style="" label="a"]; - s3 -> s4 [style="" label="b"]; - s3 -> s4 [style="bold" label="main"]; - s3 -> s4 [style="bold" label="c_1"]; - s3 -> s4 [style="bold" label="c_2"]; - s4 -> s4 [style="" label="a"]; - s4 -> s4 [style="" label="b"]; - s4 -> s4 [style="bold" label="main"]; - s4 -> s4 [style="bold" label="c_1"]; - s4 -> s4 [style="bold" label="c_2"]; - s5 -> s7 [style="" label="main"]; - s7 -> s10 [style="" label="a"]; - s7 -> s10 [style="" label="b"]; - s7 -> s10 [style="bold" label="main"]; - s7 -> s8 [style="bold" label="c_1"]; - s7 -> s10 [style="bold" label="c_2"]; - s8 -> s10 [style="" label="a"]; - s8 -> s10 [style="" label="b"]; - s8 -> s10 [style="bold" label="main"]; - s8 -> s10 [style="bold" label="c_1"]; - s8 -> s9 [style="bold" label="c_2"]; - s9 -> s6 [style="" label="r"]; - s9 -> s10 [style="" label="a"]; - s9 -> s10 [style="" label="b"]; - s9 -> s10 [style="bold" label="main"]; - s9 -> s10 [style="bold" label="c_1"]; - s9 -> s10 [style="bold" label="c_2"]; - s10 -> s10 [style="" label="a"]; - s10 -> s10 [style="" label="b"]; - s10 -> s10 [style="bold" label="main"]; - s10 -> s10 [style="bold" label="c_1"]; - s10 -> s10 [style="bold" label="c_2"]; - s11 -> s13 [style="" label="c_2"]; - s13 -> s15 [style="" label="a"]; - s13 -> s14 [style="" label="b"]; - s13 -> s15 [style="bold" label="main"]; - s13 -> s15 [style="bold" label="c_1"]; - s13 -> s15 [style="bold" label="c_2"]; - s14 -> s12 [style="" label="r"]; - s14 -> s15 [style="" label="a"]; - s14 -> s15 [style="" label="b"]; - s14 -> s15 [style="bold" label="main"]; - s14 -> s15 [style="bold" label="c_1"]; - s14 -> s15 [style="bold" label="c_2"]; - s15 -> s15 [style="" label="a"]; - s15 -> s15 [style="" label="b"]; - s15 -> s15 [style="bold" label="main"]; - s15 -> s15 [style="bold" label="c_1"]; - s15 -> s15 [style="bold" label="c_2"]; + s0 [process="c_1" shape="octagon" label=""]; + s1 [process="c_1" shape="box" final="true" label=""]; + s2 [process="c_1" shape="circle" label=""]; + s3 [process="c_1" shape="circle" label=""]; + s4 [process="c_1" shape="circle" label=""]; + s5 [process="main" shape="octagon" main="true" label=""]; + s6 [process="main" shape="box" final="true" main="true" label=""]; + s7 [process="main" shape="circle" main="true" label=""]; + s8 [process="main" shape="circle" main="true" label=""]; + s9 [process="main" shape="circle" main="true" label=""]; + s10 [process="main" shape="circle" main="true" label=""]; + s11 [process="c_2" shape="octagon" label=""]; + s12 [process="c_2" shape="box" final="true" label=""]; + s13 [process="c_2" shape="circle" label=""]; + s14 [process="c_2" shape="circle" label=""]; + s15 [process="c_2" shape="circle" label=""]; + s0 -> s2 [modality="MUST" style="" label="c_1" procedurality="INTERNAL"]; + s2 -> s3 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s2 -> s4 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s2 -> s4 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s2 -> s4 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s2 -> s4 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s3 -> s1 [modality="MUST" style="" label="r" procedurality="INTERNAL"]; + s3 -> s4 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s3 -> s4 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s3 -> s4 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s3 -> s4 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s3 -> s4 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s4 -> s4 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s4 -> s4 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s4 -> s4 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s4 -> s4 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s4 -> s4 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s5 -> s7 [modality="MUST" style="" label="main" procedurality="INTERNAL"]; + s7 -> s10 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s7 -> s10 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s7 -> s10 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s7 -> s8 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s7 -> s10 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s8 -> s10 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s8 -> s10 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s8 -> s10 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s8 -> s10 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s8 -> s9 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s9 -> s6 [modality="MUST" style="" label="r" procedurality="INTERNAL"]; + s9 -> s10 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s9 -> s10 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s9 -> s10 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s9 -> s10 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s9 -> s10 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s10 -> s10 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s10 -> s10 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s10 -> s10 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s10 -> s10 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s10 -> s10 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s11 -> s13 [modality="MUST" style="" label="c_2" procedurality="INTERNAL"]; + s13 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s13 -> s14 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s13 -> s15 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s13 -> s15 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s13 -> s15 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s14 -> s12 [modality="MUST" style="" label="r" procedurality="INTERNAL"]; + s14 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s14 -> s15 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s14 -> s15 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s14 -> s15 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s14 -> s15 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; + s15 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s15 -> s15 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s15 -> s15 [modality="MUST" style="bold" label="main" procedurality="PROCESS"]; + s15 -> s15 [modality="MUST" style="bold" label="c_1" procedurality="PROCESS"]; + s15 -> s15 [modality="MUST" style="bold" label="c_2" procedurality="PROCESS"]; __start0 [label="" shape="none" width="0" height="0"]; __start0 -> s5; diff --git a/util/src/test/resources/cfmps/palindrome.dot b/util/src/test/resources/cfmps/palindrome.dot index 6cfc2b3af..6150d0857 100644 --- a/util/src/test/resources/cfmps/palindrome.dot +++ b/util/src/test/resources/cfmps/palindrome.dot @@ -1,89 +1,89 @@ digraph g { - s0 [shape="octagon" label=""]; - s1 [shape="box" label=""]; - s2 [shape="circle" label=""]; - s3 [shape="circle" label=""]; - s4 [shape="circle" label=""]; - s5 [shape="circle" label=""]; - s6 [shape="circle" label=""]; - s7 [shape="octagon" label=""]; - s8 [shape="box" label=""]; - s9 [shape="circle" label=""]; - s10 [shape="circle" label=""]; - s11 [shape="circle" label=""]; - s12 [shape="circle" label=""]; - s13 [shape="circle" label=""]; - s14 [shape="circle" label=""]; - s15 [shape="circle" label=""]; - s0 -> s2 [style="" label="T"]; - s2 -> s6 [style="" label="a"]; - s2 -> s6 [style="" label="b"]; - s2 -> s4 [style="" label="c"]; - s2 -> s3 [style="bold" label="S"]; - s2 -> s6 [style="bold" label="T"]; - s3 -> s1 [style="" label="R"]; - s3 -> s6 [style="" label="a"]; - s3 -> s6 [style="" label="b"]; - s3 -> s6 [style="" label="c"]; - s3 -> s6 [style="bold" label="S"]; - s3 -> s6 [style="bold" label="T"]; - s4 -> s1 [style="" label="R"]; - s4 -> s6 [style="" label="a"]; - s4 -> s6 [style="" label="b"]; - s4 -> s6 [style="" label="c"]; - s4 -> s6 [style="bold" label="S"]; - s4 -> s5 [style="bold" label="T"]; - s5 -> s6 [style="" label="a"]; - s5 -> s6 [style="" label="b"]; - s5 -> s3 [style="" label="c"]; - s5 -> s6 [style="bold" label="S"]; - s5 -> s6 [style="bold" label="T"]; - s6 -> s6 [style="" label="a"]; - s6 -> s6 [style="" label="b"]; - s6 -> s6 [style="" label="c"]; - s6 -> s6 [style="bold" label="S"]; - s6 -> s6 [style="bold" label="T"]; - s7 -> s9 [style="" label="S"]; - s9 -> s8 [style="" label="R"]; - s9 -> s11 [style="" label="a"]; - s9 -> s12 [style="" label="b"]; - s9 -> s15 [style="" label="c"]; - s9 -> s15 [style="bold" label="S"]; - s9 -> s10 [style="bold" label="T"]; - s10 -> s8 [style="" label="R"]; - s10 -> s15 [style="" label="a"]; - s10 -> s15 [style="" label="b"]; - s10 -> s15 [style="" label="c"]; - s10 -> s15 [style="bold" label="S"]; - s10 -> s15 [style="bold" label="T"]; - s11 -> s8 [style="" label="R"]; - s11 -> s15 [style="" label="a"]; - s11 -> s15 [style="" label="b"]; - s11 -> s15 [style="" label="c"]; - s11 -> s13 [style="bold" label="S"]; - s11 -> s15 [style="bold" label="T"]; - s12 -> s8 [style="" label="R"]; - s12 -> s15 [style="" label="a"]; - s12 -> s15 [style="" label="b"]; - s12 -> s15 [style="" label="c"]; - s12 -> s14 [style="bold" label="S"]; - s12 -> s15 [style="bold" label="T"]; - s13 -> s10 [style="" label="a"]; - s13 -> s15 [style="" label="b"]; - s13 -> s15 [style="" label="c"]; - s13 -> s15 [style="bold" label="S"]; - s13 -> s15 [style="bold" label="T"]; - s14 -> s15 [style="" label="a"]; - s14 -> s10 [style="" label="b"]; - s14 -> s15 [style="" label="c"]; - s14 -> s15 [style="bold" label="S"]; - s14 -> s15 [style="bold" label="T"]; - s15 -> s15 [style="" label="a"]; - s15 -> s15 [style="" label="b"]; - s15 -> s15 [style="" label="c"]; - s15 -> s15 [style="bold" label="S"]; - s15 -> s15 [style="bold" label="T"]; + s0 [process="T" shape="octagon" label=""]; + s1 [process="T" shape="box" final="true" label=""]; + s2 [process="T" shape="circle" label=""]; + s3 [process="T" shape="circle" label=""]; + s4 [process="T" shape="circle" label=""]; + s5 [process="T" shape="circle" label=""]; + s6 [process="T" shape="circle" label=""]; + s7 [process="S" shape="octagon" main="true" label=""]; + s8 [process="S" shape="box" final="true" main="true" label=""]; + s9 [process="S" shape="circle" main="true" label=""]; + s10 [process="S" shape="circle" main="true" label=""]; + s11 [process="S" shape="circle" main="true" label=""]; + s12 [process="S" shape="circle" main="true" label=""]; + s13 [process="S" shape="circle" main="true" label=""]; + s14 [process="S" shape="circle" main="true" label=""]; + s15 [process="S" shape="circle" main="true" label=""]; + s0 -> s2 [modality="MUST" style="" label="T" procedurality="INTERNAL"]; + s2 -> s6 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s2 -> s6 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s2 -> s4 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s2 -> s3 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s2 -> s6 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s3 -> s1 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s3 -> s6 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s3 -> s6 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s3 -> s6 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s3 -> s6 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s3 -> s6 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s4 -> s1 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s4 -> s6 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s4 -> s6 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s4 -> s6 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s4 -> s6 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s4 -> s5 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s5 -> s6 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s5 -> s6 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s5 -> s3 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s5 -> s6 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s5 -> s6 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s6 -> s6 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s6 -> s6 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s6 -> s6 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s6 -> s6 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s6 -> s6 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s7 -> s9 [modality="MUST" style="" label="S" procedurality="INTERNAL"]; + s9 -> s8 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s9 -> s11 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s9 -> s12 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s9 -> s15 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s9 -> s15 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s9 -> s10 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s10 -> s8 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s10 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s10 -> s15 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s10 -> s15 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s10 -> s15 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s10 -> s15 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s11 -> s8 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s11 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s11 -> s15 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s11 -> s15 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s11 -> s13 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s11 -> s15 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s12 -> s8 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s12 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s12 -> s15 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s12 -> s15 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s12 -> s14 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s12 -> s15 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s13 -> s10 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s13 -> s15 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s13 -> s15 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s13 -> s15 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s13 -> s15 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s14 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s14 -> s10 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s14 -> s15 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s14 -> s15 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s14 -> s15 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; + s15 -> s15 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s15 -> s15 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s15 -> s15 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s15 -> s15 [modality="MUST" style="bold" label="S" procedurality="PROCESS"]; + s15 -> s15 [modality="MUST" style="bold" label="T" procedurality="PROCESS"]; __start0 [label="" shape="none" width="0" height="0"]; __start0 -> s7; diff --git a/util/src/test/resources/cfmps/sba.dot b/util/src/test/resources/cfmps/sba.dot index f8eaed656..f1117c90f 100644 --- a/util/src/test/resources/cfmps/sba.dot +++ b/util/src/test/resources/cfmps/sba.dot @@ -1,37 +1,37 @@ digraph g { - s0 [shape="octagon" label=""]; - s1 [shape="box" label=""]; - s2 [shape="circle" label=""]; - s3 [shape="circle" label=""]; - s4 [shape="circle" label=""]; - s5 [shape="octagon" label=""]; - s6 [shape="box" label=""]; - s7 [shape="circle" label=""]; - s8 [shape="circle" label=""]; - s9 [shape="circle" label=""]; - s10 [shape="octagon" label=""]; - s11 [shape="box" label=""]; - s12 [shape="circle" label=""]; - s13 [shape="circle" label=""]; - s14 [shape="circle" label=""]; - s15 [shape="octagon" label=""]; - s16 [shape="box" label=""]; - s17 [shape="circle" label=""]; - s0 -> s2 [style="" label="P1"]; - s2 -> s3 [style="" label="a"]; - s2 -> s3 [style="bold" label="P2"]; - s3 -> s1 [style="" label="R"]; - s5 -> s7 [style="" label="P2"]; - s7 -> s8 [style="" label="b"]; - s7 -> s9 [style="bold" label="P3"]; - s7 -> s9 [style="bold" label="P4"]; - s8 -> s6 [style="" label="R"]; - s10 -> s13 [style="" label="P3"]; - s13 -> s14 [style="" label="c"]; - s13 -> s12 [style="bold" label="P4"]; - s14 -> s12 [style="" label="d"]; - s15 -> s17 [style="" label="P4"]; + s0 [process="P1" shape="octagon" main="true" label=""]; + s1 [process="P1" shape="box" final="true" main="true" label=""]; + s2 [process="P1" shape="circle" main="true" label=""]; + s3 [process="P1" shape="circle" main="true" label=""]; + s4 [process="P1" shape="circle" main="true" label=""]; + s5 [process="P2" shape="octagon" label=""]; + s6 [process="P2" shape="box" final="true" label=""]; + s7 [process="P2" shape="circle" label=""]; + s8 [process="P2" shape="circle" label=""]; + s9 [process="P2" shape="circle" label=""]; + s10 [process="P3" shape="octagon" label=""]; + s11 [process="P3" shape="box" final="true" label=""]; + s12 [process="P3" shape="circle" label=""]; + s13 [process="P3" shape="circle" label=""]; + s14 [process="P3" shape="circle" label=""]; + s15 [process="P4" shape="octagon" label=""]; + s16 [process="P4" shape="box" final="true" label=""]; + s17 [process="P4" shape="circle" label=""]; + s0 -> s2 [modality="MUST" style="" label="P1" procedurality="INTERNAL"]; + s2 -> s3 [modality="MUST" style="" label="a" procedurality="INTERNAL"]; + s2 -> s3 [modality="MUST" style="bold" label="P2" procedurality="PROCESS"]; + s3 -> s1 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s5 -> s7 [modality="MUST" style="" label="P2" procedurality="INTERNAL"]; + s7 -> s8 [modality="MUST" style="" label="b" procedurality="INTERNAL"]; + s7 -> s9 [modality="MUST" style="bold" label="P3" procedurality="PROCESS"]; + s7 -> s9 [modality="MUST" style="bold" label="P4" procedurality="PROCESS"]; + s8 -> s6 [modality="MUST" style="" label="R" procedurality="INTERNAL"]; + s10 -> s13 [modality="MUST" style="" label="P3" procedurality="INTERNAL"]; + s13 -> s14 [modality="MUST" style="" label="c" procedurality="INTERNAL"]; + s13 -> s12 [modality="MUST" style="bold" label="P4" procedurality="PROCESS"]; + s14 -> s12 [modality="MUST" style="" label="d" procedurality="INTERNAL"]; + s15 -> s17 [modality="MUST" style="" label="P4" procedurality="INTERNAL"]; __start0 [label="" shape="none" width="0" height="0"]; __start0 -> s5;