net.ericaro.diezel.core.parser.Graph Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of diezel-maven-plugin Show documentation
Show all versions of diezel-maven-plugin Show documentation
An Embedded Domain Specific Language Parser Generator PLugin compiler
The newest version!
package net.ericaro.diezel.core.parser;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import net.ericaro.diezel.core.builder.FileUtils;
import net.ericaro.diezel.core.parser.Graph.T;
/**
* Graph manages a graph of T (edge that "handle" a Transition object) and S
* (nodes that handle a State Object).
*
* It provides some basic operation, to make it possible to have a BNF
* definition of a workflow. Handled object are moved during graph operations.
*
* @author eric
*
*/
public class Graph {
public static boolean DEBUG = false;
static int ids = 0; // dead simple id generator
/** S stands for "state", it's a node.
*
* @author eric
*
*/
public static class S {
public int id;
List ins = new ArrayList();
public List outs = new ArrayList();
public S() {
id = ++ids;
}
/** to dot graphiz protocol
*
* @param shape
* @return
*/
public String toNode(String shape) {
StringBuilder sb = new StringBuilder();
sb.append("S").append(id).append("[shape=" + shape + "]");
return sb.toString();
}
/** also part of the graphiz protocol: return the .dot name for this node
*
* @return
*/
public String ref() {
return "S" + id;
}
public String toString() {
return ref();
}
}
/** t stands for Transition, it's in fact an edge.
*
* @author eric
*
*/
public static class T {
int id;
public S in; // in state
public S out; // out state
public String name; // transition name
public boolean implicit = true; // some transition are created "implicit".
/* An implicit transition is a an artificial transition created during the graph creation process,
* because for some operations it's impossible to create the graph without "artifical", and transient edges.
* ther are intended to be "reduce" at the end.
* */
public T() {
id = ++ids; // dead simple unique id handler.
}
/** part of the graph viz protocol
*
* @return
*/
public String toEdge() {
StringBuilder sb = new StringBuilder();
sb.append(in.ref()).append(" -> ").append(out.ref()).append("[")
.append("label=\"").append(
implicit ? "*"
: name ).append("\"").append("]");// .;
return sb.toString();
}
public String toString() {
return toEdge();
}
/** copy all fields (but the id) of this into that
*
* @param that
*/
public void copyTo(T that){
that.name = name;
that.implicit= implicit ;
that.in = in;
that.out = out;
}
}
/**
* print the current graph in a graphviz format.
*/
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("digraph {\n");
for (S s : states) {
String shape = "circle"; // "box" "circle"
if (s == in || s == out)
shape = "point";
sb.append(s.toNode(shape)).append(";\n");
}
for (T t : transitions)
sb.append(t.toEdge()).append(";\n");
sb.append("}\n");
return sb.toString();
}
// a graph is a set of transitions, states, a start node, and an end node.
public Set transitions = new HashSet();
public Set states = new HashSet();
public S in;
public S out;
int id;
public Graph()
{
this.id = ++ids;
}
/** use the id as graph identifier
*
*/
@Override
public int hashCode() {
return id;
}
/** clone this graph
*
*/
public Graph clone() {
Graph g = new Graph();
Map old2new = new HashMap();
for (S s : states)
g.clone(s, old2new);
for (T t : transitions)
g.clone(t, old2new);
g.in = g.clone(in, old2new);
g.out = g.clone(out, old2new);
return g;
}
/** create and append a node, unconnected, to this graph
*
* @return
*/
public S newS() {
S s = new S();
states.add(s);
return s;
}
/** create and append a transition, unconnected, to this graph
*
* @return
*/
public T newT() {
T t = new T();
transitions.add(t);
return t;
}
public T copyOfT(T t) {
T copied = newT();
t.copyTo(copied);
return copied ;
}
/** connect two nodes, and return the corresponding transition.
*
* @param in
* @param out
* @return
*/
public T connect(S in, S out) {
T t = newT();
t.in = in;
t.out = out;
in.outs.add(t);
out.ins.add(t);
return t;
}
/** clone, or get the already clone node, in the old2new map.
*
* @param s
* @param old2new
* @return
*/
private S clone(S s, Map old2new) {
if (!old2new.containsKey(s)) {
S s2 = newS();
old2new.put(s, s2);
}
return old2new.get(s);
}
/** clone, or get the already clone edge, in the old2new map.
*
* @param t
* @param old2new
* @return
*/
private T clone(T t, Map old2new) {
T t2 = connect(clone(t.in, old2new), clone(t.out, old2new));
t2.implicit = t.implicit;
t2.name= t.name;
return t;
}
/** remove a node, and disconnect (nullate) every transition linked to it.
*
* @param s
*/
private void remove(S s) {
for (T t : s.ins)
if (t.out == s)
t.out = null;
for (T t : s.outs)
if (t.in == s)
t.in = null;
states.remove(s);
if (in == s)
in = null;
if (out == s)
out = null;
}
/** remove an edge, and also remove it from the node it is attached to.
*
* @param t
*/
public void remove(T t) {
S in = t.in;
S out = t.out;
in.outs.remove(t);
out.ins.remove(t);
transitions.remove(t);
}
/** remove an implicit transition, so that the graph remain logically equivalent.
*
* @param t
*/
public void shortcut(T t) {
S in = t.in;
S out = t.out;
assert in.outs.size() == 1 || out.ins.size() == 1 : "cannot simplify graph using a Transition that is not a singleton";
assert t.implicit : "cannot simplify graph using a Transition that is not implicit";
// start removing the transition
remove(t);
// and then merge both states
if (in.outs.size() == 0) { // remove in
out.ins.addAll(in.ins);// copy all incoming
for (T u : in.ins)
u.out = out;// and move their target to out
in.ins.clear();
if (this.in == in)
this.in = out;
if (this.out == in)
this.out = out;
assert in.outs.size() == 0 && in.outs.size() == 0 : "Failed to shortcut, removable node is not empty after operation.";
remove(in);
} else {
assert (out.ins.size() == 0) : "There is a problem with the extremity nodes.";
in.outs.addAll(out.outs);// copy all outgoing
for (T u : out.outs)
// move transition
u.in = in;// and move their source to in
out.outs.clear();
if (this.in == out)
this.in = in;
if (this.out == out)
this.out = in;
assert out.ins.size() == 0 && out.outs.size() == 0 : "Failed to shortcut, removable node is not empty after operation.";
remove(out);
}
}
/** append all states, and transitions from another graph. id are unique because the unique id generator is static.
*
* @param g
*/
public void addAll(Graph g) {
states.addAll(g.states);
transitions.addAll(g.transitions);
}
/**
* Remove all possible implicit links
*/
public void reduce() {
log(this, "shortcut");
Set shortcutable = new HashSet();
for (T t : transitions)
if ( t.implicit
&&
(
(t.in.outs.size() == 1 && ! isInorOut(t.in) )
||
(t.out.ins.size() == 1 && ! isInorOut(t.out) )
)
)
shortcutable.add(t);
for (T t : shortcutable) {
shortcut(t);
log(this, "shortcut");
}
log(this);
}
public boolean isInorOut(S state) {
return state==this.in || state == this.out;
}
private T nextImplicit(){
for(T t: transitions)
if (t.implicit) return t;
return null;
}
/** totally remove all implicit links, if it is not possible (like in reduce) then every transition are moved around to make it possible.
*
*/
public void unimplicit(){
T t= nextImplicit();
while(t!=null){
remove(t);
S in = t.in;
S out = t.out;
if (in == out ){
t=nextImplicit();
continue;
}
// alg: I'm going to remove out: false
// evry incoming to out will be transferred to in,
// every outgoing will be transfered from in
/*for(T incoming: out.ins)
incoming.out=in;
for(T outgoing: out.outs)
outgoing.in=in;
remove(out);
*/
//alg: simply copy the out out interface into the in out interface
for(T outout: out.outs){
T copied = copyOfT(outout);
// now changes the connections
copied.in = in;
in.outs.add(copied);
}
//log(this, "unimplicit");
t= nextImplicit();
}
}
/** totally remove duplicated transitions.
*
*/
public void unDuplicate(){
for(S s : states) {
ArrayList newOut = new ArrayList();
HashSet names = new HashSet();
for (T t: s.outs )
if (names.add(t.name) )
newOut.add(t);
s.outs = newOut ;
}
}
static int debugCount=0;
public static void log(Graph g){
log(g, "debug");
}
public static void log(Graph g, String name){
if (DEBUG) {
try {
g.graph("target/diezel"+name+ ++debugCount);
} catch (IOException e) {}
}
}
// STARTS THE TOP OPERATORS
/** simply add a null link between in and out, so that the whole graph is made "optional"
*
* @param g
* @return
*/
public static Graph opt(Graph g) {
// simply add a null link between in and out
g.connect(g.in, g.out);
log(g);
return g;
}
/** sequentially connect graphs togethers
*
* @param graphs
* @return
*/
public static Graph seq(Graph... graphs) {
if( graphs.length==1) return graphs[0]; // smart shortcut
Graph g = new Graph(); // the resulting graph
int last = graphs.length - 1;
T[] ts = new T[last];
for (int i = 0; i < graphs.length; i++)
g.addAll(graphs[i]);
g.in = graphs[0].in;
g.out = graphs[last].out;
for (int i = 0; i < last; i++)
ts[i] = g.connect(graphs[i].out, graphs[i + 1].in);
log(g);
return g;
}
/**
* simply add a null link between out to in making it repeatable, and a link, just like opt, making it avoidable
* @param g
* @return
*/
public static Graph iter(Graph g) {
// simply add a null link between in and out
g.connect(g.in, g.out); // removing this line forces a ()+
g.connect(g.out, g.in);
log(g);
return g;
}
/** simply add a null link between out to in making it repeatable,
*
* @param g
* @return
*/
public static Graph iter_once(Graph g) {
// simply add a null link between in and out
g.connect(g.out, g.in);
log(g);
return g;
}
/** connect graphs in parallel
*
* @param graphs
* @return
*/
public static Graph sel(Graph... graphs) {
if( graphs.length==1) return graphs[0];
Graph g = new Graph( );
g.in = g.newS();
g.out = g.newS();
for (int i = 0; i < graphs.length; i++) {
g.addAll(graphs[i]);
g.connect(g.in, graphs[i].in);
g.connect(graphs[i].out, g.out);
}
log(g);
return g;
}
/** A bang is explosive, use with care, propose a choice of graphs, and, then a choice of the same graphs minus the one just made.
* it means that it forces the user to pass through all the transitions of the bang, but in the order it wants.
*
* @param result
* @return
*/
public static Graph bang(Graph... graphs) {
if( graphs.length==1) return graphs[0];
HashSet subgraph = new HashSet();
subgraph.addAll(Arrays.asList(graphs));
return bang(subgraph);
}
/** A bang is explosive, use with care, propose a choice of graphs, and, then a choice of the same graphs minus the one just made.
* it means that it forces the user to pass through all the transitions of the bang, but in the order it wants.
*
* @param graphs
* @return
*/
static Graph bang(Set graphs) {
if (graphs.size() ==1) return graphs.iterator().next();
System.out.println("bang "+ graphs.size());
Graph g = new Graph( );
g.in = g.newS();
g.out = g.newS();
for (Graph graph : graphs) {
//graph = graph.clone();
HashSet subgraph = new HashSet();
for (Graph subs: graphs)
if (subs.id!=graph.id)
subgraph.add(subs.clone() );
Graph banged = bang(subgraph);
g.addAll(graph);
g.addAll(banged);
g.connect(g.in, graph.in);
g.connect(graph.out, banged.in);
g.connect(banged.out, g.out);
}
g.reduce();
log(g);
return g;
}
/** creates a new graph, with a single transition, named "name"
*
*/
public static Graph term(String name) {
Graph g = new Graph( );
g.in = g.newS();
g.out = g.newS();
T t = g.connect(g.in, g.out);
t.implicit = false;
t.name= name;
log(g);
return g;
}
public void graph(String name) throws IOException {
File f = new File("./" + name + ".dot");
System.out.println("generating dot file "+ f);
FileUtils.printFile(f, toString(), true);
dot(name);
}
public static void dot(String name) throws IOException {
PrintStream pout = System.out;
try {
System.out.println("generating png file "+ name);
Process r = Runtime.getRuntime().exec(
"dot " + name + ".dot -Grankdir=LR -Tpng -o " + name + ".png");
System.setOut(new PrintStream(r.getOutputStream()));
r.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
System.setOut(pout);
}
}
}