001/* Layout interface between the Ptolemy Diva editor and the KIELER
002 * layout library.*/
003/*
004 @Copyright (c) 2009-2016 The Regents of the University of California.
005 All rights reserved.
006
007 Permission is hereby granted, without written agreement and without
008 license or royalty fees, to use, copy, modify, and distribute this
009 software and its documentation for any purpose, provided that the
010 above copyright notice and the following two paragraphs appear in all
011 copies of this software.
012
013 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
014 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
015 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
016 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
017 SUCH DAMAGE.
018
019 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
020 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
021 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
022 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
023 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
024 ENHANCEMENTS, OR MODIFICATIONS.
025
026 PT_COPYRIGHT_VERSION_2
027 COPYRIGHTENDKEY
028 */
029
030package ptolemy.vergil.basic.layout.kieler;
031
032import java.awt.Dimension;
033import java.awt.geom.AffineTransform;
034import java.awt.geom.Point2D;
035import java.awt.geom.Rectangle2D;
036import java.util.Iterator;
037import java.util.LinkedList;
038import java.util.List;
039import java.util.Map;
040
041import javax.swing.SwingConstants;
042
043import com.google.common.collect.BiMap;
044import com.google.common.collect.HashBiMap;
045import com.google.common.collect.Iterators;
046import com.google.common.collect.LinkedListMultimap;
047import com.google.common.collect.ListMultimap;
048import com.google.common.collect.Lists;
049import com.google.common.collect.Maps;
050
051import de.cau.cs.kieler.core.alg.BasicProgressMonitor;
052import de.cau.cs.kieler.core.alg.DefaultFactory;
053import de.cau.cs.kieler.core.alg.IKielerProgressMonitor;
054import de.cau.cs.kieler.core.alg.InstancePool;
055import de.cau.cs.kieler.core.kgraph.KEdge;
056import de.cau.cs.kieler.core.kgraph.KLabel;
057import de.cau.cs.kieler.core.kgraph.KNode;
058import de.cau.cs.kieler.core.kgraph.KPort;
059import de.cau.cs.kieler.core.math.KVector;
060import de.cau.cs.kieler.core.util.Pair;
061import de.cau.cs.kieler.kiml.AbstractLayoutProvider;
062import de.cau.cs.kieler.kiml.klayoutdata.KEdgeLayout;
063import de.cau.cs.kieler.kiml.klayoutdata.KLayoutData;
064import de.cau.cs.kieler.kiml.klayoutdata.KPoint;
065import de.cau.cs.kieler.kiml.klayoutdata.KShapeLayout;
066import de.cau.cs.kieler.kiml.options.Alignment;
067import de.cau.cs.kieler.kiml.options.Direction;
068import de.cau.cs.kieler.kiml.options.EdgeLabelPlacement;
069import de.cau.cs.kieler.kiml.options.EdgeRouting;
070import de.cau.cs.kieler.kiml.options.LayoutOptions;
071import de.cau.cs.kieler.kiml.options.PortConstraints;
072import de.cau.cs.kieler.kiml.options.PortSide;
073import de.cau.cs.kieler.kiml.options.SizeConstraint;
074import de.cau.cs.kieler.kiml.util.KimlUtil;
075import de.cau.cs.kieler.klay.layered.LayeredLayoutProvider;
076import de.cau.cs.kieler.klay.layered.p3order.CrossingMinimizationStrategy;
077import de.cau.cs.kieler.klay.layered.p4nodes.NodePlacementStrategy;
078import de.cau.cs.kieler.klay.layered.properties.LayerConstraint;
079import de.cau.cs.kieler.klay.layered.properties.Properties;
080import diva.canvas.CanvasComponent;
081import diva.canvas.CompositeFigure;
082import diva.canvas.Figure;
083import diva.canvas.connector.AbstractConnector;
084import diva.canvas.toolbox.LabelFigure;
085import diva.graph.GraphModel;
086import diva.graph.layout.AbstractGlobalLayout;
087import diva.graph.layout.LayoutTarget;
088import diva.graph.modular.EdgeModel;
089import ptolemy.actor.Actor;
090import ptolemy.actor.CompositeActor;
091import ptolemy.actor.Director;
092import ptolemy.actor.TypedIOPort;
093import ptolemy.domains.modal.kernel.State;
094import ptolemy.domains.modal.kernel.Transition;
095import ptolemy.gui.Top;
096import ptolemy.kernel.ComponentPort;
097import ptolemy.kernel.CompositeEntity;
098import ptolemy.kernel.Entity;
099import ptolemy.kernel.Port;
100import ptolemy.kernel.Relation;
101import ptolemy.kernel.util.Attribute;
102import ptolemy.kernel.util.IllegalActionException;
103import ptolemy.kernel.util.InternalErrorException;
104import ptolemy.kernel.util.Locatable;
105import ptolemy.kernel.util.Location;
106import ptolemy.kernel.util.NamedObj;
107import ptolemy.moml.Vertex;
108import ptolemy.vergil.actor.ActorGraphModel;
109import ptolemy.vergil.actor.ActorGraphModel.ExternalPortModel;
110import ptolemy.vergil.actor.IOPortController;
111import ptolemy.vergil.actor.KielerLayoutConnector;
112import ptolemy.vergil.actor.KielerLayoutUtil;
113import ptolemy.vergil.actor.LayoutHint;
114import ptolemy.vergil.actor.PortTerminal;
115import ptolemy.vergil.basic.RelativeLocatable;
116import ptolemy.vergil.basic.RelativeLocation;
117import ptolemy.vergil.kernel.Link;
118import ptolemy.vergil.kernel.RelativeLinkFigure;
119import ptolemy.vergil.modal.FSMGraphModel;
120import ptolemy.vergil.modal.KielerLayoutArcConnector;
121import ptolemy.vergil.toolbox.SnapConstraint;
122
123///////////////////////////////////////////////////////////////////
124////KielerLayout
125/**
126 * Ptolemy Layouter that uses the KIELER layout algorithm from an external
127 * library to layout a given ptolemy model.
128 * <p>
129 * See http://www.informatik.uni-kiel.de/rtsys/kieler/ for more information
130 * about KIELER.
131 * </p>
132 * <p><b>
133 * KIELER - Kiel Integrated Environment for Layout for the Eclipse
134 * RichClientPlatform
135 * </b></p>
136 * <p>
137 * The KIELER project tries to enhance graphical modeling pragmatics. Next to
138 * higher level solutions (meta layout, view management, structure-based editing,
139 * etc.) developed for Eclipse models, it also implements custom layout algorithms.
140 * </p>
141 * <p>
142 * This class interfaces a standalone KIELER layout algorithm for actor oriented
143 * port based graphical diagrams with a Ptolemy diagram.
144 * </p>
145 * <p>
146 * While KIELER is mainly developed for an Eclipse environment, most algorithms
147 * are also available standalone and can be used in a non-Eclipse environment.
148 * This class is an approach to leverage this by employing the algorithms within Ptolemy.
149 * Two standalone external libraries are required, one containing KIELER classes
150 * and a small subset of the Eclipse Modeling Framework (EMF), the other
151 * containing the Google Guava library, which is used as utility.
152 * </p>
153 * <p>
154 * Calling the layout() method will create a new KIELER graph data structure, run
155 * KIELER layout algorithms on it and augment it with resulting layout
156 * information (locations and sizes of nodes, bend points of connections). Then
157 * this layout is applied to the Ptolemy model. Moving of nodes in Ptolemy is
158 * done via adding or changing location attributes.
159 * </p>
160 * <p>
161 * Setting bend points was not supported in Ptolemy. Ptolemy's built-in
162 * connection routing does not consider obstacle avoidance, hence overlaps
163 * with other nodes and connections might appear. In order to gap this problem,
164 * the actual drawing of the connections is performed by {@link KielerLayoutConnector}
165 * instead of the standard Manhattan connector.
166 * This KielerLayout stores bend points of connections
167 * persistently in a Ptolemy model via {@link LayoutHint} attributes
168 * attached to relations. The KielerLayoutConnector then reads these
169 * attributes and routes the edges accordingly. If the bend points are not
170 * valid anymore, the attribute is removed and layout has to be performed again.
171 * </p>
172 * @author Hauke Fuhrmann (<a href="mailto:haf@informatik.uni-kiel.de">haf</a>),
173 *         Christian Motika (<a href="mailto:cmot@informatik.uni-kiel.de">cmot</a>),
174 *         Miro Sp&ouml;nemann (<a href="mailto:msp@informatik.uni-kiel.de">msp</a>) ,
175 *         Christoph Daniel Schulze (<a href="mailto:cds@informatik.uni-kiel.de">cds</a>),
176 *         Ulf Rueegg
177 * @version $Id$
178 * @since Ptolemy II 8.0
179 * @Pt.ProposedRating Red (cxh)
180 * @Pt.AcceptedRating Red (cxh)
181 */
182public class KielerLayout extends AbstractGlobalLayout {
183
184    /**
185     * Construct an instance taking a LayoutTarget for specifying some methods
186     * for layout handling as given by the standard Ptolemy
187     * AbstractGlobalLayout. The KielerLayout will need access to the top level
188     * Ptolemy model, so either use corresponding constructor or call setModel()
189     * prior to layout invocation.
190     *
191     * @param target The LayoutTarget on which layout will be performed
192     */
193    public KielerLayout(LayoutTarget target) {
194        super(target);
195    }
196
197    /**
198     * Construct an instance setting the LayoutTarget as requested by the
199     * AbstractGlobalLayout and the containing Ptolemy model. Preferred
200     * constructor.
201     *
202     * @param target The LayoutTarget on which layout will be performed
203     * @param ptolemyContainer The composite actor that contains all elements to
204     *            be layouted
205     */
206    public KielerLayout(LayoutTarget target, CompositeActor ptolemyContainer) {
207        super(target);
208        this.setModel(ptolemyContainer);
209    }
210
211    ///////////////////////////////////////////////////////////////////
212    ////                         public methods                    ////
213
214    /**
215     * Layout the given composite. Main entry point for the layout action.
216     * Create a KIELER KGraph data structure corresponding to the Ptolemy model,
217     * instantiate a KIELER layout algorithm (AbstractLayoutProvider) and run
218     * its doLayout() method on the KGraph. The KGraph is augmented with
219     * layout information (position and sizes of objects and bend points for
220     * connections). This information is then reapplied to the ptolemy model by
221     * stating MoMLChangeRequests with location attributes for nodes.
222     * Connection bend points are applied using {@link LayoutHint}s.
223     *
224     * @param composite the container of the diagram in terms of a GraphModel.
225     */
226    @Override
227    public void layout(Object composite) {
228
229        KielerLayoutConnector.setLayoutInProgress(true);
230        KielerLayoutArcConnector.setLayoutInProgress(true);
231
232        // some variables for time statistics
233        long overallTime = System.currentTimeMillis();
234
235        _report("Performing KIELER layout... ");
236        long graphOverhead = overallTime;
237
238        _graphModel = getLayoutTarget().getGraphModel();
239
240        // Create a KGraph for the KIELER layout algorithm.
241        KNode parentNode = KimlUtil.createInitializedNode();
242        KShapeLayout parentLayout = parentNode.getData(KShapeLayout.class);
243        if (_top != null) {
244            Dimension contentSize = _top.getContentSize();
245            parentLayout.setWidth(contentSize.width);
246            parentLayout.setHeight(contentSize.height);
247        }
248
249        // Some layouts (such as FSM) may build a skeleton
250        // of hierarchical nodes to separate certain elements
251        // of the diagram from each other
252        // The 'mainModelNode' is the node that is supposed
253        // to hold the actual elements of the ptolemy model
254        KNode mainModelNode = parentNode;
255        KShapeLayout mainModelLayout = parentLayout;
256        if (_graphModel instanceof FSMGraphModel) {
257            // create a FSM skeleton to separate input and output ports
258            mainModelNode = _createFsmSkeleton(parentNode);
259            mainModelLayout = mainModelNode.getData(KShapeLayout.class);
260        }
261
262        try {
263            // Configure the layout algorithm by annotating the graph.
264            Parameters parameters = new Parameters(_compositeEntity);
265            parameters.configureLayout(mainModelLayout,
266                    getLayoutTarget().getGraphModel());
267
268            // Now read Ptolemy model and fill the KGraph with the model data.
269            _createGraph(composite, mainModelNode);
270            graphOverhead = System.currentTimeMillis() - graphOverhead;
271
272            // Create the layout provider which performs the actual layout algorithm.
273            InstancePool<AbstractLayoutProvider> layouterPool = _getLayouterPool();
274            AbstractLayoutProvider layoutProvider = layouterPool.fetch();
275
276            // Create a progress monitor for execution time measurement.
277            IKielerProgressMonitor progressMonitor = new BasicProgressMonitor();
278
279            // Perform layout on the created graph.
280            _recursivelyLayout(parentNode, layoutProvider, progressMonitor);
281
282            // Write to XML file for debugging layout (requires XMI resource factory).
283            if (DEBUG) {
284                KimlUtil.persistDataElements(parentNode);
285                KielerGraphUtil._writeToFile(parentNode);
286            }
287
288            // Set initial position as the bounding box of the hierarchical node.
289            KVector offset = KielerGraphUtil._getUpperLeftCorner(parentNode);
290            parentLayout.setXpos(parentLayout.getXpos() - (float) offset.x);
291            parentLayout.setYpos(parentLayout.getYpos() - (float) offset.y);
292
293            long momlRequestOverhead = System.currentTimeMillis();
294
295            // Create a special change request to apply the computed layout to the model.
296            ApplyLayoutRequest layoutRequest = new ApplyLayoutRequest(
297                    _compositeEntity);
298
299            // Apply layout to ptolemy model.
300            _recursivelyApplyLayout(parentNode, layoutRequest);
301
302            // Let the composite actor execute the actual changes.
303            _compositeEntity.requestChange(layoutRequest);
304
305            momlRequestOverhead = System.currentTimeMillis()
306                    - momlRequestOverhead;
307            overallTime = System.currentTimeMillis() - overallTime;
308            _report("KIELER layout done in " + overallTime
309                    + "ms (Graph conversion " + graphOverhead + "ms, Algorithm "
310                    + Math.round(progressMonitor.getExecutionTime() * 1000)
311                    + "ms, MoMLChanges " + momlRequestOverhead + "ms).");
312
313            // Release the layout provider back to the instance pool for later reuse.
314            layouterPool.release(layoutProvider);
315
316        } catch (IllegalActionException exception) {
317            // Throw some Ptolemy runtime exception.
318            throw new InternalErrorException(exception);
319        }
320
321        KielerLayoutConnector.setLayoutInProgress(false);
322        KielerLayoutArcConnector.setLayoutInProgress(false);
323    }
324
325    /**
326     * Set the Ptolemy Model that contains the graph that is to be layouted. The
327     * layouter will require access to the Ptolemy model because the lower level
328     * Diva abstraction does not consider certain properties required by the
329     * KIELER layouter such as port positions.
330     *
331     * @param model The parent composite entity which internal diagram shall be layouted.
332     */
333    public void setModel(CompositeEntity model) {
334        this._compositeEntity = model;
335    }
336
337    /**
338     * Set the Top window to enable status reports on the status bar.
339     *
340     * @param top The Top window
341     */
342    public void setTop(Top top) {
343        this._top = top;
344    }
345
346    ///////////////////////////////////////////////////////////////////
347    ////                         protected methods                 ////
348
349    /**
350     * Return a pool for layout provider instances that can be reused in subsequent layout
351     * runs. New instances can be fetched from the pool and should be released back to
352     * the pool after use.
353     *
354     * @return a layout provider pool
355     */
356    protected static synchronized InstancePool<AbstractLayoutProvider> _getLayouterPool() {
357        if (_layoutProviderPool == null) {
358            _layoutProviderPool = new InstancePool<AbstractLayoutProvider>(
359                    new DefaultFactory(LayeredLayoutProvider.class));
360        }
361        return _layoutProviderPool;
362    }
363
364    ///////////////////////////////////////////////////////////////////
365    ////                         private methods                   ////
366
367    /**
368     * Performs a recursive layout inside-out (if necessary).
369     *
370     * @param parentNode
371     *          a node for which to apply the layout
372     * @param layoutProvider
373     *          the layout provider to execute the layout
374     * @param progressMonitor
375     *          a progress monitor to measure execution time
376     */
377    private void _recursivelyLayout(KNode parentNode,
378            AbstractLayoutProvider layoutProvider,
379            IKielerProgressMonitor progressMonitor) {
380
381        for (KNode child : parentNode.getChildren()) {
382            if (!child.getChildren().isEmpty()) {
383                layoutProvider.doLayout(child, progressMonitor.subTask(1));
384            }
385        }
386
387        layoutProvider.doLayout(parentNode, progressMonitor);
388    }
389
390    /**
391     * Recusively applies {@link #_applyLayout(KNode)} to any {@link KNode}
392     * that has further children.
393     *
394     * @param parentNode The KIELER graph object containing all layout information
395     *            to apply to the Ptolemy model
396     * @param layoutRequest the common layout request for the current layout run
397     * @exception IllegalActionException if routing of edges fails.
398     */
399    private void _recursivelyApplyLayout(KNode parentNode,
400            ApplyLayoutRequest layoutRequest) throws IllegalActionException {
401
402        // the _applyLayout method applies positions to the
403        // children of a node
404        if (!parentNode.getChildren().isEmpty()) {
405            _applyLayout(parentNode, layoutRequest);
406
407            for (KNode child : parentNode.getChildren()) {
408                if (!child.getChildren().isEmpty()) {
409                    _recursivelyApplyLayout(child, layoutRequest);
410                }
411            }
412        }
413    }
414
415    /**
416     * Traverse a composite KNode containing corresponding KIELER nodes, ports
417     * and edges for the Ptolemy model and apply all layout information
418     * contained by it back to the Ptolemy model. Do most changes to the Ptolemy
419     * model via MoMLChangeRequests. Set location attributes for all visible
420     * Ptolemy nodes.
421     * Optionally route edges by inserting {@link LayoutHint} attributes.
422     *
423     * @param parentNode The KIELER graph object containing all layout information
424     *            to apply to the Ptolemy model
425     * @param layoutRequest the common layout request for the current layout run
426     * @exception IllegalActionException if routing of edges fails.
427     */
428    private void _applyLayout(KNode parentNode,
429            ApplyLayoutRequest layoutRequest) throws IllegalActionException {
430
431        // Apply node layout.
432        for (KNode knode : parentNode.getChildren()) {
433            KShapeLayout nodeLayout = knode.getData(KShapeLayout.class);
434            KVector nodePos = nodeLayout.createVector();
435            Object divaNode = _kieler2ptolemyDivaNodes.get(knode);
436            if (divaNode instanceof Location) {
437                Locatable location = (Location) divaNode;
438
439                // Transform coordinate systems.
440                KimlUtil.toAbsolute(nodePos, parentNode);
441                _kNode2Ptolemy(nodePos, divaNode, location);
442
443                // Calculate the snap-to-grid coordinates.
444                double[] snapToGridNodePoint = SnapConstraint
445                        .constrainPoint(nodePos.x, nodePos.y);
446
447                // Include the new location in the request.
448                layoutRequest.addLocation(location, snapToGridNodePoint[0],
449                        snapToGridNodePoint[1]);
450            }
451        }
452
453        GraphModel graphModel = getLayoutTarget().getGraphModel();
454        if (graphModel instanceof ActorGraphModel) {
455            // apply edge layout - bend points with layout hints
456            for (Pair<KEdge, Link> entry : _edgeList) {
457                _applyEdgeLayoutBendPointAnnotation(entry.getFirst(),
458                        entry.getSecond(), layoutRequest);
459            }
460        } else if (graphModel instanceof FSMGraphModel) {
461
462            // apply edge layout - one single point for specifying a curve
463            for (Pair<KEdge, Link> entry : _edgeList) {
464
465                if (parentNode.getData(KLayoutData.class)
466                        .getProperty(Parameters.SPLINES)) {
467                    _applyEdgeLayoutBendPointAnnotation(entry.getFirst(),
468                            entry.getSecond(), layoutRequest);
469                } else {
470                    _applyEdgeLayoutCurve(entry.getFirst(), entry.getSecond(),
471                            layoutRequest);
472                }
473            }
474        }
475    }
476
477    /**
478     * Apply the layout of an KEdge to its corresponding Diva Link. This is done
479     * by adding a LayoutHint attribute to the corresponding Relation. Only
480     * Relations are persistent objects in the abstract Ptolemy syntax and
481     * therefore add the bend points for a link in its Relation. Hence, there
482     * may be multiple link bend points stored in one Relation. The LayoutHint
483     * attribute can carry the bend points for multiple Links and identifies the
484     * link by the head and tail of the link.
485     *
486     * @param kedge The KIELER edge that holds the precomputed layout
487     *            information, i.e. bend point positions
488     * @exception IllegalActionException Exception will be thrown if replacing
489     *                of original relation is not possible, i.e. if unlink() or
490     *                link() methods fail.
491     */
492    private void _applyEdgeLayoutBendPointAnnotation(KEdge kedge, Link link,
493            ApplyLayoutRequest layoutRequest) throws IllegalActionException {
494        List<KPoint> bendPoints = kedge.getData(KEdgeLayout.class)
495                .getBendPoints();
496
497        // Translate bend points into an array of doubles for the layout hint attribute.
498        double[] layoutHintBendPoints = new double[bendPoints.size() * 2];
499        int index = 0;
500        KNode parentNode = KielerGraphUtil._getParent(kedge);
501        if (parentNode == null) {
502            throw new IllegalStateException(
503                    "Internal layout error. Every edge in the layout graph must have a parent.");
504        }
505        for (KPoint relativeKPoint : bendPoints) {
506            KVector kpoint = relativeKPoint.createVector();
507            KimlUtil.toAbsolute(kpoint, parentNode);
508
509            // calculate the snap-to-grid coordinates unless we route
510            // edges as splines. With splines the snap to grid feature would
511            // drastically deform the spline routes
512            if (parentNode.getData(KLayoutData.class).getProperty(
513                    LayoutOptions.EDGE_ROUTING) != EdgeRouting.SPLINES) {
514
515                double[] snapToGridBendPoint = SnapConstraint
516                        .constrainPoint(kpoint.x, kpoint.y);
517                layoutHintBendPoints[index] = snapToGridBendPoint[0];
518                layoutHintBendPoints[index + 1] = snapToGridBendPoint[1];
519            } else {
520
521                layoutHintBendPoints[index] = kpoint.x;
522                layoutHintBendPoints[index + 1] = kpoint.y;
523            }
524
525            index += 2;
526        }
527
528        // we support exactly one label (if one exists at all)
529        Point2D.Double labelLocation = null;
530        for (KLabel label : kedge.getLabels()) {
531            labelLocation = new Point2D.Double();
532            KVector pos = label.getData(KShapeLayout.class).createVector();
533            KimlUtil.toAbsolute(pos, parentNode);
534            labelLocation.x = pos.x;
535            labelLocation.y = pos.y;
536            break;
537        }
538
539        Relation relation = link.getRelation();
540        NamedObj head = (NamedObj) link.getHead();
541        NamedObj tail = (NamedObj) link.getTail();
542        // Determine correct direction of the edge.
543        if (head != _divaEdgeSource.get(link)) {
544            layoutRequest.addConnection(relation, tail, head,
545                    layoutHintBendPoints, labelLocation);
546        } else {
547            layoutRequest.addConnection(relation, head, tail,
548                    layoutHintBendPoints, labelLocation);
549        }
550
551    }
552
553    private void _applyEdgeLayoutCurve(KEdge kedge, Link link,
554            ApplyLayoutRequest layoutRequest) {
555        Relation relation = link.getRelation();
556        // Don't process any self-loops, since that would cause headaches.
557        if (relation instanceof Transition
558                && link.getHead() != link.getTail()) {
559            KEdgeLayout edgeLayout = kedge.getData(KEdgeLayout.class);
560            List<KPoint> bendPoints = edgeLayout.getBendPoints();
561
562            KNode source = kedge.getSource();
563            if (source == null) {
564                throw new IllegalStateException("Internal layout error. "
565                        + "Every edge must have a source node.");
566            }
567            KShapeLayout sourceLayout = source.getData(KShapeLayout.class);
568            double sourcex = sourceLayout.getXpos()
569                    + sourceLayout.getWidth() / 2;
570            double sourcey = sourceLayout.getYpos()
571                    + sourceLayout.getHeight() / 2;
572            KNode target = kedge.getTarget();
573            if (target == null) {
574                throw new IllegalStateException("Internal layout error. "
575                        + "Every edge must have a source node.");
576            }
577            KShapeLayout targetLayout = target.getData(KShapeLayout.class);
578            double targetx = targetLayout.getXpos()
579                    + targetLayout.getWidth() / 2;
580            double targety = targetLayout.getYpos()
581                    + targetLayout.getHeight() / 2;
582
583            // Determine a reference point for drawing the curve.
584            double exitAngle = 0;
585            double refx = 0, refy = 0;
586
587            // 1) if the edge has a label, we use the label's center
588            //    as reference
589            // 2) if there are no bend points, use the the center of
590            //    a straight line between source and target
591            // 3) if there are bend points, we use the center
592            //    between the two "middle" bend points
593            if (!kedge.getLabels().isEmpty()) {
594                KLabel label = kedge.getLabels().get(0);
595                KShapeLayout labelLayout = label.getData(KShapeLayout.class);
596                KVector center = labelLayout.createVector().add(
597                        labelLayout.getWidth() / 2f,
598                        labelLayout.getHeight() / 2f);
599                refx = center.x;
600                refy = center.y;
601            } else if (bendPoints.isEmpty()) {
602                refx = (edgeLayout.getSourcePoint().getX()
603                        + edgeLayout.getTargetPoint().getX()) / 2;
604                refy = (edgeLayout.getSourcePoint().getY()
605                        + edgeLayout.getTargetPoint().getY()) / 2;
606            } else {
607                int count = bendPoints.size();
608                KPoint point1 = bendPoints.get(count / 2 - 1);
609                KPoint point2 = bendPoints.get(count / 2);
610                refx = (point1.getX() + point2.getX()) / 2f;
611                refy = (point1.getY() + point2.getY()) / 2f;
612            }
613
614            // Take the angular difference between the reference point and
615            // the target point as seen from the source.
616            double targetth = Math.atan2(targety - sourcey, targetx - sourcex);
617            double meanth = Math.atan2(refy - sourcey, refx - sourcex);
618            exitAngle = meanth - targetth;
619
620            // Fit the angle into the bounds of [-pi, pi]
621            if (exitAngle > Math.PI) {
622                exitAngle -= 2 * Math.PI;
623            } else if (exitAngle < -Math.PI) {
624                exitAngle += 2 * Math.PI;
625            }
626
627            layoutRequest.addCurve((Transition) relation, exitAngle);
628        }
629    }
630
631    /**
632     * Creates a graph for the KIELER API from a Ptolemy model. Will traverse
633     * the low level GraphModel given by the composite and record all found
634     * elements in the mapping fields of this object that keep a mapping between
635     * Ptolemy/Diva objects and KIELER objects. New KIELER objects (KEdge,
636     * KNode, KPort) are created for their respective Ptolemy counterparts and
637     * initialized with the initial sizes and positions and are put in a
638     * composite KNode (the graph KIELER will perform the layout on later). To
639     * obtain the right mappings, multiple abstraction levels of Ptolemy are
640     * considered here: Diva, as this was the intended original way to do
641     * automatic layout (e.g. by GlobalAbstractLayout) and Ptolemy, since Diva
642     * lacks certain concepts that are relevant for a proper layout, such
643     * as exact port locations for considering port constraints in the
644     * model, which are supported by KIELER.
645     *
646     * @param composite The GraphModel composite object to retrieve the model
647     *            information from
648     * @param parentNode KIELER subgraph to receive all connected
649     *            model elements
650     */
651    private void _createGraph(Object composite, KNode parentNode) {
652        _kieler2ptolemyDivaNodes = HashBiMap.create();
653        _ptolemy2KielerPorts = LinkedListMultimap.create();
654        _divaEdgeSource = Maps.newHashMap();
655        _divaEdgeTarget = Maps.newHashMap();
656        _divaLabel = Maps.newHashMap();
657        _edgeList = Lists.newLinkedList();
658        KShapeLayout parentLayout = parentNode.getData(KShapeLayout.class);
659
660        // Determine whether to include unconnected nodes.
661        boolean doBoxLayout = parentLayout.getProperty(Parameters.DECORATIONS);
662
663        // On-the-fly find upper left corner for bounding box of parent node.
664        float globalX = Float.MAX_VALUE, globalY = Float.MAX_VALUE;
665
666        // Traverse the ptolemy graph.
667        ExternalPortModel externalPortModel = null;
668        if (_graphModel instanceof ActorGraphModel) {
669            externalPortModel = ((ActorGraphModel) _graphModel)
670                    .getExternalPortModel();
671        }
672        List<Link> unprocessedEdges = new LinkedList<Link>();
673        List<NamedObj> unprocessedRelatives = new LinkedList<NamedObj>();
674
675        // Process nodes.
676        for (Iterator iterator = _graphModel.nodes(composite); iterator
677                .hasNext();) {
678            Object node = iterator.next();
679            if (!(node instanceof Locatable)) {
680                continue;
681            }
682            Iterator portIter = null;
683
684            // Here we get the corresponding Ptolemy object.
685            // This breaks with Ptolemy/Diva abstraction; for now we need
686            // the ptolemy actor to get the ports and port positions
687            // and to distinguish actors and relation vertices.
688            NamedObj semanticNode = (NamedObj) _graphModel
689                    .getSemanticObject(node);
690
691            if (doBoxLayout || PtolemyModelUtil._isConnected(semanticNode)) {
692                // Temporary variable for the new KNode corresponding to one of
693                // the following cases depending on what the semantic object is.
694                KNode knode = null;
695
696                // Handle actors, text, and directors.
697                if (semanticNode instanceof Actor
698                        || semanticNode instanceof Attribute) {
699
700                    // Create a KIELER node for a ptolemy node.
701                    knode = _createKNode(node, semanticNode);
702
703                    // Handle the ports of this node.
704                    if (semanticNode instanceof Actor
705                            && semanticNode instanceof Entity) {
706                        Actor actor = (Actor) semanticNode;
707                        List<Port> inputs = actor.inputPortList();
708                        List<Port> outputs = actor.outputPortList();
709
710                        // create ports
711                        _createKPorts(knode, inputs);
712                        _createKPorts(knode, outputs);
713                        portIter = _graphModel.nodes(node);
714                    } else if (semanticNode instanceof RelativeLocatable) {
715                        unprocessedRelatives.add(semanticNode);
716                    }
717                }
718
719                // Handle relation vertices.
720                else if (semanticNode instanceof Relation) {
721                    // Regard a relation vertex as a KIELER KNode.
722                    knode = _createKNodeForVertex((Vertex) node);
723                    portIter = Iterators.singletonIterator(node);
724                }
725
726                // Handle internal ports.
727                else if (semanticNode instanceof ComponentPort) {
728                    knode = _createKNodeForPort(node,
729                            (ComponentPort) semanticNode);
730                    portIter = Iterators.singletonIterator(node);
731                }
732
733                // Handle modal model states.
734                else if (semanticNode instanceof State) {
735                    knode = _createKNodeForState(node, (State) semanticNode);
736                    portIter = Iterators.singletonIterator(node);
737                }
738
739                // Now do some common bookkeeping for all kinds of nodes.
740                if (knode != null) {
741                    // some nodes, such as FSM's ports may already
742                    // be assigned to a node other than the parent node
743                    if (knode.getParent() == null) {
744                        knode.setParent(parentNode);
745                    }
746                    // Get check bounds for global bounding box.
747                    KShapeLayout layout = knode.getData(KShapeLayout.class);
748                    if (layout.getXpos() < globalX) {
749                        globalX = layout.getXpos();
750                    }
751                    if (layout.getYpos() < globalY) {
752                        globalY = layout.getYpos();
753                    }
754
755                    // Store node for later applying layout back.
756                    _kieler2ptolemyDivaNodes.put(knode, (Locatable) node);
757                }
758            }
759
760            if (portIter != null) {
761                while (portIter.hasNext()) {
762                    Object divaPort = portIter.next();
763                    // Iterate all outgoing edges.
764                    Iterator edgeIterator;
765                    if (semanticNode instanceof Port
766                            && externalPortModel != null) { // internal ports
767                        edgeIterator = externalPortModel.outEdges(divaPort);
768                    } else {
769                        edgeIterator = _graphModel.outEdges(divaPort);
770                    }
771                    while (edgeIterator.hasNext()) {
772                        Object next = edgeIterator.next();
773                        if (next instanceof Link) {
774                            unprocessedEdges.add((Link) next);
775                        }
776                    }
777                }
778            }
779        }
780
781        // Create KIELER edges for Diva edges.
782        if (_graphModel instanceof ActorGraphModel) {
783            _storeEndpoints(unprocessedEdges);
784        }
785        for (Link divaEdge : unprocessedEdges) {
786            _createKEdge(divaEdge);
787        }
788
789        // Create edges for associations of relative locatables to their reference objects.
790        for (NamedObj relativeObj : unprocessedRelatives) {
791            _createKEdgeForAttribute(relativeObj);
792        }
793
794        // Set graph offset.
795        parentLayout.setXpos(globalX);
796        parentLayout.setYpos(globalY);
797    }
798
799    /**
800     * Creates a skeleton for placing elements of FSMs.
801     *
802     * An {@link FSMGraphModel} can have input, output, and inputoutput
803     * ports. Since the diagram is a state diagram the ports are
804     * not connected to any other diagram element. To create a
805     * clear visual experience, we place all inputs on the left
806     * side of the actual diagram, all outputs on the right, and all
807     * inputoutputs above the diagram.
808     *
809     * Inputs and outputs are stacked from top to bottom. Inputoutputs
810     * are placed left to right. The user can change the order
811     * of ports to move related ports close to each other.
812     *
813     * @param root
814     *          the root node of the layout graph
815     * @return
816     *          a node which should contain the actual diagram's elements
817     */
818    private KNode _createFsmSkeleton(KNode root) {
819
820        _fsmInputOutput = KimlUtil.createInitializedNode();
821        // hopefully no diagram gets this large
822        _fsmInputOutput.getData(KShapeLayout.class).setPos(0, 100000);
823        _fsmInputOutput.getData(KLayoutData.class)
824                .setProperty(LayoutOptions.SEPARATE_CC, false);
825        _fsmInputOutput.getData(KLayoutData.class).setProperty(
826                Properties.CROSS_MIN, CrossingMinimizationStrategy.INTERACTIVE);
827        _fsmInputOutput.getData(KLayoutData.class)
828                .setProperty(LayoutOptions.DIRECTION, Direction.DOWN);
829
830        _fsmInput = KimlUtil.createInitializedNode();
831        _fsmInput.getData(KLayoutData.class)
832                .setProperty(LayoutOptions.SEPARATE_CC, false);
833        _fsmInput.getData(KLayoutData.class).setProperty(Properties.CROSS_MIN,
834                CrossingMinimizationStrategy.INTERACTIVE);
835
836        _fsmOutput = KimlUtil.createInitializedNode();
837        _fsmOutput.getData(KLayoutData.class)
838                .setProperty(LayoutOptions.SEPARATE_CC, false);
839        _fsmOutput.getData(KLayoutData.class).setProperty(Properties.CROSS_MIN,
840                CrossingMinimizationStrategy.INTERACTIVE);
841
842        KNode newRoot = KimlUtil.createInitializedNode();
843        newRoot.getData(KShapeLayout.class).setPos(0, 0);
844
845        // add the skeleton to the root node
846        root.getChildren().add(_fsmInputOutput);
847        root.getChildren().add(_fsmInput);
848        root.getChildren().add(_fsmOutput);
849        root.getChildren().add(newRoot);
850
851        // add edges to guarantee proper placement
852        KEdge dummyEdge = KimlUtil.createInitializedEdge();
853        dummyEdge.setSource(_fsmInput);
854        dummyEdge.setTarget(newRoot);
855        dummyEdge = KimlUtil.createInitializedEdge();
856        dummyEdge.setSource(_fsmInput);
857        dummyEdge.setTarget(_fsmInputOutput);
858        dummyEdge = KimlUtil.createInitializedEdge();
859        dummyEdge.setSource(newRoot);
860        dummyEdge.setTarget(_fsmOutput);
861        // this edge is necessary to center the input outputs
862        dummyEdge = KimlUtil.createInitializedEdge();
863        dummyEdge.setSource(_fsmInputOutput);
864        dummyEdge.setTarget(_fsmOutput);
865
866        KLayoutData rootLayout = root.getData(KLayoutData.class);
867        // assure that the inputoutput port container is placed above the actual diagram
868        rootLayout.setProperty(Properties.CROSS_MIN,
869                CrossingMinimizationStrategy.INTERACTIVE);
870        // balanced positioning
871        rootLayout.setProperty(Properties.NODE_PLACER,
872                NodePlacementStrategy.SIMPLE);
873
874        return newRoot;
875    }
876
877    /**
878     * Create a KIELER edge for a Ptolemy Diva edge object. The KEdge will be
879     * setup between either two ports or relation vertices or mixed. Hence the
880     * KEdge corresponds more likely to a Ptolemy link than a relation. Diva edges
881     * have no direction related to the flow of data in Ptolemy. However, KIELER
882     * uses a directed graph to perform layout and so a meaningful direction
883     * should be set in the KEdge. This direction will be approximated by doing
884     * a tree search beginning on both endpoints of the Diva edge. Whenever
885     * either of the endpoints is connected to a source port, this will be the
886     * source of the KEdge and determine its direction.
887     *
888     * The newly created edge is stored with the corresponding Diva edge in the
889     * global maps _ptolemyDiva2KielerEdges, _kieler2PtolemyDivaEdges, such that
890     * the {@link #_applyLayout(KNode)} method will be able to reapply the
891     * layout.
892     *
893     * @param divaEdge The Ptolemy diva edge object for which to create a new KEdge.
894     */
895    private void _createKEdge(Link divaEdge) {
896        KEdge kedge = KimlUtil.createInitializedEdge();
897
898        Object source = _divaEdgeSource.get(divaEdge);
899        if (source == null) {
900            source = divaEdge.getTail();
901        }
902        Object target = _divaEdgeTarget.get(divaEdge);
903        if (target == null) {
904            target = divaEdge.getHead();
905        }
906
907        KPort kSourcePort = _getPort(source, divaEdge.getRelation());
908        if (kSourcePort != null) {
909            kedge.setSourcePort(kSourcePort);
910            kSourcePort.getEdges().add(kedge);
911            kedge.setSource(kSourcePort.getNode());
912        } else {
913            // Edge is not connected to a port.
914            kedge.setSource(_kieler2ptolemyDivaNodes.inverse().get(source));
915        }
916        KPort kTargetPort = _getPort(target, divaEdge.getRelation());
917        if (kTargetPort != null) {
918            kedge.setTargetPort(kTargetPort);
919            kTargetPort.getEdges().add(kedge);
920            kedge.setTarget(kTargetPort.getNode());
921        } else {
922            // Edge is not connected to a port.
923            kedge.setTarget(_kieler2ptolemyDivaNodes.inverse().get(target));
924        }
925
926        // Set source and target point so they are not (0, 0).
927        KEdgeLayout edgeLayout = kedge.getData(KEdgeLayout.class);
928        if (source instanceof Locatable) {
929            double[] pos = ((Locatable) source).getLocation();
930            edgeLayout.getSourcePoint().setX((float) pos[0]);
931            edgeLayout.getSourcePoint().setY((float) pos[1]);
932        }
933        if (target instanceof Locatable) {
934            double[] pos = ((Locatable) target).getLocation();
935            edgeLayout.getTargetPoint().setX((float) pos[0]);
936            edgeLayout.getTargetPoint().setY((float) pos[1]);
937        }
938
939        // Add the edge to the list.
940        _edgeList.add(new Pair<KEdge, Link>(kedge, divaEdge));
941
942        // Create a label for the edge.
943        Object figure = getLayoutTarget().getVisualObject(divaEdge);
944        if (figure instanceof AbstractConnector) {
945            LabelFigure labelFigure = ((AbstractConnector) figure)
946                    .getLabelFigure();
947            if (labelFigure != null) {
948                KLabel label = KimlUtil.createInitializedLabel(kedge);
949                label.setText(labelFigure.getString());
950                KShapeLayout labelLayout = label.getData(KShapeLayout.class);
951                labelLayout.setProperty(LayoutOptions.EDGE_LABEL_PLACEMENT,
952                        EdgeLabelPlacement.CENTER);
953                Rectangle2D bounds = labelFigure.getBounds();
954                labelLayout.setWidth((float) bounds.getWidth());
955                labelLayout.setHeight((float) bounds.getHeight());
956                labelLayout.setXpos((edgeLayout.getSourcePoint().getX()
957                        + edgeLayout.getTargetPoint().getX()) / 2);
958                labelLayout.setYpos((edgeLayout.getSourcePoint().getY()
959                        + edgeLayout.getTargetPoint().getY()) / 2);
960                kedge.getLabels().add(label);
961
962                // remember it
963                _divaLabel.put(label, labelFigure);
964            }
965        }
966    }
967
968    /**
969     * Create a dummy edge for an attribute that is relative locatable. The edge
970     * will only be used to indicate the association between the attribute and
971     * its reference object.
972     *
973     * @param attribute the attribute for which to create a dummy edge
974     */
975    private void _createKEdgeForAttribute(NamedObj attribute) {
976        Locatable source = KielerLayoutUtil.getLocation(attribute);
977        if (source instanceof RelativeLocation) {
978            NamedObj referenceObj = PtolemyModelUtil
979                    ._getReferencedObj((RelativeLocation) source);
980            if (referenceObj != null) {
981                Locatable target = KielerLayoutUtil.getLocation(referenceObj);
982                KNode sourceNode = _kieler2ptolemyDivaNodes.inverse()
983                        .get(source);
984                KNode targetNode = _kieler2ptolemyDivaNodes.inverse()
985                        .get(target);
986                if (sourceNode != null && targetNode != null) {
987                    // Create a dummy edge to connect the comment box to its reference.
988                    KEdge newEdge = KimlUtil.createInitializedEdge();
989                    newEdge.setSource(sourceNode);
990                    newEdge.setTarget(targetNode);
991
992                    KEdgeLayout edgeLayout = newEdge.getData(KEdgeLayout.class);
993                    double[] sourcePos = source.getLocation();
994                    edgeLayout.getSourcePoint().setX((float) sourcePos[0]);
995                    edgeLayout.getSourcePoint().setY((float) sourcePos[1]);
996                    double[] targetPos = target.getLocation();
997                    edgeLayout.getTargetPoint().setX((float) targetPos[0]);
998                    edgeLayout.getTargetPoint().setY((float) targetPos[1]);
999                }
1000            }
1001        }
1002    }
1003
1004    /**
1005     * Create a new KIELER KNode corresponding to a Ptolemy diva node and its
1006     * Ptolemy semantic object (e.g. an Actor).
1007     *
1008     * The newly created node is stored with the corresponding diva and ptolemy
1009     * nodes in the global maps _ptolemy2KielerNodes, _kieler2ptolemyDivaNodes,
1010     * _kieler2ptolemyEntityNodes, such that the {@link #_applyLayout(KNode)}
1011     * method will be able to reapply the layout.
1012     *
1013     * @param node The Diva node object.
1014     * @param semanticNode The corresponding Ptolemy semantic object, e.g. an
1015     *            Actor or TextAttribute
1016     * @return The initialized KIELER KNode
1017     */
1018    private KNode _createKNode(Object node, NamedObj semanticNode) {
1019        Rectangle2D bounds;
1020        if (semanticNode instanceof RelativeLocatable) {
1021            // RelativeLocatables may have a dashed line to show which actor
1022            // they are attached to. This line must not be part of the size
1023            // calculation, so we calculate the size of the object manually
1024            // without taking the line into account
1025            Figure figure = (Figure) getLayoutTarget().getVisualObject(node);
1026
1027            // The figure should be a composite figure, but let's be sure
1028            if (figure instanceof CompositeFigure) {
1029                CompositeFigure compFigure = (CompositeFigure) figure;
1030
1031                bounds = new Rectangle2D.Double();
1032                bounds.add(compFigure.getBackgroundFigure().getBounds());
1033
1034                // Iterate over the composite figure's component, adding the
1035                // bounds of each to arrive at the final bounds without dashed
1036                // line (RelativeLinkFigure)
1037                Iterator parts = compFigure.figures();
1038                while (parts.hasNext()) {
1039                    Figure part = (Figure) parts.next();
1040
1041                    if (!(part instanceof RelativeLinkFigure)) {
1042                        bounds.add(part.getBounds());
1043                    }
1044                }
1045            } else {
1046                // It's not a composite figure, so use figure's bounds
1047                bounds = getLayoutTarget().getBounds(node);
1048            }
1049        } else {
1050            bounds = getLayoutTarget().getBounds(node);
1051        }
1052
1053        // Create new node in KIELER graph and apply the initial size and position
1054        KNode knode = KimlUtil.createInitializedNode();
1055        KShapeLayout nodeLayout = knode.getData(KShapeLayout.class);
1056        nodeLayout.setWidth((float) bounds.getWidth());
1057        nodeLayout.setHeight((float) bounds.getHeight());
1058        nodeLayout.setXpos((float) bounds.getMinX());
1059        nodeLayout.setYpos((float) bounds.getMinY());
1060        nodeLayout.setProperty(LayoutOptions.SIZE_CONSTRAINT,
1061                SizeConstraint.fixed());
1062        if (semanticNode instanceof Attribute) {
1063            nodeLayout.setProperty(LayoutOptions.COMMENT_BOX, true);
1064        } else {
1065            nodeLayout.setProperty(LayoutOptions.PORT_CONSTRAINTS,
1066                    PortConstraints.FIXED_POS);
1067        }
1068
1069        // set the node label
1070        KLabel label = KimlUtil.createInitializedLabel(knode);
1071        label.setText(semanticNode.getDisplayName());
1072        KShapeLayout labelLayout = label.getData(KShapeLayout.class);
1073        labelLayout.setWidth((float) bounds.getWidth() - 2);
1074        labelLayout.setHeight(10);
1075        labelLayout.setXpos(1);
1076        labelLayout.setYpos(1);
1077        knode.getLabels().add(label);
1078
1079        // Draw the director always as first element.
1080        if (semanticNode instanceof Director) {
1081            nodeLayout.setProperty(LayoutOptions.PRIORITY, 1);
1082        }
1083
1084        return knode;
1085    }
1086
1087    /**
1088     * Create a KIELER KNode for a Ptolemy inner port. That is the graphical
1089     * representation for a port of a CompositeActor if you see the contents of
1090     * this CompositeActor. It is represented by a node where the connection may
1091     * touch the node corresponding to its type (input, output, both) on the
1092     * right, left or top.
1093     *
1094     * For now this results a crude approximation of the node, because the
1095     * figure of the original Ptolemy port cannot be obtained by the layout
1096     * target. Hence we cannot ask the port for its original bounds.
1097     *
1098     * @param divaLocation Diva Representation of an inner port
1099     * @param port The Ptolemy inner port.
1100     * @return A new KIELER node corresponding to the Ptolemy inner port.
1101     */
1102    private KNode _createKNodeForPort(Object divaLocation, ComponentPort port) {
1103        KNode knode = KimlUtil.createInitializedNode();
1104        KShapeLayout layout = knode.getData(KShapeLayout.class);
1105        layout.setProperty(LayoutOptions.PORT_CONSTRAINTS,
1106                PortConstraints.FIXED_POS);
1107
1108        // Add ports of state diagrams to designated containers to pool them
1109        if (_graphModel instanceof FSMGraphModel
1110                && port instanceof TypedIOPort) {
1111            boolean isInput = ((TypedIOPort) port).isInput();
1112            boolean isOutput = ((TypedIOPort) port).isOutput();
1113            knode.getData(KLayoutData.class)
1114                    .setProperty(LayoutOptions.ALIGNMENT, Alignment.LEFT);
1115            if (isInput && !isOutput) {
1116                knode.setParent(_fsmInput);
1117            } else if (isOutput && !isInput) {
1118                knode.setParent(_fsmOutput);
1119            } else {
1120                knode.setParent(_fsmInputOutput);
1121                knode.getData(KLayoutData.class)
1122                        .setProperty(LayoutOptions.ALIGNMENT, Alignment.BOTTOM);
1123            }
1124        }
1125
1126        Rectangle2D figureBounds = getLayoutTarget().getBounds(divaLocation);
1127        Rectangle2D shapeBounds = figureBounds;
1128        // Try to find more specific bounds of the shape that do not include the name label.
1129        Object obj = getLayoutTarget().getVisualObject(divaLocation);
1130        if (obj instanceof PortTerminal) {
1131            PortTerminal portVisual = (PortTerminal) obj;
1132            shapeBounds = portVisual.getShape().getBounds();
1133        }
1134        // Set alignment offset in order to set right height.
1135        layout.setHeight(
1136                (float) figureBounds.getHeight() + INNER_PORT_HEIGHT_OFFSET);
1137        layout.setWidth((float) figureBounds.getWidth());
1138        layout.setProperty(LayoutOptions.SIZE_CONSTRAINT,
1139                SizeConstraint.fixed());
1140        layout.setXpos((float) figureBounds.getMinX());
1141        layout.setYpos((float) figureBounds.getMinY());
1142
1143        // Create KIELER ports to specify anchor points for the layouter.
1144        // These constants are black magic, gotten by trial and error.
1145        KVector portBase = new KVector(MULTIPORT_INNER_OFFSET);
1146        portBase.y += figureBounds.getHeight() - shapeBounds.getHeight();
1147        int direction = PtolemyModelUtil._getExternalPortDirection(port);
1148        switch (direction) {
1149        case SwingConstants.NORTH:
1150            portBase.x += shapeBounds.getWidth() / 2;
1151            break;
1152        case SwingConstants.SOUTH:
1153            portBase.x += shapeBounds.getWidth() / 2;
1154            portBase.y += shapeBounds.getHeight();
1155            break;
1156        case SwingConstants.EAST:
1157            portBase.x += shapeBounds.getWidth();
1158            portBase.y += shapeBounds.getHeight() / 2;
1159            break;
1160        default:
1161            portBase.y += shapeBounds.getHeight() / 2;
1162        }
1163
1164        // Create a port for each incoming relation and set an order.
1165        List relations = port.insideRelationList();
1166        int maxIndex = relations.size() - 1;
1167        for (int index = 0; index < relations.size(); index++) {
1168            KPort kPort = KimlUtil.createInitializedPort();
1169            KShapeLayout portLayout = kPort.getData(KShapeLayout.class);
1170            portLayout.setHeight(DEFAULT_PORT_SIZE);
1171            portLayout.setWidth(DEFAULT_PORT_SIZE);
1172            KVector offset = _getMultiportOffsets(port, portLayout, index,
1173                    maxIndex, false);
1174            portLayout.setXpos((float) (portBase.x + offset.x));
1175            portLayout.setYpos((float) (portBase.y + offset.y)
1176                    - portLayout.getHeight() / 2);
1177            kPort.setNode(knode);
1178            _ptolemy2KielerPorts.put(port, kPort);
1179        }
1180        return knode;
1181    }
1182
1183    /**
1184     * Create a KIELER node for a Ptolemy Vertex. Vertices of Ptolemy can be
1185     * handles as usual KNodes in KIELER (an alternative would be to handle them
1186     * as connection bend points).
1187     *
1188     * @param vertex The Ptolemy vertex for which to create a KNode
1189     * @return An initialized KNode
1190     */
1191    private KNode _createKNodeForVertex(Vertex vertex) {
1192        Rectangle2D bounds = getLayoutTarget().getBounds(vertex);
1193        KNode knode = KimlUtil.createInitializedNode();
1194        KShapeLayout nodeLayout = knode.getData(KShapeLayout.class);
1195        nodeLayout.setHeight((float) bounds.getHeight());
1196        nodeLayout.setWidth((float) bounds.getWidth());
1197        nodeLayout.setXpos((float) bounds.getMinX());
1198        nodeLayout.setYpos((float) bounds.getMinY());
1199        nodeLayout.setProperty(LayoutOptions.SIZE_CONSTRAINT,
1200                SizeConstraint.fixed());
1201        nodeLayout.setProperty(LayoutOptions.HYPERNODE, true);
1202        return knode;
1203    }
1204
1205    /**
1206     * Create a KIELER node for a Ptolemy State.
1207     *
1208     * @param node The Diva node object
1209     * @param state The Ptolemy state for which to create a KNode
1210     * @return An initialized KNode
1211     */
1212    private KNode _createKNodeForState(Object node, State state) {
1213        Rectangle2D bounds = getLayoutTarget().getBounds(node);
1214        KNode knode = KimlUtil.createInitializedNode();
1215        KShapeLayout nodeLayout = knode.getData(KShapeLayout.class);
1216        nodeLayout.setHeight((float) bounds.getHeight());
1217        nodeLayout.setWidth((float) bounds.getWidth());
1218        nodeLayout.setXpos((float) bounds.getMinX());
1219        nodeLayout.setYpos((float) bounds.getMinY());
1220        nodeLayout.setProperty(LayoutOptions.SIZE_CONSTRAINT,
1221                SizeConstraint.fixed());
1222        nodeLayout.setProperty(LayoutOptions.PORT_CONSTRAINTS,
1223                PortConstraints.FREE);
1224
1225        if (PtolemyModelUtil._isInitialState(state)
1226                && !PtolemyModelUtil._isFinalState(state)) {
1227            nodeLayout.setProperty(Properties.LAYER_CONSTRAINT,
1228                    LayerConstraint.FIRST);
1229        }
1230
1231        if (!PtolemyModelUtil._isInitialState(state)
1232                && PtolemyModelUtil._isFinalState(state)) {
1233            nodeLayout.setProperty(Properties.LAYER_CONSTRAINT,
1234                    LayerConstraint.LAST);
1235        }
1236
1237        KLabel label = KimlUtil.createInitializedLabel(knode);
1238        label.setText(state.getDisplayName());
1239        KShapeLayout labelLayout = label.getData(KShapeLayout.class);
1240        labelLayout.setWidth(nodeLayout.getWidth() - 2);
1241        labelLayout.setHeight(nodeLayout.getHeight() - 2);
1242        labelLayout.setXpos(1);
1243        labelLayout.setYpos(1);
1244        knode.getLabels().add(label);
1245        return knode;
1246    }
1247
1248    /**
1249     * Create a KIELER KPort corresponding to a Ptolemy Port. Set the size and
1250     * position (relative to parent) and the direction of the port in the KPort
1251     * layout information. Since KIELER does not explicitly support multiports as
1252     * Ptolemy, this is emulated by creating multiple distinct ports with a
1253     * little offset each. Create only one node. For multiports call this method
1254     * multiple times with changed parameters.
1255     *
1256     * The newly created port is stored with the corresponding ptolemy port in
1257     * the global maps _kieler2PtolemyPorts, _ptolemy2KielerPorts, such that the
1258     * {@link #_applyLayout(KNode)} method will be able to reapply the layout.
1259     *
1260     * @param knode The parent KNode of the new port
1261     * @param port The corresponding Ptolemy port (might be a multiport)
1262     * @param index Index of the KPort corresponding to a multiport
1263     * @param maxIndex Width of the multiport, i.e. the number of connected
1264     *            edges to that port.
1265     * @param size Custom size (same for width and height) for a port that will
1266     *            be used instead of the real Ptolemy port size. If this value
1267     *            is negative, the original Ptolemy sizes are used.
1268     */
1269    private void _createKPort(KNode knode, Port port, int index, int maxIndex,
1270            float size) {
1271        // Create a new KIELER port and initialize its layout.
1272        KPort kport = KimlUtil.createInitializedPort();
1273        knode.getPorts().add(kport);
1274        KShapeLayout kportlayout = kport.getData(KShapeLayout.class);
1275        kportlayout.setHeight(DEFAULT_PORT_SIZE);
1276        kportlayout.setWidth(DEFAULT_PORT_SIZE);
1277
1278        // Set port side and calculate actual offset.
1279        KVector offset = _getMultiportOffsets(port, kportlayout, index,
1280                maxIndex, true);
1281
1282        // Try to set actual layout (size and position)
1283        Object portObject = getLayoutTarget().getVisualObject(port);
1284        if (portObject instanceof PortTerminal) {
1285            // Get visual Diva figure of port.
1286            PortTerminal portFigure = (PortTerminal) portObject;
1287            // Get bounds of the port figure (= relative to center of actor
1288            // symbol here given by referenceLocation).
1289            Rectangle2D portBounds = portFigure.getBounds();
1290            // Get the parent Diva figure which is the whole composite consisting of
1291            // the actor icon and its name, which might be arbitrary large.
1292            CanvasComponent parent = portFigure.getParent();
1293            if (parent instanceof CompositeFigure) {
1294                CompositeFigure parentFigure = (CompositeFigure) parent;
1295
1296                AffineTransform parentTransform = parentFigure
1297                        .getTransformContext().getTransform();
1298                Point2D.Double portLocation = new Point2D.Double(
1299                        portBounds.getMinX(), portBounds.getMinY());
1300                Point2D.Double transformedLocation = new Point2D.Double();
1301                parentTransform.transform(portLocation, transformedLocation);
1302                // Calculate coordinates relative to the KIELER nodes top left from absolutes.
1303                double width = portBounds.getWidth();
1304                double height = portBounds.getHeight();
1305                double x = transformedLocation.getX()
1306                        - parentFigure.getBounds().getMinX() + offset.x;
1307                double y = transformedLocation.getY()
1308                        - parentFigure.getBounds().getMinY() + offset.y;
1309                kportlayout.setXpos((float) x);
1310                kportlayout.setYpos((float) y);
1311                // No valid size given -> use diagram port size
1312                if (size < 0) {
1313                    kportlayout.setWidth((float) width);
1314                    kportlayout.setHeight((float) height);
1315                } else {
1316                    // If we want to use some custom port size, the new coordinates
1317                    // need to be adapted to the new size.
1318                    Rectangle2D newPortBounds = new Rectangle2D.Double();
1319                    newPortBounds.setRect(x, y, width, height);
1320                    Rectangle2D shrunkPortBounds = new Rectangle2D.Double();
1321                    shrunkPortBounds.setRect(x, y, size, size);
1322                    int direction = IOPortController.getDirection(
1323                            IOPortController.getCardinality(port));
1324                    Point2D shrunkenLocation = KielerGraphUtil
1325                            ._shrinkCoordinates(newPortBounds, shrunkPortBounds,
1326                                    direction, MULTIPORT_BOTTOM);
1327                    kportlayout.setXpos((float) shrunkenLocation.getX());
1328                    kportlayout.setYpos((float) shrunkenLocation.getY());
1329                    kportlayout.setWidth(size);
1330                    kportlayout.setHeight(size);
1331                }
1332            }
1333        }
1334        // Put ports in global maps for later use.
1335        _ptolemy2KielerPorts.put(port, kport);
1336    }
1337
1338    /**
1339     * Create KIELER ports (KPort) for a KIELER node (KNode) given a list of
1340     * Ptolemy Port objects and a port type (incoming, outgoing). The new KPorts
1341     * are initialized in terms of size and position from the Ptolemy
1342     * counterparts and attached to the corresponding KNode and registered in
1343     * the mapping fields of this object.
1344     *
1345     * For Ptolemy multiports with multiple connections, multiple KIELER KPorts
1346     * are created with slightly offset location. Hence the layouter can also
1347     * consider the location for connection crossing minimization. Hence
1348     * one Ptolemy port may correspond to multiple KIELER KPorts.
1349     *
1350     * @param knode The KNode to create KIELER ports for.
1351     * @param ports The Ptolemy ports counterparts for which to create KIELER
1352     *            ports.
1353     */
1354    private void _createKPorts(KNode knode, List<Port> ports) {
1355        for (Port port : ports) {
1356            // Handle multiports for inputs.
1357            if (port.linkedRelationList().size() > 1) {
1358                // Create a port for each incoming relation and set an order.
1359                List relations = port.linkedRelationList();
1360                int maxIndex = relations.size() - 1;
1361                for (int index = 0; index < relations.size(); index++) {
1362                    _createKPort(knode, port, index, maxIndex,
1363                            DEFAULT_PORT_SIZE);
1364                }
1365            } else {
1366                // If not a multiport, just create one port.
1367                _createKPort(knode, port, 0, 0, -1);
1368            }
1369        }
1370    }
1371
1372    /**
1373     * Get a KIELER KPort for a corresponding Ptolemy object, i.e. a Port or a
1374     * relation Vertex. If the input is a Vertex, it is determined which of the
1375     * two KPorts of the corresponding KNode is returned (since in KIELER a Vertex
1376     * is represented by one node with one input and one output port).
1377     *
1378     * If the input object is a Ptolemy Port, the KPort counterpart is searched
1379     * in the global maps. If additionally the Port is a multiport with multiple
1380     * connection, the given relation is used to determine which KPort
1381     * corresponds to the Port/Relation combination. In KIELER multiports are
1382     * represented by multiple KPorts with slightly offset locations.
1383     *
1384     * @param ptolemyObject The corresponding Ptolemy object, either a Vertex or
1385     *            a Port
1386     * @param relation The relation that is connected to the Ptolemy multiport
1387     * @return The found port, or null if no port was found.
1388     */
1389    private KPort _getPort(Object ptolemyObject, Relation relation) {
1390        if (ptolemyObject instanceof Vertex) {
1391            KNode knode = _kieler2ptolemyDivaNodes.inverse().get(ptolemyObject);
1392            for (KPort port : knode.getPorts()) {
1393                return port;
1394            }
1395        }
1396        List<Relation> relations = null;
1397        if (ptolemyObject instanceof Location) {
1398            // Handle an inner port represented by a Location.
1399            // The real port object is its container.
1400            ptolemyObject = ((Location) ptolemyObject).getContainer();
1401            if (ptolemyObject instanceof ComponentPort) {
1402                relations = ((ComponentPort) ptolemyObject)
1403                        .insideRelationList();
1404            }
1405        }
1406        if (ptolemyObject instanceof Port && relation != null) {
1407            // Special case for multiports: For a particular relation, get its
1408            // index in the relation list of the port. Then get the KIELER port with
1409            // the same index (as we mapped one Ptolemy multiport to a list of multiple
1410            // KIELER ports). For a simple port, just give the first corresponding port.
1411            if (relations == null) {
1412                // In this case: outer port
1413                relations = ((Port) ptolemyObject).linkedRelationList();
1414            }
1415            int index = relations.indexOf(relation);
1416            List<KPort> kports = _ptolemy2KielerPorts.get((Port) ptolemyObject);
1417            if (index >= 0 && index < kports.size()) {
1418                return kports.get(index);
1419            }
1420            return kports.get(0);
1421        }
1422        return null;
1423    }
1424
1425    /**
1426     * Transform a location from a KIELER node from KIELER coordinate system to
1427     * ptolemy coordinate system. That is KIELER gives locations to be the upper
1428     * left corner of an item and Ptolemy as the center point of the item.
1429     *
1430     * If the original location is not within the bounds of the referenceNode at
1431     * all, the location updated differently. This is an important distinction between
1432     * nodes and vertices.
1433     *
1434     * @param pos Position of KIELER graphical node. This object will be altered to
1435     *            fit the new location.
1436     * @param divaNode the graphical representation in Diva
1437     * @param locatable the location object of the node
1438     */
1439    private void _kNode2Ptolemy(KVector pos, Object divaNode,
1440            Locatable locatable) {
1441        Point2D location = KielerLayoutUtil.getLocationPoint(locatable);
1442        if (divaNode != null) {
1443            Rectangle2D divaBounds;
1444            if (locatable instanceof RelativeLocation) {
1445                // Consider only the background figure of a composite figure
1446                // if the node is relative locatable, otherwise the link would also
1447                // be included in the bounds.
1448                Figure figure = (Figure) getLayoutTarget()
1449                        .getVisualObject(divaNode);
1450                divaBounds = figure.getShape().getBounds2D();
1451            } else {
1452                divaBounds = getLayoutTarget().getBounds(divaNode);
1453            }
1454            double offsetX = 0, offsetY = 0;
1455
1456            // Check whether the location could be determined.
1457            // If not, we might have something that has no location attribute,
1458            // such as a relation vertex, where we use the center as offset.
1459            if (location != null) {
1460                offsetX = location.getX() - divaBounds.getMinX();
1461                offsetY = location.getY() - divaBounds.getMinY();
1462            } else {
1463                offsetX = divaBounds.getWidth() / 2;
1464                offsetY = divaBounds.getHeight() / 2;
1465            }
1466
1467            pos.x += offsetX;
1468            pos.y += offsetY;
1469        }
1470    }
1471
1472    /**
1473     * Report a message to the top window status handler if it is available.
1474     *
1475     * @param message The message to be reported.
1476     */
1477    private void _report(String message) {
1478        if (_top != null) {
1479            _top.report(message);
1480        }
1481    }
1482
1483    /**
1484     * Determine the direction of dataflow of all edges and store it in the
1485     * local maps. Iterate all edges and try to deduce the type of each edge's
1486     * endpoints, i.e. whether it is an source or target. Do this in multiple
1487     * iterations by first getting clear information from input and output ports
1488     * and then propagate this information to the adjacent edges.
1489     *
1490     * @param unprocessedEdges The list of edges that need processing.
1491     */
1492    private void _storeEndpoints(List<Link> unprocessedEdges) {
1493        ActorGraphModel aGraph = (ActorGraphModel) getLayoutTarget()
1494                .getGraphModel();
1495        boolean allDirectionsSet = false;
1496        boolean progress = false;
1497        while (!allDirectionsSet) {
1498            allDirectionsSet = true;
1499            progress = false;
1500            for (Iterator<Link> edgeIter = unprocessedEdges.iterator(); edgeIter
1501                    .hasNext();) {
1502                Link edge = edgeIter.next();
1503                EdgeModel edgeModel = aGraph.getEdgeModel(edge);
1504
1505                Object simpleEndpoint1 = edgeModel.getHead(edge);
1506                Object simpleEndpoint2 = edgeModel.getTail(edge);
1507                Object endpoint1 = aGraph.getSemanticObject(simpleEndpoint1);
1508                Object endpoint2 = aGraph.getSemanticObject(simpleEndpoint2);
1509
1510                // See if we have successfully looked at this edge before.
1511                if (_divaEdgeTarget.containsKey(edge)
1512                        && _divaEdgeSource.containsKey(edge)) {
1513                    continue;
1514                }
1515
1516                // Check whether endpoints are source or target ports.
1517                if (endpoint1 instanceof Port) {
1518                    boolean isInput1 = PtolemyModelUtil
1519                            ._isInput((Port) endpoint1);
1520                    // Check if we look at inner or outer ports.
1521                    if (simpleEndpoint1 instanceof Location) {
1522                        // Inner input port is regarded as output.
1523                        isInput1 = !isInput1;
1524                    }
1525                    // Set endpoints.
1526                    if (isInput1) {
1527                        _divaEdgeTarget.put(edge, simpleEndpoint1);
1528                        _divaEdgeSource.put(edge, simpleEndpoint2);
1529                        progress = true;
1530                    } else {
1531                        _divaEdgeTarget.put(edge, simpleEndpoint2);
1532                        _divaEdgeSource.put(edge, simpleEndpoint1);
1533                        progress = true;
1534                    }
1535
1536                } else if (endpoint2 instanceof Port) {
1537                    boolean isInput2 = PtolemyModelUtil
1538                            ._isInput((Port) endpoint2);
1539                    // Check if we look at inner or outer ports.
1540                    if (simpleEndpoint2 instanceof Location) {
1541                        // Inner input port is regarded as output.
1542                        isInput2 = !isInput2;
1543                    }
1544                    // Set endpoints.
1545                    if (isInput2) {
1546                        _divaEdgeTarget.put(edge, simpleEndpoint2);
1547                        _divaEdgeSource.put(edge, simpleEndpoint1);
1548                        progress = true;
1549                    } else {
1550                        _divaEdgeTarget.put(edge, simpleEndpoint1);
1551                        _divaEdgeSource.put(edge, simpleEndpoint2);
1552                        progress = true;
1553                    }
1554
1555                } else
1556                // See if one of the endpoints is source or target of other edges.
1557                if (_divaEdgeTarget.containsValue(simpleEndpoint1)) {
1558                    _divaEdgeTarget.put(edge, simpleEndpoint2);
1559                    _divaEdgeSource.put(edge, simpleEndpoint1);
1560                    progress = true;
1561                } else if (_divaEdgeTarget.containsValue(simpleEndpoint2)) {
1562                    _divaEdgeTarget.put(edge, simpleEndpoint1);
1563                    _divaEdgeSource.put(edge, simpleEndpoint2);
1564                    progress = true;
1565                } else if (_divaEdgeSource.containsValue(simpleEndpoint1)) {
1566                    _divaEdgeTarget.put(edge, simpleEndpoint1);
1567                    _divaEdgeSource.put(edge, simpleEndpoint2);
1568                    progress = true;
1569                } else if (_divaEdgeSource.containsValue(simpleEndpoint2)) {
1570                    _divaEdgeTarget.put(edge, simpleEndpoint2);
1571                    _divaEdgeSource.put(edge, simpleEndpoint1);
1572                    progress = true;
1573                } else {
1574                    // Now we can't deduce any information about this edge.
1575                    allDirectionsSet = false;
1576                }
1577
1578                // Guarantee progress by just setting the direction if it cannot be deduced.
1579                if (!edgeIter.hasNext() && !progress) {
1580                    _divaEdgeTarget.put(edge, simpleEndpoint1);
1581                    _divaEdgeSource.put(edge, simpleEndpoint2);
1582                }
1583            }
1584        }
1585    }
1586
1587    /**
1588     * For a given Ptolemy port, its channel index in a multiport, and the
1589     * maximum index in that multiport, calculate its offset in X and Y
1590     * coordinates. For example, the first channel on the east side has offset 0
1591     * and the next channel is moved below the first one and so on. On the north
1592     * side, the last channel has offset 0 and the first channel is at the most
1593     * left side.
1594     *
1595     * @param port the Ptolemy port
1596     * @param kportlayout the corresponding KPort KShapeLayout
1597     * @param index index of the channel
1598     * @param maxIndex maximum available channel
1599     * @param outer True if the direction of the ports is obtained by calling
1600     * IOPortController.getDirection().  Otherwise, the direction is obtained
1601     * by calling PtolemyModelUtil._getExternalPortDirection().
1602     * @return offset vector
1603     */
1604    protected static KVector _getMultiportOffsets(Port port,
1605            KShapeLayout kportlayout, int index, int maxIndex, boolean outer) {
1606        KVector offset = new KVector();
1607        int direction = 0;
1608        if (outer) {
1609            direction = IOPortController
1610                    .getDirection(IOPortController.getCardinality(port));
1611        } else {
1612            direction = PtolemyModelUtil._getExternalPortDirection(port);
1613        }
1614        switch (direction) {
1615        case SwingConstants.NORTH:
1616            kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.NORTH);
1617            // Ports are extended to left with leftmost port index 0.
1618            offset.x = -((maxIndex - index) * MULTIPORT_OFFSET);
1619            break;
1620        case SwingConstants.EAST:
1621            kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.EAST);
1622            // Ports are extended to bottom with top port index 0.
1623            offset.y = index * MULTIPORT_OFFSET;
1624            break;
1625        case SwingConstants.SOUTH:
1626            kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.SOUTH);
1627            offset.x = index * MULTIPORT_OFFSET;
1628            break;
1629        default:
1630            kportlayout.setProperty(LayoutOptions.PORT_SIDE, PortSide.WEST);
1631            // Ports are extended to top beginning with top port index 0.
1632            offset.y = -((maxIndex - index) * MULTIPORT_OFFSET);
1633            break;
1634        }
1635        return offset;
1636    }
1637
1638    ///////////////////////////////////////////////////////////////////
1639    ////                         private variables                 ////
1640
1641    /**
1642     * Offset between KIELER KPorts corresponding to a Ptolemy multiport. I.e.
1643     * the distance between multiple single KPorts.
1644     */
1645    private static final float MULTIPORT_OFFSET = 5.0f;
1646
1647    /**
1648     * Debug flag that will trigger output of additional information during
1649     * layout run. With the flag set to true especially the KIELER graph
1650     * structure will be written to a file on hard disk in order to review the
1651     * graph later on.
1652     */
1653    private static final boolean DEBUG = false;
1654
1655    /**
1656     * Default size of a port that will be used in KIELER layout if no explicit
1657     * size (e.g. copied from Ptolemy port) is given.
1658     */
1659    private static final float DEFAULT_PORT_SIZE = 5.0f;
1660
1661    /**
1662     * The offset height used by KIELER for inner ports to correct connection anchor.
1663     */
1664    private static final float INNER_PORT_HEIGHT_OFFSET = 11.0f;
1665
1666    /**
1667     * Offset at the x and y-coordinate of the shape of a Multiport to its figure.
1668     */
1669    private static final KVector MULTIPORT_INNER_OFFSET = new KVector(3f, -3f);
1670
1671    /**
1672     * Offset between bottom of Multiport to the first KIELER KPort
1673     */
1674    private static final float MULTIPORT_BOTTOM = 4.5f;
1675
1676    /**
1677     * A pool of layout provider instances, which are used to perform the actual
1678     * layout. The pool is accessed statically, so that its instances can be reused
1679     * in subsequent layout runs.
1680     */
1681    private static InstancePool<AbstractLayoutProvider> _layoutProviderPool;
1682
1683    /**
1684     * The top level Ptolemy composite entity that contains the diagram that is
1685     * to be laid out.
1686     */
1687    private CompositeEntity _compositeEntity;
1688
1689    /**
1690     * Storage of actual sources of diva edges corresponding to data flow.
1691     */
1692    private Map<Link, Object> _divaEdgeSource;
1693
1694    /**
1695     * Storage of actual targets of diva edges corresponding to data flow.
1696     */
1697    private Map<Link, Object> _divaEdgeTarget;
1698
1699    /**
1700     * Mapping of KIELER labels to corresponding diva labels.
1701     */
1702    private Map<KLabel, LabelFigure> _divaLabel;
1703
1704    /**
1705     * List of KIELER edges and corresponding Diva links.
1706     */
1707    private List<Pair<KEdge, Link>> _edgeList;
1708
1709    /**
1710     * Map KIELER nodes to Diva Nodes and back.
1711     */
1712    private BiMap<KNode, Locatable> _kieler2ptolemyDivaNodes;
1713
1714    /**
1715     * Map Ptolemy ports to KIELER ports. A Ptolemy multiport could correspond
1716     * to multiple KIELER ports. Hence it's a mapping to a List of KPorts.
1717     */
1718    private ListMultimap<Port, KPort> _ptolemy2KielerPorts;
1719
1720    /**
1721     * Pointer to Top in order to report the current status.
1722     */
1723    private Top _top;
1724
1725    /** The graph model that is about to be laid out. */
1726    private GraphModel _graphModel;
1727
1728    /**
1729     * When layouting FSMs, we use the following nodes to separate
1730     * ports from the actual model.
1731     */
1732    private KNode _fsmInput;
1733    private KNode _fsmOutput;
1734    private KNode _fsmInputOutput;
1735
1736}