1515 */
1616package guru .nidi .graphviz .model ;
1717
18- import guru .nidi .graphviz .attribute .Label ;
19- import guru .nidi .graphviz .attribute .SimpleLabel ;
18+ import guru .nidi .graphviz .attribute .*;
2019
2120import java .util .*;
2221import java .util .Map .Entry ;
22+ import java .util .stream .Stream ;
2323
2424public class Serializer {
2525 private final MutableGraph graph ;
@@ -31,12 +31,36 @@ public Serializer(MutableGraph graph) {
3131 }
3232
3333 public String serialize () {
34- graph (graph , true );
34+ toplevelGraph (graph );
3535 return str .toString ();
3636 }
3737
38- private void graph (MutableGraph graph , boolean toplevel ) {
39- graphInit (graph , toplevel );
38+ private void toplevelGraph (MutableGraph graph ) {
39+ final boolean useDir = hasDifferentlyDirectedSubgraphs (graph );
40+ str .append (graph .strict ? "strict " : "" ).append (graph .directed || useDir ? "digraph " : "graph " );
41+ if (!graph .name .isEmpty ()) {
42+ str .append (SimpleLabel .of (graph .name ).serialized ()).append (' ' );
43+ }
44+ str .append ("{\n " );
45+ doGraph (graph , useDir );
46+ str .append ('}' );
47+ }
48+
49+ private void subGraph (MutableGraph graph , boolean useDir ) {
50+ if (!graph .name .isEmpty () || graph .cluster ) {
51+ str .append ("subgraph " )
52+ .append (Label .of ((graph .cluster ? "cluster_" : "" ) + graph .name ).serialized ())
53+ .append (' ' );
54+ }
55+ str .append ("{\n " );
56+ doGraph (graph , useDir );
57+ str .append ('}' );
58+ }
59+
60+ private void doGraph (MutableGraph graph , boolean useDir ) {
61+ if (useDir && graph .graphAttrs .get ("dir" ) == null ) {
62+ attributes ("edge" , Attributes .attr ("dir" , graph .directed ? "forward" : "none" ));
63+ }
4064 graphAttrs (graph );
4165
4266 final List <MutableNode > nodes = new ArrayList <>();
@@ -58,12 +82,17 @@ private void graph(MutableGraph graph, boolean toplevel) {
5882 }
5983
6084 nodes (graph , nodes );
61- graphs (graphs , nodes );
85+ graphs (graphs , nodes , useDir );
6286
63- edges (nodes );
64- edges (graphs );
87+ edges (nodes , useDir );
88+ edges (graphs , useDir );
89+ }
6590
66- str .append ('}' );
91+ private boolean hasDifferentlyDirectedSubgraphs (MutableGraph graph ) {
92+ return Stream .concat (linkedNodes (graph .nodes ).stream (), linkedNodes (graph .subgraphs ).stream ())
93+ .filter (n -> n instanceof MutableGraph )
94+ .map (n -> (MutableGraph ) n )
95+ .anyMatch (sub -> sub .directed != graph .directed );
6796 }
6897
6998 private void graphAttrs (MutableGraph graph ) {
@@ -72,20 +101,6 @@ private void graphAttrs(MutableGraph graph) {
72101 attributes ("edge" , graph .linkAttrs );
73102 }
74103
75- private void graphInit (MutableGraph graph , boolean toplevel ) {
76- if (toplevel ) {
77- str .append (graph .strict ? "strict " : "" ).append (graph .directed ? "digraph " : "graph " );
78- if (!graph .name .isEmpty ()) {
79- str .append (SimpleLabel .of (graph .name ).serialized ()).append (' ' );
80- }
81- } else if (!graph .name .isEmpty () || graph .cluster ) {
82- str .append ("subgraph " )
83- .append (Label .of ((graph .cluster ? "cluster_" : "" ) + graph .name ).serialized ())
84- .append (' ' );
85- }
86- str .append ("{\n " );
87- }
88-
89104 private int indexOfName (List <MutableNode > nodes , Label name ) {
90105 for (int i = 0 ; i < nodes .size (); i ++) {
91106 if (nodes .get (i ).name .equals (name )) {
@@ -95,7 +110,7 @@ private int indexOfName(List<MutableNode> nodes, Label name) {
95110 return -1 ;
96111 }
97112
98- private void attributes (String name , MutableAttributed <?, ?> attributed ) {
113+ private void attributes (String name , Attributes < ?> attributed ) {
99114 if (!attributed .isEmpty ()) {
100115 str .append (name );
101116 attrs (attributed );
@@ -161,34 +176,34 @@ private boolean isNode(LinkTarget target, MutableNode node) {
161176 return target == node || (target instanceof ImmutablePortNode && ((ImmutablePortNode ) target ).node () == node );
162177 }
163178
164- private void graphs (List <MutableGraph > graphs , List <MutableNode > nodes ) {
179+ private void graphs (List <MutableGraph > graphs , List <MutableNode > nodes , boolean useDir ) {
165180 for (final MutableGraph graph : graphs ) {
166181 if (graph .links .isEmpty () && !isLinked (graph , nodes ) && !isLinked (graph , graphs )) {
167- graph (graph , false );
182+ subGraph (graph , useDir );
168183 str .append ('\n' );
169184 }
170185 }
171186 }
172187
173- private void edges (List <? extends LinkSource > linkSources ) {
188+ private void edges (List <? extends LinkSource > linkSources , boolean useDir ) {
174189 for (final LinkSource linkSource : linkSources ) {
175190 for (final Link link : linkSource .links ()) {
176- linkTarget (link .from );
177- str .append (graph .directed ? " -> " : " -- " );
178- linkTarget (link .to );
191+ linkTarget (link .from , useDir );
192+ str .append (graph .directed || useDir ? " -> " : " -- " );
193+ linkTarget (link .to , useDir );
179194 attrs (link .attributes );
180195 str .append ('\n' );
181196 }
182197 }
183198 }
184199
185- private void linkTarget (Object linkable ) {
200+ private void linkTarget (Object linkable , boolean useDir ) {
186201 if (linkable instanceof MutableNode ) {
187202 str .append (((MutableNode ) linkable ).name .serialized ());
188203 } else if (linkable instanceof ImmutablePortNode ) {
189204 port ((ImmutablePortNode ) linkable );
190205 } else if (linkable instanceof MutableGraph ) {
191- graph ((MutableGraph ) linkable , false );
206+ subGraph ((MutableGraph ) linkable , useDir );
192207 } else {
193208 throw new IllegalStateException ("unexpected link target " + linkable );
194209 }
@@ -206,7 +221,7 @@ private void port(ImmutablePortNode portNode) {
206221 }
207222 }
208223
209- private void attrs (MutableAttributed <?, ?> attrs ) {
224+ private void attrs (Attributes < ?> attrs ) {
210225 if (!attrs .isEmpty ()) {
211226 str .append (" [" );
212227 boolean first = true ;
0 commit comments