1   /****************************************************************************
2    * This demo file is part of yFiles for Java 2.14.
3    * Copyright (c) 2000-2017 by yWorks GmbH, Vor dem Kreuzberg 28,
4    * 72070 Tuebingen, Germany. All rights reserved.
5    * 
6    * yFiles demo files exhibit yFiles for Java functionalities. Any redistribution
7    * of demo files in source code or binary form, with or without
8    * modification, is not permitted.
9    * 
10   * Owners of a valid software license for a yFiles for Java version that this
11   * demo is shipped with are allowed to use the demo source code as basis
12   * for their own yFiles for Java powered applications. Use of such programs is
13   * governed by the rights and conditions as set out in the yFiles for Java
14   * license agreement.
15   * 
16   * THIS SOFTWARE IS PROVIDED ''AS IS'' AND ANY EXPRESS OR IMPLIED
17   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18   * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19   * NO EVENT SHALL yWorks BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
21   * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23   * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24   * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   *
27   ***************************************************************************/
28  package demo.view.flowchart.painters;
29  
30  import demo.view.flowchart.layout.FlowchartElements;
31  import demo.view.flowchart.layout.FlowchartLayouter;
32  import y.base.DataMap;
33  import y.base.Edge;
34  import y.base.EdgeCursor;
35  import y.base.EdgeMap;
36  import y.base.Graph;
37  import y.base.Node;
38  import y.base.NodeCursor;
39  import y.base.NodeMap;
40  import y.layout.IntersectionCalculator;
41  import y.util.DataProviderAdapter;
42  import y.util.Maps;
43  import y.view.EdgeRealizer;
44  import y.view.GenericNodeRealizer;
45  import y.view.Graph2D;
46  import y.view.NodeLabel;
47  import y.view.NodeRealizer;
48  import y.view.NodeRealizerIntersectionCalculator;
49  import y.view.YLabel;
50  import y.view.hierarchy.GroupNodeRealizer;
51  import y.view.hierarchy.HierarchyManager;
52  import y.view.tabular.TableGroupNodeRealizer;
53  
54  import java.util.Collection;
55  import java.util.HashSet;
56  
57  /**
58   * Prepares flowchart specific layout hints for the realizers created by {@link FlowchartRealizerFactory} and configures
59   * the preferred direction of outgoing edges of decision nodes ('branch' edges). These hints are interpreted by {@link
60   * demo.view.flowchart.layout.FlowchartLayouter}.
61   */
62  public class FlowchartLayoutConfigurator {
63    private static final Object LABEL_LAYOUT_DPKEY = FlowchartLayouter.LABEL_LAYOUT_DPKEY;
64  
65  
66    private final Collection nodeActivityElements;
67    private final Collection nodeAnnotationElements;
68    private final Collection nodeDataElements;
69    private final Collection nodeEndElements;
70    private final Collection nodeEventElements;
71    private final Collection nodeGatewayElements;
72    private final Collection nodeReferenceElements;
73    private final Collection nodeStartElements;
74  
75    private boolean portIntersectionDataProviderCreationEnabled;
76    private int preferredPositiveBranchDirection;
77    private int preferredNegativeBranchDirection;
78    private int adjustedPositiveBranchDirection;
79    private int adjustedNegativeBranchDirection;
80    private String negativeBranchLabel;
81    private String positiveBranchLabel;
82  
83    /**
84     * Creates a new <code>FlowchartLayoutConfigurator</code>.
85     */
86    public FlowchartLayoutConfigurator() {
87      portIntersectionDataProviderCreationEnabled = false;
88      setPositiveBranchLabel("Yes");
89      setNegativeBranchLabel("No");
90      setPreferredPositiveBranchDirection(FlowchartLayouter.DIRECTION_WITH_THE_FLOW);
91      setPreferredNegativeBranchDirection(FlowchartLayouter.DIRECTION_FLATWISE);
92  
93      nodeActivityElements = new HashSet();
94      nodeActivityElements.add(FlowchartRealizerConstants.FLOWCHART_PROCESS_CONFIG_NAME);
95      nodeActivityElements.add(FlowchartRealizerConstants.FLOWCHART_PREDEFINED_PROCESS_CONFIG_NAME);
96      nodeActivityElements.add(FlowchartRealizerConstants.FLOWCHART_LOOP_LIMIT_CONFIG_NAME);
97      nodeActivityElements.add(FlowchartRealizerConstants.FLOWCHART_LOOP_LIMIT_END_CONFIG_NAME);
98  
99      nodeAnnotationElements = new HashSet();
100     nodeAnnotationElements.add(FlowchartRealizerConstants.FLOWCHART_ANNOTATION_CONFIG_NAME);
101 
102     nodeDataElements = new HashSet();
103     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_CARD_CONFIG_NAME);
104     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_CLOUD_TYPE_CONFIG_NAME);
105     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_DATA_CONFIG_NAME);
106     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_DATABASE_CONFIG_NAME);
107     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_DIRECT_DATA_CONFIG_NAME);
108     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_DOCUMENT_CONFIG_NAME);
109     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_INTERNAL_STORAGE_CONFIG_NAME);
110     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_MANUAL_INPUT_CONFIG_NAME);
111     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_PAPER_TYPE_CONFIG_NAME);
112     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_STORED_DATA_CONFIG_NAME);
113     nodeDataElements.add(FlowchartRealizerConstants.FLOWCHART_SEQUENTIAL_DATA_CONFIG_NAME);
114 
115     nodeGatewayElements = new HashSet();
116     nodeGatewayElements.add(FlowchartRealizerConstants.FLOWCHART_DECISION_CONFIG_NAME);
117 
118     nodeEndElements = new HashSet();
119     nodeEndElements.add(FlowchartRealizerConstants.FLOWCHART_TERMINATOR_CONFIG_NAME);
120 
121     nodeEventElements = new HashSet();
122     nodeEventElements.add(FlowchartRealizerConstants.FLOWCHART_DELAY_CONFIG_NAME);
123     nodeEventElements.add(FlowchartRealizerConstants.FLOWCHART_DISPLAY_CONFIG_NAME);
124     nodeEventElements.add(FlowchartRealizerConstants.FLOWCHART_MANUAL_OPERATION_CONFIG_NAME);
125     nodeEventElements.add(FlowchartRealizerConstants.FLOWCHART_PREPARATION_CONFIG_NAME);
126 
127     nodeReferenceElements = new HashSet();
128     nodeReferenceElements.add(FlowchartRealizerConstants.FLOWCHART_ON_PAGE_REFERENCE_CONFIG_NAME);
129     nodeReferenceElements.add(FlowchartRealizerConstants.FLOWCHART_OFF_PAGE_REFERENCE_CONFIG_NAME);
130 
131     nodeStartElements = new HashSet();
132     nodeStartElements.add(FlowchartRealizerConstants.FLOWCHART_START1_CONFIG_NAME);
133     nodeStartElements.add(FlowchartRealizerConstants.FLOWCHART_START2_CONFIG_NAME);
134   }
135 
136   /**
137    * Returns the label text that defines a negative branch.
138    *
139    * @return the label text that defines a negative branch.
140    */
141   public String getNegativeBranchLabel() {
142     return negativeBranchLabel;
143   }
144 
145   /**
146    * Sets the label text that defines a negative branch.
147    *
148    * @param label the label text.
149    */
150   public void setNegativeBranchLabel(String label) {
151     this.negativeBranchLabel = label;
152   }
153 
154   /**
155    * Returns the label text that defines a positive branch.
156    *
157    * @return the label text that defines a positive branch.
158    */
159 
160   public String getPositiveBranchLabel() {
161     return positiveBranchLabel;
162   }
163 
164   /**
165    * Sets the label text that defines a positive branch.
166    *
167    * @param label the label text.
168    */
169   public void setPositiveBranchLabel(String label) {
170     this.positiveBranchLabel = label;
171   }
172 
173   /**
174    * Returns the preferred direction for negative branches.
175    *
176    * @return the preferred direction for negative branches.
177    */
178   public int getPreferredNegativeBranchDirection() {
179     return preferredNegativeBranchDirection;
180   }
181 
182   /**
183    * Sets the preferred direction for negative branches.
184    *
185    * @param direction the preferred direction for negative branches.
186    */
187   public void setPreferredNegativeBranchDirection(int direction) {
188     preferredNegativeBranchDirection = direction;
189     adjustedPositiveBranchDirection = calculateAdjustedPositiveBranchDirection();
190     adjustedNegativeBranchDirection = calculateAdjustedNegativeBranchDirection();
191   }
192 
193   /**
194    * Returns the preferred direction for positive branches.
195    *
196    * @return the preferred direction for positive branches.
197    */
198   public int getPreferredPositiveBranchDirection() {
199     return preferredPositiveBranchDirection;
200   }
201 
202   /**
203    * Sets the preferred direction for positive branches.
204    *
205    * @param direction the preferred direction for positive branches.
206    */
207   public void setPreferredPositiveBranchDirection(int direction) {
208     preferredPositiveBranchDirection = direction;
209     adjustedPositiveBranchDirection = calculateAdjustedPositiveBranchDirection();
210     adjustedNegativeBranchDirection = calculateAdjustedNegativeBranchDirection();
211   }
212 
213   /**
214    * Returns the adjusted direction that is set to negative branches. If the preferred positive and negative branches
215    * interfere, this class adjusts them.
216    *
217    * @return the adjusted direction that is set to negative branches.
218    */
219   protected int getAdjustedNegativeBranchDirection() {
220     return adjustedNegativeBranchDirection;
221   }
222 
223   /**
224    * Returns the adjusted direction that is set to positive branches. If the preferred positive and negative branches
225    * interfere, this class adjusts them.
226    *
227    * @return the adjusted direction that is set to positive branches.
228    */
229   protected int getAdjustedPositiveBranchDirection() {
230     return adjustedPositiveBranchDirection;
231   }
232 
233   /**
234    * Returns whether the creation of data providers for the {@link IntersectionCalculator} is enabled or disabled. By
235    * default, this property is set to <code>false</code>.
236    *
237    * @return <code>true</code> if he creation of data providers for the IntersectionCalculator is enabled.
238    */
239   public boolean isPortIntersectionDataProviderCreationEnabled() {
240     return portIntersectionDataProviderCreationEnabled;
241   }
242 
243   /**
244    * Specifies whether the creation of data providers for the {@link IntersectionCalculator} is enabled or disabled.
245    *
246    * @param enabled specifies whether or not data providers for the {@link IntersectionCalculator} are created.
247    */
248   public void setPortIntersectionDataProviderCreationEnabled(boolean enabled) {
249     this.portIntersectionDataProviderCreationEnabled = enabled;
250   }
251 
252   /**
253    * Performs all necessary preparations for the specified graph.
254    *
255    * @param graph the <code>Graph2D</code> instance that is prepared for automated layout calculation.
256    */
257   public void prepareAll(final Graph2D graph) {
258     if (portIntersectionDataProviderCreationEnabled) {
259       graph.addDataProvider(IntersectionCalculator.SOURCE_INTERSECTION_CALCULATOR_DPKEY,
260           new NodeRealizerIntersectionCalculator(graph, true));
261       graph.addDataProvider(IntersectionCalculator.TARGET_INTERSECTION_CALCULATOR_DPKEY,
262           new NodeRealizerIntersectionCalculator(graph, false));
263     }
264 
265     final DataMap branchMap = Maps.createHashedDataMap();
266     graph.addDataProvider(FlowchartLayouter.PREFERRED_DIRECTION_KEY, branchMap);
267 
268     final NodeMap nodeTypeMap = Maps.createHashedNodeMap();
269     graph.addDataProvider(FlowchartLayouter.NODE_TYPE_DPKEY, nodeTypeMap);
270 
271     for (NodeCursor nc = graph.nodes(); nc.ok(); nc.next()) {
272       final Node node = nc.node();
273       nodeTypeMap.setInt(node, (int) getType(graph.getRealizer(node)));
274     }
275 
276     final EdgeMap edgeTypeMap = Maps.createHashedEdgeMap();
277     graph.addDataProvider(FlowchartLayouter.EDGE_TYPE_DPKEY, edgeTypeMap);
278 
279     for (EdgeCursor ec = graph.edges(); ec.ok(); ec.next()) {
280       final Edge edge = ec.edge();
281       final EdgeRealizer realizer = graph.getRealizer(edge);
282 
283       edgeTypeMap.setInt(edge, (int) getType(realizer));
284       branchMap.setInt(edge, getBranchType(realizer));
285     }
286 
287     final HierarchyManager hm = graph.getHierarchyManager();
288     if (hm != null) {
289       graph.addDataProvider(LABEL_LAYOUT_DPKEY, new DefaultGroupLabelExclusion(hm));
290     }
291   }
292 
293   /**
294    * Performs all necessary resource cleanup and data translation after a layout calculation.
295    *
296    * @param graph the <code>Graph2D</code> instance that was previously prepared for automated layout calculation.
297    */
298   public void restoreAll(final Graph2D graph) {
299     final HierarchyManager hm = graph.getHierarchyManager();
300     if (hm != null) {
301       graph.removeDataProvider(LABEL_LAYOUT_DPKEY);
302     }
303 
304     if (portIntersectionDataProviderCreationEnabled) {
305       graph.removeDataProvider(IntersectionCalculator.SOURCE_INTERSECTION_CALCULATOR_DPKEY);
306       graph.removeDataProvider(IntersectionCalculator.TARGET_INTERSECTION_CALCULATOR_DPKEY);
307     }
308     graph.removeDataProvider(FlowchartLayouter.NODE_TYPE_DPKEY);
309     graph.removeDataProvider(FlowchartLayouter.EDGE_TYPE_DPKEY);
310     graph.removeDataProvider(FlowchartLayouter.PREFERRED_DIRECTION_KEY);
311   }
312 
313   /**
314    * Returns the flowchart element type of the given edge realizer.
315    *
316    * @return one of the edge type constants in {@link FlowchartElements}.
317    */
318   protected byte getType(EdgeRealizer realizer) {
319     final NodeRealizer sourceRealizer = realizer.getSourceRealizer();
320     final NodeRealizer targetRealizer = realizer.getTargetRealizer();
321     if (sourceRealizer != null && getType(sourceRealizer) == FlowchartElements.NODE_TYPE_ANNOTATION
322         || targetRealizer != null && getType(targetRealizer) == FlowchartElements.NODE_TYPE_ANNOTATION) {
323       return FlowchartElements.EDGE_TYPE_MESSAGE_FLOW;
324     } else {
325       return FlowchartElements.EDGE_TYPE_SEQUENCE_FLOW;
326     }
327   }
328 
329   /**
330    * Returns the flowchart element type of the given node realizer.
331    *
332    * @return one of the node type constants in {@link FlowchartElements}.
333    */
334   protected byte getType(NodeRealizer realizer) {
335     if (realizer instanceof TableGroupNodeRealizer) {
336       return FlowchartElements.NODE_TYPE_POOL;
337     } else if (realizer instanceof GroupNodeRealizer) {
338       return FlowchartElements.NODE_TYPE_GROUP;
339     } else if (realizer instanceof GenericNodeRealizer) {
340       final String configuration = ((GenericNodeRealizer) realizer).getConfiguration();
341       if (nodeActivityElements.contains(configuration)) {
342         return FlowchartElements.NODE_TYPE_PROCESS;
343       } else if (nodeDataElements.contains(configuration)) {
344         return FlowchartElements.NODE_TYPE_DATA;
345       } else if (nodeAnnotationElements.contains(configuration)) {
346         return FlowchartElements.NODE_TYPE_ANNOTATION;
347       } else if (nodeGatewayElements.contains(configuration)) {
348         return FlowchartElements.NODE_TYPE_DECISION;
349       } else if (nodeEndElements.contains(configuration)) {
350         return FlowchartElements.NODE_TYPE_END_EVENT;
351       } else if (nodeEventElements.contains(configuration)) {
352         return FlowchartElements.NODE_TYPE_EVENT;
353       } else if (nodeReferenceElements.contains(configuration)) {
354         return FlowchartElements.NODE_TYPE_PROCESS;
355       } else if (nodeStartElements.contains(configuration)) {
356         return FlowchartElements.NODE_TYPE_START_EVENT;
357       }
358     }
359 
360     return FlowchartElements.TYPE_INVALID;
361   }
362 
363   /**
364    * Returns the branch type of the given edge realizer.
365    *
366    * @return one of the direction constants in {@link FlowchartLayouter}.
367    */
368   protected int getBranchType(EdgeRealizer realizer) {
369     if (isPositiveBranch(realizer)) {
370       return getAdjustedPositiveBranchDirection();
371     } else if (isNegativeBranch(realizer)) {
372       return getAdjustedNegativeBranchDirection();
373     } else {
374       return FlowchartLayouter.DIRECTION_UNDEFINED;
375     }
376   }
377 
378   /**
379    * Returns whether or not the given edge realizer is a positive branch. This default implementation considers an edge
380    * as positive branch if its source is a decision and if its label text equals 'Yes' (ignoring case considerations).
381    *
382    * @param realizer the realizer to consider.
383    * @return whether or not the given edge realizer is a positive branch.
384    */
385   protected boolean isPositiveBranch(EdgeRealizer realizer) {
386     return (int) getType(realizer.getSourceRealizer()) == (int) FlowchartElements.NODE_TYPE_DECISION
387         && realizer.labelCount() > 0 && isMatchingLabelText(realizer.getLabel(0), positiveBranchLabel);
388   }
389 
390   /**
391    * Returns whether or not the given edge realizer is a positive branch. This default implementation considers an edge
392    * as negative branch if its source is a decision and if its label text equals 'No' (ignoring case considerations).
393    *
394    * @param realizer the realizer to consider.
395    * @return whether or not the given edge realizer is a negative branch.
396    */
397   protected boolean isNegativeBranch(EdgeRealizer realizer) {
398     return (int) getType(realizer.getSourceRealizer()) == (int) FlowchartElements.NODE_TYPE_DECISION
399         && realizer.labelCount() > 0 && isMatchingLabelText(realizer.getLabel(0), negativeBranchLabel);
400   }
401 
402   /**
403    * Returns <code>true</code> if the given label is not null and its text equals, case ignored, the given text.
404    */
405   private static boolean isMatchingLabelText(YLabel label, String text) {
406     final String labelText = label != null ? label.getText() : null;
407     return labelText != null && labelText.equalsIgnoreCase(text);
408   }
409 
410   /**
411    * @see #getAdjustedNegativeBranchDirection()
412    * @see #getAdjustedPositiveBranchDirection()
413    */
414   private int calculateAdjustedNegativeBranchDirection() {
415     final int positiveDir = getAdjustedPositiveBranchDirection();
416     final int negativeDir = preferredNegativeBranchDirection;
417 
418     switch (negativeDir) {
419       case FlowchartLayouter.DIRECTION_STRAIGHT:
420         return positiveDir != FlowchartLayouter.DIRECTION_WITH_THE_FLOW ?
421             FlowchartLayouter.DIRECTION_WITH_THE_FLOW : FlowchartLayouter.DIRECTION_FLATWISE;
422 
423       case FlowchartLayouter.DIRECTION_FLATWISE:
424         if (positiveDir == FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW) {
425           return FlowchartLayouter.DIRECTION_LEFT_IN_FLOW;
426         } else if (positiveDir == FlowchartLayouter.DIRECTION_LEFT_IN_FLOW) {
427           return FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW;
428         } else {
429           return negativeDir;
430         }
431 
432       default:
433       case FlowchartLayouter.DIRECTION_AGAINST_THE_FLOW:
434         return FlowchartLayouter.DIRECTION_UNDEFINED;
435 
436       case FlowchartLayouter.DIRECTION_WITH_THE_FLOW:
437         return positiveDir != negativeDir ? negativeDir : FlowchartLayouter.DIRECTION_FLATWISE;
438 
439       case FlowchartLayouter.DIRECTION_LEFT_IN_FLOW:
440         return positiveDir != negativeDir ? negativeDir : FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW;
441 
442       case FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW:
443         return positiveDir != negativeDir ? negativeDir : FlowchartLayouter.DIRECTION_LEFT_IN_FLOW;
444     }
445   }
446 
447   /**
448    * @see #getAdjustedNegativeBranchDirection()
449    * @see #getAdjustedPositiveBranchDirection()
450    */
451   private int calculateAdjustedPositiveBranchDirection() {
452     switch (preferredPositiveBranchDirection) {
453       case FlowchartLayouter.DIRECTION_STRAIGHT:
454         return FlowchartLayouter.DIRECTION_WITH_THE_FLOW;
455       case FlowchartLayouter.DIRECTION_AGAINST_THE_FLOW:
456         return FlowchartLayouter.DIRECTION_UNDEFINED;
457       case FlowchartLayouter.DIRECTION_FLATWISE:
458         if (preferredNegativeBranchDirection == FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW) {
459           return FlowchartLayouter.DIRECTION_LEFT_IN_FLOW;
460         } else if (preferredNegativeBranchDirection == FlowchartLayouter.DIRECTION_LEFT_IN_FLOW) {
461           return FlowchartLayouter.DIRECTION_RIGHT_IN_FLOW;
462         } else {
463           return preferredPositiveBranchDirection;
464         }
465       default:
466         return preferredPositiveBranchDirection;
467     }
468   }
469 
470 
471   private static class DefaultGroupLabelExclusion extends DataProviderAdapter {
472     private final HierarchyManager hm;
473 
474     DefaultGroupLabelExclusion( final HierarchyManager hm ) {
475       this.hm = hm;
476     }
477 
478     public boolean getBool( final Object dataHolder ) {
479       if (dataHolder instanceof NodeLabel) {
480         final NodeRealizer nr = ((NodeLabel) dataHolder).getRealizer();
481         if (nr != null && nr.labelCount() > 0 && dataHolder == nr.getLabel(0)) {
482           final Node n = nr.getNode();
483           if (n != null) {
484             final Graph g = n.getGraph();
485             if (g != null && hm.contains(g)) {
486               return !hm.isGroupNode(n);
487             }
488           }
489         }
490       }
491       return true;
492     }
493   }
494 }
495