diff --git a/drools-retediagram/.gitignore b/drools-retediagram/.gitignore
new file mode 100644
index 00000000000..ec1ce76316b
--- /dev/null
+++ b/drools-retediagram/.gitignore
@@ -0,0 +1,19 @@
+/target
+/local
+
+# Eclipse, Netbeans and IntelliJ files
+/.*
+!.gitignore
+/nbproject
+/*.ipr
+/*.iws
+/*.iml
+
+# Repository wide ignore mac DS_Store files
+.DS_Store
+
+# generated files
+dependency-reduced-pom.xml
+
+# these modules don't exist anymore on master
+/drools-clips/
diff --git a/drools-retediagram/README.md b/drools-retediagram/README.md
new file mode 100644
index 00000000000..9874a202d42
--- /dev/null
+++ b/drools-retediagram/README.md
@@ -0,0 +1,126 @@
+# drools-retediagram
+
+An experiment to plot Rete diagram, similar to `ReteDumper`, using Dot language format.
+
+## Usage
+
+To plot the diagram files for a Knowledge Base of a given `KieSession` using the defaults:
+
+```java
+ReteDiagram.newInstance().diagramRete(kieSession);
+```
+
+It is possible to change some settings and formatting, as in the following example:
+
+```java
+ReteDiagram.newInstance()
+ .configLayout(Layout.PARTITION) // use Partition layout, instead of default Vertical layout
+ .configFilenameScheme(new File("./target"), true) // set output directory manually instead of OS default for temporary directory
+ .configOpenFile(true, false) // automatically open SVG file with OS default application
+ .diagramRete(kieSession);
+```
+
+Please notice to leverage the OS capability to automatically open file with the default application, it is required that the JVM property `java.awt.headless` but be `false`, which can be set manually with:
+
+```java
+System.setProperty("java.awt.headless", "false");
+```
+
+## Example: simple KB plotted with default Vertical layout
+
+Given the following rules in the Knowledge Base:
+
+```
+rule "For color"
+no-loop
+when
+ Measurement( id == "color", $colorVal : val )
+then
+ controlSet.add($colorVal);
+end
+
+rule "Likes cheddar"
+when
+ Cheese( $cheddar : name == "cheddar" )
+ $person : Person( favouriteCheese == $cheddar )
+then
+ System.out.println( $person.getName() + " likes cheddar" );
+end
+
+rule "Don't like cheddar"
+when
+ Cheese( $cheddar : name == "cheddar" )
+ $person : Person( favouriteCheese != $cheddar )
+then
+ System.out.println( $person.getName() + " does not like cheddar" );
+end
+
+rule "Color count"
+when
+ accumulate( $m: Measurement( id == "color" ); $c: count($m) )
+then
+ System.out.println( $c );
+end
+
+rule "Not a Color"
+when
+ not ( Measurement( id == "color" ) and String() )
+then
+ System.out.println( "no color yet." );
+end
+```
+
+The diagram plotted with the defaults settings and Vertical layout:
+
+```java
+ReteDiagram.newInstance().diagramRete(kieSession);
+```
+
+is rendered as:
+
+![example1](example1.png)
+
+the diagram displays the default entry point, Object Type nodes, Alpha nodes, Left Input Adapter nodes, join Beta nodes, etc. down to Rule Terminal nodes.
+
+## Example: plotting Partition layout
+
+Given the following rules in the Knowledge Base:
+
+```
+rule R0 when
+ $i : Integer( intValue == 0 )
+ String( toString == $i.toString )
+then
+ list.add($i);
+end
+rule R1 when
+ $i : Integer( intValue == 1 )
+ String( toString == $i.toString )
+then
+ list.add($i);
+end
+rule R2 when
+ $i : Integer( intValue == 2 )
+ String( toString == $i.toString )
+then
+ list.add($i);
+end
+rule R3 when
+ $i : Integer( intValue == 2 )
+ String( length == $i )
+then
+ list.add($i);
+end
+```
+
+The diagram plotted with the Partition layout:
+
+```java
+ReteDiagram.newInstance().configLayout(Layout.PARTITION).diagramRete(ksession);
+```
+
+is rendered as:
+
+![example2](example2.png)
+
+The diagram displays nodes in their respective Partitions.
diff --git a/drools-retediagram/example1.png b/drools-retediagram/example1.png
new file mode 100644
index 00000000000..e5df317fb28
Binary files /dev/null and b/drools-retediagram/example1.png differ
diff --git a/drools-retediagram/example2.png b/drools-retediagram/example2.png
new file mode 100644
index 00000000000..6b298b1d23d
Binary files /dev/null and b/drools-retediagram/example2.png differ
diff --git a/drools-retediagram/pom.xml b/drools-retediagram/pom.xml
new file mode 100644
index 00000000000..e74b15743b7
--- /dev/null
+++ b/drools-retediagram/pom.xml
@@ -0,0 +1,69 @@
+
+
+ 4.0.0
+
+ org.drools
+ drools
+ 7.53.0-SNAPSHOT
+
+ drools-retediagram
+ jar
+
+ drools-retediagram
+ Internal utility (EXPERIMENTAL) to plot Rete diagram, similar to `ReteDumper`, using Dot language format
+ http://drools.org
+
+
+ org.drools.retediagram
+
+
+
+
+ guru.nidi
+ graphviz-java
+
+
+ org.drools
+ drools-compiler
+
+
+ org.drools
+ drools-mvel
+ test
+
+
+ org.drools
+ drools-core
+ test-jar
+ test
+
+
+ org.drools
+ drools-compiler
+ test-jar
+ test
+
+
+ org.drools
+ drools-legacy-test-util
+ test-jar
+ test
+
+
+
+ junit
+ junit
+ test
+
+
+ org.slf4j
+ jul-to-slf4j
+ test
+
+
+ ch.qos.logback
+ logback-classic
+ test
+
+
+
diff --git a/drools-retediagram/src/main/java/org/drools/retediagram/ReteDiagram.java b/drools-retediagram/src/main/java/org/drools/retediagram/ReteDiagram.java
new file mode 100644
index 00000000000..a8ebe891e35
--- /dev/null
+++ b/drools-retediagram/src/main/java/org/drools/retediagram/ReteDiagram.java
@@ -0,0 +1,532 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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 org.drools.retediagram;
+
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.drools.core.base.ClassObjectType;
+import org.drools.core.common.BaseNode;
+import org.drools.core.impl.InternalKnowledgeBase;
+import org.drools.core.reteoo.AccumulateNode;
+import org.drools.core.reteoo.AlphaNode;
+import org.drools.core.reteoo.BetaNode;
+import org.drools.core.reteoo.EntryPointNode;
+import org.drools.core.reteoo.JoinNode;
+import org.drools.core.reteoo.LeftInputAdapterNode;
+import org.drools.core.reteoo.LeftTupleSource;
+import org.drools.core.reteoo.NotNode;
+import org.drools.core.reteoo.ObjectSource;
+import org.drools.core.reteoo.ObjectTypeNode;
+import org.drools.core.reteoo.Rete;
+import org.drools.core.reteoo.RightInputAdapterNode;
+import org.drools.core.reteoo.RuleTerminalNode;
+import org.drools.core.reteoo.Sink;
+import org.drools.core.spi.BetaNodeFieldConstraint;
+import org.drools.core.spi.ObjectType;
+import org.kie.api.KieBase;
+import org.kie.api.runtime.KieRuntime;
+import org.kie.api.runtime.KieSession;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import guru.nidi.graphviz.engine.Format;
+import guru.nidi.graphviz.engine.Graphviz;
+import guru.nidi.graphviz.model.MutableGraph;
+import guru.nidi.graphviz.parse.Parser;
+
+public class ReteDiagram {
+
+ private static final Logger LOG = LoggerFactory.getLogger(ReteDiagram.class);
+
+ public enum Layout {
+ PARTITION, VLEVEL
+ }
+
+ private Layout layout;
+ private File outputPath;
+ private boolean prefixTimestamp;
+ private boolean outputSVG;
+ private boolean outputPNG;
+ private boolean openSVG;
+ private boolean openPNG;
+ private boolean printDebugVerticalCluster = false;
+
+ private ReteDiagram() { }
+
+ /**
+ * With default settings.
+ */
+ public static ReteDiagram newInstance() {
+ File outpath = new File(".");
+ try {
+ outpath = Files.createTempDirectory("retediagram").toFile();
+ } catch (Exception e) {
+ // do nothing.
+ }
+ return new ReteDiagram()
+ .configLayout(Layout.VLEVEL)
+ .configFilenameScheme(outpath, true)
+ .configGraphvizRender(true, true)
+ .configOpenFile(false, false)
+ ;
+ }
+
+ /**
+ * Changes diagram Layout
+ */
+ public ReteDiagram configLayout(Layout layout) {
+ this.layout = layout;
+ return this;
+ }
+
+ public ReteDiagram configPrintDebugVerticalCluster(boolean printDebugVerticalCluster) {
+ this.printDebugVerticalCluster = printDebugVerticalCluster;
+ return this;
+ }
+
+ public ReteDiagram configFilenameScheme(File outputPath, boolean prefixTimestamp) {
+ this.outputPath = outputPath;
+ this.prefixTimestamp = prefixTimestamp;
+ return this;
+ }
+
+ public ReteDiagram configGraphvizRender(boolean outputSVG, boolean outputPNG) {
+ this.outputSVG = outputSVG;
+ this.outputPNG = outputPNG;
+ return this;
+ }
+
+ public ReteDiagram configOpenFile(boolean openSVG, boolean openPNG) {
+ this.openSVG = openSVG;
+ this.openPNG = openPNG;
+ return this;
+ }
+
+ public void diagramRete(KieBase kbase) {
+ diagramRete((InternalKnowledgeBase) kbase);
+ }
+
+ public void diagramRete(KieRuntime session) {
+ diagramRete((InternalKnowledgeBase)session.getKieBase());
+ }
+
+ public void diagramRete(KieSession session) {
+ diagramRete((InternalKnowledgeBase)session.getKieBase());
+ }
+
+ public void diagramRete(InternalKnowledgeBase kBase) {
+ diagramRete(kBase.getRete());
+ }
+
+ public void diagramRete(Rete rete) {
+ String timestampPrefix = (new SimpleDateFormat("yyyyMMddHHmmssSSS")).format(new Date());
+ String fileNameNoExtension = (prefixTimestamp?timestampPrefix+".":"") + rete.getKnowledgeBase().getId();
+ String gvFileName = fileNameNoExtension + ".gv";
+ String svgFileName = fileNameNoExtension + ".svg";
+ String pngFileName = fileNameNoExtension + ".png";
+ File gvFile = new File(outputPath, gvFileName);
+ File svgFile = new File(outputPath, svgFileName);
+ File pngFile = new File(outputPath, pngFileName);
+ try (PrintStream out = new PrintStream(new FileOutputStream(gvFile));) {
+ out.println("digraph g {\n" +
+ "graph [fontname = \"Overpass\" fontsize=11];\n" +
+ " node [fontname = \"Overpass\" fontsize=11];\n" +
+ " edge [fontname = \"Overpass\" fontsize=11];");
+ HashMap, Set> levelMap = new HashMap<>();
+ HashMap, List> nodeMap = new HashMap<>();
+ List> vertexes = new ArrayList<>();
+ Set visitedNodesIDs = new HashSet<>();
+ for (EntryPointNode entryPointNode : rete.getEntryPointNodes().values()) {
+ visitNodes( entryPointNode, "", visitedNodesIDs, nodeMap, vertexes, levelMap, out);
+ }
+
+ out.println("");
+ printNodeMap(nodeMap, out);
+
+ out.println("");
+ printVertexes(vertexes, out);
+
+ out.println("");
+ printLevelMap(levelMap, out, vertexes);
+
+ out.println("");
+ if (layout == Layout.PARTITION) {
+ printPartitionMap(nodeMap, out, vertexes);
+ }
+
+ out.println("}");
+ } catch (Exception e) {
+ LOG.error("Error building diagram", e);
+ }
+ LOG.info("Written gvFile: {}", gvFile);
+
+ if (outputSVG) {
+ try {
+ MutableGraph g = new Parser().read(gvFile);
+ Graphviz.fromGraph(g).render(Format.SVG).toFile(svgFile);
+ LOG.info("Written svgFile: {}", svgFile);
+ } catch (Exception e) {
+ LOG.error("Error building SVG file", e);
+ }
+ }
+ if (outputPNG) {
+ try {
+ MutableGraph g = new Parser().read(gvFile);
+ Graphviz.fromGraph(g).render(Format.PNG).toFile(pngFile);
+ LOG.info("Written pngFile: {}", pngFile);
+ } catch (Exception e) {
+ LOG.error("Error building PNG file", e);
+ }
+ }
+
+ if (outputSVG && openSVG) {
+ try {
+ java.awt.Desktop.getDesktop().open(svgFile);
+ } catch (Exception e) {
+ LOG.error("Error opening SVG file", e);
+ }
+ }
+ if (outputPNG && openPNG) {
+ try {
+ java.awt.Desktop.getDesktop().open(pngFile);
+ } catch (Exception e) {
+ LOG.error("Error opening PNG file", e);
+ }
+ }
+ }
+
+ private static void printVertexes(List> vertexes, PrintStream out ) {
+ for ( Vertex v : vertexes ) {
+ out.println(printNodeId(v.from) + " -> " + printNodeId(v.to) + " ;");
+ }
+ }
+
+ private static void printNodeMap(HashMap, List> nodeMap, PrintStream out) {
+ printNodeMapNodes(nodeMap.get(EntryPointNode.class), out);
+ printNodeMapNodes(nodeMap.get(ObjectTypeNode.class), out);
+ printNodeMapNodes(nodeMap.getOrDefault(AlphaNode.class, Collections.emptyList()), out);
+ // LIAs
+ List l3 = nodeMap.entrySet().stream()
+ .filter(kv->LeftInputAdapterNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream()).collect(toList());
+ printNodeMapNodes(l3, out);
+ printNodeMapNodes(nodeMap.getOrDefault(RightInputAdapterNode.class, Collections.emptyList()), out);
+ // Level 4: BN
+ List l4 = nodeMap.entrySet().stream()
+ .filter(kv->BetaNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream()).collect(toList());
+ printNodeMapNodes(l4, out);
+ printNodeMapNodes(nodeMap.get(RuleTerminalNode.class), out);
+ }
+
+ public static void printNodeMapNodes(List nodes, PrintStream out) {
+ for (BaseNode node : nodes) {
+ out.println(printNodeId(node) + " " + printNodeAttributes(node) + " ;");
+ }
+ }
+
+ public static class Vertex {
+ public final F from;
+ public final T to;
+ public Vertex(F from, T to) {
+ this.from = from;
+ this.to = to;
+ }
+ public static Vertex of(F from, T to) {
+ return new Vertex(from, to);
+ }
+ }
+
+ private static void printPartitionMap(HashMap, List> nodeMap, PrintStream out, List> vertexes) {
+ Map> byPartition = nodeMap.entrySet().stream()
+ .flatMap(kv->kv.getValue().stream())
+ .collect(groupingBy(n->n.getPartitionId() == null ? 0 : n.getPartitionId().getId()));
+
+ for (Entry> kv : byPartition.entrySet()) {
+ printClusterMapCluster("P"+kv.getKey(), new HashSet<>(kv.getValue()), out);
+ }
+ }
+
+ private void printLevelMap(HashMap, Set> levelMap, PrintStream out, List> vertexes) {
+
+ // Level 1: OTN
+ Set l1 = levelMap.entrySet().stream()
+ .filter(kv->ObjectTypeNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream()).collect(toSet());
+ printLevelMapLevel("l1", l1, out);
+
+ // Level 2: AN
+ Set l2 = levelMap.entrySet().stream()
+ .filter(kv->AlphaNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream()).collect(toSet());
+ printLevelMapLevel("l2", l2, out);
+
+ // Level 3: LIA
+ Set l3 = levelMap.entrySet().stream()
+ .filter(kv->LeftInputAdapterNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream()).collect(toSet());
+ printLevelMapLevel("l3", l3, out);
+
+ // RIA
+ Set lria = levelMap.entrySet().stream()
+ .filter(kv->RightInputAdapterNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream()).collect(toSet());
+ printLevelMapLevel("lria", lria, out);
+
+ // RIA beta sources
+ Set lriaSources = new HashSet<>();
+ Set> onlyBetas = vertexes.stream().filter(v->v.from instanceof BetaNode).collect(toSet());
+ for (BaseNode ria : lria) {
+ Set t = onlyBetas.stream()
+ .filter(v->v.to.equals(ria))
+ .map(v->v.from)
+ .collect(toSet());
+ lriaSources.addAll(t);
+ }
+ for (BaseNode lriaSource : lriaSources) {
+ lriaSources.addAll( recurseIncomingVertex(lriaSource, onlyBetas) );
+ }
+ printLevelMapLevel("lriaSources", lriaSources, out);
+
+ // subnetwork Betas
+ Set lsubbeta = levelMap.entrySet().stream()
+ .filter(kv->BetaNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream())
+ .filter(b-> ((BetaNode) b).getObjectType() == null )
+ .collect(toSet());
+ printLevelMapLevel("lsubbeta", lsubbeta, out);
+
+ // Level 4: BN
+ Set l4 = levelMap.entrySet().stream()
+ .filter(kv->BetaNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream())
+ .filter(b-> !lriaSources.contains(b) )
+ .filter(b-> !lsubbeta.contains(b) )
+ .collect(toSet());
+ printLevelMapLevel("l4", l4, out);
+
+ // Level 5: RTN
+ Set l5 = levelMap.entrySet().stream()
+ .filter(kv->RuleTerminalNode.class.isAssignableFrom( kv.getKey() ))
+ .flatMap(kv->kv.getValue().stream()).collect(toSet());
+ printLevelMapLevel("l5", l5, out);
+
+ out.println(
+ ((this.printDebugVerticalCluster) ? "" : " edge[style=invis];\n") +
+ " l1->l2->l3->lriaSources->lria->lsubbeta->l4->l5;");
+ }
+
+ private static Set recurseIncomingVertex(BaseNode to, Set> vertexes) {
+ Set acc = new HashSet<>();
+ for (Vertex v : vertexes) {
+ if (v.to.equals(to)) {
+ acc.add( v.from );
+ acc.addAll( recurseIncomingVertex(v.from, vertexes) );
+ }
+ }
+ return acc;
+ }
+
+ private static void printClusterMapCluster(String levelId, Set value, PrintStream out) {
+ StringBuilder nodeIds = new StringBuilder();
+ for (BaseNode n : value) {
+ nodeIds.append(printNodeId(n)+"; ");
+ }
+ String level = String.format(" subgraph cluster_%1$s{style=dotted; labelloc=b; label=\"%1$s\"; %2$s}",
+ levelId,
+ nodeIds.toString());
+ out.println(level);
+ }
+
+ private void printLevelMapLevel(String levelId, Set value, PrintStream out) {
+ StringBuilder nodeIds = new StringBuilder();
+ for (BaseNode n : value) {
+ nodeIds.append(printNodeId(n)+"; ");
+ }
+ if (layout == Layout.PARTITION) {
+ String level = String.format(" subgraph %1$s{%1$s[" + ((this.printDebugVerticalCluster) ? "shape=point, xlabel=\"%1$s\"" : "shape=none, label=\"\"") + "]; %2$s}",
+ levelId,
+ nodeIds.toString());
+ out.println(level);
+ } else {
+ String level = String.format(" {rank=same; %1$s[" + ((this.printDebugVerticalCluster) ? "shape=point, xlabel=\"%1$s\"" : "shape=none, label=\"\"") + "]; %2$s}",
+ levelId,
+ nodeIds.toString());
+ out.println(level);
+ }
+ }
+
+ private static void visitNodes(BaseNode node, String ident, Set visitedNodesIDs, HashMap, List> nodeMap, List> vertexes, Map, Set> levelMap, PrintStream out) {
+ if (!visitedNodesIDs.add( node.getId() )) {
+ return;
+ }
+ addToNodeMap(node, nodeMap);
+ addToLevel(node, levelMap);
+ Sink[] sinks = getSinks( node );
+ if (sinks != null) {
+ for (Sink sink : sinks) {
+ vertexes.add(Vertex.of(node, (BaseNode)sink));
+ if (sink instanceof BaseNode) {
+ visitNodes((BaseNode)sink, ident + " ", visitedNodesIDs, nodeMap, vertexes, levelMap, out);
+ }
+ }
+ }
+ }
+
+ private static void addToNodeMap(BaseNode node, HashMap, List> nodeMap) {
+ nodeMap.computeIfAbsent(node.getClass(), k -> new ArrayList<>()).add(node);
+ }
+
+ private static void addToLevel(BaseNode node, Map, Set> levelMap) {
+ levelMap.computeIfAbsent(node.getClass(), k -> new HashSet<>()).add(node);
+ }
+
+ private static String printNodeId(BaseNode node) {
+ if (node instanceof EntryPointNode ) {
+ return "EP"+node.getId();
+ } else if (node instanceof ObjectTypeNode ) {
+ return "OTN"+node.getId();
+ } else if (node instanceof AlphaNode ) {
+ return "AN"+node.getId();
+ } else if (node instanceof LeftInputAdapterNode ) {
+ return "LIA"+node.getId();
+ } else if (node instanceof RightInputAdapterNode ) {
+ return "RIA"+node.getId();
+ } else if (node instanceof BetaNode ) {
+ return "BN"+node.getId();
+ } else if (node instanceof RuleTerminalNode ) {
+ return "RTN"+node.getId();
+ }
+ return "UNK"+node.getId();
+ }
+
+ private static String printNodeAttributes(BaseNode node) {
+ if (node instanceof EntryPointNode ) {
+ EntryPointNode n = (EntryPointNode) node;
+ return String.format("[shape=circle width=0.15 fillcolor=black style=filled label=\"\" xlabel=\"%1$s\"]",
+ n.getEntryPoint().getEntryPointId());
+ } else if (node instanceof ObjectTypeNode ) {
+ ObjectTypeNode n = (ObjectTypeNode) node;
+ return String.format("[shape=rect style=rounded label=\"%1$s\"]",
+ strObjectType(n.getObjectType()) );
+ } else if (node instanceof AlphaNode ) {
+ AlphaNode n = (AlphaNode) node;
+ return String.format("[label=\"%1$s\"]",
+ escapeDot(n.getConstraint().toString()));
+ } else if (node instanceof LeftInputAdapterNode ) {
+ return "[shape=house orientation=-90]";
+ } else if (node instanceof RightInputAdapterNode ) {
+ return "[shape=house orientation=90]";
+ } else if (node instanceof JoinNode ) {
+ BetaNode n = (BetaNode) node;
+ BetaNodeFieldConstraint[] constraints = n.getConstraints();
+ String label = "\u22C8";
+ if (constraints.length > 0) {
+ label = strObjectType(n.getObjectType(), false);
+ label = label + "( "+ Arrays.stream(constraints).map(Object::toString).collect(joining(", ")) + " )";
+ }
+ return String.format("[shape=box label=\"%1$s\" href=\"http://drools.org\"]",
+ escapeDot(label));
+ } else if (node instanceof NotNode ) {
+ NotNode n = (NotNode) node;
+ String label = "\u22C8";
+ if (n.getObjectType() != null) {
+ label = strObjectType(n.getObjectType(), false);
+ label = label + "(";
+ if ( n.getConstraints().length>0 ) {
+ label = label + " "+ Arrays.stream(n.getConstraints()).map(Object::toString).collect(joining(", ")) + " ";
+ }
+ label = label + ")";
+ }
+ return String.format("[shape=box label=\"not( %1$s )\"]", label );
+ } else if (node instanceof AccumulateNode ) {
+ AccumulateNode n = (AccumulateNode) node;
+ return String.format("[shape=box label=<%1$s
%2$s
%3$s>]",
+ n, Arrays.asList(n.getAccumulate().getAccumulators()), Arrays.asList(n.getConstraints()) );
+ } else if (node instanceof RuleTerminalNode ) {
+ RuleTerminalNode n = (RuleTerminalNode) node;
+ return String.format("[shape=doublecircle width=0.2 fillcolor=black style=filled label=\"\" xlabel=\"%1$s\" href=\"http://drools.org\"]",
+ n.getRule().getName());
+ }
+ return String.format("[shape=box style=dotted label=\"%1$s\"]", node.toString());
+ }
+
+ private static String strObjectType(ObjectType ot) {
+ return strObjectType(ot, true);
+ }
+
+ private static String strObjectType(ObjectType ot, boolean prependAbbrPackage) {
+ if (ot instanceof ClassObjectType) {
+ return abbrvClassForObjectType((ClassObjectType) ot, prependAbbrPackage);
+ }
+ return "??"+ ((ot==null)?"null":ot.toString());
+ }
+
+ private static String abbrvClassForObjectType(ClassObjectType cot, boolean prependAbbrPackage) {
+ Class> classType = cot.getClassType();
+ StringBuilder result = new StringBuilder();
+ if (prependAbbrPackage) {
+ String[] packageToken = classType.getPackage().getName().split("\\.");
+ for (String pt : packageToken) {
+ result.append(pt.charAt(0) + ".");
+ }
+ }
+ result.append(classType.getSimpleName());
+ return result.toString();
+ }
+
+ private static String escapeDot(String string) {
+ String escapeQuote = string.replace("\"", "\\\"");
+ return escapeQuote;
+ }
+
+ public static Sink[] getSinks( BaseNode node ) {
+ Sink[] sinks = null;
+ if (node instanceof EntryPointNode ) {
+ EntryPointNode source = (EntryPointNode) node;
+ Collection otns = source.getObjectTypeNodes().values();
+ sinks = otns.toArray(new Sink[otns.size()]);
+ } else if (node instanceof ObjectSource ) {
+ ObjectSource source = (ObjectSource) node;
+ sinks = source.getObjectSinkPropagator().getSinks();
+ } else if (node instanceof LeftTupleSource ) {
+ LeftTupleSource source = (LeftTupleSource) node;
+ sinks = source.getSinkPropagator().getSinks();
+ }
+ return sinks;
+ }
+}
diff --git a/drools-retediagram/src/test/java/org/drools/retediagram/RuleTest.java b/drools-retediagram/src/test/java/org/drools/retediagram/RuleTest.java
new file mode 100644
index 00000000000..579592633cc
--- /dev/null
+++ b/drools-retediagram/src/test/java/org/drools/retediagram/RuleTest.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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 org.drools.retediagram;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+
+import org.drools.core.reteoo.ReteDumper;
+import org.drools.mvel.CommonTestMethodBase;
+import org.drools.retediagram.ReteDiagram.Layout;
+import org.drools.retediagram.model.Measurement;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.kie.api.KieBase;
+import org.kie.api.event.rule.DebugAgendaEventListener;
+import org.kie.api.io.ResourceType;
+import org.kie.api.runtime.KieSession;
+import org.kie.internal.builder.KnowledgeBuilderConfiguration;
+import org.kie.internal.builder.KnowledgeBuilderFactory;
+import org.kie.internal.utils.KieHelper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.slf4j.bridge.SLF4JBridgeHandler;
+
+public class RuleTest extends CommonTestMethodBase {
+ static final Logger LOG = LoggerFactory.getLogger(RuleTest.class);
+
+ @BeforeClass
+ public static void init() { // route dependencies using java util Logging to slf4j
+ SLF4JBridgeHandler.removeHandlersForRootLogger();
+ SLF4JBridgeHandler.install();
+ java.util.logging.Logger.getLogger("").setLevel(Level.FINEST);
+ }
+
+ @Test
+ public void test() {
+ KieBase kieBase = new KieHelper()
+ .addFromClassPath("/rules.drl")
+ .build();
+
+ LOG.info("Creating kieSession");
+ KieSession session = kieBase.newKieSession();
+
+ LOG.info("Populating globals");
+ Set check = new HashSet();
+ session.setGlobal("controlSet", check);
+
+ LOG.info("Now running data");
+
+ Measurement mRed= new Measurement("color", "red");
+ session.insert(mRed);
+ session.fireAllRules();
+
+ Measurement mGreen= new Measurement("color", "green");
+ session.insert(mGreen);
+ session.fireAllRules();
+
+ Measurement mBlue= new Measurement("color", "blue");
+ session.insert(mBlue);
+ session.fireAllRules();
+
+ LOG.info("Final checks");
+
+ assertEquals("Size of object in Working Memory is 3", 3, session.getObjects().size());
+ assertTrue("contains red", check.contains("red"));
+ assertTrue("contains green", check.contains("green"));
+ assertTrue("contains blue", check.contains("blue"));
+
+ ReteDumper.dumpRete(session);
+ System.out.println("---");
+ ReteDiagram.newInstance()
+ .configLayout(Layout.VLEVEL)
+ // needs: System.setProperty("java.awt.headless", "false"); for: .configOpenFile(true, true)
+ .diagramRete(session.getKieBase());
+ }
+
+ @Test
+ public void testVeryBasic() {
+ String drl =
+ "import org.drools.retediagram.model.*;\n" +
+ "rule R1\n" +
+ "when\n" +
+ " $p : Person( age > 18 )\n" +
+ "then\n" +
+ " System.out.println(\"Person can drive \"+$p);\n"+
+ "end"
+ ;
+ KieSession kieSession = new KieHelper().addContent( drl, ResourceType.DRL )
+ .build().newKieSession();
+
+ ReteDiagram.newInstance().diagramRete(kieSession);
+ }
+
+ @Test
+ public void testManyAccumulatesWithSubnetworks() {
+ String drl = "package org.drools.compiler.tests; \n" +
+ "" +
+ "declare FunctionResult\n" +
+ " father : Applied\n" +
+ "end\n" +
+ "\n" +
+ "declare Field\n" +
+ " applied : Applied\n" +
+ "end\n" +
+ "\n" +
+ "declare Applied\n" +
+ "end\n" +
+ "\n" +
+ "\n" +
+ "rule \"Seed\"\n" +
+ "when\n" +
+ "then\n" +
+ " Applied app = new Applied();\n" +
+ " Field fld = new Field();\n" +
+ "\n" +
+ " insert( app );\n" +
+ " insert( fld );\n" +
+ "end\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "\n" +
+ "rule \"complexSubNetworks\"\n" +
+ "when\n" +
+ " $fld : Field( $app : applied )\n" +
+ " $a : Applied( this == $app )\n" +
+ " accumulate (\n" +
+ " $res : FunctionResult( father == $a ),\n" +
+ " $args : collectList( $res )\n" +
+ " )\n" +
+ " accumulate (\n" +
+ " $res : FunctionResult( father == $a ),\n" +
+ " $deps : collectList( $res )\n" +
+ " )\n" +
+ " accumulate (\n" +
+ " $x : String()\n" +
+ " and\n" +
+ " not String( this == $x ),\n" +
+ " $exprFieldList : collectList( $x )\n" +
+ " )\n" +
+ "then\n" +
+ "end\n" +
+ "\n";
+
+ KnowledgeBuilderConfiguration kbConf = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
+ KieBase kbase = loadKnowledgeBaseFromString(kbConf, drl);
+
+ KieSession ksession = kbase.newKieSession();
+
+ int num = ksession.fireAllRules();
+ // only one rule should fire, but the partial propagation of the asserted facts should not cause a runtime NPE
+ assertEquals( 1, num );
+ ReteDiagram.newInstance().configLayout(Layout.PARTITION).diagramRete(ksession);
+ }
+
+
+ @Test
+ public void testLinkRiaNodesWithSubSubNetworks() {
+ String drl = "package org.drools.compiler.tests; \n" +
+ "" +
+ "import java.util.*; \n" +
+ "" +
+ "global List list; \n" +
+ "" +
+ "declare MyNode\n" +
+ "end\n" +
+ "" +
+ "rule Init\n" +
+ "when\n" +
+ "then\n" +
+ " insert( new MyNode() );\n" +
+ " insert( new MyNode() );\n" +
+ "end\n" +
+ "" +
+ "" +
+ "rule \"Init tree nodes\"\n" +
+ "salience -10\n" +
+ "when\n" +
+ " accumulate (\n" +
+ " MyNode(),\n" +
+ " $x : count( 1 )\n" +
+ " )\n" +
+ " accumulate (\n" +
+ " $n : MyNode()\n" +
+ " and\n" +
+ " accumulate (\n" +
+ " $val : Double( ) from Arrays.asList( 1.0, 2.0, 3.0 ),\n" +
+ " $rc : count( $val );\n" +
+ " $rc == 3 \n" +
+ " ),\n" +
+ " $y : count( $n )\n" +
+ " )\n" +
+ "then\n" +
+ " list.add( $x ); \n" +
+ " list.add( $y ); \n" +
+ " System.out.println( $x ); \n" +
+ " System.out.println( $y ); \n" +
+ "end\n";
+
+ KnowledgeBuilderConfiguration kbConf = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
+ KieBase kbase = loadKnowledgeBaseFromString(kbConf, drl);
+
+ KieSession ksession = kbase.newKieSession();
+ List list = new ArrayList();
+ ksession.setGlobal( "list", list );
+
+ ksession.fireAllRules();
+
+ assertEquals( 2, list.size() );
+ assertEquals( 2, list.get( 0 ).intValue() );
+ assertEquals( 2, list.get( 1 ).intValue() );
+
+ ReteDiagram.newInstance().configLayout(Layout.PARTITION).diagramRete((KieSession)ksession);
+ }
+
+ @Test
+ public void testMArio() {
+ String drl =
+ "rule R1y ruleflow-group \"Y\" when\n" +
+ " Integer() \n" +
+ " Number() from accumulate ( Integer( ) and $s : String( ) ; count($s) )\n" +
+ "then\n" +
+ " System.out.println(\"R1\");" +
+ "end\n" +
+ "\n" +
+ "rule R1x ruleflow-group \"X\" when\n" +
+ " Integer() \n" +
+ " Number() from accumulate ( Integer( ) and $s : String( ) ; count($s) )\n" +
+ "then\n" +
+ " System.out.println(\"R1\");" +
+ "end\n" +
+ "" +
+ "rule R2 ruleflow-group \"X\" when\n" +
+ " $i : Integer()\n" +
+ "then\n" +
+ " System.out.println(\"R2\");" +
+ " update($i);" +
+ "end\n";
+
+ KieSession kieSession = new KieHelper().addContent( drl, ResourceType.DRL )
+ .build().newKieSession();
+
+ ReteDiagram.newInstance().diagramRete(kieSession);
+ }
+
+ @Test
+ public void testMario20161021() {
+ String drl = "import java.util.Set;\n" +
+ "declare Notification end\n" +
+ "declare Fall end\n" +
+ "declare NetworkElement end\n" +
+ "rule R1 when\n" +
+ " $notification : Notification()\n" +
+ " $epcs : Set() from collect( Fall() )\n" +
+ " then\n" +
+ "end\n" +
+ "\n" +
+ "rule R2 when\n" +
+ " $notification : Notification()\n" +
+ " not Fall()\n" +
+ " $epcs : Set() from collect(Fall())\n" +
+ "\n" +
+ " then\n" +
+ "end\n" +
+ "\n" +
+ "rule R3 when\n" +
+ " $ne : NetworkElement()\n" +
+ " then\n" +
+ "end";
+
+ KieSession kieSession = new KieHelper().addContent( drl, ResourceType.DRL )
+ .build().newKieSession();
+ ReteDumper.dumpRete(kieSession);
+ ReteDiagram.newInstance().diagramRete(kieSession);
+ }
+
+ @Test
+ public void testTzimani20161021() {
+ String drl = "import java.util.Set;\ndeclare Person end\ndeclare FactWithCheese end\ndeclare Cheese end\n"+
+ "rule \"R1\"\n" +
+ " when\n" +
+ " $person : Person()\n" +
+ " $aFacts : Set() from collect( FactWithCheese() )\n" +
+ " then\n" +
+ "end\n" +
+ "\n" +
+ "rule \"R2\"\n" +
+ " when\n" +
+ " $person : Person()\n" +
+ " not FactWithCheese()\n" +
+ " $aFacts : Set() from collect( FactWithCheese() )\n" +
+ "\n" +
+ " then\n" +
+ "end\n" +
+ "\n" +
+ "rule \"R3\"\n" +
+ " when\n" +
+ " $cheese : Cheese()\n" +
+ " then\n" +
+ "end";
+
+ KieSession kieSession = new KieHelper().addContent( drl, ResourceType.DRL )
+ .build().newKieSession();
+ ReteDumper.dumpRete(kieSession);
+ ReteDiagram.newInstance().diagramRete(kieSession);
+ }
+
+ @Test
+ public void testDROOLS1360() {
+ String drl =
+ "import " + AtomicInteger.class.getCanonicalName() + ";\n" +
+ "global java.util.List list;\n" +
+ // "rule R1y when\n" +
+ // " AtomicInteger() \n" +
+ // " Number() from accumulate ( AtomicInteger( ) and $s : String( ) ; count($s) )" +
+ // " eval(false)\n" +
+ // "then\n" +
+ // "end\n" +
+ "\n" +
+ "rule R2 when\n" +
+ " $i : AtomicInteger( get() < 3 )\n" +
+ "then\n" +
+ " $i.incrementAndGet();" +
+ " insert(\"test\" + $i.get());" +
+ " update($i);" +
+ "end\n" +
+ "\n" +
+ "rule R1x when\n" +
+ " AtomicInteger() \n" +
+ " $c : Number() from accumulate ( AtomicInteger( ) and $s : String( ) ; count($s) )\n" +
+ " eval(true)\n" +
+ "then\n" +
+ " list.add($c);" +
+ "end\n"
+ ;
+
+ KieSession kieSession = new KieHelper().addContent( drl, ResourceType.DRL )
+ .build().newKieSession();
+ ReteDumper.dumpRete(kieSession);
+ ReteDiagram.newInstance().diagramRete(kieSession);
+ }
+
+ @Test
+ public void testDROOLS_1326_simple() {
+ String drl = "package "+this.getClass().getPackage().getName()+";\n" +
+ "import "+MyPojo.class.getCanonicalName()+"\n" +
+ "global java.util.Set controlSet;\n" +
+ "rule R1\n" +
+ "when\n" +
+ " $my: MyPojo(\n" +
+ " vBoolean == true,\n" +
+ " $s : vString, vString != null,\n" +
+ " $l : vLong\n" +
+ " )\n" +
+ " not MyPojo(\n" +
+ " vBoolean == true,\n" +
+ " vString == $s,\n" +
+ " vLong > $l\n" +
+ " )\n" +
+ "then\n" +
+ " System.out.println($my);\n" +
+ " System.out.println($l);\n" +
+ " controlSet.add($l);\n" +
+ "end";
+ System.out.println(drl);
+
+ KieSession session = new KieHelper().addContent(drl, ResourceType.DRL).build().newKieSession();
+ ReteDumper.dumpRete(session);
+ ReteDiagram.newInstance().diagramRete(session);
+ }
+
+ @Test
+ public void testDROOLS_1326_simple_again() {
+ String drl = "package "+this.getClass().getPackage().getName()+";\n" +
+ "import "+MyPojo.class.getCanonicalName()+"\n" +
+ "global java.util.Set controlSet;\n" +
+ "rule R1\n" +
+ "when\n" +
+ " $my: MyPojo(\n" +
+ " vBoolean == true,\n" +
+ " $s : vString, vString != null,\n" +
+ " $l : vLong\n" +
+ " )\n" +
+ " not MyPojo(\n" +
+ " vBoolean == true,\n" +
+ " vString.equals($s),\n" +
+ " vLong > $l\n" +
+ " )\n" +
+ "then\n" +
+ " System.out.println(\"->> firing with \"+ kcontext.getKieRuntime().getFactHandle($my) );\n" +
+ " System.out.println(\"->> firing with \"+$l);\n" +
+ " controlSet.add($l);\n" +
+ "end";
+ System.out.println(drl);
+
+ KieSession session = new KieHelper().addContent(drl, ResourceType.DRL).build().newKieSession();
+ ReteDumper.dumpRete(session);
+ ReteDiagram.newInstance().diagramRete(session);
+ }
+
+ public static class MyPojo {
+ private boolean vBoolean;
+ private String vString;
+ private long vLong;
+
+ public MyPojo(boolean vBoolean, String vString, long vLong) {
+ super();
+ this.vBoolean = vBoolean;
+ this.vString = vString;
+ this.vLong = vLong;
+ }
+
+ public boolean isvBoolean() {
+ return vBoolean;
+ }
+
+ public boolean getvBoolean() {
+ return vBoolean;
+ }
+
+
+ public String getvString() {
+ return vString;
+ }
+
+ public long getvLong() {
+ return vLong;
+ }
+
+ public void setvBoolean(boolean vBoolean) {
+ this.vBoolean = vBoolean;
+ }
+
+ public void setvString(String vString) {
+ this.vString = vString;
+ }
+
+ public void setvLong(long vLong) {
+ this.vLong = vLong;
+ }
+ }
+
+ public static class LongHolder {
+
+ private final Long value;
+
+ public LongHolder(Long value) {
+ this.value = value;
+ }
+
+ public Long getValue() {
+ return value;
+ }
+
+ }
+
+ public static class WeirdObject {
+ private Integer status;
+ private WeirdNested nested;
+
+ public WeirdObject(Integer status, WeirdNested nested) {
+ this.status = status;
+ this.nested = nested;
+ }
+
+ public Integer getStatus() {
+ return status;
+ }
+
+ public Long added() {
+ return nested.getId();
+ }
+
+ public WeirdNested getNested() {
+ return nested;
+ }
+
+ }
+ public static class WeirdNested {
+ private Long id;
+
+ public WeirdNested(Long id) {
+ this.id = id;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ }
+
+ @Test
+ public void testCheck() {
+ String drl = "package i.p;\n"
+ + "import " + LongHolder.class.getCanonicalName() + "\n"
+ + "import " + WeirdObject.class.getCanonicalName() + "\n"
+ + "import " + TestObjectEnum.class.getCanonicalName() + "\n"
+ + "rule fileArule1 when\n"
+ + " $t : LongHolder()\n"
+ + " WeirdObject(added == $t.value, status == TestObjectEnum.ONE.getValue() )\n"
+ + "then\n"
+ + "end\n"
+ + "rule fileArule2 when\n"
+ + " $t : LongHolder()\n"
+ + " WeirdObject(nested.id == $t.value, status == 0 )\n"
+ + "then\n"
+ + "end\n"
+ ;
+
+ String drl2 = "package c.t.p;\n"
+ + "import " + WeirdObject.class.getCanonicalName() + "\n"
+ + "import " + TestObjectEnum.class.getCanonicalName() + "\n"
+ + "rule fileBrule1 when\n"
+ + " WeirdObject(status == 1)\n"
+ + "then\n"
+ + "end\n"
+ ;
+
+ KieSession kieSession = new KieHelper()
+ .addContent(drl2, ResourceType.DRL)
+ .addContent(drl, ResourceType.DRL)
+ .build().newKieSession();
+
+ ReteDiagram.newInstance().diagramRete(kieSession);
+ kieSession.addEventListener(new DebugAgendaEventListener());
+
+ kieSession.insert(new LongHolder(12345L));
+ kieSession.insert(new WeirdObject(1, new WeirdNested(12345L)));
+
+ kieSession.fireAllRules();
+ }
+
+ public static class TestObjectFunction {
+ public static int return1() {
+ return 1;
+ }
+ }
+
+ public static enum TestObjectEnum {
+ ZERO(0),
+ ONE(1);
+ private int value;
+ TestObjectEnum(int value) {
+ this.value = value;
+ }
+ public int getValue() {
+ return this.value;
+ }
+ }
+
+ @Test
+ public void test20180111() {
+ String drl = "global java.util.List list;\n" +
+ "rule R0 when\n" +
+ " $i : Integer( intValue == 0 )\n" +
+ " String( toString == $i.toString )\n" +
+ "then\n" +
+ " list.add($i);\n" +
+ "end\n" +
+ "rule R1 when\n" +
+ " $i : Integer( intValue == 1 )\n" +
+ " String( toString == $i.toString )\n" +
+ "then\n" +
+ " list.add($i);\n" +
+ "end\n" +
+ "rule R2 when\n" +
+ " $i : Integer( intValue == 2 )\n" +
+ " String( toString == $i.toString )\n" +
+ "then\n" +
+ " list.add($i);\n" +
+ "end\n" +
+ "rule R3 when\n" +
+ " $i : Integer( intValue == 2 )\n" +
+ " String( length == $i )\n" +
+ "then\n" +
+ " list.add($i);\n" +
+ "end";
+ KnowledgeBuilderConfiguration kbConf = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
+ KieBase kbase = loadKnowledgeBaseFromString(kbConf, drl);
+
+ KieSession ksession = kbase.newKieSession();
+ ReteDiagram.newInstance().configLayout(Layout.PARTITION).diagramRete(ksession);
+ }
+}
\ No newline at end of file
diff --git a/drools-retediagram/src/test/java/org/drools/retediagram/model/Cheese.java b/drools-retediagram/src/test/java/org/drools/retediagram/model/Cheese.java
new file mode 100644
index 00000000000..397029e862d
--- /dev/null
+++ b/drools-retediagram/src/test/java/org/drools/retediagram/model/Cheese.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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 org.drools.retediagram.model;
+
+public class Cheese {
+
+ private final String name;
+
+ public Cheese(String name) {
+ super();
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+}
diff --git a/drools-retediagram/src/test/java/org/drools/retediagram/model/Measurement.java b/drools-retediagram/src/test/java/org/drools/retediagram/model/Measurement.java
new file mode 100644
index 00000000000..31f96d4fb49
--- /dev/null
+++ b/drools-retediagram/src/test/java/org/drools/retediagram/model/Measurement.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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 org.drools.retediagram.model;
+
+public class Measurement {
+ private String id;
+ private String val;
+
+ public Measurement(String id, String val) {
+ super();
+ this.id = id;
+ this.val = val;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getVal() {
+ return val;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("Measurement [");
+ if (id != null)
+ builder.append("id=").append(id).append(", ");
+ if (val != null)
+ builder.append("val=").append(val);
+ builder.append("]");
+ return builder.toString();
+ }
+
+
+}
diff --git a/drools-retediagram/src/test/java/org/drools/retediagram/model/Person.java b/drools-retediagram/src/test/java/org/drools/retediagram/model/Person.java
new file mode 100644
index 00000000000..6ecbfb1977b
--- /dev/null
+++ b/drools-retediagram/src/test/java/org/drools/retediagram/model/Person.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2021 Red Hat, Inc. and/or its affiliates.
+ *
+ * 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 org.drools.retediagram.model;
+
+public class Person {
+ private String name;
+ private long age;
+ private Cheese favouriteCheese;
+
+ public Person(String name, Cheese favouriteCheese) {
+ this.name = name;
+ this.favouriteCheese = favouriteCheese;
+ }
+
+ public Person(String name, long age) {
+ this.name = name;
+ this.age = age;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Cheese getFavouriteCheese() {
+ return favouriteCheese;
+ }
+
+ public long getAge() {
+ return age;
+ }
+}
diff --git a/drools-retediagram/src/test/resources/logback.xml b/drools-retediagram/src/test/resources/logback.xml
new file mode 100644
index 00000000000..b8d959f55c7
--- /dev/null
+++ b/drools-retediagram/src/test/resources/logback.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+ %date{HH:mm:ss.SSS} [%thread] %-5level %class{36}.%method:%line - %msg%n
+
+
+
+
+
+
+
+
+
+
+
diff --git a/drools-retediagram/src/test/resources/rules.drl b/drools-retediagram/src/test/resources/rules.drl
new file mode 100644
index 00000000000..136d286ce58
--- /dev/null
+++ b/drools-retediagram/src/test/resources/rules.drl
@@ -0,0 +1,41 @@
+package org.drools.retediagram.model;
+
+global java.util.Set controlSet;
+
+rule "For color"
+no-loop
+when
+ Measurement( id == "color", $colorVal : val )
+then
+ controlSet.add($colorVal);
+end
+
+rule "Likes cheddar"
+when
+ Cheese( $cheddar : name == "cheddar" )
+ $person : Person( favouriteCheese == $cheddar )
+then
+ System.out.println( $person.getName() + " likes cheddar" );
+end
+
+rule "Don't like cheddar"
+when
+ Cheese( $cheddar : name == "cheddar" )
+ $person : Person( favouriteCheese != $cheddar )
+then
+ System.out.println( $person.getName() + " does not like cheddar" );
+end
+
+rule "Color count"
+when
+ accumulate( $m: Measurement( id == "color" ); $c: count($m) )
+then
+ System.out.println( $c );
+end
+
+rule "Not a Color"
+when
+ not ( Measurement( id == "color" ) and String() )
+then
+ System.out.println( "no color yet." );
+end
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 4d745d28933..d0a0d52d821 100644
--- a/pom.xml
+++ b/pom.xml
@@ -86,6 +86,7 @@
drools-engine
drools-engine-classic
+ drools-retediagram