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.layout.module;
15  
16  import y.module.LayoutModule;
17  import y.module.YModule;
18  
19  import y.base.DataProvider;
20  import y.base.Edge;
21  import y.base.EdgeCursor;
22  import y.base.EdgeMap;
23  import y.base.Node;
24  import y.base.NodeCursor;
25  import y.layout.router.BusDescriptor;
26  import y.layout.router.BusRouter;
27  import y.option.ConstraintManager;
28  import y.option.DoubleOptionItem;
29  import y.option.IntOptionItem;
30  import y.option.OptionGroup;
31  import y.option.OptionHandler;
32  import y.option.OptionItem;
33  import y.util.DataProviderAdapter;
34  import y.util.Maps;
35  import y.view.Graph2D;
36  
37  import java.awt.Color;
38  import java.util.HashSet;
39  import java.util.Set;
40  
41  /**
42   * Module for the {@link y.layout.router.BusRouter}.
43   * <p>
44   * There are more scopes in this module than in {@link BusRouter}. Each additional scope is mapped to an appropriate
45   * combinations of scope and fixed edges in BusRouter.
46   * </p>
47   * <dl>
48   * <dt>ALL</dt>
49   * <dd>Maps to <code>BusRouter.SCOPE_ALL</code>. All edges are in scope, and all of them are movable.</dd>
50   * <dt>SUBSET</dt>
51   * <dd>Maps to <code>BusRouter.SCOPE_SUBSET</code>. The selected edges are in scope, and all of them are movable.</dd>
52   * <dt>SUBSET_BUS</dt>
53   * <dd>Each bus with at least one selected edge is in scope, and all of their edges are movable.</dd>
54   * <dt>PARTIAL</dt>
55   * <dd>Each bus with at least one selected node is in scope, and only the adjacent edges of the selected nodes are
56   * movable.</dd>
57   * </dl>
58   *
59   *
60   * @see <a href="http://docs.yworks.com/yfiles/doc/developers-guide/orthogonal_bus_router.html#orthogonal_bus_router">Section Orthogonal Bus-style Edge Routing</a> in the yFiles for Java Developer's Guide
61   */
62  public class BusRouterModule extends LayoutModule {
63  
64    private static final String NAME = "BUS_ROUTER";
65    private static final String GROUP_LAYOUT = "GROUP_LAYOUT";
66    private static final String GROUP_SELECTION = "GROUP_SELECTION";
67    private static final String GROUP_ROUTING = "GROUP_ROUTING";
68  
69    private static final String ALL = "ALL";
70    private static final String BUSES = "BUSES";
71    private static final String COLOR = "COLOR";
72    private static final String CROSSING_COST = "CROSSING_COST";
73    private static final String CROSSING_REROUTING = "CROSSING_REROUTING";
74    private static final String GRID_ENABLED = "GRID_ENABLED";
75    private static final String GRID_SPACING = "GRID_SPACING";
76    private static final String MINIMUM_CONNECTIONS_COUNT = "MINIMUM_CONNECTIONS_COUNT";
77    private static final String MINIMUM_BACKBONE_LENGTH = "MINIMUM_BACKBONE_LENGTH";
78    private static final String MIN_DISTANCE_TO_EDGES = "MIN_DISTANCE_TO_EDGES";
79    private static final String MIN_DISTANCE_TO_NODES = "MIN_DISTANCE_TO_NODES";
80    private static final String PREFERRED_BACKBONE_COUNT = "PREFERRED_BACKBONE_COUNT";
81    private static final String PARTIAL = "PARTIAL";
82    private static final String SCOPE = "SCOPE";
83    private static final String SINGLE = "SINGLE";
84    private static final String SUBSET = "SUBSET";
85    private static final String SUBSET_BUS = "SUBSET_BUS";
86  
87    private final BusRouter busRouter;
88  
89    /**
90     * Specifies whether the options of group layout are used.
91     */
92    protected boolean optionsLayout;
93    /**
94     * Specifies whether the options for initial backbone selection are used.
95     */
96    protected boolean optionsSelection;
97    /**
98     * Specifies whether the options for routing and recombination are used.
99     */
100   protected boolean optionsRouting;
101 
102   /**
103    * Creates a new instance of this module.
104    */
105   public BusRouterModule() {
106     super(NAME, "yFiles Layout Team", "Routes edges in bus-style");
107     busRouter = new BusRouter();
108     optionsLayout = true;
109     optionsSelection = true;
110     optionsRouting = true;
111   }
112 
113   /**
114    * Creates an option handler for this module.
115    */
116   protected OptionHandler createOptionHandler() {
117     final OptionHandler oh = new OptionHandler(NAME);
118     addOptionItems(oh);
119     return oh;
120   }
121 
122   /**
123    * Adds the option items used by this module to the given <code>OptionHandler</code>.
124    * @param oh the <code>OptionHandler</code> to add the items to
125    */
126   protected void addOptionItems(OptionHandler oh) {
127     ConstraintManager cm = new ConstraintManager(oh);
128 
129     BusRouter localRouter = new BusRouter();
130 
131     if (optionsLayout) {
132       OptionItem item;
133       oh.addEnum(SCOPE, new String[]{ALL, SUBSET, SUBSET_BUS, PARTIAL}, (int) localRouter.getScope());
134       oh.addEnum(BUSES, new String[]{SINGLE, COLOR}, 0);
135       oh.addBool(GRID_ENABLED, localRouter.isGridRoutingEnabled());
136       item = oh.addInt(GRID_SPACING, localRouter.getGridSpacing());
137       item.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
138       item = oh.addInt(MIN_DISTANCE_TO_NODES, localRouter.getMinimumDistanceToNode());
139       item.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
140       item = oh.addInt(MIN_DISTANCE_TO_EDGES, localRouter.getMinimumDistanceToEdge());
141       item.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
142 
143       cm.setEnabledOnValueEquals(GRID_ENABLED, Boolean.TRUE, GRID_SPACING);
144 
145       OptionGroup og = new OptionGroup();
146       og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, GROUP_LAYOUT);
147       og.addItem(oh.getItem(SCOPE));
148       og.addItem(oh.getItem(BUSES));
149       og.addItem(oh.getItem(GRID_ENABLED));
150       og.addItem(oh.getItem(GRID_SPACING));
151       og.addItem(oh.getItem(MIN_DISTANCE_TO_NODES));
152       og.addItem(oh.getItem(MIN_DISTANCE_TO_EDGES));
153     }
154 
155     if (optionsSelection) {
156       OptionItem item;
157       item = oh.addInt(PREFERRED_BACKBONE_COUNT, localRouter.getPreferredBackboneSegmentCount());
158       item.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
159       item = oh.addDouble(MINIMUM_BACKBONE_LENGTH, localRouter.getMinimumBackboneSegmentLength());
160       item.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(1.0));
161 
162       OptionGroup og = new OptionGroup();
163       og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, GROUP_SELECTION);
164       og.addItem(oh.getItem(PREFERRED_BACKBONE_COUNT));
165       og.addItem(oh.getItem(MINIMUM_BACKBONE_LENGTH));
166     }
167 
168     if (optionsRouting) {
169       OptionItem item;
170       item = oh.addDouble(CROSSING_COST, localRouter.getCrossingCost());
171       item.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(0.0));
172       oh.addBool(CROSSING_REROUTING, localRouter.isReroutingEnabled());
173       item.setAttribute(DoubleOptionItem.ATTRIBUTE_MIN_VALUE, new Double(0.0));
174       item = oh.addInt(MINIMUM_CONNECTIONS_COUNT, localRouter.getMinimumBusConnectionsCount());
175       item.setAttribute(IntOptionItem.ATTRIBUTE_MIN_VALUE, new Integer(1));
176 
177       OptionGroup og = new OptionGroup();
178       og.setAttribute(OptionGroup.ATTRIBUTE_TITLE, GROUP_ROUTING);
179       og.addItem(oh.getItem(CROSSING_COST));
180       og.addItem(oh.getItem(CROSSING_REROUTING));
181       og.addItem(oh.getItem(MINIMUM_CONNECTIONS_COUNT));
182     }
183 
184     initOptionHandler(oh, busRouter);
185   }
186 
187   /**
188    * Sets the option items of the given option handler to the settings of the given <code>BusRouter</code>.
189    * @param oh        the option handler
190    * @param busRouter the bus router
191    */
192   protected void initOptionHandler(OptionHandler oh, BusRouter busRouter) {
193     if (optionsLayout) {
194       oh.set(SCOPE, toScopeName(busRouter.getScope()));
195       oh.set(GRID_ENABLED, Boolean.valueOf(busRouter.isGridRoutingEnabled()));
196       oh.set(GRID_SPACING, new Integer(busRouter.getGridSpacing()));
197       oh.set(MIN_DISTANCE_TO_NODES, new Integer(busRouter.getMinimumDistanceToNode()));
198       oh.set(MIN_DISTANCE_TO_EDGES, new Integer(busRouter.getMinimumDistanceToEdge()));
199     }
200 
201     if (optionsSelection) {
202       oh.set(CROSSING_COST, new Double(busRouter.getCrossingCost()));
203       oh.set(CROSSING_REROUTING, Boolean.valueOf(busRouter.isReroutingEnabled()));
204     }
205 
206     if (optionsRouting) {
207       oh.set(PREFERRED_BACKBONE_COUNT, new Integer(busRouter.getPreferredBackboneSegmentCount()));
208       oh.set(MINIMUM_CONNECTIONS_COUNT, new Integer(busRouter.getMinimumBusConnectionsCount()));
209       oh.set(MINIMUM_BACKBONE_LENGTH, new Double(busRouter.getMinimumBackboneSegmentLength()));
210     }
211   }
212 
213   /**
214    * Configures an instance of {@link y.layout.router.BusRouter}. The values provided by this module's option handler
215    * are being used for this purpose.
216    * @param busRouter the BusRouter to be configured.
217    */
218   public void configure(BusRouter busRouter) {
219     final OptionHandler oh = getOptionHandler();
220 
221     if (optionsLayout) {
222       busRouter.setScope(toBusRouterScope(oh.get(SCOPE)));
223       busRouter.setGridRoutingEnabled(oh.getBool(GRID_ENABLED));
224       busRouter.setGridSpacing(oh.getInt(GRID_SPACING));
225       busRouter.setMinimumDistanceToNode(oh.getInt(MIN_DISTANCE_TO_NODES));
226       busRouter.setMinimumDistanceToEdge(oh.getInt(MIN_DISTANCE_TO_EDGES));
227     }
228 
229     if (optionsSelection) {
230       busRouter.setCrossingCost(oh.getDouble(CROSSING_COST));
231       busRouter.setReroutingEnabled(oh.getBool(CROSSING_REROUTING));
232     }
233 
234     if (optionsRouting) {
235       busRouter.setPreferredBackboneSegmentCount(oh.getInt(PREFERRED_BACKBONE_COUNT));
236       busRouter.setMinimumBusConnectionsCount(oh.getInt(MINIMUM_CONNECTIONS_COUNT));
237       busRouter.setMinimumBackboneSegmentLength(oh.getDouble(MINIMUM_BACKBONE_LENGTH));
238     }
239   }
240 
241   /**
242    * Returns the bus router used by this module.
243    * @return the bus router used by this modul
244    */
245   protected BusRouter getBusRouter() {
246     return busRouter;
247   }
248 
249   /**
250    * Launches the layouter of this module.
251    * @noinspection ConstantConditions,PointlessBooleanExpression
252    */
253   protected void mainrun() {
254     launchLayouter(busRouter);
255   }
256 
257   /**
258    * Prepares this module for {@link #mainrun()}.
259    */
260   protected void init() {
261     super.init();
262     configure(busRouter);
263 
264     if (!optionsLayout) {
265       return;
266     }
267 
268     final Graph2D graph = getGraph2D();
269     final OptionHandler oh = getOptionHandler();
270 
271     // Explicit boolean variables for BUSES and SCOPE since these are often queried
272     final boolean busByColor = COLOR.equals(oh.get(BUSES));
273     final boolean scopePartial = PARTIAL.equals(oh.get(SCOPE));
274 
275     /*
276      * The following creates bus descriptors according to the set options for SCOPE and BUSES and with respect to the
277      * current selection.
278      */
279     final EdgeMap descriptorMap = Maps.createHashedEdgeMap();
280     graph.addDataProvider(BusRouter.EDGE_DESCRIPTOR_DPKEY, descriptorMap);
281     // Create the bus descriptors. For scopes SUBSET_BUS and PARTIAL, this is done for all edges since the bus IDs
282     // are required to determine which edges belong to the final scope.
283     for (EdgeCursor ec = SUBSET.equals(oh.get(SCOPE)) ? graph.selectedEdges() : graph.edges(); ec.ok(); ec.next()) {
284       final Edge edge = ec.edge();
285       // Except for scope PARTIAL, all edges in scope are movable
286       final boolean fixed = scopePartial && !graph.isSelected(edge.source()) && !graph.isSelected(edge.target());
287       final Color id = busByColor ? graph.getRealizer(edge).getLineColor() : Color.BLACK;
288       descriptorMap.set(edge, new BusDescriptor(id, fixed));
289     }
290 
291     /*
292      * Create the appropriate selected edges data provider for the current scope.
293      */
294     final DataProvider descriptorDP = graph.getDataProvider(BusRouter.EDGE_DESCRIPTOR_DPKEY);
295     if (SUBSET.equals(oh.get(SCOPE))) {
296       // The selected edges are in scope, and all of them are movable
297       graph.addDataProvider(busRouter.getSelectedEdgesDpKey(), new DataProviderAdapter() {
298         public boolean getBool(Object dataHolder) {
299           return dataHolder instanceof Edge && graph.isSelected((Edge) dataHolder);
300         }
301       });
302     } else if (SUBSET_BUS.equals(oh.get(SCOPE))) {
303       // Each bus with at least one selected edge is in scope, and all of their edges are movable.
304       final Set selectedIDs = new HashSet();
305       for (EdgeCursor ec = graph.selectedEdges(); ec.ok(); ec.next()) {
306         selectedIDs.add(((BusDescriptor) descriptorDP.get(ec.edge())).getID());
307       }
308       graph.addDataProvider(busRouter.getSelectedEdgesDpKey(), new DataProviderAdapter() {
309         public boolean getBool(Object dataHolder) {
310           return selectedIDs.contains(((BusDescriptor) descriptorDP.get(dataHolder)).getID());
311         }
312       });
313     } else if (PARTIAL.equals(oh.get(SCOPE))) {
314       // Each bus with at least one selected node is in scope, and the adjacent edges of the selected
315       // nodes are movable.
316       final Set selectedIDs = new HashSet();
317       for (NodeCursor nc = graph.selectedNodes(); nc.ok(); nc.next()) {
318         final Node node = nc.node();
319         for (EdgeCursor ec = node.edges(); ec.ok(); ec.next()) {
320           selectedIDs.add(((BusDescriptor) descriptorDP.get(ec.edge())).getID());
321         }
322       }
323       graph.addDataProvider(busRouter.getSelectedEdgesDpKey(), new DataProviderAdapter() {
324         public boolean getBool(Object dataHolder) {
325           return selectedIDs.contains(((BusDescriptor) descriptorDP.get(dataHolder)).getID());
326         }
327       });
328     }
329 
330   }
331 
332   /**
333    * Disposes this module after {@link #mainrun()}.
334    */
335   protected void dispose() {
336     if (optionsLayout) {
337       // remove the data providers set by this module
338       getGraph2D().removeDataProvider(BusRouter.EDGE_DESCRIPTOR_DPKEY);
339 
340       if (!ALL.equals(getOptionHandler().get(SCOPE))) {
341         getGraph2D().removeDataProvider(busRouter.getSelectedEdgesDpKey());
342       }
343     }
344 
345     super.dispose();
346   }
347 
348   private static byte toBusRouterScope(Object scopeName) {
349     if (ALL.equals(scopeName)) {
350       return BusRouter.SCOPE_ALL;
351     } else if (SUBSET.equals(scopeName) || SUBSET_BUS.equals(scopeName) || PARTIAL.equals(scopeName)) {
352       return BusRouter.SCOPE_SUBSET;
353     } else {
354       return BusRouter.SCOPE_ALL;
355     }
356   }
357 
358   private static String toScopeName(byte busRouterScope) {
359     if (BusRouter.SCOPE_ALL == busRouterScope) {
360       return ALL;
361     } else if (BusRouter.SCOPE_SUBSET == busRouterScope) {
362       return SUBSET;
363     } else {
364       throw new IllegalArgumentException("Unknown scope: " + busRouterScope);
365     }
366   }
367 }
368