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