001/* A Port is an aggregation of links to relations.
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.Iterator;
033import java.util.LinkedList;
034import java.util.List;
035
036import ptolemy.kernel.util.ChangeRequest;
037import ptolemy.kernel.util.CrossRefList;
038import ptolemy.kernel.util.IllegalActionException;
039import ptolemy.kernel.util.InternalErrorException;
040import ptolemy.kernel.util.NameDuplicationException;
041import ptolemy.kernel.util.NamedObj;
042import ptolemy.kernel.util.Workspace;
043
044///////////////////////////////////////////////////////////////////
045//// Port
046
047/**
048 A Port is the interface of an Entity to any number of Relations.
049 Normally, a Port is contained by an Entity, although a port
050 may exist with no container.  The role of a port is to aggregate
051 a set of links to relations.  Thus, for example, to represent
052 a directed graph, entities can be created with two ports, one for
053 incoming arcs and one for outgoing arcs.  More generally, the arcs
054 to an entity may be divided into any number of subsets, with one port
055 representing each subset.
056 <p>
057
058 A Port can link to any instance of Relation.  Derived classes may wish
059 to constrain links to a subclass of Relation.  To do this, subclasses
060 should override the protected method {@link #_checkLink(Relation)} to
061 throw an exception if its argument is a relation that is not of the
062 appropriate subclass.  Similarly, if a subclass wishes to constrain
063 the containers of the port to be of a subclass of Entity, they should
064 override the protected method {@link #_checkContainer(Entity)}.
065
066 @author Mudit Goel, Edward A. Lee, Jie Liu
067 @version $Id$
068 @since Ptolemy II 0.2
069 @Pt.ProposedRating Green (eal)
070 @Pt.AcceptedRating Green (cxh)
071 @see Entity
072 @see Relation
073 */
074public class Port extends NamedObj {
075    /** Construct a port in the default workspace with an empty string
076     *  as its name.
077     *  The object is added to the workspace directory.
078     *  Increment the version number of the workspace.
079     */
080    public Port() {
081        super();
082        _elementName = "port";
083    }
084
085    /** Construct a port in the specified workspace with an empty
086     *  string as a name. You can then change the name with setName().
087     *  If the workspace argument
088     *  is null, then use the default workspace.
089     *  The object is added to the workspace directory.
090     *  Increment the version number of the workspace.
091     *  @param workspace The workspace that will list the port.
092     */
093    public Port(Workspace workspace) {
094        super(workspace);
095        _elementName = "port";
096    }
097
098    /** Construct a port with the given name contained by the specified
099     *  entity. The container argument must not be null, or a
100     *  NullPointerException will be thrown.  This port will use the
101     *  workspace of the container for synchronization and version counts.
102     *  If the name argument is null, then the name is set to the empty string.
103     *  The object is not added to the workspace directory,
104     *  unless the container is null.
105     *  Increment the version of the workspace.
106     *  @param container The parent entity.
107     *  @param name The name of the Port.
108     *  @exception IllegalActionException If the port is not of an acceptable
109     *   class for the container.
110     *  @exception NameDuplicationException If the name coincides with
111     *   a port already in the container.
112     */
113    public Port(Entity container, String name)
114            throws IllegalActionException, NameDuplicationException {
115        super(container.workspace(), name);
116        _elementName = "port";
117        setContainer(container);
118    }
119
120    ///////////////////////////////////////////////////////////////////
121    ////                         public methods                    ////
122
123    /** Clone the object into the specified workspace. The new object is
124     *  <i>not</i> added to the directory of that workspace (you must do this
125     *  yourself if you want it there).
126     *  The result is a new port with no connections and no container.
127     *  @param workspace The workspace for the cloned object.
128     *  @exception CloneNotSupportedException If one of the attributes
129     *   cannot be cloned.
130     *  @return A new Port.
131     */
132    @Override
133    public Object clone(Workspace workspace) throws CloneNotSupportedException {
134        Port newObject = (Port) super.clone(workspace);
135        newObject._relationsList = new CrossRefList(newObject);
136        newObject._insideLinks = new CrossRefList(newObject);
137        newObject._container = null;
138        return newObject;
139    }
140
141    /** List the connected ports.  Note that a port may be listed
142     *  more than once if more than one connection to it has been established.
143     *  This method is read-synchronized on the workspace.
144     *  @return An unmodifiable list of Port objects.
145     */
146    public List connectedPortList() {
147        try {
148            _workspace.getReadAccess();
149
150            LinkedList result = new LinkedList();
151            Iterator relations = linkedRelationList().iterator();
152
153            while (relations.hasNext()) {
154                Relation relation = (Relation) relations.next();
155
156                // A null link (supported since indexed links) might
157                // yield a null relation here. EAL 7/19/00.
158                if (relation != null) {
159                    result.addAll(relation.linkedPortList(this));
160                }
161            }
162
163            return Collections.unmodifiableList(result);
164        } finally {
165            _workspace.doneReading();
166        }
167    }
168
169    /** Enumerate the connected ports.  Note that a port may be listed
170     *  more than once if more than one connection to it has been established.
171     *  This method is read-synchronized on the workspace.
172     *  @deprecated Use connectedPortList() instead.
173     *  @return An enumeration of Port objects.
174     */
175    @Deprecated
176    public Enumeration connectedPorts() {
177        return Collections.enumeration(connectedPortList());
178    }
179
180    /** Get the container entity.
181     *  @return An instance of Entity.
182     *  @see #setContainer(Entity)
183     */
184    @Override
185    public NamedObj getContainer() {
186        return _container;
187    }
188
189    /** Insert a link to the specified relation at the specified index,
190     *  and notify the container by calling its connectionsChanged() method.
191     *  The relation is required to be at the same level of the hierarchy
192     *  as the entity that contains this port, meaning that the container
193     *  of the relation is the same as the container of the container of
194     *  the port. That is, level-crossing links are not allowed.
195     *  <p>
196     *  The specified index can be any non-negative integer.
197     *  Any links with indices larger than or equal to the one specified
198     *  here will henceforth have indices that are larger by one.
199     *  If the index is larger than the number of existing
200     *  links (as returned by numLinks()), then empty links
201     *  are inserted (these will be null elements in the list returned
202     *  by linkedRelationsList() or in the enumeration returned by
203     *  linkedRelations()). If the specified relation is null, then
204     *  an empty link is inserted at the specified index.
205     *  <p>
206     *  Note that a port may be linked to the same relation more than
207     *  once, in which case the link will be reported more than once
208     *  by the linkedRelations() method.
209     *  <p>
210     *  In derived classes, the relation may be required to be an
211     *  instance of a particular subclass of Relation (this is checked
212     *  by the _checkLink() protected method).
213     *  <p>
214     *  This method is write-synchronized on the workspace and increments
215     *  its version number.
216     *  @param index The index at which to insert the link.
217     *  @param relation The relation to link to this port.
218     *  @exception IllegalActionException If the link would cross levels of
219     *   the hierarchy, or the relation is incompatible,
220     *   or the port has no container, or the port is not in the
221     *   same workspace as the relation, or if the port is contained
222     *   by a class definition.
223     */
224    public void insertLink(int index, Relation relation)
225            throws IllegalActionException {
226        if (_workspace != relation.workspace()) {
227            throw new IllegalActionException(this, relation,
228                    "Cannot link because workspaces are different.");
229        }
230
231        try {
232            _workspace.getWriteAccess();
233            _checkLink(relation);
234            _relationsList.insertLink(index, relation._linkList);
235
236            if (_container != null) {
237                _container.connectionsChanged(this);
238            }
239        } finally {
240            _workspace.doneWriting();
241        }
242    }
243
244    /** Return true if the given relation or one in its relation
245     *  group is linked to this port.
246     *  @param r The relation.
247     *  @return True if the given relation is linked to this port.
248     *  @see #isLinked(Relation)
249     */
250    public boolean isGroupLinked(Relation r) {
251        try {
252            _workspace.getReadAccess();
253
254            Iterator relations = r.relationGroupList().iterator();
255
256            while (relations.hasNext()) {
257                Relation groupRelation = (Relation) relations.next();
258
259                if (_relationsList.isLinked(groupRelation)) {
260                    return true;
261                }
262            }
263
264            return false;
265        } finally {
266            _workspace.doneReading();
267        }
268    }
269
270    /** Return true if the given relation is linked to this port.
271     *  Note that this returns true only if the relation is directly
272     *  linked to the port. There is no support here for relation groups.
273     *  This method is read-synchronized on the workspace.
274     *  @param r The relation.
275     *  @return True if the given relation is linked to this port.
276     *  @see #isGroupLinked(Relation)
277     */
278    public boolean isLinked(Relation r) {
279        try {
280            _workspace.getReadAccess();
281            return _relationsList.isLinked(r);
282        } finally {
283            _workspace.doneReading();
284        }
285    }
286
287    /** Link this port with a relation, and notify the container by
288     *  calling its connectionsChanged() method.  The relation is required
289     *  to be at the same level of the hierarchy as the entity that contains
290     *  this port, meaning that the container of the relation
291     *  is the same as the container of the container of the port.
292     *  That is, level-crossing links are not allowed.
293     *  <p>
294     *  If the argument is null, then create a null link. Note that a port
295     *  may be linked to the same relation more than once, in which case
296     *  the link will be reported more than once by the linkedRelations()
297     *  method. In derived classes, the relation may be required to be an
298     *  instance of a particular subclass of Relation (this is checked
299     *  by the _checkLink() protected method).
300     *  This method is write-synchronized on the workspace and increments
301     *  its version number.
302     *  @param relation The relation to link to this port.
303     *  @exception IllegalActionException If the link would cross levels of
304     *   the hierarchy, or the relation is incompatible,
305     *   or the port has no container, or the port is not in the
306     *   same workspace as the relation, or if the port is contained
307     *   by a class definition.
308     */
309    public void link(Relation relation) throws IllegalActionException {
310        if (relation != null && _workspace != relation.workspace()) {
311            throw new IllegalActionException(this, relation,
312                    "Cannot link because workspaces are different.");
313        }
314
315        try {
316            _workspace.getWriteAccess();
317
318            if (relation != null) {
319                _checkLink(relation);
320                _relationsList.link(relation._linkList);
321            } else {
322                _relationsList.link(null);
323            }
324
325            if (_container != null) {
326                _container.connectionsChanged(this);
327            }
328        } finally {
329            _workspace.doneWriting();
330        }
331    }
332
333    /** List the linked relations.  Note that a relation may appear
334     *  more than once if more than one link to it has been established.
335     *  Also, some entries in the list may be null, indicating a <b>null
336     *  link</b>, where there is no linked relation. A null link causes
337     *  a skip in the link indexes.
338     *  This method is read-synchronized on the workspace.
339     *  @return A list of Relation objects.
340     */
341    public List linkedRelationList() {
342        try {
343            _workspace.getReadAccess();
344
345            // Unfortunately, CrossRefList returns an enumeration only.
346            // Use it to construct a list.
347            // NOTE: This list should be cached.
348            LinkedList result = new LinkedList();
349            Enumeration relations = _relationsList.getContainers();
350
351            while (relations.hasMoreElements()) {
352                result.add(relations.nextElement());
353            }
354
355            return result;
356        } finally {
357            _workspace.doneReading();
358        }
359    }
360
361    /** Enumerate the linked relations.  Note that a relation may appear
362     *  more than once if more than one link to it has been established.
363     *  Also, some entries in the enumeration may be null, indicating a
364     *  <b>null link</b>, where there is no linked relation. A null link
365     *  causes a skip in the link indexes.
366     *  This method is read-synchronized on the workspace.
367     *  @return An enumeration of Relation objects.
368     */
369    public Enumeration linkedRelations() {
370        // NOTE: There is no reason to deprecate this because it does not
371        // depend on Doug Lea's collections, and it is more efficient than
372        // the list version.
373        try {
374            _workspace.getReadAccess();
375            return _relationsList.getContainers();
376        } finally {
377            _workspace.doneReading();
378        }
379    }
380
381    /** Move this object down by one in the list of ports of
382     *  its container. If this object is already last, do nothing.
383     *  Increment the version of the workspace.
384     *  @return The index of the specified object prior to moving it,
385     *   or -1 if it is not moved.
386     *  @exception IllegalActionException If this object has
387     *   no container.
388     */
389    @Override
390    public int moveDown() throws IllegalActionException {
391        Entity container = (Entity) getContainer();
392
393        if (container == null) {
394            throw new IllegalActionException(this, "Has no container.");
395        }
396
397        try {
398            _workspace.getWriteAccess();
399
400            int result = container._portList.moveDown(this);
401
402            // Propagate.
403            Iterator derivedObjects = getDerivedList().iterator();
404
405            while (derivedObjects.hasNext()) {
406                NamedObj derived = (NamedObj) derivedObjects.next();
407                container = (Entity) derived.getContainer();
408                container._portList.moveDown(derived);
409            }
410
411            return result;
412        } finally {
413            _workspace.doneWriting();
414        }
415    }
416
417    /** Move this object to the first position in the list
418     *  of ports of the container. If this object is already first,
419     *  do nothing. Increment the version of the workspace.
420     *  @return The index of the specified object prior to moving it,
421     *   or -1 if it is not moved.
422     *  @exception IllegalActionException If this object has
423     *   no container.
424     */
425    @Override
426    public int moveToFirst() throws IllegalActionException {
427        Entity container = (Entity) getContainer();
428
429        if (container == null) {
430            throw new IllegalActionException(this, "Has no container.");
431        }
432
433        try {
434            _workspace.getWriteAccess();
435
436            int result = container._portList.moveToFirst(this);
437
438            // Propagate.
439            Iterator derivedObjects = getDerivedList().iterator();
440
441            while (derivedObjects.hasNext()) {
442                NamedObj derived = (NamedObj) derivedObjects.next();
443                container = (Entity) derived.getContainer();
444                container._portList.moveToFirst(derived);
445            }
446
447            return result;
448        } finally {
449            _workspace.doneWriting();
450        }
451    }
452
453    /** Move this object to the specified position in the list
454     *  of ports of the container. If this object is already at the
455     *  specified position, do nothing. Increment the version of the
456     *  workspace.
457     *  @param index The position to move this object to.
458     *  @return The index of the specified object prior to moving it,
459     *   or -1 if it is not moved.
460     *  @exception IllegalActionException If this object has
461     *   no container or if the index is out of bounds.
462     */
463    @Override
464    public int moveToIndex(int index) throws IllegalActionException {
465        Entity container = (Entity) getContainer();
466
467        if (container == null) {
468            throw new IllegalActionException(this, "Has no container.");
469        }
470
471        try {
472            _workspace.getWriteAccess();
473
474            int result = container._portList.moveToIndex(this, index);
475
476            // Propagate.
477            Iterator derivedObjects = getDerivedList().iterator();
478
479            while (derivedObjects.hasNext()) {
480                NamedObj derived = (NamedObj) derivedObjects.next();
481                container = (Entity) derived.getContainer();
482                container._portList.moveToIndex(derived, index);
483            }
484
485            return result;
486        } finally {
487            _workspace.doneWriting();
488        }
489    }
490
491    /** Move this object to the last position in the list
492     *  of ports of the container.  If this object is already last,
493     *  do nothing. Increment the version of the workspace.
494     *  @return The index of the specified object prior to moving it,
495     *   or -1 if it is not moved.
496     *  @exception IllegalActionException If this object has
497     *   no container.
498     */
499    @Override
500    public int moveToLast() throws IllegalActionException {
501        Entity container = (Entity) getContainer();
502
503        if (container == null) {
504            throw new IllegalActionException(this, "Has no container.");
505        }
506
507        try {
508            _workspace.getWriteAccess();
509
510            int result = container._portList.moveToLast(this);
511
512            // Propagate.
513            Iterator derivedObjects = getDerivedList().iterator();
514
515            while (derivedObjects.hasNext()) {
516                NamedObj derived = (NamedObj) derivedObjects.next();
517                container = (Entity) derived.getContainer();
518                container._portList.moveToLast(derived);
519            }
520
521            return result;
522        } finally {
523            _workspace.doneWriting();
524        }
525    }
526
527    /** Move this object up by one in the list of
528     *  ports of the container. If this object is already first, do
529     *  nothing. Increment the version of the workspace.
530     *  @return The index of the specified object prior to moving it,
531     *   or -1 if it is not moved.
532     *  @exception IllegalActionException If this object has
533     *   no container.
534     */
535    @Override
536    public int moveUp() throws IllegalActionException {
537        Entity container = (Entity) getContainer();
538
539        if (container == null) {
540            throw new IllegalActionException(this, "Has no container.");
541        }
542
543        try {
544            _workspace.getWriteAccess();
545
546            int result = container._portList.moveUp(this);
547
548            // Propagate.
549            Iterator derivedObjects = getDerivedList().iterator();
550
551            while (derivedObjects.hasNext()) {
552                NamedObj derived = (NamedObj) derivedObjects.next();
553                container = (Entity) derived.getContainer();
554                container._portList.moveUp(derived);
555            }
556
557            return result;
558        } finally {
559            _workspace.doneWriting();
560        }
561    }
562
563    /** Return the number of links to relations.
564     *  This method is read-synchronized on the workspace.
565     *  @return The number of links, a non-negative integer.
566     */
567    public int numLinks() {
568        try {
569            _workspace.getReadAccess();
570            return _relationsList.size();
571        } finally {
572            _workspace.doneReading();
573        }
574    }
575
576    /** Specify the container entity, adding the port to the list of ports
577     *  in the container.  If the container already contains
578     *  a port with the same name, then throw an exception and do not make
579     *  any changes.  Similarly, if the container is not in the same
580     *  workspace as this port, throw an exception. If the port is
581     *  a class element and the proposed container does not match
582     *  the current container, then also throw an exception.
583     *  If the port is already contained by the entity, do nothing.
584     *  If the port already has a container, remove
585     *  this port from its port list first.  Otherwise, remove it from
586     *  the workspace directory, if it is present.
587     *  If the argument is null, then
588     *  unlink the port from any relations and remove it from its container.
589     *  It is not added to the workspace directory, so this could result in
590     *  this port being garbage collected. This method validates all
591     *  deeply contained instances of Settable, since they may no longer
592     *  be valid in the new context.
593     *  This method is write-synchronized on the
594     *  workspace and increments its version number.
595     *  @param entity The container.
596     *  @exception IllegalActionException If this port is not of the
597     *   expected class for the container, or it has no name,
598     *   or the port and container are not in the same workspace, or if
599     *   a contained Settable becomes invalid and the error handler
600     *   throws it, or if this port is a class element and the argument
601     *   does not match the current container.
602     *  @exception NameDuplicationException If the container already has
603     *   a port with the name of this port.
604     *  @see #getContainer()
605     *  @see #_checkContainer(Entity)
606     */
607    public void setContainer(Entity entity)
608            throws IllegalActionException, NameDuplicationException {
609        if (entity != null && _workspace != entity.workspace()) {
610            throw new IllegalActionException(this, entity,
611                    "Cannot set container because workspaces are different.");
612        }
613
614        try {
615            _workspace.getWriteAccess();
616            _checkContainer(entity);
617
618            Entity previousContainer = _container;
619
620            if (previousContainer == entity) {
621                return;
622            }
623
624            _notifyHierarchyListenersBeforeChange();
625
626            try {
627                _container = entity;
628
629                // Do this first, because it may throw an exception.
630                if (entity != null) {
631                    try {
632                        entity._addPort(this);
633                    } catch (IllegalActionException ex) {
634                        _container = previousContainer;
635                        throw ex;
636                    } catch (NameDuplicationException ex) {
637                        _container = previousContainer;
638                        throw ex;
639                    }
640
641                    if (previousContainer == null) {
642                        _workspace.remove(this);
643                    }
644
645                    // We have successfully set a new container for this
646                    // object. Mark it modified to ensure MoML export.
647                    // FIXME: Inappropriate?
648                    // setOverrideDepth(0);
649                }
650
651                if (previousContainer != null) {
652                    previousContainer._removePort(this);
653                }
654
655                if (entity == null) {
656                    unlinkAll();
657                } else {
658                    // Transfer any queued change requests to the
659                    // new container.  There could be queued change
660                    // requests if this component is deferring change
661                    // requests.
662                    if (_changeRequests != null) {
663                        Iterator requests = _changeRequests.iterator();
664
665                        while (requests.hasNext()) {
666                            ChangeRequest request = (ChangeRequest) requests
667                                    .next();
668                            entity.requestChange(request);
669                        }
670
671                        _changeRequests = null;
672                    }
673                }
674
675                // Validate all deeply contained settables, since
676                // they may no longer be valid in the new context.
677                validateSettables();
678            } finally {
679                // Since we definitely notified the listeners
680                // before the change, we must definitely notify
681                // them after the change, even if the change caused
682                // some exceptions. Note that this too may trigger
683                // exceptions.
684                _notifyHierarchyListenersAfterChange();
685            }
686        } finally {
687            _workspace.doneWriting();
688        }
689    }
690
691    /** Set the name of the port. If there is already an port
692     *  of the container entity with the same name, then throw an
693     *  exception.
694     *  @exception IllegalActionException If the name has a period.
695     *  @exception NameDuplicationException If there is already a port
696     *   with the same name in the container.
697     */
698    @Override
699    public void setName(String name)
700            throws IllegalActionException, NameDuplicationException {
701        if (name == null) {
702            name = "";
703        }
704
705        Entity container = (Entity) getContainer();
706
707        if (container != null) {
708            Port another = container.getPort(name);
709
710            if (another != null && another != this) {
711                throw new NameDuplicationException(container,
712                        "Name duplication: " + name);
713            }
714        }
715
716        super.setName(name);
717    }
718
719    /** Unlink whatever relation is currently linked at the specified index
720     *  number. If there is no such relation, do nothing.
721     *  If a link is removed, then any links at higher index numbers
722     *  will have their index numbers decremented by one.
723     *  If there is a container, notify it by calling connectionsChanged().
724     *  This method is write-synchronized on the
725     *  workspace and increments its version number.
726     *  @param index The index number of the link to remove.
727     */
728    public void unlink(int index) {
729        try {
730            _workspace.getWriteAccess();
731            _relationsList.unlink(index);
732
733            if (_container != null) {
734                _container.connectionsChanged(this);
735            }
736        } finally {
737            _workspace.doneWriting();
738        }
739    }
740
741    /** Unlink the specified Relation. If the Relation
742     *  is not linked to this port, do nothing. If the relation is linked
743     *  more than once, then unlink all occurrences.
744     *  If there is a container, notify it by calling connectionsChanged().
745     *  This method is write-synchronized on the
746     *  workspace and increments its version number.
747     *  @param relation The relation to unlink.
748     */
749    public void unlink(Relation relation) {
750        try {
751            _workspace.getWriteAccess();
752            _relationsList.unlink(relation);
753
754            if (_container != null) {
755                _container.connectionsChanged(this);
756            }
757        } finally {
758            _workspace.doneWriting();
759        }
760    }
761
762    /** Unlink all relations.
763     *  If there is a container, notify it by calling connectionsChanged().
764     *  This method is write-synchronized on the
765     *  workspace and increments its version number.
766     */
767    public void unlinkAll() {
768        try {
769            _workspace.getWriteAccess();
770            _relationsList.unlinkAll();
771
772            if (_container != null) {
773                _container.connectionsChanged(this);
774            }
775        } finally {
776            _workspace.doneWriting();
777        }
778    }
779
780    ///////////////////////////////////////////////////////////////////
781    ////                         protected methods                 ////
782
783    /** Check that the specified container is of a suitable class for
784     *  this port.  In this base class, this method returns immediately
785     *  without doing anything.
786     *  @param container The proposed container.
787     *  @exception IllegalActionException If the container is not of
788     *   an acceptable class.  Not thrown in this base class.
789     */
790    protected void _checkContainer(Entity container)
791            throws IllegalActionException {
792    }
793
794    /** Check that this port is compatible with the specified relation,
795     *  that it has a container. If the argument is null, do nothing.
796     *  If this port has no container, throw an exception.
797     *  Derived classes may constrain the argument to be a subclass of
798     *  Relation. Level-crossing links are allowed.
799     *  This port and the relation are assumed to be in the same workspace,
800     *  but this is not checked here.  The caller should check.
801     *  This method is used in a "strategy pattern," where the link
802     *  methods call it to check the validity of a link, and derived
803     *  classes perform more elaborate checks.
804     *  This method is <i>not</i> synchronized on the
805     *  workspace, so the caller should be.
806     *  @param relation The relation to link to.
807     *  @exception IllegalActionException If this port has no container,
808     *   or if this port is not an acceptable port for the specified
809     *   relation.
810     */
811    protected void _checkLink(Relation relation) throws IllegalActionException {
812        if (relation != null) {
813            if (_container == null) {
814                throw new IllegalActionException(this, relation,
815                        "Port must have a container to establish a link.");
816            }
817
818            // Throw an exception if this port is not of an acceptable
819            // class for the relation.
820            relation._checkPort(this);
821        }
822    }
823
824    /** Return a description of the object.  The level of detail depends
825     *  on the argument, which is an or-ing of the static final constants
826     *  defined in the NamedObj class.  Lines are indented according to
827     *  to the level argument using the protected method _getIndentPrefix().
828     *  Zero, one or two brackets can be specified to surround the returned
829     *  description.  If one is specified it is the the leading bracket.
830     *  This is used by derived classes that will append to the description.
831     *  Those derived classes are responsible for the closing bracket.
832     *  An argument other than 0, 1, or 2 is taken to be equivalent to 0.
833     *  This method is read-synchronized on the workspace.
834     *  @param detail The level of detail.
835     *  @param indent The amount of indenting.
836     *  @param bracket The number of surrounding brackets (0, 1, or 2).
837     *  @return A description of the object.
838     *  @exception IllegalActionException If thrown by the parent class.
839     */
840    @Override
841    protected String _description(int detail, int indent, int bracket)
842            throws IllegalActionException {
843        try {
844            _workspace.getReadAccess();
845
846            StringBuffer result = new StringBuffer();
847
848            if (bracket == 1 || bracket == 2) {
849                result.append(super._description(detail, indent, 1));
850            } else {
851                result.append(super._description(detail, indent, 0));
852            }
853
854            if ((detail & LINKS) != 0) {
855                if (result.toString().trim().length() > 0) {
856                    result.append(" ");
857                }
858
859                // To avoid infinite loop, turn off the LINKS flag
860                // when querying the Ports.
861                detail &= ~LINKS;
862                result.append("links {\n");
863
864                Enumeration linkedRelations = linkedRelations();
865
866                while (linkedRelations.hasMoreElements()) {
867                    Relation relation = (Relation) linkedRelations
868                            .nextElement();
869
870                    if (relation != null) {
871                        result.append(
872                                relation._description(detail, indent + 1, 2)
873                                        + "\n");
874                    } else {
875                        // A null link (supported since indexed links) might
876                        // yield a null relation here. EAL 7/19/00.
877                        result.append(_getIndentPrefix(indent + 1) + "null\n");
878                    }
879                }
880
881                result.append(_getIndentPrefix(indent) + "}");
882            }
883
884            if (bracket == 2) {
885                result.append("}");
886            }
887
888            return result.toString();
889        } finally {
890            _workspace.doneReading();
891        }
892    }
893
894    /** Get a port with the specified name in the specified container.
895     *  The returned object is assured of being an
896     *  instance of the same class as this object.
897     *  @param relativeName The name relative to the container.
898     *  @param container The container expected to contain the object, which
899     *   must be an instance of Entity.
900     *  @return An object of the same class as this object, or null if there
901     *   is none.
902     *  @exception IllegalActionException If the object exists
903     *   and has the wrong class, or if the specified container is not
904     *   an instance of CompositeEntity.
905     */
906    @Override
907    protected NamedObj _getContainedObject(NamedObj container,
908            String relativeName) throws IllegalActionException {
909        if (!(container instanceof Entity)) {
910            throw new IllegalActionException(this,
911                    "Expected " + container.getFullName()
912                            + " to be an instance of ptolemy.kernel.Entity,"
913                            + " but it is " + container.getClass().getName());
914        }
915
916        Port candidate = ((Entity) container).getPort(relativeName);
917
918        if (candidate != null && !getClass().isInstance(candidate)) {
919            throw new IllegalActionException(this,
920                    "Expected " + candidate.getFullName()
921                            + " to be an instance of " + getClass().getName()
922                            + ", but it is " + candidate.getClass().getName());
923        }
924
925        return candidate;
926    }
927
928    /** Propagate existence of this object to the
929     *  specified object. This overrides the base class
930     *  to set the container.
931     *  @param container Object to contain the new object.
932     *  @exception IllegalActionException If the object
933     *   cannot be cloned.
934     *  @return A new object of the same class and name
935     *   as this one.
936     */
937    @Override
938    protected NamedObj _propagateExistence(NamedObj container)
939            throws IllegalActionException {
940        try {
941            Port newObject = (Port) super._propagateExistence(container);
942            // FindBugs warns that the cast of container is
943            // unchecked.
944            if (!(container instanceof Entity)) {
945                throw new InternalErrorException(
946                        container + " is not a Entity.");
947            } else {
948                newObject.setContainer((Entity) container);
949            }
950            return newObject;
951        } catch (NameDuplicationException e) {
952            throw new InternalErrorException(e);
953        }
954    }
955
956    ///////////////////////////////////////////////////////////////////
957    ////                         protected variables               ////
958    // NOTE: This is defined here in the base class rather than in
959    // ComponentPort even though it is not used until ComponentPort
960    // so that derived classes can safely create links to ports in
961    // the _addPort() method.  This is a bit of a kludge, but I see
962    // no other way to make this possible. EAL
963
964    /** The list of inside relations for this port. */
965    protected CrossRefList _insideLinks = new CrossRefList(this);
966
967    /** The list of relations for this port. */
968    protected CrossRefList _relationsList = new CrossRefList(this);
969
970    ///////////////////////////////////////////////////////////////////
971    ////                         private variables                 ////
972
973    /** @serial The entity that contains this port. */
974    private Entity _container;
975}