From 5dacbf5389051913e58ac494a6d3370ea80c7344 Mon Sep 17 00:00:00 2001 From: Matteo Mortari Date: Mon, 12 Apr 2021 18:36:31 +0200 Subject: [PATCH] DROOLS-6271 Drools phreak rete diagram (#3530) * Initial commit * Print nodes by layer to try align left the LIA. * clean. * prepare for unknown nodes. * le bug correction. * Collecting at level 1-5 all the required nodes. * MANUAL experiment for subnetwork. * Trying to collate as possible the subnetwork. * Fixing breaking when complex subnetwork although same level nodes. * Fixing beta node constraints and classpath problems. * fixes. * Avoiding problem when using as a dependency (until merged upstream). * comsmetics on the not display. * with partitioning instead of vertical layering. * Still trying to merge the 2 approaches, for now it's a switch b/w them. * with the files. * Moar tests * latest imgs. * wrong loop nesting. * renaming to diagramRete() * Clean-up. * Adding browser FIREFOX, tested works. * Aligning with DROOLS-1024. * print debug vertical cluster flag in configuration * bugfix * oops commit * Bump junit from 4.12 to 4.13.1 (#1) Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * realign * Migrate to graphviz-java * Use platform agnostic open file * main refactors without package renames * Refactor naming and package to drools-retediagram as agreed * subdir drools-retediagram * move into drools-retediagram * wire module into the build * dependency mgt * update logback for proper package Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- drools-retediagram/.gitignore | 19 + drools-retediagram/README.md | 126 ++++ drools-retediagram/example1.png | Bin 0 -> 50513 bytes drools-retediagram/example2.png | Bin 0 -> 30114 bytes drools-retediagram/pom.xml | 69 ++ .../org/drools/retediagram/ReteDiagram.java | 532 +++++++++++++++ .../java/org/drools/retediagram/RuleTest.java | 605 ++++++++++++++++++ .../org/drools/retediagram/model/Cheese.java | 32 + .../drools/retediagram/model/Measurement.java | 50 ++ .../org/drools/retediagram/model/Person.java | 45 ++ .../src/test/resources/logback.xml | 33 + .../src/test/resources/rules.drl | 41 ++ pom.xml | 1 + 13 files changed, 1553 insertions(+) create mode 100644 drools-retediagram/.gitignore create mode 100644 drools-retediagram/README.md create mode 100644 drools-retediagram/example1.png create mode 100644 drools-retediagram/example2.png create mode 100644 drools-retediagram/pom.xml create mode 100644 drools-retediagram/src/main/java/org/drools/retediagram/ReteDiagram.java create mode 100644 drools-retediagram/src/test/java/org/drools/retediagram/RuleTest.java create mode 100644 drools-retediagram/src/test/java/org/drools/retediagram/model/Cheese.java create mode 100644 drools-retediagram/src/test/java/org/drools/retediagram/model/Measurement.java create mode 100644 drools-retediagram/src/test/java/org/drools/retediagram/model/Person.java create mode 100644 drools-retediagram/src/test/resources/logback.xml create mode 100644 drools-retediagram/src/test/resources/rules.drl 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 0000000000000000000000000000000000000000..e5df317fb28c9ba95530722832a163aff8edb841 GIT binary patch literal 50513 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 z z=Qhz0;oW!da+ZMp$%gbU6gC#`V4V3Kat6!gHQl7%7ulJyWnVW$c@L7Y|c2|zHR17_r ziQF#hH`#T~8sGCTwK=yW?9g;Fr%zMu-0QOMa%0AYuI>Xf%24HmJ;2GnzGp01BKyt3 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 G-v1AZZfyww literal 0 HcmV?d00001 diff --git a/drools-retediagram/example2.png b/drools-retediagram/example2.png new file mode 100644 index 0000000000000000000000000000000000000000..6b298b1d23ddfd3bd2dae0f13cec3b81e3ebe41b GIT binary patch literal 30114 zcmYg%1yoew);2M8cf$Zfmw>|10z*nmcZx97kRsjPh$1DaDBay9DTs6l!jK}Mbo>Xs z_rKp+%e7?AI&bXW&)z#mOGAkOmlhWV1%*IG8K#4Rf(}7JK@-Hr1b%6I=Klr-#mhkj zCae2mVLwN`%-q1MQ+>TpW8FVhPFig}IA2510k?$O_LAsfU7}+&PR4^6R8d^Z*nIw$ zVty}XITu7qK9_rnLc+9ag=0PcSeK5J@+(yuLq}b^wGi3TzOFEQAO(FwSa~BF?8^$rd0ip`bOu)j2xMuz|S_4$o=`+CyX1eVJ~i8N!uI|llr|bFdgPKPqr&q833RmqSEqcw z*Pl7^MsIHq?*exco{hatS+`v{BmcXcvViaR2&$Ub<}V%QH(+N`w=cAl`!+A1B?lFGm%0`>7!w$+?NY2IjuLd@TDLWlEYKq|Pi{euDi z#e)j>_eTd|U5sW5k1)!3?;CpWOcl$@n3F?}pmxUZHFAu}A%`z#TWd4}kJI9kBA`u| zyPr-5&kk2Re*`2lO1)k7UUY6I_Fa$RsQLO{Q=sFvI}A$+H}(9g0L&G1OAO&MrD8fe zdc{a}&zGThoVckdAtWrJtJOclkgxht3&IA)IL~l%121>_b@)^pW7#Y-oB$Txb5EZf zQrUiL+;PgWD_C8$6=~U#EUahi{KRq51Kz2$5UcbnFghMRUAF+SW-}@~f?8*(U ztGNh4VYNH*)<^++n}RJ!S3}rQzf?SMqezdZuT}L2Ats$b^OGEM^GTi6rHlNlsr4 z>)%I>^YRtwBbrYg%0;O(@~>R?EBl?xA|G+AM0hU*LtQb?+oF|c(J&{EG+=-@3R!aM zaSPN@0s?t2jYGO^=j(A78o#jBSV#WtnWEU__Fe9@p|PE>^#n! z(BHlz*c=A?E4cN)dYNt?xblq#vTtq-^Hh#T;Na~j!0tC;(cwzBE4SzjETZOOY+QM{ zmpVqLa2agYaQ|g0w)gz50Fl&hE$@B`gLn$a-c0#OBE8r;PTC5N%dA`spdJv^{hDtU z#IzfI%JfXPzoMk9{&O~5R~@q06wXC}HuuL@w)>(D*O7G}Wbk>>^Ph8lJ8JE`Y9)&y znaSK=Y@8rtXSr7}l2XD`CaZ}QMCgy*PnF~RW-UK((#)-1^WCol2{miD9NS2e$u;v? z_Tq^p2|VNJJm#6Nwi&FMHTN4MV{h-~H@WAC3aHiT50!{y+d;!(O&Au@79BY90W<6I zmT0G##ZVxiT&8Y67wUOh?{2S7%3`CV|LXb{Wh$Mh%V@6O&*tkpfzE4p@7?Lr{X(Xj z=cm7ViR>pNPt;BU3i4iL3EPkVK->C~FFz8<@t-Tlyg2^MQ9G6;nq*4Va|qQBWmp6> zAkTTBj%y+puqXG@YRh7Xe%wcTR;YRy^M6DNlb3~q4&YcK#Tm83lVlXWP%27FClcO- z|HI^ks7JI!(&v^9erG?MXW`Rq%CQ$ zjLUxy@tfw9+RAxgvQUY~Z;TzJ^0;($JV(Nrg!iw6rF3*Nvetc*3b^zvu3Rhv4i`Lqek7^Q3_BP-dC4`(%{+a<|k!DW##DQ~XvB2R?#aDk#aO;B-+EURBc-@Y9_)-q8co@48$xgLkF^doEn3y3 zV88h3oJZTCV4MPl2Yd-*Ju!b}un5EO$5<|d-|7BmVJxq&Y_g=*yvCrDO1@Bqzv?_i zyf0YewKua5$XL`@i`vi?6OA$;QR?~4We;ytZWye%+PuqFQgCvt&+gWHk| zS0`#+mpn82`%TqI#=UZo0;*e!xH#TygT?(JGPA}Gg;=V`@P&Tu|DQ^$PxQx8)h5u1 zZpF>D4kUwcz68Yao8HUnUn_I^@{{ol^vgwd&?LW_0tH8h;fu*t>KJXJDIngzYeow zrzFdXL{e+Nh7B-PDX^-MnPDxKK1Np9`qk7X7lo`2eWxiCsE{EuGU9&%PfIWlkk5+5 zE3e?N6eo2Znn>C0q|fzF7FUkf;{>++;_0RRn|$)5^kmWcDZ4%8$^NnO(6alXUwk$c zQ9zaV+UarnGJ;;*lT#)(skoto#TW$m*HwT;f8!Dtp^shyiM)0)0mjO4y#brA44%!J z`It3W^+vJ{KAJU66S&ksbo*2*JUDp!A4#H_nHQBY!lzrDQuvx~P8U6*a>~%m{0%*-eNVQ= z4+U#TZBbmBzDL@Hh7VqHR_!)z)nPt+I^rI;Mig}3JFjP8w)r-V(E2|{NzT!%A@Eli z(+@dF_Gy-DhuBpghxBBel%jsrR=b$zPrK&VugFY5{-8 zVWHV)Og}Jkda#(bq^tAyhV7vlwNOvM1drw1sAq|I?Vi_LT-CR+@sk|o-R#@vK<#Y> zhQ@zo&ec^8A3mwe+YWqHsgkv{-0EWwY__KcH?5)4l=`FFU_X(2eWLfrY^O<4g{mNY ziqE^{aN*6*Y^nvflvgT61q?Z7jN+gKm1T*XH?R=gs1N<=tiF z$qmPOnNkv?b9vTG1Rj-pR00MPh`)VHB2&}3I}bI3F27y}eDqhJd0N$a6M~L)JceQ9 z@pXnh)t?cDJ$oF(kvk+ql~-AD6c&AOzkByw9EyS|>+p}iK1GKMI@P%BZSwQoDHpD) zCzDc)vP+u88`faAQDPP-JRp*keZ~^3Hgx>ZXbKh?dFK37ci2-FFI;q%w|LCpZ;VU; zjfhT>_F*n4q?}x2??WwCNUu3qY!nc1rNf1_e-=++_;lsR{PGlt3SK{J&U$&*^y0|6 z0ni)ME{(AN8jP==^DIJRXh+PROa(ta{EWv)6i328@Vy|xwGAX@_j;x}7g==#zo{Qll=WLaj?6} zlf%C(dMf)nE74e<&3HkdYt5~wG)EJo4}0>E8-tqRb9@OAQH^=}_rEf4%ktrdy+G`T z6viA?IKGzN`0t~Q!4rl3Qdz}lkv6@@R1arXhkm`2r#%_t{m%z;77z?1t^H-DeQxQG z`nmmI^Y(e_E@jDM;6+^w-i%5iINNe)8IQV<{{Oc_2(t#mDpS4b){*3I&=e z$FqdSHD-BY&?MKre@Lmu54ced-j?$p8_5*5Pc^TKqyAKoP&4j`rjb$b^xjPreEo%~ zr{MHir25l~k6MvW`!8uQVjBM0*Qa6xW!;>wy}!Rnbfg;HfK4IH=iw~x3aMHHD?w&e zyY8o1T|aXV|5QEPZ8p zm^joTIQXJ-U2x=9z(4XnnYzsr)K_rWdWbZT{p$%zryZ#EX6jQt#sewt6G;ZXmV;l1 zMFsCFj;l+AA%z`ShZd9+X&oycI`xvLeRAICu=ekb{o&0mMA*DVMsDK>GL3OUpR+&U z^N(C#s6MuLT)$3~aQ!dg*6_=>h=GVSx+;UUo=5C!**EHM)>KMy{OV8(7~EG%M4PoG zJ~+g>=*^H&Cx?WVzf|Dh6^l2NJq9ewrEK_H$}#6NRJP}A%{19-{m`Zy^A6cYAxa8C z(~PQ<@;l|dxV0^M5sy!Bkj=_H&Yqa9bX&3wI+pe{V%?RBp>~o_{&jQi@1JKqzz4o^ zJO1(cS}eO$yK*d-`Z`hY(XO96 zZZ=U7rLJf5(fJkiS36lDZq{C(A1A_?FWQ?bY$=Rp`@9nNdXO8!#MIIF&$N?&%>haL ztGlrnGLGY5)K275-}#Sh;l>x)tm7)LLi_$3na~p@`eLYvt+%}ONUUuGG$(WFW zFbre_{D}%#C~->H#OB`kqpgz@v5m_N44esMi#vHg8Cvx0cjJMR=JD(J7k2ar!gD%l zbUPE~@Yv9B#ppaoF#BV*{=wN#>qV5sD-#~-6)*fWgxq#SP3tBQkF&v)YGKfgrH9<+YO-iJPq znoBk@coeU`=cfFg3&+toIlYq%`SDFAN}sZSi8eC#kZSzSG0(m@Kzle85{^LZM}WB` z?l$33!H>_uXrfMY4|I}~=cD0zZ6eh%TtRfJDYLYCr*+1kDGw<;QW3;^ImC}fYqdB0 zfmG~bl}GuX$0LErBEsslLyNA@6l8VC8NdJf{zcnSNLNx2BcV;XPdUOP?_Fh2vNV}tqXO*$}pbo*(QgoZ_*^iSA|^{6D8jmO+7eV(Zc4M$f>oJHCf+?Y{9{E?hl zID_f&n(1nQuAb*+lig;_u1Tg|tVt?VVYIuZ`*_4^E(+r65QLPiUQ$&dM35nxPgBG>D=df-Y%v%r26p~CL~c9WZdL(cZ@f#T@G-F?GIo zVSGVvA9?m;)$L7)I~~1U_Qv`umUc-*uRia$@AlcOU^%tO;Oz&MOoxjKvOPUphD)P9 z8xmmbCTVC}zlWAI6M|n3ca3+pMQbUorp z(v&9J5UzM)>SO+TVh|u}wYEK??Db#(@p|rrW(KFUK+=g6RMa%fu(8M9vSP5ndq6Kf z!nzkE9aJW`Aw00>K7Qe!Z8IL=Ke6tyMFIJby~V;tl4B8ZWPRYCRHY|$>_B=|UoGX2kZIiS*?5mPUHiIHR>k6t0A>Cfy{i7!*A+xmf^Hw?#~ z7|h>1^kq=hf3_Tl@(~C}i(XBvvPEA{nQ;%Rx3DM=&A6E<)l0eHN;4rbLrfmwrh0-t z7kR)L7yqV?nZbsBA!L!YG)DN@(yEp$Vcla?`6Osz_*h;`lCOV4(t*r`fWBVn2ar1cwiOBYV-OQYPD6c98$+Zh0r0{k)!x(4eyc)0ijo6=(# zJ9-q3eNI?jp-+&lpqtu`3t6#t00u`pyx7pI>n$GWM_XClHk9Jyf?%#MTW^GuV5Z!C z)&RjHxtYVs-iHYm%3hExJ99eV~hfm3We zrd)9xo3gX)VE%TWuJ&cr&L1}1W7M+@tW+iu$h+wGum#I1=47q8shp&NS73qjdo_}^8_>Ss)n#3 z5kE9oUQlK>5K0Amm?HP#T3wguyTg*fBK2ixXjmXh>q;UT!Z|pa@_n4) zK#@&APhIeCKdQ?bk!@b^Z2H}az@&CIEKn1RdtuR7^&26tBh$w)hD6VAHgWhW6mXqO zw3A07c4fF*QX$dI_DG#DUI>lBe)Bz+K-+xe_O-hd92erD7$ElNPr6>p=3G78AQzSS zQUixFv2t8VH2-(Po@mjY{KGtivWkUSWloPcw(9gQEuF2>Q&MmF$RpV`u82ntfP|z( zGCOL_a={dA#iN6@jTJo8^qbQAh^6cpqU`i;A|9ccPB2H4f`m0ZbKp|MbE6o2r&?C# zX_I9%fpIBKGSr^;c+V)l*AY}dU5li;2=mP&hV<68Ic@WPW6(_CZ{Jk{ zqh&FOwR&&x#~Iu@)Pgts@c8+b9M*-bR0{faTKYayva4yZH!<5BL(Y8Q0zu99^grrf zp*?>!Plep&bb9*fGldw!NjLhCxod%`1DJx5)9%~ETj7Jmt#}f8ZI(N|`p?ep{;Y3F zCO0UL4RFw;7SvexuLz6ZfFQ&r>hmb{M*Ael>e!sMVw$GcZiiqAq4GC%e*qOz1gyDw z;|+-~{`YQzJUST-jw}>+d1Wi~F5S$ckr~eD>y30F^P#t1UZQ8j^Vs{u-<L4$BP`Yni*)Qv}~DPWv!V5-E0fvm-MyO%V8H0>Nr>Lg2@5W{2&IvdFvxCHqoUo zMjPD(VM7!&LHc};5=^YvgX_it$`)i`ZBq8u2mgpy&Cy8Mvmq)Ty;>w7sBCOE7ec%65%r^x|Yp6q)(D?nLHJ__P^YAW>yprt(7I?pVMk zZ}!g^4Ds?kjSu4=20-LfK>6em&*9ZS9PcBBJC#hb_Ma!~HZ&r(dWz2egH3h;J<$7N zhd>5{P&Bs?SK2clPW6p9ptrijWnUEkBFyZeFFk7L$`0Nwv3m2ab_NBlbVt=QmJAjP zJTT`vpvGW{*k6HCA7URh%m_~ttP)v?sE;fsSH9&mUC~HXU|K`-Sgak52OUX#2 zH?7hcM)}vhD2J^DR~x23XJa$4l{u%1N*FXdVfGS)zb^MRDm8^0%c4blw#X3RP=eBA z-+K3U1MXPs^7FdeEyX?93qjMsqlrDT`*S%!yk66~MzoVVJmVg4o2~ky|Abgh*`S7Ar5WJIs_8 z!*WyjBuZuGI4Dgo-}BQU8)Gz2P2s+g5MC`VX_s_!E&%AsvY?ZKc?WuTtS9-km!+$80AE!UB0*(62L%aM?B=atj%Q~C4o_e)U*BeGnKRck)P}_ zo%wFXXMgbH?Z_|`t3r{*Lsg6CEhCh8Uj~7T!#_Qnf1a?0Li47hoWn{p*+{|gRVZl4 zT>JK!SZUoq9J~e%=EG%giOR^DvkgZNkX1ZKQ<-O z=y2{IolhvalszJ-RLG{9#oQWDTO6XI;9S5(T>-D?%@s z2ErOT2Bqv~mYCuV73od}QbxUX!b5xi#5!E(gG#M}Gj8fPX@T<0>MVNOmve&Yj=ShAHzsc8hxZZ(bl)Rajh;{CS zj^_%1aj$zkz10WecXL5(nQ#@`uB!5~`Z#zJCwV63U!V;WM^uT$Ym$M-C4+_QtM2P^ z2Y?grQ1_ZX79{?mt&Jtw*ED0Z^#ZV4F?=f{a>#WeTy2NPPhgUb54L+&rzI;qD>(-z$?E#8E-^|AL z*h_Ynqu?6A02$_M8gPebzjPl0W#ELIL*OcbY1bCAJr^628KUnbfwC~a-1EFdXg)~O zmiBMpv4a66VTOG$$h9}HPth%H9iiMbuJuf3LF)uiY2!|bZhV&OLi%0bd`UdnZ=SE; zv16#T5#1e|jTBd3=kb!`Ei07##r{*r@koZ5q9vzPb;8($B)oj)SyZLrGpP|@yzB&q z6CZ^naElztFIA_2=NCRIXv~6CNQA&ZITP-Ed+p2d3a~Y{=W^l4M8hC*7MA5mcfRat zqgi(nFIUGTFuh|az2|xsML5-IOxa_Tz1am)h#OGi?>}4(PbLQlqgl7i*y?g3B9g=H9Ok%|cs6p7i9fDSWFuRwi zb~f?pHTpNVR6A5B8E{k$=>(ny6y}I8-~}0Oo)MdC!tYbX3X5hWkjIhXyi{1u*$O2g z;Gz&^5)2i%X@|d-zktdAPo1&6Er~KfoEJ2-W1(eJ_bu-GA6Z z$-9#Z#?iXqNi7%CC9UOYt)f66GT6OKtX0O%2;>A7{%w{Zt_ zE944zD-8**5WAa+yGpMt-?tV_QF~71tFxB|P|3yCp^tZcI4*Kvy4%qVdb{j@GBy&5 z`?X^{@9rYc{vL;S&huE>z$VGIuc=Re_I+FCbk1gerrhL2O6LXrWcXt-|8utvR2C95 zaTM#Xw;M9Ig((w8l#nEu+ZP*$5<44p`X$o@Rz1k;L*OtPff`JQ;~PM{=rD@2{%m4h zsg{N128$zb#htIKnL}$5;yGG?@gS?brWd+uqdSk?fQx)v?=r^^!hxH!RP|hv-R3Tj ze!e`u<1=aa(TRM=`wBVOtr)BA(%&rxu`!h{h_@@=fLmM{=f*rHR`+}85QLDtSivTf zcJ-a0qj^cyVGGf2{Gv zhgdx*N7Jvuz~Aat^LNp$nirZi{ya7`5Nj9`srcPkxRQr&g3us-$T`lirGs#p^kms$ zkb`a&0M41Rf2hHEk#~}mh8TKw;ysJ|BHsI^pewm~w)N=XD7Y0&c8XO{P8T)>!b-cmdTZw%J+cZYC6Xqpl!kDM_ zc7NX%D@3qxq&VVMupdtpShCEU*rQtc^{ly1Y~9ZtXdaQeAi296Qj(t9tM|42GH;tA z#GpFKN!4@F`;3^&-hAxjhFab#j#w(%U`xm&>x@_N8wVG>SA(MPz+b;lU?UB?HF5V> zxY`_e0MhUbIi7O??Z0&+>gBfzqPAzjO-<}7`oUgGts?KamP{r%s5~fV;WD>0XA|NI z{nOvFsvXCXrJRz)&46vsMwlI2u)W4k9vY54ulnbaBe?T_V|F11{eo5hqkm?jqKk~}=P zXVva{Wv=B<`vn38W!^K36MI9SL}$Av{Kk?w@7_Of4Y~qqZ9Xf$_A7yn*(5Ee3-FV{ zk$G@#5Ku?uGl5-ZbzWpWF=rZ#J-hkAA(Of6ci0}6L`Km86hU80bzb|PXJ&c$GiJ*% za=bMhl_SaTxcb?8;@t|3X4e1Qb-ihETfBk1V?>&+{mAGa`ZH^hmy}OF-g}>)@Q*VM zVA*W8uHLw;7&mdwkUYH3&6siy2&C7O#9Nw6Ni7xl*4<>UgpYuf<-5Ha)4Ew69u zW_k4{2WkIm&`wbn4aPOH=6`j*cOc<I$W3qi}epj?-#44!SFR{7oL zb$FC_ca3b`=CTjF-Rr#Nxw<{;>~td8=>-0IwtBU4mchq;bAQ+U#ns!>^^HT{3BTh` z0hv5Hjkdrx0T%DniMv;o{&w?ku`4&G2E|?%f^f2+l)`bjjV{uRlb+$&HVjsB#35_F zR2{z}?24sx8*gPaGH37WuWj^`C$-0I$v}}LK9Y`w1Y96Hr8(s2RttHlG!vG}S}+@Q zY9Eww1C;!-IFz{$D)c}{VZUB|Yd-9_%D_ogV)X4J`)W0mT2@zq6C$_6LPIW;WJ`WQ zGdM=5*&DpihAK7E$eu@qOPY!27=SP@w&=q|Oe4w43-$B18B%fzYKNG0ynM>n$6`nY zjTl(U)7K-YnOU8us~6R5WD3h7QU|cHt@6y0=3QA>w`dFIw<1De?2#WXC7mUTX&-n` zmnX)RA##Rn`}x16TO9?lLWL4Pw!$d}A#g3*YhUgRXA;<#&on>l2l#FBdNnk;HHgo> zFLx;?+kiMghs^U-Hh%hh(a;aKH0zWj3Io$>+YtECSsB*j@U^}#7ZFGr-!zrdPo_L; z^>&gElOM-FnDug-sy>WPQR)9YD06$XKS1X?N@eiGr-Iq;fPX(j74(@{r0^rH&M<7V z*i0#fU*Wz*i{aH-$65Qu1~jnGN`V8-Z5Db8rd z{q~6I4#5wL^#f0Oo3^0t2{((jh7@orbBF%HRQt&6r4l_>5zD8!5zR@dglIg1Hj$Lo zrHM`w)0ewT{P1Pw9$*9&dXwC4E6EtoU(FN9NAGLz8rc5|=|6@AJ`JCmowH4f_MRgQ z#Zg%7WqdS8S5S5sc-H#bx#Y8Uoz$G5dDPkmEUb-5ezawLx%2TwFtatLP%9%_=aM zx1z8$=t6K>Y>%X>@od96pUQ7*zYGftJ?Q2SKj<1ytdX*~T9~al^hZjJIBB+d)6X@| z)zzF!&lSCJ&Q=Pg)5B6L^1b>jWviRa4a3R$q_MGPAmw7vVqXGv5lhOZLPl^ZZ^ic| zGjw+!au?MI7Ew##vq`NKmh&Z2_&F!ZI`|8Wo~xZHd{Btydr^) zz1f<2WA%hZBbxQ5P5O1E`DGn)hzBuEScSSv4I@my=do3p+bQDuY8s8CH%`01X~3mp ziCX$ksiqy!wt7V=4E7&{z93YpX4`hXJ9U*XEG9)Y^J%8kD(KEWAcK-Y9YDl}nuFIH z7W7awTwB{KUH8z9s9qIt)FI3*{18?J{9|t%3Hgv7Q?CFjY~uLx8~hWmza75vyF_%e z^Xjy*^PtI@2CdM`KG*JDXrGJR^5?`gdaK~*EiK%L-th?-B5E#(Aw`ah8ZCnM`=V>7 zl;7`3rP}+>$N8XJWYCraJW-^8Z#>RQ9##{R#eS0EyW}Tg-jgqzoW?p)y<31h2R7{DB z-ZHGHx>>$M)1feP*OMpY1$-nrOPt*Cc=IXKU0*133g!&@j&VzAYbe#HFNM5~2EWw{ zjv}pG1P8GPhy&Q5!DK1x{&AMXG^QX3A;ow_xtj1``G&V+lj71~yH8xGEpBvC)p_gA zb6hsobaN|R8v%k5%$49W<^8ttKUP+5ubcx9B2)O|SwXH(Js97Y@}OdFQ558fDKJQR zj0Wgr$UoyTo><=|shW~*w3lIMyrFAcYW-|2CO{4mVvY`BZ!WR@)bEd-(9rjoJ`Qm2Ttj=DH-gS>tia<@=M*zgB5a} zUxn)olX^#W#cAKpGplNri0miz^da_|%otQZW4FbI!Ui7ye)lt-#X1Tn=2w1&&Q?^5 zE{tK4ie73@*N#?u(YBfL!C{P+2o5R{%P^FAA!ql1=!{^W$PTu^bWgvBy1~B!<((dY zA7Q8Jw&GZwyQ%G}aimf(_sGZSt2&I7bj?j#MZBEn3+}z8|i9m6eC^45E%&l81Tv8(pcI^6PFIgl}{3kQxx6K*0?{rKa zbA0{k*-S}%ZRtNlK%zNgNCL`N`1laL{CV}CIe)~GxXWui_2EMxi}(a(R~r(4myNyQ zs9z+u%`f*hNXP!G3-!*VVzYi%Lg!B}8(7BYK10SyN!ah|i!#KM5blHb8{X#|lvjf+ zen`OcsV3KyjR-Q+2HQBDm2Gvek;LTo{hB9Wm0IAO#pN{laH806%r_`zKUTVV&5R>_-bI??t^?Y9rtCM?XaaziSRpMX)j1iL#W_cQAWz}24 zP3dao#4dDR>R6zUYocmbvT4Nst&5TO8 z>_r~-6#V8SI_zz-lgVU1^G{>MO`UGRv3d~=(j`bF)bOGxwJ*RHL|n9`VD_EE!clvp z!y>E*5DK6-oEHAM>x?|G-z)*ny0^jrQ}`Wu${`lV$9EB$K(m79;#7 zy#-_Le9;8jdoRrB)400^U`8~jQNFMlA@L(iR&4jRrv4?cTMf|Q*gv~=k+K7Uh8Air zdW;9n4HVc6c9Q6RU$sWTFo|r(&C;Ls;#z3q9>89uTQq%NzAwJ94+uiX8t&QYx#`l~ zU`#20{NlDaH!kUg%mX1W&sSaNy5APa(5VO?MH1zQQG?%7k>IOVTQem-7@(fuROo$7 z=~@v@68|Xa=w{uJ32&3vJRiQ6@<0Cl$&y@}k zZ={1d*bNdclA==FCUAN~jchZ{7qzoKy*j{Eg68`Vyb5g8c^o6Cqi4k6J){3VaZ!B0 z`NDjxz3t;}Xv-kq>L5V^1>`u0_*H4)BD&(Ij~WzqvOS@OmMoP(Q+ze=C&L=O`0QVP z_)jKLS>N`IQHBI}$EQ{yLc?c&WFHA1CUMH0r|0kmT1d*({p#SksnC#>`#j^g=e8f2 zLKttA9oEo#K3y$KO{k{lwQ4uWzYj@@Ix*xA2;mIs&gW5b%(R)qL*sy6r-^%)?W`SE zQC%N=DD$4LaO=D1FE!oqv0L{zk@~p6OK9IP7H_&d3I~*{Hpv90?>mA9QlYSE{E3Kr zP&C!+n-5lDq_?LB*5OQW{~!)hnM|fsP&P_NiHzOH>Exjm!sk~cFN{sWw-MJ!w!C&L zIuD}X#8#yehO`YjPHMDT6`>Q#<@gb*<9fW`&7$az{n)aR&d^%Lr$F~`xOzk4yXAT? zMVQd202=2U{qVP*4z=~U6r3&2g&gfu>lJ*QOlKj3jkiH+ct|v)=ZF8CbJ2b0>G`7o z>c*wCR?E0naljSegm|cnH}cz=>Z$KP3@vKoOvc7qFPu||j|UZmDhpc2XUQ5R_w~3J zJgH`kJY#;@w>rbTzDo+x#)B&np|WI;#x;CbwD1xG8FhUW$JE%JOEAWh-y&~)y6>Wt zA>??9C{PshZqo;Ge)yhr#;m&7hh~^PD6XnZ?V%T!&HTM7=kRtwN=!%%B=>395wH?R z3QeM--!-E{i@rWU?3sR0Yu2SWc*a2ifzc7m_D?}ni=uy~Q%zevw1Iw@~1xZn%I=jKd=Pa{Potx2T%)~t>IJuUUyPUO~QZEjzA7%;V`hKVP$_ILj zp)SZU6qdtYB+tsHs|PMeM*1cbY&dwS>3}VP?(WZpYJcqc{+K?zs#}@d#~*oQ?TOl} z>)kLs!6oy0mS?6cBd_-`+LqiR%t_u>bGP7QyL`0ym_*4z%_IC4oeB&Xqm7i|H_>x$ zp~rI(Rcs1$It{YdrY_Xlff`2$C)`R3_#p6W7SLtFRmHbC3qCQO&6WFF=9-1cvgKup zlopJ+@mv}wI((|vkY9?3+phGpf3Z^vR*B$VJg)E#D{QE3+ctR{7EP7H`m_hljB6H+ zKI>yakAhq53p3FTI8)7xp3~`Tw8X`SgM7s&n`*B(l~Y5>tti@C24&L|bj$zrj)f7! zh&5`w`&_7dIaG;Tv??g3>l<+1C(bq$Kl9VVeBvC0JNu6NYNtuvb&0UKNV`)`R*0ks z`_l)a-02~q?mb?jv|?fmjs=lY?Rq*zTAKnmgPGx?t^w=~Cx=HDo%M<39-{1N%&CC` zpjSHyCi|9UHeTkyInW1-K8l2;DKZ#ZD%t1oD?E?FtZyx&VZPcVP#^Z_;Mp{m-4D>B znuBIb(7tH;&=NT0J;{e_A_K$v#6{oekg(Wt@QzbioWM?3&$HkSW;k|exQga-kuDyC zW>ZY{i>XJfI=OzZ+ZIZEFsJNcbX zRD~`=A7~(3T)0wd04WtMZOWHKsbQDL?2X12JrIagqZ+9XzOeWfsIURXmIFryP}E6$ zTVSfGfsQu_l&{=J6*4YHHYT&VZc{e0a=R|ubVF9t+u%ud7)$gIvdQ=LxqDAas9sG! z8_;N2+TZ=(s%SH>A5mo5;(zt!Kcy0SD$#nw+^TROaT>)?&d2xhZ@ePqVzf8kpvlfa z4&e$uS$!qqL58GVgWAHNRUYJ z9VaQ4cS_|rSkN3is2boy^LWnYdr5(tTAh4(a9puStryRe^7PW9wwlQ(Bz@ODhI{Qp zit!e`+eUXaeSKWTkfUd_Dc*;?3O}o>qMp2yKKt$vW_VmV#4k;UGMH1cJ3k^KVoAmN z1`UgTzIFk{NEKC2bo8+N?jhm}@RJAO$NceZqC^FK zrdj8?)cUk2hNR3V@#XwLl*q=R&gYbexdlmN*c8l`FZi;*3cI;YMneY$M_ZE?v0(T4 z6w{{%M{iQz^dc4~5OGOi7Oc3b3Od{DeMJXwI$50xHDeeK=Ieb_5@vee3c7)&y>TjS zdLij0PPe{uLR-1#S4z)5#ASkkL8-Kg*KiEamYw6aV$2*;oluZAT2BaTpE(K4>MFUr zKTwGMoDpV=SjP_jCFN#)!C0X}od{_EDVBd|iO8pnT{`F-D}Dq52G5i?IXaWBejicxodfeB`4*cpp^nA^Hya4$t3URdn+?yl2f%0+&Y zo1(o<}}~$w&qV zh<}6ULdKA_R%E{M6R@p$(l5V(=%pcRP1q7kK{j;;$4L14X=u0H2g?^bOA6!>pT}R4 z>Euzc@HhzV`3rKPzE+}D6!frD#~#MKRp;Nh03n`yvPJB(@%z8l&DlRoG}Sdv4fujVJ71s!t)r7H13KjTl{hG(A0$)@5{qqC6Kh^N04ODUT8U2KZSx%^I2ekB+m{<@VpHa?8<7Moh- zX)*xw%j%%=rdPhKBuXg(!o*RBdai<09hOUo38;TbTgFcAF5px$suw0?ODm8 zwnCh6;qvV9Hfma1#Y_DpwXv5L3n@VNZO^Y4=`fz!-i&t!^d}Nm-R4IRULT4F7LHdvu{Z_;Eb9iGoy0V$NN z-V%YQB$wjzgGj-6I5d183G^b@L_}cRU-L=~#py`Bzz%H)uRiCde!)z1S%z#y*_32v&W zaz0&T)?wM`=QNI!3UKO5+qSSg*DN#KROx#0*rhCD(*r=Ez3Law%LIy4^(6WMBpm6V$*IqBbl_` zz1wH?bn7c}{_oQd((g$?4v>J6vW}=+?Hiz8GOb6b7a%fbEpBTVVK+{ zB4zY$p zkuW~rr`K{)B(BkptB$QmD!)if;1YY&Se}so%k3F3-vD$^oz@}&s@si%0rwE0um}~Z zA>S1(-B9}mULYv6NEQQs^ah6Ex&xIK?n)N2EbhgewBgo_W@c`GrG4=}0WypEg3JicV3cl1B7`S-uo&uZ0{^aI-JEInxQ!;Y|u;pnU<%5AD`< zRkWWjI4!q$&FA>6WZvvn-a4KF)quTbvd5G$IwbTgDg1I7!VxW=p&VSWdABe3$x%@K^K*$B?;2cN<0MJO`>&%z^K3#NWa!7(o{|X_3{wg+D3OHN7 zX#f2dUSTAXOTvCE?k%mC91nTrLJiF3dV2KTZJMlJ&REx#yja`6S;LZXJB5e{T*~1B z5)TXqo{Y-kCg-r(m^3KOS_B1nU;cK$>Nu@6&~oT%_e}2!JpUfWKz)Zr!Sj>gJ=jQ* zd-;K><*7J~&86*Vz;56em>(0CNL_2&#+$Zz|6`5#u?jO?AER<%!Dm<|pX+4cX#y~3 z%;(cEvOzQH9K4rMZ8ZW6I35#@5&QV$Qo0-)SOokbwTY2WHa4+swfeosKEZCx1>NRa z_~g^RXEs6)RTYO_%_|LUWz<5Pwj3Y-_A;~)o_1;kthNvd?>lkN?PQMipk`#QUxuBM zPTaG3f?hhq*ZJrx6=toe=N0L()?q!2DcxQ!jxtxHTVq+TmuxCqcDaQE@30seB6s8N z^@XfEm#*P9Am`@lOn_Fk#iiMy$TM?#xE=)=@xc*S13y!MzQNizZnJR8eoiymOG~h1 zIryC5bbo&1@&Sg*g4Sui_KTeL9tp>Ki@*yz%~((Gl@k35iv8tQ(M-CttJS;PB}Ow~ zK5*V%`h*6n<2R!SLjwpQW}EbUi5akmMbiZ6=qUv@Pa`9Wut`}`EN+iq=Lp73#PO$D z1NwrFdn%-iIx}LKc0VR~{Wk8j_OOasxH#JOj`JxgRUMBqJ2kkpj0u?LvCLI{#Vd+y zs{~9+vuP@>pA?J~8CFmvGZZZ9gu%1T$Vf6>NLWmmr&i00UzB0`{SVflQ$-C@(5c2hnsCBT+mX3ct29z#RJA)f$^Zjox0b!b+4sPj~y z2oX(-_`t&iF3$Ov0J6(*OE++;6-F#?{-pw_3uNN%QkIVVBrWo4#;L6+{2X=?j4(V9 zgUXk+(pb03wOV;H6*^QdmNksaer?!!VsWW+I_4VaQGg_z7jclu!eV|b(vXO_`65rG z{>LALv&S3RgjoARRkvrrsa#S3mjHWwbuGM_Ei!ZZV1pc3tshOr&r2VvkFBdeEq7Ra z=xopDoCeHS5Tlz>&vw}}OGwt~2>3iVNLEH|G4m`fwlxX0rFDxWc5n4LY~VMf?RGC0 zwInJQq3n0e0xI5&ws*u!&aa&G#{>e8@Fa4$mGwF5U8svLnG1!a})fyr0-bS?b6~8J4F4Wbr9OgWy+4r59UytpK#rFEd55` zWsNZqmW zI+lN<4sPFG9ZnFaSr9;WYzTK4GvEo{4SD{@D$Y;QfbUDeGRFEzy=@bDeX{3=bU{pZrU7P>&Jm>$O-+4d0&v}36)0t1hu=iSfue{cE-}iN= zHkZrDlKyqKoKtNk$J|E&`8&UBc7mDcj{6o-{ySWK4?Gm^WCPP6o2289@}H64c9hzX zmm~a;c!hVInB!=~wj)?2sfDr&Yn5i10XDiMuvC=ku&sHlA4;~V4ZPs4ScVS+?{*uy z>+*NflU-7U)8x<5E@eei4}V?`W-YxvDqx+-WB?o-sb2p?g3XOvoZH~XaF)>Oef9WK z<;aCl5Jx+e?m#5fuyysXS&KK+tW&1=16I>X1;y0w>y#2BPj^A+%HR>R0>Wp$5-J{)ptyUZv`rEQ*@Op{VJDY%}WJ=yC9wM4Yb`4>lROCG+gmshLG z@h4-tFOxp9X#Ubvw|_UWkPP_oT)I)(^pU{CO3>e!`_iyus_&~gg8WQDa9Yv|H&#$r z5~{tZ^bH*zu3DvGN!0NVVq?=*Xd$V-Vz3n<)}1L%Or7t2iEW*$-VvOm4=VL-`Cj)q zm^wHARVSS3{sxiiSRFo{0QKBAfYbV~f6UFhu_L$fZVPje@9}~~NmoFTW80iN#KoLg z+Q@Ki#PnlVIMK+0ArFI~lE<~gfCprN``fE*v?Yz2YqrUZBm|L5Qv&ki!RCcx!K_sr zspY(jGD2TOQ>P-#QshKjqj(o?iYU})mcE^l1Fv|ckjNm76|?xW%PU50dtJbHzJ01x zr=}aRf3`DKVKl}_#oNhn4{IQVNIVS$WIC3AN53{18fFcbVls{fEl88)nHAe)+uLlF zc|}1T_~MBiJV|H?ua^gb7|{!=(RT<~%y?{aZhvcpnQW%H2Vgu)*_K7>#EM`tm_I~` z@pSTynZ$x~^@0eS{UVj(K1kk#0==S?BlimgJ;Kl9oHGU2BNI^bPCs+!!VKy&o_Sj} zep=&++cBJ9l%B)FN~)Beu*SJcllYD0?OxjXt@y!tL z&_QiPXt24d+BfFV4!haR6W3cJxz^QGpIH6fxDQ&kX~$=DOd#jB7Twl{jXULzo>>PdDYgi&EB@ zmh2Vu_0Zsb>n$OE^*H4oL2wkl<&+-D!*DG5_dF)(+Cn-x$YV^FPRcl0knPUm&xi>! z0`2F50qyB8^~2kNKf^N9McBK9GRgakrrT8C#e7EkY zv(|U6pGW(PcY%oB{SPvllz7#KDK3>o@RYlct!b8_(G&`B3P1N{L=#;uRS+51h=nA_ z@COA!{}+0SH6(S#E|$LdHwmpWvjumnQKw~Z=P0&&W&6&=gJ5aYOKMT12@;7AsG9A9b4F%h(G?F@Ea zWTv=G+!Q+G^~1fQI1_e9#qRT{(WpGS4lmVPl0tfJa~bfal_jqqx*jzN3@Nrlf)(4d z>;+Nv)+8PI_K5;m^3GX(X@ruU4R=5^cx+V+Z7`x2zHG!tfhp}+7ArnOIxXF2H_N#H z{k5_wINRK8#(QU2Klv%fYB5th>=lB4tvw#L9@S58?>Cqbx$-^81+A(YdxuK87rwOx zqJw`uQABcQ+`t8Mg?9PNEAz~iUz=r9-}1qh>7f#z+~@s4wwFtZ0(U=VR$458_J`+s z-;=6h+}$J=m3j|xo$&2b^`cu$9;7~E&u6uUemwG&!inZ_uQb>FQ~z<$dlWy#R?|#s zdwNatV!NT=ogl1w`fg}Q9HzMoGjB{$T29c&e0*?j1d*;}7}k$(*vGb)z0D91=%yG`qU}PPSSl&csZwl- zgBx#9yO(ezje-s4r)E6eeO8e*j3@n^>~H6;Fap=IAmDzvZya3`%Twew{z^YIQ!H#W zTgQTgW2$5^bZ S8;f6m;DBz%nJ^YGsGr=- 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 literal 0 HcmV?d00001 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