001/* A Relation links ports, and therefore the entities that contain them.
002
003 Copyright (c) 1997-2014 The Regents of the University of California.
004 All rights reserved.
005 Permission is hereby granted, without written agreement and without
006 license or royalty fees, to use, copy, modify, and distribute this
007 software and its documentation for any purpose, provided that the above
008 copyright notice and the following two paragraphs appear in all copies
009 of this software.
010
011 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
012 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
013 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
014 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
015 SUCH DAMAGE.
016
017 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
018 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
019 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
020 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
021 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
022 ENHANCEMENTS, OR MODIFICATIONS.
023
024 PT_COPYRIGHT_VERSION_2
025 COPYRIGHTENDKEY
026
027 */
028package ptolemy.kernel;
029
030import java.util.Collections;
031import java.util.Enumeration;
032import java.util.HashSet;
033import java.util.LinkedList;
034import java.util.List;
035import java.util.Set;
036
037import ptolemy.kernel.util.CrossRefList;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.InternalErrorException;
040import ptolemy.kernel.util.NamedObj;
041import ptolemy.kernel.util.Workspace;
042
043///////////////////////////////////////////////////////////////////
044//// Relation
045
046/**
047 A Relation links ports, and therefore the entities that contain them.
048 To link a port to a relation, use the link() method
049 in the Port class.  To remove a link, use the unlink() method in the
050 Port class.
051 <p>
052 Relations can also be linked to other Relations.
053 To create such a link, use the link() method of this class.
054 To remove such a link, use the unlink() method. A group of linked
055 relations behaves exactly as if it were one relation directly linked
056 to all the ports linked to by each relation. In particular, the
057 connectedPortList() method of the Port class returns the same list
058 whether a single relation is used or a relation group. The order
059 in which the ports are listed is the order in which links were
060 made between the port and relations in the relation group.
061 It is not relevant which relation in a relation group the port
062 links to.
063 <p>
064 Derived classes may wish to disallow links under certain circumstances,
065 for example if the proposed port is not an instance of an appropriate
066 subclass of Port, or if the relation cannot support any more links.
067 Such derived classes should override the protected method _checkPort()
068 or _checkRelation() to throw an exception.
069
070 @author Edward A. Lee, Neil Smyth
071 @version $Id$
072 @since Ptolemy II 0.2
073 @Pt.ProposedRating Green (eal)
074 @Pt.AcceptedRating Green (acataldo)
075 @see Port
076 @see Entity
077 */
078public class Relation extends NamedObj {
079    /** Construct a relation in the default workspace with an empty string
080     *  as its name. Increment the version number of the workspace.
081     *  The object is added to the workspace directory.
082     */
083    public Relation() {
084        super();
085        _elementName = "relation";
086    }
087
088    /** Construct a relation in the default workspace with the given name.
089     *  If the name argument is null, then the name is set to the empty string.
090     *  Increment the version number of the workspace.
091     *  The object is added to the workspace directory.
092     *  @param name Name of this object.
093     *  @exception IllegalActionException If the name has a period.
094     */
095    public Relation(String name) throws IllegalActionException {
096        super(name);
097        _elementName = "relation";
098    }
099
100    /** Construct a relation in the given workspace with an empty string
101     *  as a name.
102     *  If the workspace argument is null, use the default workspace.
103     *  The object is added to the workspace directory.
104     *  Increment the version of the workspace.
105     *  @param workspace The workspace for synchronization and version tracking.
106     */
107    public Relation(Workspace workspace) {
108        super(workspace);
109        _elementName = "relation";
110    }
111
112    /** Construct a relation in the given workspace with the given name.
113     *  If the workspace argument is null, use the default workspace.
114     *  If the name argument is null, then the name is set to the empty string.
115     *  The object is added to the workspace directory.
116     *  Increment the version of the workspace.
117     *  @param workspace Workspace for synchronization and version tracking
118     *  @param name Name of this object.
119     *  @exception IllegalActionException If the name has a period.
120     */
121    public Relation(Workspace workspace, String name)
122            throws IllegalActionException {
123        super(workspace, name);
124        _elementName = "relation";
125    }
126
127    ///////////////////////////////////////////////////////////////////
128    ////                         public methods                    ////
129
130    /** Clone the object into the specified workspace. The new object is
131     *  <i>not</i> added to the directory of that workspace (you must do this
132     *  yourself if you want it there).
133     *  The result is a new relation with no links and no container.
134     *  @param workspace The workspace for the cloned object.
135     *  @exception CloneNotSupportedException If one of the attributes cannot
136     *   be cloned.
137     *  @return A new Relation.
138     */
139    @Override
140    public Object clone(Workspace workspace) throws CloneNotSupportedException {
141        Relation newObject = (Relation) super.clone(workspace);
142        newObject._linkList = new CrossRefList(newObject);
143        return newObject;
144    }
145
146    /** Link this relation with another relation.  The relation is required
147     *  to be at the same level of the hierarchy as this relation.
148     *  That is, level-crossing links are not allowed.
149     *  If the specified relation is already linked to this one,
150     *  do nothing.
151     *  In derived classes, the relation may be required to be an
152     *  instance of a particular subclass of Relation (this is checked
153     *  by the _checkRelation() protected method).
154     *  This method is write-synchronized on the workspace and increments
155     *  its version number.
156     *  @param relation The relation to link to this relation.
157     *  @exception IllegalActionException If the link would cross levels of
158     *   the hierarchy, or the relation is incompatible,
159     *   or this relation has no container, or this relation is not in the
160     *   same workspace as the relation.
161     */
162    public void link(Relation relation) throws IllegalActionException {
163        if (relation != null && _workspace != relation.workspace()) {
164            throw new IllegalActionException(this, relation,
165                    "Cannot link because workspaces are different.");
166        }
167
168        try {
169            _workspace.getWriteAccess();
170
171            if (relation != null) {
172                _checkRelation(relation, true);
173
174                if (!_linkList.isLinked(relation)) {
175                    _linkList.link(relation._linkList);
176                }
177            } else {
178                _linkList.link(null);
179            }
180        } finally {
181            _workspace.doneWriting();
182        }
183    }
184
185    /** Return a list of the objects directly linked to this
186     *  relation (ports and relations).
187     *  Note that a port may appear more than
188     *  once if more than one link to it has been established,
189     *  but a relation will appear only once. There is no significance
190     *  to multiple links between the same relations.
191     *  This method is read-synchronized on the workspace.
192     *  @return A list of Port and Relation objects.
193     */
194    public List linkedObjectsList() {
195        try {
196            _workspace.getReadAccess();
197
198            // NOTE: This should probably be cached.
199            // Unfortunately, CrossRefList returns an enumeration only.
200            // Use it to construct a list.
201            LinkedList result = new LinkedList();
202            Enumeration links = _linkList.getContainers();
203
204            while (links.hasMoreElements()) {
205                Object next = links.nextElement();
206                result.add(next);
207            }
208
209            return result;
210        } finally {
211            _workspace.doneReading();
212        }
213    }
214
215    /** List the ports linked to any relation in this relation's
216     *  group.  Note that a port may appear more than
217     *  once if more than one link to it has been established.
218     *  The order in which ports are listed has no significance.
219     *  This method is read-synchronized on the workspace.
220     *  @return A list of Port objects.
221     */
222    public List linkedPortList() {
223        try {
224            _workspace.getReadAccess();
225
226            // Unfortunately, CrossRefList returns an enumeration only.
227            // Use it to construct a list.
228            LinkedList result = new LinkedList();
229            Enumeration links = _linkList.getContainers();
230
231            Set<Relation> exceptRelations = new HashSet<Relation>();
232            exceptRelations.add(this);
233
234            while (links.hasMoreElements()) {
235                Object next = links.nextElement();
236
237                if (next instanceof Port) {
238                    result.add(next);
239                } else {
240                    // Must be another relation.
241                    result.addAll(((Relation) next)._linkedPortList(null,
242                            exceptRelations));
243                }
244            }
245
246            return result;
247        } finally {
248            _workspace.doneReading();
249        }
250    }
251
252    /** List the ports linked to any relation in this relation's
253     *  group except the specified port.
254     *  Note that a port may appear more than
255     *  once if more than on link to it has been established.
256     *  The order in which ports are listed has no significance.
257     *  This method is read-synchronized on the workspace.
258     *  @param except Port to exclude from the list.
259     *  @return A list of Port objects.
260     */
261    public List linkedPortList(Port except) {
262        // This works by constructing a linked list and then returning it.
263        try {
264            _workspace.getReadAccess();
265
266            LinkedList result = new LinkedList();
267            Enumeration links = _linkList.getContainers();
268
269            Set<Relation> exceptRelations = new HashSet<Relation>();
270            exceptRelations.add(this);
271
272            while (links.hasMoreElements()) {
273                Object link = links.nextElement();
274
275                if (link instanceof Port) {
276                    if (link != except) {
277                        result.add(link);
278                    }
279                } else {
280                    // Must be another relation.
281                    result.addAll(((Relation) link)._linkedPortList(except,
282                            exceptRelations));
283                }
284            }
285
286            return result;
287        } finally {
288            _workspace.doneReading();
289        }
290    }
291
292    /** Enumerate the linked ports.  Note that a port may appear more than
293     *  once if more than one link to it has been established.
294     *  This method is read-synchronized on the workspace.
295     *  @deprecated Use linkedPortList() instead.
296     *  @return An Enumeration of Port objects.
297     */
298    @Deprecated
299    public Enumeration linkedPorts() {
300        return Collections.enumeration(linkedPortList());
301    }
302
303    /** Enumerate the linked ports except the specified port.
304     *  Note that a port may appear more than
305     *  once if more than on link to it has been established.
306     *  This method is read-synchronized on the workspace.
307     *  @param except Port to exclude from the enumeration.
308     *  @return An Enumeration of Port objects.
309     *  @deprecated Use linkedPortList(Port) instead.
310     */
311    @Deprecated
312    public Enumeration linkedPorts(Port except) {
313        return Collections.enumeration(linkedPortList(except));
314    }
315
316    /** Return the number of links to ports, either directly
317     *  or indirectly via other relations in the relation
318     *  group. This is the size
319     *  of the list returned by linkedPortList().
320     *  This method is read-synchronized on the workspace.
321     *  @return The number of links.
322     *  @see #linkedPortList()
323     */
324    public int numLinks() {
325        return linkedPortList().size();
326    }
327
328    /** Return the list of relations in the relation group containing
329     *  this relation.
330     *  The relation group includes this relation, all relations
331     *  directly linked to it, all relations directly linked to
332     *  those, etc. That is, it is a maximal set of linked
333     *  relations. There is no significance to the order of the
334     *  returned list, but the returned value is a List rather than
335     *  a Set to facilitate testing.
336     *  @return The relation group.
337     */
338    public List relationGroupList() {
339        LinkedList result = new LinkedList();
340        _relationGroup(result);
341        return result;
342    }
343
344    /** Unlink the specified Relation. If the Relation
345     *  is not linked to this relation, do nothing.
346     *  This method is write-synchronized on the
347     *  workspace and increments its version number.
348     *  @param relation The relation to unlink.
349     */
350    public void unlink(Relation relation) {
351        try {
352            _workspace.getWriteAccess();
353            _linkList.unlink(relation);
354        } finally {
355            _workspace.doneWriting();
356        }
357    }
358
359    /** Unlink all ports and relations that are directly linked
360     *  to this relation.
361     *  This method is write-synchronized on the workspace and increments
362     *  its version number.
363     */
364    public void unlinkAll() {
365        try {
366            _workspace.getWriteAccess();
367
368            // NOTE: Do not just use _linkList.unlinkAll() because then the
369            // containers of ports are not notified of the change.
370            // Also, have to first copy the link references, then remove
371            // them, to avoid a corrupted enumeration exception.
372            int size = _linkList.size();
373            Object[] linkedObjectsArray = new Object[size];
374            int i = 0;
375            Enumeration links = _linkList.getContainers();
376
377            while (links.hasMoreElements()) {
378                Object linkedObject = links.nextElement();
379                linkedObjectsArray[i++] = linkedObject;
380            }
381
382            // NOTE: It would be better if there were a
383            // Linkable interface so that these instanceof
384            // tests would not be needed.
385            for (i = 0; i < size; i++) {
386                if (linkedObjectsArray[i] instanceof Port) {
387                    ((Port) linkedObjectsArray[i]).unlink(this);
388                } else {
389                    ((Relation) linkedObjectsArray[i]).unlink(this);
390                }
391            }
392        } finally {
393            _workspace.doneWriting();
394        }
395    }
396
397    ///////////////////////////////////////////////////////////////////
398    ////                         protected methods                 ////
399
400    /** Throw an exception if the specified port cannot be linked to this
401     *  relation.  In this base class, the exception is not thrown, but
402     *  in derived classes, it might be, for example if the relation cannot
403     *  support any more links, or the port is not an instance of an
404     *  appropriate subclass of Port.
405     *  @param port The port to link to.
406     *  @exception IllegalActionException Not thrown in this base class.
407     */
408    protected void _checkPort(Port port) throws IllegalActionException {
409    }
410
411    /** Check that this relation is compatible with the specified relation.
412     *  In this base class, this method calls the corresponding check method
413     *  of the specified relation, and if that returns, then it returns.
414     *  Derived classes should override this method to check validity
415     *  of the specified relation, and then call super._checkRelation().
416     *  @param relation The relation to link to.
417     *  @param symmetric If true, the call _checkRelation on the specified
418     *   relation with this as an argument.
419     *  @exception IllegalActionException If this relation has no container,
420     *   or if this relation is not an acceptable relation for the specified
421     *   relation.
422     */
423    protected void _checkRelation(Relation relation, boolean symmetric)
424            throws IllegalActionException {
425        if (relation != null && symmetric) {
426            // Throw an exception if this relation is not of an acceptable
427            // class for the specified relation.
428            relation._checkRelation(this, false);
429        }
430    }
431
432    /** Return a description of the object.  The level of detail depends
433     *  on the argument, which is an or-ing of the static final constants
434     *  defined in the NamedObj class.  Lines are indented according to
435     *  to the level argument using the protected method _getIndentPrefix().
436     *  Zero, one or two brackets can be specified to surround the returned
437     *  description.  If one is specified it is the the leading bracket.
438     *  This is used by derived classes that will append to the description.
439     *  Those derived classes are responsible for the closing bracket.
440     *  An argument other than 0, 1, or 2 is taken to be equivalent to 0.
441     *  This method is read-synchronized on the workspace.
442     *  @param detail The level of detail.
443     *  @param indent The amount of indenting.
444     *  @param bracket The number of surrounding brackets (0, 1, or 2).
445     *  @return A description of the object.
446     *  @exception IllegalActionException If thrown while getting the
447     *  description of subcomponents.
448     */
449    @Override
450    protected String _description(int detail, int indent, int bracket)
451            throws IllegalActionException {
452        try {
453            _workspace.getReadAccess();
454
455            StringBuffer result = new StringBuffer();
456
457            if (bracket == 1 || bracket == 2) {
458                result.append(super._description(detail, indent, 1));
459            } else {
460                result.append(super._description(detail, indent, 0));
461            }
462
463            if ((detail & LINKS) != 0) {
464                if (result.toString().trim().length() > 0) {
465                    result.append(" ");
466                }
467
468                // To avoid infinite loop, turn off the LINKS flag
469                // when querying the Ports.
470                detail &= ~LINKS;
471                result.append("links {\n");
472
473                Enumeration links = _linkList.getContainers();
474
475                while (links.hasMoreElements()) {
476                    Object object = links.nextElement();
477
478                    if (object instanceof Port) {
479                        result.append(((Port) object)._description(detail,
480                                indent + 1, 2) + "\n");
481                    } else {
482                        result.append(((Relation) object)._description(detail,
483                                indent + 1, 2) + "\n");
484                    }
485                }
486
487                result.append(_getIndentPrefix(indent) + "}");
488            }
489
490            if (bracket == 2) {
491                result.append("}");
492            }
493
494            return result.toString();
495        } finally {
496            _workspace.doneReading();
497        }
498    }
499
500    /** Get a relation with the specified name in the specified container.
501     *  The returned object is assured of being an
502     *  instance of the same class as this object.
503     *  @param relativeName The name relative to the container.
504     *  @param container The container expected to contain the object, which
505     *   must be an instance of CompositeEntity.
506     *  @return An object of the same class as this object, or null if
507     *   there is none.
508     *  @exception IllegalActionException If the object exists
509     *   and has the wrong class, or if the specified container is not
510     *   an instance of CompositeEntity.
511     */
512    @Override
513    protected NamedObj _getContainedObject(NamedObj container,
514            String relativeName) throws IllegalActionException {
515        if (!(container instanceof CompositeEntity)) {
516            throw new InternalErrorException("Expected "
517                    + container.getFullName()
518                    + " to be an instance of ptolemy.kernel.CompositeEntity, "
519                    + "but it is " + container.getClass().getName());
520        }
521
522        Relation candidate = ((CompositeEntity) container)
523                .getRelation(relativeName);
524
525        if (candidate != null && !getClass().isInstance(candidate)) {
526            throw new IllegalActionException(this,
527                    "Expected " + candidate.getFullName()
528                            + " to be an instance of " + getClass().getName()
529                            + ", but it is " + candidate.getClass().getName());
530        }
531
532        return candidate;
533    }
534
535    ///////////////////////////////////////////////////////////////////
536    ////                         protected variables               ////
537
538    /** The list of Ports and Relations that are linked to this Relation. */
539    protected CrossRefList _linkList = new CrossRefList(this);
540
541    ///////////////////////////////////////////////////////////////////
542    ////                         private methods                   ////
543
544    /** List the linked ports except the <i>exceptPort</i> and
545     *  those linked via the relations in the <i>exceptRelations</i>
546     *  list. This relation is
547     *  added to the <i>exceptRelations</i> list before returning.
548     *  Note that a port may appear more than
549     *  once if more than one link to it has been established.
550     *  This method is not read-synchronized on the workspace,
551     *  so the caller is expected to be.
552     *  @param exceptPort A port to exclude, or null to not
553     *   specify one.
554     *  @param exceptRelations Set of relations to exclude.
555     *  @return A list of Port objects.
556     */
557    private List _linkedPortList(Port exceptPort, Set exceptRelations) {
558        // This works by constructing a linked list and then returning it.
559        LinkedList result = new LinkedList();
560
561        if (exceptRelations.contains(this)) {
562            return result;
563        }
564
565        // Prevent listing the ports connected to this relation again.
566        exceptRelations.add(this);
567
568        Enumeration links = _linkList.getContainers();
569
570        while (links.hasMoreElements()) {
571            Object link = links.nextElement();
572
573            if (link instanceof Port) {
574                if (link != exceptPort) {
575                    result.add(link);
576                }
577            } else {
578                // Link must be to a relation.
579                Relation relation = (Relation) link;
580
581                if (!exceptRelations.contains(relation)) {
582                    result.addAll(relation._linkedPortList(exceptPort,
583                            exceptRelations));
584                }
585            }
586        }
587
588        return result;
589    }
590
591    /** Append to the specified list all relations in the relation
592     *  group with this one that are not already on the list.
593     *  The relation group includes this relation, all relations
594     *  directly linked to it, all relations directly linked to
595     *  those, etc.
596     *  @param list The list to append to.
597     */
598    private void _relationGroup(List list) {
599        if (!list.contains(this)) {
600            list.add(this);
601
602            Enumeration links = _linkList.getContainers();
603
604            while (links.hasMoreElements()) {
605                Object link = links.nextElement();
606
607                if (link instanceof Relation) {
608                    ((Relation) link)._relationGroup(list);
609                }
610            }
611        }
612    }
613}