001/* A port supporting clustered graphs.
002
003 Copyright (c) 1997-2018 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.CrossRefList;
037import ptolemy.kernel.util.IllegalActionException;
038import ptolemy.kernel.util.InvalidStateException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.Nameable;
041import ptolemy.kernel.util.NamedObj;
042import ptolemy.kernel.util.Workspace;
043
044///////////////////////////////////////////////////////////////////
045//// ComponentPort
046
047/**
048 A port supporting hierarchy. A component port can have "inside"
049 links as well as the usual "outside" links supported by the base
050 class. That is, while the basic port has only links to relations
051 that are on the exterior of its containing entity, this port can have
052 links to relations on the interior.
053 An inside link is a link to a relation that is contained by the
054 container of the port.
055 <p>
056 A ComponentPort may be transparent or opaque.  If it is transparent,
057 then "deep" accesses of the topology see through the port.
058 Methods that read the topology come in two versions, shallow and deep.
059 The deep versions pass through transparent ports. This is
060 done with a simple rule. If a transparent port is encountered from
061 inside, then the traversal continues with its outside links. If it
062 is encountered from outside, then the traversal continues with its
063 inside links.  A ComponentPort is opaque if its container is opaque.
064 (its isOpaque() method returns true).  Derived classes may use other
065 strategies to specify whether a port is opaque.
066 <p>
067 Normally, links to a transparent port from the outside are to
068 relations contained by the container of the container of the port.
069 Links from the inside are to relations contained by the container
070 of the port.  That is, levels of the hierarchy are not crossed.
071 For a few applications, links that cross levels of the hierarchy
072 are needed. The links in these connections are created
073 using the liberalLink() method. The link() method
074 prohibits such links, throwing an exception if they are attempted
075 (most applications will prohibit level-crossing connections by using
076 only the link() method).
077 <p>
078 A ComponentPort can link to any instance of ComponentRelation.
079 An attempt to link to an instance of Relation will trigger an exception.
080 Derived classes may wish to further constrain links to a subclass
081 of ComponentRelation.  To do this, subclasses should override the
082 protected methods _checkLink() and _checkLiberalLink() to throw an exception
083 if their arguments are relations that are not of the appropriate
084 subclass.  Similarly, a ComponentPort can only be contained by a
085 ComponentEntity, and an attempt to set the container to an instance
086 of Entity will trigger an exception.  If a subclass wishes to
087 constrain the containers of the port to be of a subclass of
088 ComponentEntity, they should override _checkContainer().
089
090 @author Edward A. Lee, Xiaojun Liu
091 @version $Id$
092 @since Ptolemy II 0.2
093 @Pt.ProposedRating Green (eal)
094 @Pt.AcceptedRating Green (bart)
095 */
096public class ComponentPort extends Port {
097    /** Construct a port in the default workspace with an empty string
098     *  as its name. Increment the version number of the workspace.
099     *  The object is added to the workspace directory.
100     */
101    public ComponentPort() {
102        super();
103    }
104
105    /** Construct a port in the specified workspace with an empty
106     *  string as a name. You can then change the name with setName().
107     *  If the workspace argument is null, then use the default workspace.
108     *  The object is added to the workspace directory.
109     *  Increment the version number of the workspace.
110     *  @param workspace The workspace that will list the port.
111     */
112    public ComponentPort(Workspace workspace) {
113        super(workspace);
114    }
115
116    /** Construct a port with the given name contained by the specified
117     *  entity. The container argument must not be null, or a
118     *  NullPointerException will be thrown.  This port will use the
119     *  workspace of the container for synchronization and version counts.
120     *  If the name argument is null, then the name is set to the empty
121     *  string.  Increment the version of the workspace.
122     *  @param container The container entity.
123     *  @param name The name of the port.
124     *  @exception IllegalActionException If the port is not of an acceptable
125     *   class for the container.
126     *  @exception NameDuplicationException If the name coincides with
127     *   a port already in the container.
128     */
129    public ComponentPort(ComponentEntity container, String name)
130            throws IllegalActionException, NameDuplicationException {
131        super(container, name);
132    }
133
134    ///////////////////////////////////////////////////////////////////
135    ////                         public methods                    ////
136
137    /** Clone the object into the specified workspace. The new object is
138     *  <i>not</i> added to the directory of that workspace (you must do this
139     *  yourself if you want it there).
140     *  The result is a new port with no connections and no container.
141     *  @param workspace The workspace for the cloned object.
142     *  @exception CloneNotSupportedException If one or more of the
143     *   attributes cannot be cloned.
144     *  @return A new ComponentPort.
145     */
146    @Override
147    public Object clone(Workspace workspace) throws CloneNotSupportedException {
148        ComponentPort newObject = (ComponentPort) super.clone(workspace);
149        newObject._insideLinks = new CrossRefList(newObject);
150        newObject._deepLinkedInPortsVersion = -1L;
151        newObject._deepLinkedPortsVersion = -1L;
152        newObject._isOpaqueVersion = -1L;
153        return newObject;
154    }
155
156    /** Deeply list the ports connected to this port on the outside.
157     *  Begin by listing the ports that are connected to this port.
158     *  If any of those are transparent ports that we are connected to
159     *  from the inside, then list all the ports deeply connected
160     *  on the outside to that transparent port.  If any are transparent
161     *  ports that we are connected to from the outside, then list
162     *  opaque ports deeply inside that port. Note that a port may
163     *  be listed more than once. This method is read synchronized on the
164     *  workspace.
165     *  @return An unmodifiable list of ComponentPort objects.
166     */
167    public List deepConnectedPortList() {
168        try {
169            _workspace.getReadAccess();
170            return _deepConnectedPortList(null);
171        } finally {
172            _workspace.doneReading();
173        }
174    }
175
176    /** Deeply enumerate the ports connected to this port on the outside.
177     *  Begin by enumerating the ports that are connected to this port.
178     *  If any of those are transparent ports that we are connected to
179     *  from the inside, then enumerate all the ports deeply connected
180     *  on the outside to that transparent port.  Note that a port may
181     *  be listed more than once. This method read synchronized on the
182     *  workspace.
183     *  @return An enumeration of ComponentPort objects.
184     *  @deprecated Use deepConnectedPortList() instead.
185     */
186    @Deprecated
187    public Enumeration deepConnectedPorts() {
188        return Collections.enumeration(deepConnectedPortList());
189    }
190
191    /** Deeply list the ports connected on the inside.
192     *  All ports listed are opaque. Note that
193     *  the returned list could conceivably be empty, for
194     *  example if this port has no inside links.
195     *  Also, a port may be listed more than once if more than one
196     *  inside connection to it has been established.
197     *  @return An unmodifiable list of ComponentPort objects.
198     */
199    public List deepInsidePortList() {
200        try {
201            _workspace.getReadAccess();
202            return _deepInsidePortList(null);
203        } finally {
204            _workspace.doneReading();
205        }
206    }
207
208    /** If this port is transparent, then deeply enumerate the ports
209     *  connected on the inside.  Otherwise, enumerate
210     *  just this port. All ports enumerated are opaque. Note that
211     *  the returned enumeration could conceivably be empty, for
212     *  example if this port is transparent but has no inside links.
213     *  Also, a port may be listed more than once if more than one
214     *  inside connection to it has been established.
215     *  @return An enumeration of ComponentPort objects.
216     *  @deprecated Use deepInsidePortList() instead.
217     */
218    @Deprecated
219    public Enumeration deepInsidePorts() {
220        return Collections.enumeration(deepInsidePortList());
221    }
222
223    /** Insert a link to the specified relation at the specified index,
224     *  and notify the container by calling its connectionsChanged() method.
225     *  This method defaults to adding an inside  null link at the given index
226     *  if the relation argument is null. Otherwise it simply invokes the
227     *  insertLink) method.
228     *  <p>
229     *  The specified index can be any non-negative integer.
230     *  Any links with indices larger than or equal to the one specified
231     *  here will henceforth have indices that are larger by one.
232     *  If the index is larger than the number of existing
233     *  links (as returned by numLinks()), then empty links
234     *  are inserted (these will be null elements in the list returned
235     *  by linkedRelationsList() or in the enumeration returned by
236     *  linkedRelations()). If the specified relation is null, then
237     *  an empty inside link is inserted at the specified index.
238     *  <p>
239     *  Note that a port may be linked to the same relation more than
240     *  once, in which case the link will be reported more than once
241     *  by the linkedRelations() method.
242     *  <p>
243     *  In derived classes, the relation may be required to be an
244     *  instance of a particular subclass of Relation (this is checked
245     *  by the _checkLink() protected method).
246     *  <p>
247     *  This method is write-synchronized on the workspace and increments
248     *  its version number.
249     *  @param index The index at which to insert the link.
250     *  @param relation The relation to link to this port.
251     *  @exception IllegalActionException If the link would cross levels of
252     *   the hierarchy, or the relation is incompatible,
253     *   or the port has no container, or the port is not in the
254     *   same workspace as the relation.
255     */
256    public void insertInsideLink(int index, Relation relation)
257            throws IllegalActionException {
258        if (relation != null) {
259            insertLink(index, relation);
260            return;
261        }
262
263        try {
264            _workspace.getWriteAccess();
265
266            // Assume an inside link
267            _insideLinks.insertLink(index, null);
268
269            // NOTE: _checkLink() ensures that the container is
270            // not null, and the class ensures that it is an Entity.
271            ((Entity) getContainer()).connectionsChanged(this);
272        } finally {
273            _workspace.doneWriting();
274        }
275    }
276
277    /** Insert a link to the specified relation at the specified index,
278     *  and notify the container by calling its connectionsChanged() method.
279     *  This overrides the base class to allow inside links as well as links
280     *  at the same level of the hierarchy.
281     *  <p>
282     *  The specified index can be any non-negative integer.
283     *  Any links with indices larger than or equal to the one specified
284     *  here will henceforth have indices that are larger by one.
285     *  If the index is larger than the number of existing
286     *  links (as returned by numLinks()), then empty links
287     *  are inserted (these will be null elements in the list returned
288     *  by linkedRelationsList() or in the enumeration returned by
289     *  linkedRelations()). If the specified relation is null, then
290     *  an empty outside link is inserted at the specified index.
291     *  <p>
292     *  Note that a port may be linked to the same relation more than
293     *  once, in which case the link will be reported more than once
294     *  by the linkedRelations() method.
295     *  <p>
296     *  In derived classes, the relation may be required to be an
297     *  instance of a particular subclass of Relation (this is checked
298     *  by the _checkLink() protected method).
299     *  <p>
300     *  This method is write-synchronized on the workspace and increments
301     *  its version number.
302     *  @param index The index at which to insert the link.
303     *  @param relation The relation to link to this port.
304     *  @exception IllegalActionException If the link would cross levels of
305     *   the hierarchy, or the relation is incompatible,
306     *   or the port has no container, or the port is not in the
307     *   same workspace as the relation.
308     */
309    @Override
310    public void insertLink(int index, Relation relation)
311            throws IllegalActionException {
312        if (relation != null && _workspace != relation.workspace()) {
313            throw new IllegalActionException(this, relation,
314                    "Cannot link because workspaces are different.");
315        }
316
317        try {
318            _workspace.getWriteAccess();
319
320            if (relation == null) {
321                // Assume outside link
322                _relationsList.insertLink(index, null);
323            } else {
324                _checkLink(relation);
325
326                if (_isInsideLinkable(relation.getContainer())) {
327                    // An inside link
328                    _insideLinks.insertLink(index, relation._linkList);
329                } else {
330                    // An outside link
331                    _relationsList.insertLink(index, relation._linkList);
332                }
333            }
334
335            // NOTE: _checkLink() ensures that the container is
336            // not null, and the class ensures that it is an Entity.
337            ((Entity) getContainer()).connectionsChanged(this);
338        } finally {
339            _workspace.doneWriting();
340        }
341    }
342
343    /** List the ports connected on the inside to this port. Note that
344     *  a port may be listed more than once if more than one inside connection
345     *  has been established to it.
346     *  This method is read-synchronized on the workspace.
347     *  @return A list of ComponentPort objects.
348     */
349    public List insidePortList() {
350        try {
351            _workspace.getReadAccess();
352
353            LinkedList result = new LinkedList();
354            Iterator relations = insideRelationList().iterator();
355
356            while (relations.hasNext()) {
357                Relation relation = (Relation) relations.next();
358
359                // A null link might yield a null relation here.
360                if (relation != null) {
361                    result.addAll(relation.linkedPortList(this));
362                }
363            }
364
365            return result;
366        } finally {
367            _workspace.doneReading();
368        }
369    }
370
371    /** Enumerate the ports connected on the inside to this port. Note that
372     *  a port may be listed more than once if more than one inside connection
373     *  has been established to it.
374     *  This method is read-synchronized on the workspace.
375     *  @return An enumeration of ComponentPort objects.
376     *  @deprecated Use insidePortList() instead.
377     */
378    @Deprecated
379    public Enumeration insidePorts() {
380        return Collections.enumeration(insidePortList());
381    }
382
383    /** List the relations linked on the inside to this port.
384     *  Note that a relation may be listed more than once if more than link
385     *  to it has been established.
386     *  This method is read-synchronized on the workspace.
387     *  @return A list of ComponentRelation objects.
388     */
389    public List insideRelationList() {
390        try {
391            _workspace.getReadAccess();
392
393            // Unfortunately, CrossRefList returns an enumeration only.
394            // Use it to construct a list.
395            LinkedList result = new LinkedList();
396            Enumeration relations = _insideLinks.getContainers();
397
398            while (relations.hasMoreElements()) {
399                result.add(relations.nextElement());
400            }
401
402            return result;
403        } finally {
404            _workspace.doneReading();
405        }
406    }
407
408    /** Enumerate the relations linked on the inside to this port.
409     *  Note that a relation may be listed more than once if more than link
410     *  to it has been established.
411     *  This method is read-synchronized on the workspace.
412     *  @return An enumeration of ComponentRelation objects.
413     */
414    public Enumeration insideRelations() {
415        // NOTE: There is no reason to deprecate this because it does
416        // depend on Doug Lea's collections, and it is more efficient than
417        // the list version.
418        try {
419            _workspace.getReadAccess();
420            return _insideLinks.getContainers();
421        } finally {
422            _workspace.doneReading();
423        }
424    }
425
426    /** Return true the the given port is deeply connected with this port.
427     *  This method is read-synchronized on the workspace.
428     *  @param port The port that is checked for deep connectivity.
429     *  @return True if the given port is deeply connected.
430     */
431    public boolean isDeeplyConnected(ComponentPort port) {
432        if (port == null) {
433            return false;
434        }
435
436        try {
437            _workspace.getReadAccess();
438            return deepConnectedPortList().contains(port);
439        } finally {
440            _workspace.doneReading();
441        }
442    }
443
444    /** Return true if the given relation or one in its relation
445     *  group is linked to this port on the inside.
446     *  @param r The relation.
447     *  @return True if the given relation is linked to this port.
448     *  @see #isLinked(Relation)
449     */
450    public boolean isInsideGroupLinked(Relation r) {
451        try {
452            _workspace.getReadAccess();
453
454            Iterator relations = r.relationGroupList().iterator();
455
456            while (relations.hasNext()) {
457                Relation groupRelation = (Relation) relations.next();
458
459                if (isInsideLinked(groupRelation)) {
460                    return true;
461                }
462            }
463
464            return false;
465        } finally {
466            _workspace.doneReading();
467        }
468    }
469
470    /** Return true if the given relation is linked from inside.
471     *  Note that this returns true only if the relation is directly
472     *  linked to the port. There is no support here for relation groups.
473     *  @param relation The relation that is checked.
474     *  @return True if the given relation is linked from inside.
475     */
476    public boolean isInsideLinked(Relation relation) {
477        return _insideLinks.isLinked(relation);
478    }
479
480    /** Return true if the container entity is opaque.
481     *  @return True if the container entity is opaque.
482     */
483    public boolean isOpaque() {
484        long workspaceVersion = _workspace.getVersion();
485
486        if (_isOpaqueVersion != workspaceVersion) {
487            ComponentEntity entity = (ComponentEntity) getContainer();
488
489            if (entity == null) {
490                _isOpaque = true;
491            } else {
492                _isOpaque = entity.isOpaque();
493            }
494
495            _isOpaqueVersion = workspaceVersion;
496        }
497
498        return _isOpaque;
499    }
500
501    /** Link this port with the specified relation.  The only constraints are
502     *  that the port and the relation share the same workspace, and
503     *  that the relation be of a compatible type (ComponentRelation).
504     *  They are not required to be at the same level of the hierarchy.
505     *  To prohibit links across levels of the hierarchy, use link().
506     *  Note that generally it is a bad idea to allow level-crossing
507     *  links, since it breaks modularity.  This loss of modularity
508     *  means, among other things, that the composite within which this
509     *  port exists cannot be cloned.
510     *  Nonetheless, this capability is provided for the benefit of users
511     *  that feel they just must have it, and who are willing to sacrifice
512     *  clonability and modularity.
513     *  <p>
514     *  Both inside and outside links are supported.  Note that a port may
515     *  be linked to the same relation more than once, in which case
516     *  the link will be reported more than once by the linkedRelations()
517     *  method. If the <i>relation</i> argument is null, then create a
518     *  null link (on the outside).
519     *  This method is write-synchronized on the workspace
520     *  and increments its version number.
521     *  @param relation The relation to link to.
522     *  @exception IllegalActionException If the relation does not share
523     *   the same workspace, or the port has no container.
524     */
525    public void liberalLink(ComponentRelation relation)
526            throws IllegalActionException {
527        if (relation != null) {
528            _checkLiberalLink(relation);
529        }
530
531        _doLink(relation);
532    }
533
534    /** Link this port with the specified relation. Note that a port may
535     *  be linked to the same relation more than once, in which case
536     *  the link will be reported more than once by the linkedRelations()
537     *  method.  If the argument is null, then create a null link (on
538     *  the outside).
539     *  This method is write-synchronized on the workspace
540     *  and increments its version number.
541     *  @param relation The relation to link to.
542     *  @exception IllegalActionException If the link crosses levels of
543     *   the hierarchy, or the port has no container, or the relation
544     *   is not a ComponentRelation, or if the port is contained
545     *   by a class definition.
546     */
547    @Override
548    public void link(Relation relation) throws IllegalActionException {
549        if (relation != null) {
550            _checkLink(relation);
551        }
552
553        _doLink(relation);
554    }
555
556    /** Return the number of inside links.
557     *  This method is read-synchronized on the workspace.
558     *  @return The number of inside links.
559     */
560    public int numInsideLinks() {
561        try {
562            _workspace.getReadAccess();
563            return _insideLinks.size();
564        } finally {
565            _workspace.doneReading();
566        }
567    }
568
569    /** Specify the container entity, adding the port to the list of ports
570     *  in the container.  This class overrides the base class to remove
571     *  all inside links if the given container is null.
572     *  This method is write-synchronized on the
573     *  workspace and increments its version number.
574     *  @param entity The container.
575     *  @exception IllegalActionException If this port is not of the
576     *   expected class for the container, or it has no name,
577     *   or the port and container are not in the same workspace.
578     *  @exception NameDuplicationException If the container already has
579     *   a port with the name of this port.
580     */
581    @Override
582    public void setContainer(Entity entity)
583            throws IllegalActionException, NameDuplicationException {
584        if (entity != null && _workspace != entity.workspace()) {
585            throw new IllegalActionException(this, entity,
586                    "Cannot set container because workspaces are different.");
587        }
588
589        try {
590            _workspace.getWriteAccess();
591            super.setContainer(entity);
592
593            if (entity == null) {
594                unlinkAllInside();
595            }
596        } finally {
597            _workspace.doneWriting();
598        }
599    }
600
601    /** Unlink the specified Relation. If the Relation
602     *  is not linked to this port, do nothing. If the relation is linked
603     *  more than once, then unlink all occurrences.
604     *  If there is a container, notify it by calling connectionsChanged().
605     *  This overrides the base class to check to see whether the link
606     *  is an inside link, based on the container of the relation, and
607     *  to call unlinkInside() if it is.
608     *  This method is write-synchronized on the
609     *  workspace and increments its version number.
610     *  @param relation The relation to unlink.
611     */
612    @Override
613    public void unlink(Relation relation) {
614        if (relation != null && _isInsideLinkable(relation.getContainer())) {
615            // An inside link
616            unlinkInside(relation);
617        } else {
618            super.unlink(relation);
619        }
620    }
621
622    /** Unlink all outside links.
623     *  If there is a container, notify it by calling connectionsChanged().
624     *  This method is write-synchronized on the workspace
625     *  and increments its version number.
626     */
627    @Override
628    public void unlinkAll() {
629        // NOTE: This overrides the base class only to update the docs
630        // to refer to _outside_ links.
631        super.unlinkAll();
632    }
633
634    /** Unlink all inside links.
635     *  If there is a container, notify it by calling connectionsChanged().
636     *  This method is write-synchronized on the workspace
637     *  and increments its version number.
638     */
639    public void unlinkAllInside() {
640        try {
641            _workspace.getWriteAccess();
642            _insideLinks.unlinkAll();
643
644            Entity container = (Entity) getContainer();
645
646            if (container != null) {
647                container.connectionsChanged(this);
648            }
649        } finally {
650            _workspace.doneWriting();
651        }
652    }
653
654    /** Unlink whatever relation is currently linked on the inside
655     *  with the specified index number. If the relation
656     *  is not linked to this port on the inside, do nothing.
657     *  If a link is removed, then any links at higher index numbers
658     *  will have their index numbers decremented by one.
659     *  If there is a container, notify it by calling connectionsChanged().
660     *  This method is write-synchronized on the workspace
661     *  and increments its version number.
662     *  @param index The index number of the link to remove.
663     */
664    public void unlinkInside(int index) {
665        try {
666            _workspace.getWriteAccess();
667            _insideLinks.unlink(index);
668
669            Entity container = (Entity) getContainer();
670
671            if (container != null) {
672                container.connectionsChanged(this);
673            }
674        } finally {
675            _workspace.doneWriting();
676        }
677    }
678
679    /** Unlink the specified relation on the inside. If the relation
680     *  is not linked to this port on the inside, do nothing.
681     *  If the relation is linked more than once on the inside,
682     *  remove all occurrences of the link.
683     *  If there is a container, notify it by calling connectionsChanged().
684     *  This method is write-synchronized on the workspace
685     *  and increments its version number.
686     *  @param relation The relation to unlink.
687     */
688    public void unlinkInside(Relation relation) {
689        try {
690            _workspace.getWriteAccess();
691            _insideLinks.unlink(relation);
692
693            Entity container = (Entity) getContainer();
694
695            if (container != null) {
696                container.connectionsChanged(this);
697            }
698        } finally {
699            _workspace.doneWriting();
700        }
701    }
702
703    ///////////////////////////////////////////////////////////////////
704    ////                         protected methods                 ////
705
706    /** Override the base class to ensure that the proposed container is a
707     *  ComponentEntity.
708     *  @param container The proposed container.
709     *  @exception IllegalActionException If the container is not a
710     *   ComponentEntity.
711     */
712    @Override
713    protected void _checkContainer(Entity container)
714            throws IllegalActionException {
715        if (!(container instanceof ComponentEntity) && container != null) {
716            throw new IllegalActionException(container, this,
717                    "ComponentPort can only be contained by ComponentEntity");
718        }
719    }
720
721    /** Check the validity of a link.
722     *  If the link crosses levels of the hierarchy, then set
723     *  the container persistent to ensure that MoML is exported for
724     *  this link.
725     *  This is used in a "strategy pattern," where the link
726     *  methods call it to check the validity of a link, and derived
727     *  classes perform more elaborate checks.
728     *  @param relation The relation to link to.
729     *  @exception IllegalActionException If this port has no container or
730     *   the relation is not a ComponentRelation, or the relation has
731     *   no container, or the link crosses levels of the hierarchy.
732     */
733    protected void _checkLiberalLink(Relation relation)
734            throws IllegalActionException {
735        if (relation != null) {
736            if (!(relation instanceof ComponentRelation)) {
737                throw new IllegalActionException(this, relation,
738                        "Attempt to link to an incompatible relation "
739                                + "(expected ComponentRelation).");
740            }
741
742            Entity container = (Entity) getContainer();
743
744            if (container == null) {
745                throw new IllegalActionException(this, relation,
746                        "Port must have a container to establish a link.");
747            }
748
749            // Check that the container is not a class or that
750            // if it is, that this is an inside link.
751            if (container.isClassDefinition()
752                    && container != relation.getContainer()) {
753                throw new IllegalActionException(this, relation,
754                        "Cannot establish a link to a port contained "
755                                + "by a class definition");
756            }
757
758            // Throw an exception if this port is not of an acceptable
759            // class for the relation.
760            relation._checkPort(this);
761
762            // Superclass assures that the container is not null.
763            Nameable relationContainer = relation.getContainer();
764
765            if (container != relationContainer
766                    && container.getContainer() != relationContainer) {
767                // Link crosses levels of the hierarchy.
768                // Ensure that an export occurs.
769                // If it's an inside link, then make the container
770                // persistent. Otherwise, make the container's container
771                // persistent.
772                if (container.deepContains(relation)) {
773                    container.setPersistent(true);
774                } else {
775                    NamedObj containersContainer = container.getContainer();
776                    if (containersContainer != null) {
777                        containersContainer.setPersistent(true);
778                    }
779                }
780            }
781        }
782    }
783
784    /** Override the base class to throw an exception if the relation is
785     *  not a ComponentRelation, or if the container of the port or
786     *  relation is null, or if the container of this port is a class
787     *  definition and the link is not an inside link.
788     *  This method used to also throw an exception
789     *  if the link crosses levels of the hierarchy,
790     *  but it no longer does. The Vergil user interface provides
791     *  no mechanism for creating such links, so this error would
792     *  be rather difficult to make. We now assume that the designer
793     *  truly intended to do this, so this method is identical
794     *  to _checkLiberalLink().
795     *  <p>
796     *  This method is used in a "strategy pattern," where the link
797     *  methods call it to check the validity of a link, and derived
798     *  classes perform more elaborate checks.
799     *  This method is <i>not</i> synchronized on the
800     *  workspace, so the caller should be.
801     *  If the relation argument is null, do nothing.
802     *  @param relation The relation to link to.
803     *  @exception IllegalActionException If this port has no container, or
804     *   the relation is not a ComponentRelation, or the relation has
805     *   no container, or the link crosses levels of the hierarchy, or
806     *   this port is not an acceptable port for the specified relation,
807     *   or if the container of this port is a class definition and the
808     *   link is not an inside link.
809     */
810    @Override
811    protected void _checkLink(Relation relation) throws IllegalActionException {
812        _checkLiberalLink(relation);
813        super._checkLink(relation);
814        if (relation != null) {
815            if (!(relation instanceof ComponentRelation)) {
816                throw new IllegalActionException(this, relation,
817                        "Attempt to link to an incompatible relation "
818                                + "(expected ComponentRelation).");
819            }
820            // Check that the container is not a class or that
821            // if it is, that this is an inside link.
822            Entity container = (Entity) getContainer();
823            // Superclass assures that the container is not null.
824            Nameable relationContainer = relation.getContainer();
825            if (container.isClassDefinition()
826                    && container != relationContainer) {
827                throw new IllegalActionException(this, relation,
828                        "Cannot establish a link to a port contained "
829                                + "by a class definition");
830            }
831        }
832
833        /* Removed to support models that have level crossing links in MoML.
834         * EAL 4/21/09.
835        if (relation != null) {
836            Entity container = (Entity) getContainer();
837
838            // Superclass assures that the container is not null.
839            Nameable relationContainer = relation.getContainer();
840
841            if ((container != relationContainer)
842                    && (container.getContainer() != relationContainer)) {
843                throw new IllegalActionException(this, relation,
844                        "Link crosses levels of the hierarchy");
845            }
846        }
847         */
848    }
849
850    /** Deeply list the opaque ports connected to this port on the outside.
851     *  Begin by listing the ports that are connected to this port.
852     *  If any of those are transparent ports that we are connected to
853     *  from the inside, then list all the ports deeply connected
854     *  on the outside to that transparent port.  If any are transparent
855     *  ports that we are connected to from the outside, then list
856     *  opaque ports deeply inside that port. Note that a port may
857     *  be listed more than once. The path argument is the path from
858     *  the port that originally calls this method to this port.
859     *  If this port is already on the list of ports on the path to this
860     *  port in deeply traversing the topology, then there is a loop in
861     *  the topology, and an InvalidStateException is thrown.
862     *  This method not synchronized on the workspace, so the
863     *  caller should.
864     *  @param path The list of ports on the path to this port in deeply
865     *   traversing the topology.
866     *  @return An unmodifiable list of ComponentPort objects.
867     */
868    protected List _deepConnectedPortList(LinkedList path) {
869        if (_deepLinkedPortsVersion == _workspace.getVersion()) {
870            // Cache is valid.  Use it.
871            return _deepLinkedPorts;
872        }
873
874        if (path == null) {
875            path = new LinkedList();
876        } else {
877            if (path.indexOf(this) >= 0) {
878                throw new InvalidStateException(path, "Loop in topology!");
879            }
880        }
881
882        path.add(0, this);
883
884        Iterator nearRelations = linkedRelationList().iterator();
885        LinkedList result = new LinkedList();
886
887        while (nearRelations.hasNext()) {
888            ComponentRelation relation = (ComponentRelation) nearRelations
889                    .next();
890
891            // A null link (supported since indexed links) might
892            // yield a null relation here. EAL 7/19/00.
893            if (relation != null) {
894                Iterator connectedPorts = relation.linkedPortList(this)
895                        .iterator();
896
897                while (connectedPorts.hasNext()) {
898                    ComponentPort port = (ComponentPort) connectedPorts.next();
899
900                    // NOTE: If level-crossing transitions are not allowed,
901                    // then a simpler test than that of the following
902                    // would work.
903                    if (port._isInsideLinkable(relation.getContainer())) {
904                        // We are coming at the port from the inside.
905                        if (port.isOpaque()) {
906                            result.add(port);
907                        } else {
908                            // Port is transparent
909                            result.addAll(port._deepConnectedPortList(path));
910                        }
911                    } else {
912                        // We are coming at the port from the outside.
913                        if (port.isOpaque()) {
914                            result.add(port);
915                        } else {
916                            // It is transparent.
917                            result.addAll(port._deepInsidePortList(path));
918                        }
919                    }
920                }
921            }
922        }
923
924        _deepLinkedPorts = Collections.unmodifiableList(result);
925        _deepLinkedPortsVersion = _workspace.getVersion();
926        path.remove(0);
927        return _deepLinkedPorts;
928    }
929
930    /** Deeply enumerate the ports connected to this port on the outside.
931     *  Begin by enumerating the ports that are connected to this port.
932     *  If any of those are transparent ports that we are connected to
933     *  from the inside, then list all the ports deeply connected
934     *  on the outside to that transparent port.  Note that a port may
935     *  be enumerated more than once. The path argument is the path from
936     *  the port that originally calls this method to this port.
937     *  If this port is already on the list of ports on the path to this
938     *  port in deeply traversing the topology, then there is a loop in
939     *  the topology, and an InvalidStateException is thrown.
940     *  This method not synchronized on the workspace, so the
941     *  caller should.
942     *  @param path The list of ports on the path to this port in deeply
943     *   traversing the topology.
944     *  @deprecated Use _deepConnectedPortList() instead.
945     *  @return An enumeration of ComponentPort objects.
946     */
947    @Deprecated
948    protected Enumeration _deepConnectedPorts(LinkedList path) {
949        return Collections.enumeration(_deepConnectedPortList(path));
950    }
951
952    /** If this port is transparent, then deeply list the ports
953     *  connected on the inside.  Otherwise, list
954     *  just this port. All ports listed are opaque. Note that
955     *  the returned list could conceivably be empty, for
956     *  example if this port is transparent but has no inside links.
957     *  Also, a port may be listed more than once if more than one
958     *  inside connection to it has been established.
959     *  The path argument is the path from
960     *  the port that originally calls this method to this port.
961     *  If this port is already on the list of ports on the path to this
962     *  port in deeply traversing the topology, then there is a loop in
963     *  the topology, and an InvalidStateException is thrown.
964     *  This method is read-synchronized on the workspace.
965     *  @param path The list of ports on the path to this port in deeply
966     *   traversing the topology.
967     *  @return An unmodifiable list of ComponentPort objects.
968     */
969    protected List _deepInsidePortList(LinkedList path) {
970        if (_deepLinkedInPortsVersion == _workspace.getVersion()) {
971            // Cache is valid.  Use it.
972            return _deepLinkedInPorts;
973        }
974
975        if (path == null) {
976            path = new LinkedList();
977        } else {
978            if (path.indexOf(this) >= 0) {
979                throw new InvalidStateException(path, "Loop in topology!");
980            }
981        }
982
983        path.add(0, this);
984
985        LinkedList result = new LinkedList();
986
987        // Port is transparent.
988        Iterator relations = insideRelationList().iterator();
989
990        while (relations.hasNext()) {
991            Relation relation = (Relation) relations.next();
992
993            // A null link might yield a null relation here.
994            if (relation != null) {
995                Iterator insidePorts = relation.linkedPortList(this).iterator();
996
997                while (insidePorts.hasNext()) {
998                    ComponentPort port = (ComponentPort) insidePorts.next();
999
1000                    // The inside port may not be actually inside,
1001                    // in which case we want to look through it
1002                    // from the inside (this supports transparent
1003                    // entities).
1004                    if (port._isInsideLinkable(relation.getContainer())) {
1005                        // The inside port is not truly inside.
1006                        // Check to see whether it is transparent.
1007                        if (port.isOpaque()) {
1008                            result.add(port);
1009                        } else {
1010                            result.addAll(port._deepConnectedPortList(path));
1011                        }
1012                    } else {
1013                        // We are coming at the port from the outside.
1014                        if (port.isOpaque()) {
1015                            // The inside port is truly inside.
1016                            result.add(port);
1017                        } else {
1018                            result.addAll(port._deepInsidePortList(path));
1019                        }
1020                    }
1021                }
1022            }
1023        }
1024
1025        _deepLinkedInPorts = Collections.unmodifiableList(result);
1026        _deepLinkedInPortsVersion = _workspace.getVersion();
1027        path.remove(0);
1028        return _deepLinkedInPorts;
1029    }
1030
1031    /** If this port is transparent, then deeply enumerate the ports
1032     *  connected on the inside.  Otherwise, enumerate
1033     *  just this port. All ports enumerated are opaque. Note that
1034     *  the returned enumeration could conceivably be empty, for
1035     *  example if this port is transparent but has no inside links.
1036     *  Also, a port may be listed more than once if more than one
1037     *  inside connection to it has been established.
1038     *  The path argument is the path from
1039     *  the port that originally calls this method to this port.
1040     *  If this port is already on the list of ports on the path to this
1041     *  port in deeply traversing the topology, then there is a loop in
1042     *  the topology, and an InvalidStateException is thrown.
1043     *  This method is read-synchronized on the workspace.
1044     *  @param path The list of ports on the path to this port in deeply
1045     *   traversing the topology.
1046     *  @return An enumeration of ComponentPort objects.
1047     *  @deprecated Use _deepInsidePortList() instead.
1048     */
1049    @Deprecated
1050    protected Enumeration _deepInsidePorts(LinkedList path) {
1051        return Collections.enumeration(_deepInsidePortList(path));
1052    }
1053
1054    /** Return a description of the object.  The level of detail depends
1055     *  on the argument, which is an or-ing of the static final constants
1056     *  defined in the NamedObj class.  Lines are indented according to
1057     *  to the level argument using the protected method _getIndentPrefix().
1058     *  Zero, one or two brackets can be specified to surround the returned
1059     *  description.  If one is specified it is the the leading bracket.
1060     *  This is used by derived classes that will append to the description.
1061     *  Those derived classes are responsible for the closing bracket.
1062     *  An argument other than 0, 1, or 2 is taken to be equivalent to 0.
1063     *  This method is read-synchronized on the workspace.
1064     *  @param detail The level of detail.
1065     *  @param indent The amount of indenting.
1066     *  @param bracket The number of surrounding brackets (0, 1, or 2).
1067     *  @return A description of the object.
1068     *  @exception IllegalActionException If thrown by the parent class.
1069     */
1070    @Override
1071    protected String _description(int detail, int indent, int bracket)
1072            throws IllegalActionException {
1073        try {
1074            _workspace.getReadAccess();
1075
1076            StringBuffer result = new StringBuffer();
1077
1078            if (bracket == 1 || bracket == 2) {
1079                result.append(super._description(detail, indent, 1));
1080            } else {
1081                result.append(super._description(detail, indent, 0));
1082            }
1083
1084            if ((detail & LINKS) != 0) {
1085                if (result.toString().trim().length() > 0) {
1086                    result.append(" ");
1087                }
1088
1089                // To avoid infinite loop, turn off the LINKS flag
1090                // when querying the Ports.
1091                detail &= ~LINKS;
1092                result.append("insidelinks {\n");
1093
1094                Iterator insideRelations = insideRelationList().iterator();
1095
1096                while (insideRelations.hasNext()) {
1097                    Relation relation = (Relation) insideRelations.next();
1098
1099                    if (relation != null) {
1100                        result.append(
1101                                relation._description(detail, indent + 1, 2)
1102                                        + "\n");
1103                    } else {
1104                        result.append(_getIndentPrefix(indent + 1) + "null\n");
1105                    }
1106                }
1107
1108                result.append(_getIndentPrefix(indent) + "}");
1109            }
1110
1111            if (bracket == 2) {
1112                result.append("}");
1113            }
1114
1115            return result.toString();
1116        } finally {
1117            _workspace.doneReading();
1118        }
1119    }
1120
1121    /** Return true if this port is either a port of the specified entity,
1122     *  or a port of an entity that (deeply) contains the specified entity.
1123     *  This method is read-synchronized on the workspace.
1124     *  @param entity A possible container.
1125     *  @return True if this port is outside the entity.
1126     */
1127    protected boolean _isInsideLinkable(Nameable entity) {
1128        try {
1129            _workspace.getReadAccess();
1130
1131            Nameable portContainer = getContainer();
1132
1133            while (entity != null) {
1134                if (portContainer == entity) {
1135                    return true;
1136                }
1137
1138                entity = entity.getContainer();
1139            }
1140
1141            return false;
1142        } finally {
1143            _workspace.doneReading();
1144        }
1145    }
1146
1147    ///////////////////////////////////////////////////////////////////
1148    ////                         private methods                   ////
1149
1150    /** Create the link.  This method does not do validity checks.
1151     *  @param relation The relation to link to.
1152     */
1153    private void _doLink(Relation relation) throws IllegalActionException {
1154        if (relation != null && _workspace != relation.workspace()) {
1155            throw new IllegalActionException(this, relation,
1156                    "Cannot link because workspaces are different.");
1157        }
1158
1159        try {
1160            _workspace.getWriteAccess();
1161
1162            if (relation == null) {
1163                // Create a null link.
1164                _relationsList.link(null);
1165            } else {
1166                if (_isInsideLinkable(relation.getContainer())) {
1167                    // An inside link
1168                    _insideLinks.link(relation._linkList);
1169                } else {
1170                    // An outside link
1171                    _relationsList.link(relation._linkList);
1172                }
1173            }
1174
1175            // NOTE: _checkLink() and _checkLiberalLink()
1176            // ensure that the container is
1177            // not null, and the class ensures that it is an Entity.
1178            ((Entity) getContainer()).connectionsChanged(this);
1179        } finally {
1180            _workspace.doneWriting();
1181        }
1182    }
1183
1184    ///////////////////////////////////////////////////////////////////
1185    ////                         private variables                 ////
1186    // A cache of the deeply linked ports, and the version used to
1187    // construct it.
1188    // 'transient' means that the variable will not be serialized.
1189    private transient List _deepLinkedPorts;
1190
1191    private transient long _deepLinkedPortsVersion = -1;
1192
1193    private transient List _deepLinkedInPorts;
1194
1195    private transient long _deepLinkedInPortsVersion = -1;
1196
1197    // A cache of the opaqueness of this port.
1198    private transient boolean _isOpaque;
1199
1200    private transient long _isOpaqueVersion = -1;
1201}