001/* Layout Hint Attribute for Ptolemy relations to specify bendpoints for an explicit routing for links.
002
003 @Copyright (c) 2011-2018 The Regents of the University of California.
004 All rights reserved.
005
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the
009 above copyright notice and the following two paragraphs appear in all
010 copies of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027 */
028
029package ptolemy.vergil.actor;
030
031import java.awt.geom.Point2D;
032import java.io.IOException;
033import java.io.Writer;
034import java.util.ArrayList;
035import java.util.Collection;
036import java.util.Iterator;
037import java.util.LinkedList;
038import java.util.List;
039import java.util.Map;
040
041import diva.canvas.connector.ManhattanConnector;
042import ptolemy.actor.CompositeActor;
043import ptolemy.actor.IOPort;
044import ptolemy.data.ArrayToken;
045import ptolemy.data.DoubleToken;
046import ptolemy.data.IntToken;
047import ptolemy.data.RecordToken;
048import ptolemy.data.ScalarToken;
049import ptolemy.data.StringToken;
050import ptolemy.data.Token;
051import ptolemy.data.expr.ASTPtRootNode;
052import ptolemy.data.expr.ParseTreeEvaluator;
053import ptolemy.data.expr.PtParser;
054import ptolemy.kernel.ComponentPort;
055import ptolemy.kernel.Port;
056import ptolemy.kernel.Relation;
057import ptolemy.kernel.util.Attribute;
058import ptolemy.kernel.util.ChangeRequest;
059import ptolemy.kernel.util.IllegalActionException;
060import ptolemy.kernel.util.Location;
061import ptolemy.kernel.util.NameDuplicationException;
062import ptolemy.kernel.util.NamedObj;
063import ptolemy.kernel.util.Settable;
064import ptolemy.kernel.util.SingletonAttribute;
065import ptolemy.kernel.util.ValueListener;
066import ptolemy.kernel.util.Workspace;
067import ptolemy.moml.Vertex;
068import ptolemy.util.StringUtilities;
069
070///////////////////////////////////////////////////////////////////
071////                      LayoutHint
072/**
073 * A LayoutHint is an Attribute for Ptolemy Relations that holds the
074 * specification of bend points for links. Its value field contains a list of
075 * {@link LayoutHintItem} objects because one Relation can correspond to
076 * multiple links, which are not real objects in the Ptolemy abstract syntax and
077 * therefore can not carry any attributes. Each item carries a list of
078 * bendpoints for a specific link as well as a location for a label
079 * of the link (if it exists).
080 * <p>
081 * The LayoutHint uses a Ptolemy Expression as its value in which the
082 * {@link LayoutHintItem} objects are encoded. Therefore the Expression is
083 * expected to contain an {@link ArrayToken} of {@link LayoutHintItem} objects.</p>
084 *
085 * <p>
086 * A complete LayoutHint with two {@link LayoutHintItem}s could look like this:</p>
087 *
088 * <pre>
089 * {
090 *   {
091 *     head={id="Discard.input",x=60.0,y=115.0,index=2},
092 *     tail={id="CompositeActor.port3",x=300.0,y=380.0,index=3},
093 *     points={105.0,235.0,105.0,190.0,265.0,190.0,265.0,135.0}
094 *   },
095 *   {
096 *     head={id="Ramp.output",x=320.0,y=225.0},
097 *     tail={id="CompositeActor.port2",x=580.0,y=200.0,index=3},
098 *     points={135.0,25.0,135.0,125.0},
099 *     labelLocation={x=340.0,y=210.0}
100 *   }
101 * }
102 * </pre>
103 *
104 * <p>This storage works like a {@link Map} with always two keys. One
105 * {@link LayoutHintItem} is unambiguously identified by its head and tail,
106 * which are Ptolemy objects like {@link Port}s or {@link Relation}s. The
107 * methods to access this are {@link #getLayoutHintItem(Object, Object)},
108 * {@link #setLayoutHintItem(NamedObj, NamedObj, double[], Point2D.Double)} and
109 * {@link #removeLayoutHintItem(LayoutHintItem)}.</p>
110 *
111 * <p> The class extends {@link SingletonAttribute} because every
112 * Relation is expected to have only one such Attribute, while one of
113 * these Attributes can carry multiple {@link LayoutHintItem}s as
114 * explained above. It is also {@link ptolemy.kernel.util.Settable} as
115 * it can be set by loading a MOML file or by setting it manually
116 * through the GUI. However, usually its visibility is set to EXPERT
117 * mode only.</p>
118 *
119 * <p>
120 * Some of the standard code for example for value listeners is copied from
121 * {@link Location}.</p>
122 *
123 * @author Hauke Fuhrmann, (kieler@informatik.uni-kiel.de), Ulf Rueegg
124 * @since Ptolemy II 11.0
125 * @Pt.ProposedRating Red (haf)
126 * @Pt.AcceptedRating Red (haf)
127 */
128public class LayoutHint extends SingletonAttribute implements Settable {
129
130    ///////////////////////////////////////////////////////////////////
131    ////                         public methods                    ////
132
133    /** Construct an attribute with the given container and name.
134     *  @param container The container.
135     *  @param name The name of this attribute.
136     *  @exception IllegalActionException If the attribute cannot be contained
137     *   by the proposed container.
138     *  @exception NameDuplicationException If the container already has an
139     *   attribute with this name, and the class of that container is not
140     *   SingletonAttribute.
141     *
142     * @see SingletonAttribute#SingletonAttribute(NamedObj, String)
143     */
144    public LayoutHint(NamedObj container, String name)
145            throws IllegalActionException, NameDuplicationException {
146        super(container, name);
147    }
148
149    /** Construct a new attribute with
150     *  no container and an empty string as a name.
151     *  @param workspace The workspace that will list the attribute.
152     *  @see SingletonAttribute#SingletonAttribute(Workspace)
153     */
154    public LayoutHint(Workspace workspace) {
155        super(workspace);
156    }
157
158    /**
159     * Add a listener to be notified when the value of this attribute changes.
160     * If the listener is already on the list of listeners, then do nothing.
161     *
162     * @param listener The listener to add.
163     * @see #removeValueListener(ValueListener)
164     */
165    @Override
166    public void addValueListener(ValueListener listener) {
167        if (_valueListeners == null) {
168            _valueListeners = new LinkedList();
169        }
170
171        if (!_valueListeners.contains(listener)) {
172            _valueListeners.add(listener);
173        }
174    }
175
176    /**
177     * Write a MoML description of this object. MoML is an XML modeling markup
178     * language. In this class, the object is identified by the "property"
179     * element, with "name", "class", and "value" (XML) attributes. The body of
180     * the element, between the "&lt;property&gt;" and "&lt;/property&gt;", is
181     * written using the _exportMoMLContents() protected method, so that derived
182     * classes can override that method alone to alter only how the contents of
183     * this object are described. The text that is written is indented according
184     * to the specified depth, with each line (including the last one)
185     * terminated with a newline. If this object is non-persistent, then nothing
186     * is written.
187     *
188     * @see Location#exportMoML(Writer, int, String)
189     * @param output The output writer to write to.
190     * @param depth The depth in the hierarchy, to determine indenting.
191     * @param name The name to use instead of the current name.
192     * @exception IOException If an I/O error occurs.
193     * @see #isPersistent()
194     */
195    @Override
196    public void exportMoML(Writer output, int depth, String name)
197            throws IOException {
198        // If the object is not persistent, and we are not at level 0, do nothing.
199        if (_isMoMLSuppressed(depth)) {
200            return;
201        }
202
203        String value = getExpression();
204        String valueTerm = "";
205
206        if (value != null && !value.equals("")) {
207            valueTerm = " value=\"" + StringUtilities.escapeForXML(value)
208                    + "\"";
209        }
210
211        // It might be better to use multiple writes here for performance.
212        output.write(_getIndentPrefix(depth) + "<" + _elementName + " name=\""
213                + name + "\" class=\"" + getClassName() + "\"" + valueTerm
214                + ">\n");
215        _exportMoMLContents(output, depth + 1);
216        output.write(_getIndentPrefix(depth) + "</" + _elementName + ">\n");
217    }
218
219    /**
220     * A LayoutHint has no default expression.
221     * @return always null
222     */
223    @Override
224    public String getDefaultExpression() {
225        return null;
226    }
227
228    /**
229     * Get the value that has been set by setExpression() or by
230     * setLayoutHintItem(), whichever was most recently called, or return an
231     * empty string if neither has been called.
232     *
233     * <p>
234     * If setExpression(String value) was called, then the return value is
235     * exactly what ever was passed in as the argument to setExpression. This
236     * means that there is no guarantee that the return value of getExpression()
237     * is a well formed Ptolemy array expression.</p>
238     *
239     * <p>
240     * If setLayoutHintItem(NamedObj, NamedObj, double[]) was called, then the
241     * return value is a well formed Ptolemy array expression that starts with
242     * "{" and ends with "}", and contains the expressions of
243     * {@link LayoutHintItem}s as array elements. Example:</p>
244     *
245     * <pre>
246     * { item1, item2 }
247     * </pre>
248     *
249     * @return The expression.
250     * @see Location#getExpression()
251     * @see #setExpression(String)
252     */
253    @Override
254    public String getExpression() {
255        if (_expressionSet) {
256            // FIXME: If setExpression() was called with a string that does
257            // not begin and end with curly brackets, then getExpression()
258            // will not return something that is parseable by setExpression()
259            return _expression;
260        }
261        StringBuffer buffer = new StringBuffer();
262        buffer.append("{ ");
263        int i = 0;
264        for (LayoutHintItem item : _layoutHintItems) {
265            if (i > 0) {
266                buffer.append(",");
267            }
268            buffer.append(item.getExpression());
269            i++;
270        }
271        buffer.append(" }");
272        return buffer.toString();
273    }
274
275    /**
276     * Get the {@link LayoutHintItem} stored in this LayoutHint that is
277     * identified by the head and tail of the link for which it specifies bend
278     * points. If no {@link LayoutHintItem} is stored for the given head and
279     * tail, null is returned. It works like a map with two keys that have to
280     * match. As links in Ptolemy are not directed, it does not matter if head
281     * and tail get switched. However, for layout the direction does matter and
282     * the bendpoint list is directed from head to tail. So if there is an item
283     * available where head and tail are swapped, then this item will be
284     * returned but the entries get swapped again to guarantee that head and
285     * tail and the bendpoint order are correct.
286     *
287     * @param head The starting point of the link, e.g. a Ptolemy Port or
288     *            Relation.
289     * @param tail The ending point of the link, e.g. a Ptolemy Port or
290     *            Relation.
291     * @return the LayoutHintItem stored for this link or null
292     * @see #setLayoutHintItem(NamedObj, NamedObj, double[], Point2D.Double)
293     */
294    public LayoutHintItem getLayoutHintItem(Object head, Object tail) {
295        for (LayoutHintItem item : _layoutHintItems) {
296            if (item.getHead() == head && item.getTail() == tail) {
297                return item;
298            }
299            // also return this hint if head and tail are switched
300            if (item.getHead() == tail && item.getTail() == head) {
301                item._reverse();
302                return item;
303            }
304        }
305        return null;
306    }
307
308    /**
309     * Get the value of the attribute, which is the evaluated expression.
310     * @return The value.
311     * @see Settable#getValueAsString()
312     */
313    @Override
314    public String getValueAsString() {
315        return getExpression();
316    }
317
318    /**
319     * Get the visibility of this Settable, as set by setVisibility().
320     * The returned value is one of the static
321     * instances of the {@link ptolemy.kernel.util.Settable.Visibility} inner class.
322     * @return The visibility of this Settable.
323     * @see #setVisibility(ptolemy.kernel.util.Settable.Visibility)
324     * @see ptolemy.kernel.util.Settable#getVisibility()
325     */
326    @Override
327    public Visibility getVisibility() {
328        return _visibility;
329    }
330
331    /**
332     * Remove a {@link LayoutHintItem} from this storage. If that is the last
333     * item contained in this layout hint, then the layout hint itself is
334     * removed from its container.
335     *
336     * @param itemToRemove The layout hint item to remove
337     */
338    public void removeLayoutHintItem(final LayoutHintItem itemToRemove) {
339        final NamedObj container = getContainer();
340        if (container != null) {
341            container.requestChange(
342                    new ChangeRequest(container, "Remove Layout Hint") {
343                        @Override
344                        protected void _execute() throws Exception {
345                            _layoutHintItems.remove(itemToRemove);
346                            if (_layoutHintItems.isEmpty()) {
347                                setContainer(null);
348                            }
349                        }
350                    });
351        }
352    }
353
354    /**
355     * Remove a listener from the list of listeners that is notified when the
356     * value of this variable changes. If no such listener exists, do nothing.
357     *
358     * @param listener The listener to remove.
359     * @see Location#removeValueListener(ValueListener)
360     * @see #addValueListener(ValueListener)
361     */
362    @Override
363    public void removeValueListener(ValueListener listener) {
364        if (_valueListeners != null) {
365            _valueListeners.remove(listener);
366        }
367    }
368
369    /**
370     * Set the value of the attribute by giving some expression. This expression
371     * is not parsed until validate() is called, and the container and value
372     * listeners are not notified until validate() is called. See the class
373     * comment for a description of the format.
374     *
375     * @param expression The value of the attribute.
376     * @see #getExpression()
377     */
378    @Override
379    public void setExpression(String expression) {
380        _expression = expression;
381        _expressionSet = true;
382    }
383
384    /**
385     * Set a {@link LayoutHintItem} for a link which is specified by its head
386     * and tail, i.e. Ptolemy {@link Port}s or {@link Relation}s. For this link
387     * store the given list of bend points. Like in a {@link Map} with two keys,
388     * a possibly existing item for the given head and tail will be reused and
389     * updated with the bend points. If no such item yet exists, a new one is
390     * added.
391     *
392     * @param head the head object of the corresponding link
393     * @param tail the tail object of the corresponding link
394     * @param bendPoints an array of double coordinates, where always two
395     *            correspond to a bend point
396     * @param labelLocation the location of a label if it exists, may be null
397     * @see #getLayoutHintItem(Object, Object)
398     */
399    public void setLayoutHintItem(NamedObj head, NamedObj tail,
400            double[] bendPoints, Point2D.Double labelLocation) {
401        _expressionSet = false;
402        LayoutHintItem item = getLayoutHintItem(head, tail);
403        if (item == null) {
404            item = new LayoutHintItem(head, tail);
405            _layoutHintItems.add(item);
406        }
407        // make sure head and tail are in the right order
408        if (head == item.getTail() && tail == item.getHead()) {
409            // they are reversed, so reverse also bendpoints
410            _reverseCoordinateArray(bendPoints);
411        }
412
413        item.setBendpoints(bendPoints);
414
415        item.setLabelLocation(labelLocation);
416
417        if (_valueListeners != null) {
418            Iterator listeners = _valueListeners.iterator();
419
420            while (listeners.hasNext()) {
421                ValueListener listener = (ValueListener) listeners.next();
422                listener.valueChanged(this);
423            }
424        }
425    }
426
427    /**
428     * Set the visibility of this attribute. The argument should be one of the
429     * public static instances in Settable.
430     *
431     * @param visibility The visibility of this attribute.
432     * @see #getVisibility()
433     */
434    @Override
435    public void setVisibility(Settable.Visibility visibility) {
436        _visibility = visibility;
437    }
438
439    /**
440     * Parse the layout hint specification given by setExpression(), if there
441     * has been one, and otherwise do nothing, i.e. keep the list of layout
442     * hints empty. Notify the container and any value listeners of the new
443     * location, if it has changed. See the class comment for a description of
444     * the format.
445     *
446     * @return Null, indicating that no other instances of Settable are
447     *         validated.
448     * @exception IllegalActionException If the expression is invalid.
449     */
450    @Override
451    public Collection validate() throws IllegalActionException {
452        _layoutHintItems = new ArrayList<LayoutHintItem>();
453        if (_expression == null) {
454            return null; // don't parse anything if there is no expression
455        }
456        // parse the expression
457        Token result;
458        try {
459            PtParser parser = new PtParser();
460            ASTPtRootNode parseTree = parser.generateParseTree(_expression);
461            ParseTreeEvaluator parseTreeEvaluator = new ParseTreeEvaluator();
462            result = parseTreeEvaluator.evaluateParseTree(parseTree, null);
463        } catch (Throwable throwable) {
464            // Chain exceptions to get the actor that threw the exception.
465            // Note that if evaluateParseTree does a divide by zero, we
466            // need to catch an ArithmeticException here.
467            throw new IllegalActionException(this, throwable,
468                    "Expression invalid.");
469        }
470        if (result == null) {
471            throw new IllegalActionException(this,
472                    "Expression yields a null result: " + _expression);
473        }
474        _addLayoutHintItems(result);
475        return null;
476    }
477
478    ///////////////////////////////////////////////////////////////////
479    ////                         protected methods                 ////
480
481    /**
482     * Propagate the value of this object to the specified object. The specified
483     * object is required to be an instance of the same class as this one, or a
484     * ClassCastException will be thrown.
485     *
486     * @param destination Object to which to propagate the value.
487     * @exception IllegalActionException If the value cannot be propagated.
488     * @see Location#_propagateValue(NamedObj)
489     */
490    @Override
491    protected void _propagateValue(NamedObj destination)
492            throws IllegalActionException {
493        ((LayoutHint) destination).setExpression(getExpression());
494    }
495
496    ///////////////////////////////////////////////////////////////////
497    ////                         private methods                   ////
498
499    /**
500     * Create {@link LayoutHintItem}s from parsed Ptolemy Expression in form of
501     * a {@link Token}. The token is expected to have exactly a specific format
502     * as given in the class comment.
503     *
504     * @param hints a Token containing the required information about the
505     *            LayoutHintItems
506     * @exception IllegalActionException thrown when the Token does not conform to
507     *             the expected format.
508     */
509    private void _addLayoutHintItems(Token hints)
510            throws IllegalActionException {
511        try {
512            // The token is expected to be an array of LayoutHintItems.
513            for (int i = 0; i < ((ArrayToken) hints).length(); i++) {
514                // Each LayoutHintItem is expected to be a Record.
515                RecordToken layoutItem = (RecordToken) ((ArrayToken) hints)
516                        .getElement(i);
517                // A LayoutHintItem has a head and tail entry, which are
518                // records containing an identifying String, coordinates,
519                // and optionally the width of a multiport.
520                RecordToken headToken = (RecordToken) layoutItem.get("head");
521                NamedObj head = _findNamedObj(this,
522                        ((StringToken) headToken.get("id")).stringValue());
523                Point2D.Double headLocation = new Point2D.Double();
524                headLocation.x = ((DoubleToken) headToken.get("x"))
525                        .doubleValue();
526                headLocation.y = ((DoubleToken) headToken.get("y"))
527                        .doubleValue();
528                int headMultiportWidth = 1;
529                if (headToken.get("index") != null) {
530                    headMultiportWidth = ((IntToken) headToken.get("index"))
531                            .intValue();
532                }
533
534                RecordToken tailToken = (RecordToken) layoutItem.get("tail");
535                NamedObj tail = _findNamedObj(this,
536                        ((StringToken) tailToken.get("id")).stringValue());
537                Point2D.Double tailLocation = new Point2D.Double();
538                tailLocation.x = ((DoubleToken) tailToken.get("x"))
539                        .doubleValue();
540                tailLocation.y = ((DoubleToken) tailToken.get("y"))
541                        .doubleValue();
542                int tailMultiportWidth = 1;
543                if (tailToken.get("index") != null) {
544                    tailMultiportWidth = ((IntToken) tailToken.get("index"))
545                            .intValue();
546                }
547
548                // label location
549                Point2D.Double labelLocation = null;
550                Token labelLocationToken = layoutItem.get("labelLocation");
551                if (labelLocationToken != null) {
552                    labelLocation = new Point2D.Double();
553                    RecordToken labelRecord = (RecordToken) labelLocationToken;
554                    labelLocation.x = ((DoubleToken) labelRecord.get("x"))
555                            .doubleValue();
556                    labelLocation.y = ((DoubleToken) labelRecord.get("y"))
557                            .doubleValue();
558                }
559
560                // The LayoutHintItem record contains a points entry, containing
561                // an array of bend points.
562                ArrayToken bendPoints = (ArrayToken) layoutItem.get("points");
563                // Only do if head and tail could be resolved.
564                // This can fail if you insert a new relation vertex into an
565                // existing link. Then first the attribute is copied and only
566                // then the relation is inserted into the diagram. Therefore the
567                // head and tail cannot be found. However, this LayoutHint will
568                // be invalid in such case anyways.
569                if (head != null && tail != null) {
570                    // create new LayoutHintItem and add it to this LayoutHint
571                    LayoutHintItem item = new LayoutHintItem(head, tail,
572                            headLocation, tailLocation, headMultiportWidth,
573                            tailMultiportWidth, labelLocation);
574                    double[] primitiveBendPoints = new double[bendPoints
575                            .length()];
576                    for (int ii = 0; ii < bendPoints.length(); ii++) {
577                        primitiveBendPoints[ii] = ((ScalarToken) bendPoints
578                                .getElement(ii)).doubleValue();
579                    }
580                    item.setBendpoints(primitiveBendPoints);
581                    _layoutHintItems.add(item);
582                }
583            }
584        } catch (Exception e) {
585            throw new IllegalActionException(this, e, e.getMessage()
586                    + "\nExpression is expected to be an Array of layout hint Records. "
587                    + "The following expression is of wrong format: \n"
588                    + _expression
589                    + "\nAn example for a layoutHint expression is\n"
590                    + EXAMPLE_EXPRESSION);
591        }
592    }
593
594    /**
595     * Find the first CompositeActor in the parent hierarchy of the given
596     * NamedObj and find some other NamedObj with a given String name in there.
597     *
598     * @param start start object in the parent hierarchy
599     * @param name name of the object to find in the first CompositeActor found
600     * @return the object found or null if not found
601     */
602    private static NamedObj _findNamedObj(NamedObj start, String name) {
603        NamedObj result = null;
604        NamedObj container = start.getContainer();
605        while (container != null && !(container instanceof CompositeActor)) {
606            container = container.getContainer();
607        }
608        if (container != null) {
609            result = ((CompositeActor) container).getPort(name);
610            if (result == null) {
611                result = ((CompositeActor) container).getEntity(name);
612            }
613            if (result == null) {
614                result = ((CompositeActor) container).getAttribute(name);
615            }
616        }
617        return result;
618    }
619
620    /**
621     * Reverse the order of an array of coordinates, i.e. every two entries are
622     * assumed to belong together and will be kept in right order.
623     *
624     * @param bendPoints the array to reverse, will be changed
625     * @return the changed array
626     */
627    private static double[] _reverseCoordinateArray(double[] bendPoints) {
628        int size = bendPoints.length - bendPoints.length % 2;
629        // Make sure only to process even length.
630        // Don't do anything if the array has only one location.
631        if (size >= 4) {
632            double tempx, tempy;
633            int lastX, lastY;
634            int iterations = size / 4;
635            for (int i = 0; i < iterations; i += 1) {
636                int index = i * 2;
637                tempx = bendPoints[index];
638                tempy = bendPoints[index + 1];
639                lastY = size - 1 - index;
640                lastX = lastY - 1;
641                bendPoints[index] = bendPoints[lastX];
642                bendPoints[index + 1] = bendPoints[lastY];
643                bendPoints[lastX] = tempx;
644                bendPoints[lastY] = tempy;
645            }
646        }
647        return bendPoints;
648    }
649
650    /** A valid example expression to show in the GUI in case of errors. */
651    private static final String EXAMPLE_EXPRESSION = "{  \n{head={id=\"a.out\",x=10,y=11},"
652            + "tail={id=\"relation1\",x=20,y=21},points={1,2,3,4,5,6}} ,"
653            + " \n{head={id=\"b.out1\",x=10,y=11},"
654            + "tail={id=\"relation2\",x=20,y=21},points={1,2,3,4,5,6}} \n}";
655    /** The expression given in setExpression(). */
656    private String _expression;
657    /** Indicator that the expression is the most recent spec for the location. */
658    private boolean _expressionSet = false;
659    /** List of layout hint items stored by this layout hint */
660    private List<LayoutHintItem> _layoutHintItems = new ArrayList<LayoutHintItem>();
661    /** Listeners for changes in value. */
662    private List _valueListeners;
663    /** The visibility of this attribute, which defaults to EXPERT. */
664    private Settable.Visibility _visibility = Settable.EXPERT;
665
666    /**
667     * A LayoutHintItem is the specification of layout information for one Link.
668     * As there are usually multiple links corresponding to one {@link Relation},
669     * a {@link LayoutHint} is attached to a Relation and carries multiple of
670     * these LayoutHintItems corresponding to the links. As links are no
671     * persisted objects in Ptolemy, a link is identified by its head and tail
672     * objects, which are {@link Port}s or {@link Relation}s.
673     * <p>
674     * The most important information such item carries is a list of bend points
675     * that can be used to explicitly route a link along these bend points
676     * instead of using a simple routing strategy like the
677     * {@link ManhattanConnector}. A router that uses the bend point information
678     * for example is the {@link KielerLayoutConnector}.
679     * <p>
680     * Such item can be serialized to the String representation of a Ptolemy
681     * Expression by {@link #getExpression()}. This is used for persisting
682     * LayoutHintItems. However, the bend point data are absolute coordinates
683     * and therefore are only valid until the head and/or tail of the link are
684     * moved. Hence, the LayoutHintItem also stores the coordinates and
685     * optionally the multiport width of head and tail, which specify for which
686     * layout of nodes the bend point information is only valid. The
687     * {@link #revalidate()} method is used to check the validity of the
688     * LayoutHintItem by comparing the stored positions with the actual
689     * positions in the diagram, i.e. checking whether head and/or tail have
690     * been moved or the width of a multiport has changed. If the LayoutHintItem
691     * is not valid anymore, its bend points should not be used.
692     * <p>
693     * A special case is when head and tail moved relatively exactly the same,
694     * which happens, if multiple elements are selected and moved together. In
695     * such case the bend points are still valid relatively, but not absolutely.
696     * Therefore the {@link #revalidate()} method also checks this case and
697     * translates the bend point coordinates as well as the new head and tail
698     * locations making the LayoutHintItem valid again. This avoids invalidating
699     * bend points when whole model parts get moved.
700     * <p>
701     * An example for one LayoutHintItem's String representation is the
702     * following
703     *
704     * <pre>
705     * { head={"CompositeActor.port",20.0,200.0,2}, tail={"Discard.input",70.0,25.0}, points={135.0,25.0,135.0,125.0} }
706     * </pre>
707     *
708     * The head contains the object's name, its coordinates in x and y and the
709     * width, because the port is a multiport. The width defaults to 1 as can be
710     * seen at the tail where it is omitted.
711     */
712    public static class LayoutHintItem {
713
714        ///////////////////////////////////////////////////////////////////
715        ////                     public methods                        ////
716
717        /**
718         * Simple constructor specifying only head and tail for this
719         * LayoutHintItem. The current layout of head and tail that is required
720         * for validity checking is obtained from these objects automatically.
721         *
722         * @param head the head object of the corresponding link
723         * @param tail the tail object of the corresponding link
724         */
725        public LayoutHintItem(NamedObj head, NamedObj tail) {
726            this._head = head;
727            this._tail = tail;
728            _updateHeadTailLocations();
729        }
730
731        /**
732         * Constructor passing not only head and tail but also all required
733         * layout information for the conditions under which this LayoutHintItem
734         * is only valid.
735         *
736         * @param head the head object of the corresponding link
737         * @param tail the tail object of the corresponding link
738         * @param locationHead the location of the head as vector
739         * @param locationTail the location of the tail as vector
740         * @param multiportWidthHead the width of the head, which is relevant
741         *            for multiports, 1, if no multiport
742         * @param multiportWidthTail the width of the tail, which is relevant
743         *            for multiports, 1, if no multiport
744         * @param labelPosition The position of the label.
745         */
746        public LayoutHintItem(NamedObj head, NamedObj tail,
747                Point2D.Double locationHead, Point2D.Double locationTail,
748                int multiportWidthHead, int multiportWidthTail,
749                Point2D.Double labelPosition) {
750            this(head, tail);
751            this._headLocation = locationHead;
752            this._tailLocation = locationTail;
753            this._headMultiportIndex[1] = multiportWidthHead;
754            this._tailMultiportIndex[1] = multiportWidthTail;
755            this._labelLocation = labelPosition;
756        }
757
758        /**
759         * Get the bend points stored in this hint as an array of doubles, where
760         * each two entries correspond to x and y of one bend point.
761         *
762         * @return array containing bend point coordinates
763         */
764        public double[] getBendPoints() {
765            return _bendPoints;
766        }
767
768        /**
769         * Get a list of {@link Point2D} corresponding to the bend points stored
770         * in this item. If by setting the bend points with
771         * {@link #setBendpoints(double[])} the list of bend point coordinates
772         * is odd, the last coordinate is discarded, and a list of points
773         * without the dangling coordinate is returned.
774         *
775         * @return list of bend points
776         */
777        public List<Point2D> getBendPointList() {
778            int size = _bendPoints.length / 2; // integer arithmetics will cut
779            // off in odd cases
780            ArrayList<Point2D> list = new ArrayList<Point2D>(size);
781            for (int i = 0; i < size; i++) {
782                Point2D point = new Point2D.Double(_bendPoints[2 * i],
783                        _bendPoints[2 * i + 1]);
784                list.add(point);
785            }
786            return list;
787        }
788
789        /**
790         * A {@link Point2D} representing the position where a label of an
791         * edge should be positioned.
792         *
793         * @return the label location.
794         * @see #setLabelLocation(Point2D.Double)
795         */
796        public Point2D.Double getLabelLocation() {
797            return _labelLocation;
798        }
799
800        /**
801         * Get the String representation of the Ptolemy Expression by which this
802         * LayoutHint is persisted. See the class comment for the concrete
803         * specification.
804         *
805         * @return String representation of this LayoutHint
806         */
807        public String getExpression() {
808            StringBuffer buffer = new StringBuffer();
809            buffer.append("{ head={id=\"" + _getName(_head) + "\"" + ",x="
810                    + _headLocation.x + ",y=" + _headLocation.y);
811            if (_headMultiportIndex[1] != 1) {
812                buffer.append(",index=" + _headMultiportIndex[1]);
813            }
814            buffer.append("}, " + "tail={id=\"" + _getName(_tail) + "\"" + ",x="
815                    + _tailLocation.x + ",y=" + _tailLocation.y);
816            if (_tailMultiportIndex[1] != 1) {
817                buffer.append(",index=" + _tailMultiportIndex[1]);
818            }
819            buffer.append("}, " + "points={");
820            for (int i = 0; i < _bendPoints.length - 1; i += 2) {
821                if (i > 0) {
822                    buffer.append(",");
823                }
824                buffer.append(_bendPoints[i] + "," + _bendPoints[i + 1]);
825            }
826            if (_labelLocation != null) {
827                buffer.append("}, " + "labelLocation={");
828                buffer.append("x=" + _labelLocation.x);
829                buffer.append(",y=" + _labelLocation.y);
830            }
831            buffer.append("} }");
832            return buffer.toString();
833        }
834
835        /**
836         * Get the head of this LayoutHint which is used to identify this hint.
837         *
838         * @return head object of this LayoutHint
839         */
840        public NamedObj getHead() {
841            return _head;
842        }
843
844        /**
845         * Get the tail of this LayoutHint which is used to identify this hint.
846         *
847         * @return tail object of this LayoutHint
848         */
849        public NamedObj getTail() {
850            return _tail;
851        }
852
853        /**
854         * Check if the head and tail objects have been moved. If this is the
855         * case but both have been moved while keeping the same relative
856         * position to each other, the bend points can be translated
857         * accordingly. In this case, update the bend points and the head and
858         * tail location. If they have been moved and the relative positions are
859         * different now, then return false. In that case the bend point list is
860         * no longer feasible and should not be used anymore, however, no update
861         * is done here.
862         *
863         * @return true if the relative positions of head and tail are the same
864         *         as before, false otherwise
865         */
866        public boolean revalidate() {
867            if (_bendPoints == null) {
868                return false;
869            }
870            if (_head instanceof Port) {
871                // check if the width of a multiport has changed
872                int width = _getChannelWidth(_head);
873                if (width != _headMultiportIndex[1]) {
874                    return false;
875                }
876            }
877            if (_tail instanceof Port) {
878                int width = _getChannelWidth(_tail);
879                if (width != _tailMultiportIndex[1]) {
880                    return false;
881                }
882            }
883            Point2D.Double newHeadLocation = _getEndpointLocation(_head);
884            Point2D.Double newTailLocation = _getEndpointLocation(_tail);
885
886            if (newHeadLocation.equals(_headLocation)
887                    && newTailLocation.equals(_tailLocation)) {
888                // nothing has changed, we don't need to update anything
889                return true;
890            }
891
892            double oldDistanceX = _headLocation.x - _tailLocation.x;
893            double oldDistanceY = _headLocation.y - _tailLocation.y;
894            double newDistanceX = newHeadLocation.x - newTailLocation.x;
895            double newDistanceY = newHeadLocation.y - newTailLocation.y;
896
897            // do an integer compare. This is safe and enough.
898            if ((int) oldDistanceX != (int) newDistanceX
899                    || (int) oldDistanceY != (int) newDistanceY) {
900                // in this case we cannot use the bend points anymore
901                return false;
902            }
903            // now we know the head and tail have been moved but the relative
904            // location is the same
905            // we can safely translate everything
906            _translate(newHeadLocation.x - _headLocation.x,
907                    newHeadLocation.y - _headLocation.y);
908            return true;
909        }
910
911        /**
912         * Set a new list of bend points and update the current validation
913         * information such as the current location of head and tail and their
914         * port widths. Hence, this LayoutHint will be valid until the head and
915         * tail are moved again.
916         *
917         * @param bendPoints new bend points
918         */
919        public void setBendpoints(double[] bendPoints) {
920            _bendPoints = bendPoints;
921            _updateHeadTailLocations();
922        }
923
924        /**
925         * Sets the position a label should be placed at.
926         *
927         * @param labelLocation a {@link Point2D} with the position, may be null.
928         * @see #getLabelLocation()
929         */
930        public void setLabelLocation(Point2D.Double labelLocation) {
931            _labelLocation = labelLocation;
932        }
933
934        /**
935         * Get a String representation of a LayoutHint which will be the same as
936         * {@link #getExpression()}.
937         *
938         * @see #getExpression()
939         * @return String representation of this LayoutHint
940         */
941        @Override
942        public String toString() {
943            return getExpression();
944        }
945
946        /**
947         * Reverse the list of bend points. This may be neces
948         */
949        protected void _reverse() {
950            Object temp;
951            // swap head and tail
952            temp = _head;
953            _head = _tail;
954            _tail = (NamedObj) temp;
955            // swap location
956            temp = _headLocation;
957            _headLocation = _tailLocation;
958            _tailLocation = (Point2D.Double) temp;
959            // swap multiport stuff
960            temp = _headMultiportIndex;
961            _headMultiportIndex = _tailMultiportIndex;
962            _tailMultiportIndex = (int[]) temp;
963            // reverse bendpoints
964            _reverseCoordinateArray(_bendPoints);
965        }
966
967        ///////////////////////////////////////////////////////////////////
968        ////                     private methods                       ////
969
970        /**
971         * Translate all coordinates given in this hint item. Include the
972         * memorized head and tail location as well as all bend points.
973         *
974         * @param x amount on the x-axis to translate
975         * @param y amount on the x-axis to translate
976         */
977        private void _translate(double x, double y) {
978            _headLocation.x += x;
979            _headLocation.y += y;
980            _tailLocation.x += x;
981            _tailLocation.y += y;
982            // iterate all bend points, make sure that in a faulty odd
983            // array, the last entry is ignored
984            for (int i = 0; i < _bendPoints.length - 1; i += 2) {
985                _bendPoints[i] += x;
986                _bendPoints[i + 1] += y;
987            }
988            if (_labelLocation != null) {
989                _labelLocation.x += x;
990                _labelLocation.y += y;
991            }
992        }
993
994        /**
995         * Update the layout information for head and tail of this hint that is
996         * required to check validity. This is the location and channel width
997         * get read from the current model objects.
998         */
999        private void _updateHeadTailLocations() {
1000            _headLocation = _getEndpointLocation(_head);
1001            _tailLocation = _getEndpointLocation(_tail);
1002            _updateChannelIndex(_head, _headMultiportIndex);
1003            _updateChannelIndex(_tail, _tailMultiportIndex);
1004        }
1005
1006        /**
1007         * Get the width of a channel corresponding to a port. If no
1008         * {@link IOPort} is passed, return 0.
1009         *
1010         * @param port port for which to determine the channel width
1011         * @return channel width if applicable, else 0
1012         */
1013        private static int _getChannelWidth(Object port) {
1014            if (port instanceof IOPort) {
1015                return ((IOPort) port).numLinks();
1016                // return ((IOPort)port).getWidth();
1017            }
1018            return 0;
1019        }
1020
1021        /**
1022         * Get a String name of an object that is sufficient to identify it on
1023         * one level of hierarchy. The name will be the name of the object
1024         * relative to some parent. E.g. a port "input" of a "Discard" actor
1025         * will result in the name "Discard.input".
1026         *
1027         * @param obj The object for which the name should be obtained
1028         * @return a String representation that is sufficient to identify the
1029         *         object
1030         */
1031        private static String _getName(NamedObj obj) {
1032            NamedObj parent = obj.getContainer();
1033            if ((obj instanceof Port || obj instanceof Vertex
1034                    || obj instanceof Attribute) && parent != null) {
1035                parent = parent.getContainer();
1036            }
1037            return obj.getName(parent);
1038        }
1039
1040        /**
1041         * Determine the correct location of a link endpoint.
1042         *
1043         * @param obj an endpoint of a link
1044         * @return the location for the given endpoint
1045         */
1046        private static Point2D.Double _getEndpointLocation(NamedObj obj) {
1047            // In case of a component port, the endpoint is the external port of
1048            // a composite actor, so take the actor's position instead of the port's
1049            // internal position.
1050            if (obj instanceof ComponentPort) {
1051                return (Point2D.Double) KielerLayoutUtil
1052                        .getLocationPoint(obj.getContainer());
1053            }
1054            return (Point2D.Double) KielerLayoutUtil.getLocationPoint(obj);
1055        }
1056
1057        /**
1058         * Update the channel width of a given object. For an IOPort, the actual
1059         * channel width is obtained and otherwise set it to default 1.
1060         *
1061         * @param port The port for which the update is required, may also be
1062         *            some other type than port, e.g. a Relation
1063         * @param indexLocation array with at least 2 entries in where to store
1064         *            the index locations
1065         */
1066        private static void _updateChannelIndex(Object port,
1067                int[] indexLocation) {
1068            int width = 1;
1069            int index = 1; // currently the index is ignored
1070            if (port instanceof IOPort) {
1071                width = ((IOPort) port).numLinks();
1072            }
1073            indexLocation[0] = index;
1074            indexLocation[1] = width;
1075        }
1076
1077        /**
1078         * Local storage of bend points, where every 2 doubles form x and y
1079         * coordinates.
1080         */
1081        private double[] _bendPoints = {};
1082        /** Head object to identify this item. */
1083        private NamedObj _head = null;
1084        /** Coordinates for the head at which this item is only valid. */
1085        private Point2D.Double _headLocation = new Point2D.Double();
1086        /** Width and index of the multiport, if the head actually is a multiport. */
1087        private int[] _headMultiportIndex = { 1, 1 };
1088        /** Tail object to identify this item. */
1089        private NamedObj _tail = null;
1090        /** Coordinates for the tail at which this item is only valid. */
1091        private Point2D.Double _tailLocation = new Point2D.Double();
1092        /** Width and index of the multiport, if the tail actually is a multiport. */
1093        private int[] _tailMultiportIndex = { 1, 1 };
1094        /** The position of a label if it exists. */
1095        private Point2D.Double _labelLocation = null;
1096    }
1097}