001/* A relation supporting clustered graphs.
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.IllegalActionException;
038import ptolemy.kernel.util.InternalErrorException;
039import ptolemy.kernel.util.NameDuplicationException;
040import ptolemy.kernel.util.NamedObj;
041import ptolemy.kernel.util.Workspace;
042
043///////////////////////////////////////////////////////////////////
044//// ComponentRelation
045
046/**
047 This class defines a relation supporting hierarchy (clustered graphs).
048 Specifically, a method is added for defining a container and for
049 performing deep traversals of
050 a graph. Most importantly, however, instances of this class refuse to link
051 to ports that are not instances of ComponentPort.  Thus, this class
052 ensures that ComponentPort instances are only connected to other
053 ComponentPort instances.
054 <p>
055 Derived classes may wish to further constrain linked ports to a subclass
056 of ComponentPort, or to disallow links under other circumstances,
057 for example if the relation cannot support any more links.
058 Such derived classes should override the protected method _checkPort()
059 to throw an exception.
060 <p>
061 To link a ComponentPort to a ComponentRelation, use the link() or
062 liberalLink() method in the ComponentPort class.  To remove a link,
063 use the unlink() method.
064 <p>
065 The container for instances of this class can only be instances of
066 ComponentEntity.  Derived classes may wish to further constrain the
067 container to subclasses of ComponentEntity.  To do this, they should
068 override the protected _checkContainer() method.
069
070 @author Edward A. Lee
071 @version $Id$
072 @since Ptolemy II 0.2
073 @Pt.ProposedRating Green (eal)
074 @Pt.AcceptedRating Green (johnr)
075 */
076public class ComponentRelation extends Relation {
077    /** Construct a relation in the default workspace with an empty string
078     *  as its name. Add the relation to the directory of the workspace.
079     */
080    public ComponentRelation() {
081        super();
082    }
083
084    /** Construct a relation in the specified workspace with an empty
085     *  string as a name. You can then change the name with setName().
086     *  If the workspace argument is null, then use the default workspace.
087     *  Add the relation to the workspace directory.
088     *
089     *  @param workspace The workspace that will list the relation.
090     */
091    public ComponentRelation(Workspace workspace) {
092        super(workspace);
093    }
094
095    /** Construct a relation with the given name contained by the specified
096     *  entity. The container argument must not be null, or a
097     *  NullPointerException will be thrown.  This relation will use the
098     *  workspace of the container for synchronization and version counts.
099     *  If the name argument is null, then the name is set to the empty string.
100     *  This constructor write-synchronizes on the workspace.
101     *
102     *  @param container The container.
103     *  @param name The name of the relation.
104     *  @exception IllegalActionException If the container is incompatible
105     *   with this relation.
106     *  @exception NameDuplicationException If the name coincides with
107     *   a relation already in the container.
108     */
109    public ComponentRelation(CompositeEntity container, String name)
110            throws IllegalActionException, NameDuplicationException {
111        super(container.workspace(), name);
112        setContainer(container);
113    }
114
115    ///////////////////////////////////////////////////////////////////
116    ////                         public methods                    ////
117
118    /** Clone the object into the specified workspace. The new object is
119     *  <i>not</i> added to the directory of that workspace (you must do this
120     *  yourself if you want it there).
121     *  The result is a new relation with no links and no container.
122     *  @param workspace The workspace for the cloned object.
123     *  @exception CloneNotSupportedException If one or more of the attributes
124     *   cannot be cloned.
125     *  @return A new ComponentRelation.
126     */
127    @Override
128    public Object clone(Workspace workspace) throws CloneNotSupportedException {
129        ComponentRelation newObject = (ComponentRelation) super.clone(
130                workspace);
131        newObject._container = null;
132        return newObject;
133    }
134
135    /** Deeply list the ports linked to this relation. Look through
136     *  all transparent ports and return only opaque ports.
137     *  This method is read-synchronized on the workspace.
138     *  @return An unmodifiable list of ComponentPorts.
139     */
140    public List deepLinkedPortList() {
141        try {
142            _workspace.getReadAccess();
143
144            if (_deepLinkedPortsVersion == _workspace.getVersion()) {
145                // Cache is valid.  Use it.
146                return _deepLinkedPorts;
147            }
148
149            Iterator nearPorts = linkedPortList().iterator();
150            _deepLinkedPorts = new LinkedList();
151
152            while (nearPorts.hasNext()) {
153                ComponentPort port = (ComponentPort) nearPorts.next();
154
155                if (port._isInsideLinkable(this.getContainer())) {
156                    // Port is above me in the hierarchy.
157                    if (port.isOpaque()) {
158                        // Port is opaque.  Append it to list.
159                        _deepLinkedPorts.add(port);
160                    } else {
161                        // Port is transparent.  See through it.
162                        _deepLinkedPorts.addAll(port.deepConnectedPortList());
163                    }
164                } else {
165                    // Port below me in the hierarchy.
166                    if (port.isOpaque()) {
167                        _deepLinkedPorts.add(port);
168                    } else {
169                        _deepLinkedPorts.addAll(port.deepInsidePortList());
170                    }
171                }
172            }
173
174            _deepLinkedPortsVersion = _workspace.getVersion();
175            return Collections.unmodifiableList(_deepLinkedPorts);
176        } finally {
177            _workspace.doneReading();
178        }
179    }
180
181    /** Deeply enumerate the ports linked to this relation. Look through
182     *  all transparent ports and return only opaque ports.
183     *  This method is read-synchronized on the workspace.
184     *  @return An enumeration of ComponentPorts.
185     *  @deprecated Use deepLinkedPortList() instead.
186     */
187    @Deprecated
188    public Enumeration deepLinkedPorts() {
189        return Collections.enumeration(deepLinkedPortList());
190    }
191
192    /** Get the container entity.
193     *  @return An instance of CompositeEntity.
194     *  @see #setContainer(CompositeEntity)
195     */
196    @Override
197    public NamedObj getContainer() {
198        return _container;
199    }
200
201    /** Move this object down by one in the list of relations of
202     *  its container. If this object is already last, do nothing.
203     *  Increment the version of the workspace.
204     *  @return The index of the specified object prior to moving it,
205     *   or -1 if it is not moved.
206     *  @exception IllegalActionException If this object has
207     *   no container.
208     */
209    @Override
210    public int moveDown() throws IllegalActionException {
211        CompositeEntity container = (CompositeEntity) getContainer();
212
213        if (container == null) {
214            throw new IllegalActionException(this, "Has no container.");
215        }
216
217        try {
218            _workspace.getWriteAccess();
219
220            int result = container._containedRelations.moveDown(this);
221
222            // Propagate.
223            Iterator derivedObjects = getDerivedList().iterator();
224
225            while (derivedObjects.hasNext()) {
226                NamedObj derived = (NamedObj) derivedObjects.next();
227                container = (CompositeEntity) derived.getContainer();
228                container._containedRelations.moveDown(derived);
229            }
230
231            return result;
232        } finally {
233            _workspace.doneWriting();
234        }
235    }
236
237    /** Move this object to the first position in the list
238     *  of relations of the container. If this object is already first,
239     *  do nothing. Increment the version of the workspace.
240     *  @return The index of the specified object prior to moving it,
241     *   or -1 if it is not moved.
242     *  @exception IllegalActionException If this object has
243     *   no container.
244     */
245    @Override
246    public int moveToFirst() throws IllegalActionException {
247        CompositeEntity container = (CompositeEntity) getContainer();
248
249        if (container == null) {
250            throw new IllegalActionException(this, "Has no container.");
251        }
252
253        try {
254            _workspace.getWriteAccess();
255
256            int result = container._containedRelations.moveToFirst(this);
257
258            // Propagate.
259            Iterator derivedObjects = getDerivedList().iterator();
260
261            while (derivedObjects.hasNext()) {
262                NamedObj derived = (NamedObj) derivedObjects.next();
263                container = (CompositeEntity) derived.getContainer();
264                container._containedRelations.moveToFirst(derived);
265            }
266
267            return result;
268        } finally {
269            _workspace.doneWriting();
270        }
271    }
272
273    /** Move this object to the specified position in the list
274     *  of relations of the container. If  this object is already at
275     *  the specified position, do nothing. Increment the version of the
276     *  workspace.
277     *  @param index The position to move this object to.
278     *  @return The index of the specified object prior to moving it,
279     *   or -1 if it is not moved.
280     *  @exception IllegalActionException If this object has
281     *   no container or if the index is out of bounds.
282     */
283    @Override
284    public int moveToIndex(int index) throws IllegalActionException {
285        CompositeEntity container = (CompositeEntity) getContainer();
286
287        if (container == null) {
288            throw new IllegalActionException(this, "Has no container.");
289        }
290
291        try {
292            _workspace.getWriteAccess();
293
294            int result = container._containedRelations.moveToIndex(this, index);
295
296            // Propagate.
297            Iterator derivedObjects = getDerivedList().iterator();
298
299            while (derivedObjects.hasNext()) {
300                NamedObj derived = (NamedObj) derivedObjects.next();
301                container = (CompositeEntity) derived.getContainer();
302                container._containedRelations.moveToIndex(derived, index);
303            }
304
305            return result;
306        } finally {
307            _workspace.doneWriting();
308        }
309    }
310
311    /** Move this object to the last position in the list
312     *  of relations of the container.  If this object is already last,
313     *  do nothing. Increment the version of the workspace.
314     *  @return The index of the specified object prior to moving it,
315     *   or -1 if it is not moved.
316     *  @exception IllegalActionException If this object has
317     *   no container.
318     */
319    @Override
320    public int moveToLast() throws IllegalActionException {
321        CompositeEntity container = (CompositeEntity) getContainer();
322
323        if (container == null) {
324            throw new IllegalActionException(this, "Has no container.");
325        }
326
327        try {
328            _workspace.getWriteAccess();
329
330            int result = container._containedRelations.moveToLast(this);
331
332            // Propagate.
333            Iterator derivedObjects = getDerivedList().iterator();
334
335            while (derivedObjects.hasNext()) {
336                NamedObj derived = (NamedObj) derivedObjects.next();
337                container = (CompositeEntity) derived.getContainer();
338                container._containedRelations.moveToLast(derived);
339            }
340
341            return result;
342        } finally {
343            _workspace.doneWriting();
344        }
345    }
346
347    /** Move this object up by one in the list of
348     *  relations of the container. If this object is already first, do
349     *  nothing. Increment the version of the workspace.
350     *  @return The index of the specified object prior to moving it,
351     *   or -1 if it is not moved.
352     *  @exception IllegalActionException If this object has
353     *   no container.
354     */
355    @Override
356    public int moveUp() throws IllegalActionException {
357        CompositeEntity container = (CompositeEntity) getContainer();
358
359        if (container == null) {
360            throw new IllegalActionException(this, "Has no container.");
361        }
362
363        try {
364            _workspace.getWriteAccess();
365
366            int result = container._containedRelations.moveUp(this);
367
368            // Propagate.
369            Iterator derivedObjects = getDerivedList().iterator();
370
371            while (derivedObjects.hasNext()) {
372                NamedObj derived = (NamedObj) derivedObjects.next();
373                container = (CompositeEntity) derived.getContainer();
374                container._containedRelations.moveUp(derived);
375            }
376
377            return result;
378        } finally {
379            _workspace.doneWriting();
380        }
381    }
382
383    /** Specify the container entity, adding the relation to the list
384     *  of relations in the container.  If the container already contains
385     *  a relation with the same name, then throw an exception and do not make
386     *  any changes.  Similarly, if the container is not in the same
387     *  workspace as this relation, throw an exception. If the relation is
388     *  a class element and the proposed container does not match
389     *  the current container, then also throw an exception.
390     *  If the relation is already contained by the container, do nothing.
391     *  If this relation already has a container, remove it
392     *  from that container first.  Otherwise, remove it from
393     *  the workspace directory, if it is present.
394     *  If the argument is null, then unlink the ports from the relation and
395     *  remove it from its container.
396     *  It is not added to the workspace directory, so this could result in
397     *  this relation being garbage collected.
398     *  Derived classes may further constrain the class of the container
399     *  to a subclass of CompositeEntity. This method validates all
400     *  deeply contained instances of Settable, since they may no longer
401     *  be valid in the new context. This method is write-synchronized
402     *  on the workspace and increments its version number.
403     *  @param container The proposed container.
404     *  @exception IllegalActionException If this entity and the container
405     *   are not in the same workspace, or if
406     *   a contained Settable becomes invalid and the error handler
407     *   throws it.
408     *  @exception NameDuplicationException If the name collides with a name
409     *   already on the contents list of the container.
410     *  @see #getContainer()
411     */
412    public void setContainer(CompositeEntity container)
413            throws IllegalActionException, NameDuplicationException {
414        if (container != null && _workspace != container.workspace()) {
415            throw new IllegalActionException(this, container,
416                    "Cannot set container because workspaces are different.");
417        }
418
419        try {
420            _workspace.getWriteAccess();
421            _checkContainer(container);
422
423            CompositeEntity previousContainer = (CompositeEntity) getContainer();
424
425            if (previousContainer == container) {
426                return;
427            }
428
429            // Do this first, because it may throw an exception.
430            if (container != null) {
431                container._addRelation(this);
432
433                if (previousContainer == null) {
434                    _workspace.remove(this);
435                }
436            }
437
438            _notifyHierarchyListenersBeforeChange();
439
440            try {
441                _container = container;
442
443                if (previousContainer != null) {
444                    previousContainer._removeRelation(this);
445                }
446
447                if (container == null) {
448                    unlinkAll();
449                } else {
450                    // We have successfully set a new container for this
451                    // object. Mark it modified to ensure MoML export.
452                    // Transfer any queued change requests to the
453                    // new container.  There could be queued change
454                    // requests if this component is deferring change
455                    // requests.
456                    if (_changeRequests != null) {
457                        Iterator requests = _changeRequests.iterator();
458
459                        while (requests.hasNext()) {
460                            ChangeRequest request = (ChangeRequest) requests
461                                    .next();
462                            container.requestChange(request);
463                        }
464
465                        _changeRequests = null;
466                    }
467                }
468
469                // Validate all deeply contained settables, since
470                // they may no longer be valid in the new context.
471                validateSettables();
472            } finally {
473                // Since we definitely notified the listeners
474                // before the change, we must definitely notify
475                // them after the change, even if the change caused
476                // some exceptions. Note that this too may trigger
477                // exceptions.
478                _notifyHierarchyListenersAfterChange();
479            }
480        } finally {
481            _workspace.doneWriting();
482        }
483    }
484
485    /** Set the name of the ComponentRelation. If there is already
486     *  a ComponentRelation of the container with the same name, throw an
487     *  exception.
488     *  @exception IllegalActionException If the name has a period.
489     *  @exception NameDuplicationException If there is already a relation
490     *   with the same name in the container.
491     */
492    @Override
493    public void setName(String name)
494            throws IllegalActionException, NameDuplicationException {
495        if (name == null) {
496            name = "";
497        }
498
499        CompositeEntity container = (CompositeEntity) getContainer();
500
501        if (container != null) {
502            ComponentRelation another = container.getRelation(name);
503
504            if (another != null && another != this) {
505                throw new NameDuplicationException(container,
506                        "Name duplication: " + name);
507            }
508        }
509
510        super.setName(name);
511    }
512
513    /** Override the base class to break inside links on ports as well
514     *  as outside lists.
515     *  This method is write-synchronized on the workspace and increments
516     *  its version number.
517     */
518    @Override
519    public void unlinkAll() {
520        // NOTE: Do not just use _portList.unlinkAll() because then the
521        // containers of the ports are not notified of the change.
522        // Also, have to first copy the ports references, then remove
523        // them, to avoid a corrupted enumeration exception.
524        // Unlink the outside links of linked ports.
525        super.unlinkAll();
526        try {
527
528            // Next, remove the links that are inside links of ports.
529            _workspace.getWriteAccess();
530
531            LinkedList ports = new LinkedList();
532            Enumeration links = _linkList.getContainers();
533
534            while (links.hasMoreElements()) {
535                Object link = links.nextElement();
536
537                if (link instanceof ComponentPort) {
538                    ports.add(link);
539                }
540            }
541
542            Iterator portsIterator = ports.iterator();
543
544            while (portsIterator.hasNext()) {
545                ((ComponentPort) portsIterator.next()).unlinkInside(this);
546            }
547        } finally {
548            _workspace.doneWriting();
549        }
550    }
551
552    ///////////////////////////////////////////////////////////////////
553    ////                         protected methods                 ////
554
555    /** Check that the specified container is of a suitable class for
556     *  this relation.  In this base class, this method returns immediately
557     *  without doing anything.
558     *  @param container The proposed container.
559     *  @exception IllegalActionException If the container is not of
560     *   an acceptable class.  Not thrown in this base class.
561     */
562    protected void _checkContainer(CompositeEntity container)
563            throws IllegalActionException {
564    }
565
566    /** Throw an exception if the specified port cannot be linked to this
567     *  relation (is not of class ComponentPort).
568     *  @param port The port to link to.
569     *  @exception IllegalActionException If the port is not a ComponentPort.
570     */
571    @Override
572    protected void _checkPort(Port port) throws IllegalActionException {
573        if (!(port instanceof ComponentPort)) {
574            throw new IllegalActionException(this, port,
575                    "ComponentRelation can only link to a ComponentPort.");
576        }
577    }
578
579    /** Throw an exception if the specified relation is not an instance
580     *  of ComponentRelation.
581     *  @param relation The relation to link to.
582     *  @param symmetric If true, the call _checkRelation on the specified
583     *   relation with this as an argument.
584     *  @exception IllegalActionException If this port has no container,
585     *   or if this port is not an acceptable port for the specified
586     *   relation.
587     */
588    @Override
589    protected void _checkRelation(Relation relation, boolean symmetric)
590            throws IllegalActionException {
591        if (!(relation instanceof ComponentRelation)) {
592            throw new IllegalActionException(this, relation,
593                    "ComponentRelation can only link to a ComponentRelation.");
594        }
595
596        super._checkRelation(relation, symmetric);
597    }
598
599    /** Propagate existence of this object to the
600     *  specified object. This overrides the base class
601     *  to set the container.
602     *  @param container Object to contain the new object.
603     *  @exception IllegalActionException If the object
604     *   cannot be cloned.
605     *  @return A new object of the same class and name
606     *   as this one.
607     */
608    @Override
609    protected NamedObj _propagateExistence(NamedObj container)
610            throws IllegalActionException {
611        try {
612            ComponentRelation newObject = (ComponentRelation) super._propagateExistence(
613                    container);
614            // FindBugs warns that the cast of container is
615            // unchecked.
616            if (!(container instanceof CompositeEntity)) {
617                throw new InternalErrorException(
618                        container + " is not a CompositeEntity.");
619            } else {
620                newObject.setContainer((CompositeEntity) container);
621            }
622            return newObject;
623        } catch (NameDuplicationException e) {
624            throw new InternalErrorException(e);
625        }
626    }
627
628    ///////////////////////////////////////////////////////////////////
629    ////                         private variables                 ////
630
631    /** @serial The entity that contains this entity. */
632    private CompositeEntity _container;
633
634    // A cache of the deeply linked ports, and the version used to
635    // construct it.
636    // 'transient' means that the variable will not be serialized.
637    private transient List _deepLinkedPorts;
638
639    private transient long _deepLinkedPortsVersion = -1;
640}