001/* An Entity is an aggregation of ports.
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.io.IOException;
031import java.io.Writer;
032import java.lang.reflect.Field;
033import java.util.Collection;
034import java.util.Collections;
035import java.util.Enumeration;
036import java.util.Iterator;
037import java.util.LinkedList;
038import java.util.List;
039
040import ptolemy.kernel.util.Attribute;
041import ptolemy.kernel.util.IllegalActionException;
042import ptolemy.kernel.util.InternalErrorException;
043import ptolemy.kernel.util.InvalidStateException;
044import ptolemy.kernel.util.KernelException;
045import ptolemy.kernel.util.NameDuplicationException;
046import ptolemy.kernel.util.NamedList;
047import ptolemy.kernel.util.NamedObj;
048import ptolemy.kernel.util.Settable;
049import ptolemy.kernel.util.Workspace;
050
051///////////////////////////////////////////////////////////////////
052//// Entity
053
054/**
055 An Entity is a vertex in a generalized graph. It is an aggregation
056 of ports. The ports can be linked to relations. The
057 relations thus represent connections between ports, and hence,
058 connections between entities. To add a port to an entity, simply
059 set its container to the entity.  To remove it, set its container
060 to null, or to some other entity.
061 <p>
062 Entities are intended for flat graphs. Derived classes support
063 hierarchy (clustered graphs) by defining entities that aggregate
064 other entities.
065 <p>
066 An Entity can contain any instance of Port.  Derived classes may
067 wish to constrain to a subclass of Port.  To do this, subclasses
068 should override the public method newPort() to create a port of
069 the appropriate subclass, and the protected method _addPort() to throw
070 an exception if its argument is a port that is not of the appropriate
071 subclass.
072 <p>
073 An Entity is created within a workspace.  If the workspace is
074 not specified as a constructor argument, then the default workspace
075 is used. The workspace is used to synchronize simultaneous accesses
076 to a topology from multiple threads.  The workspace is immutable
077 (it cannot be changed during the lifetime of the Entity).
078
079 @author John S. Davis II, Edward A. Lee
080 @version $Id$
081 @since Ptolemy II 0.2
082 @Pt.ProposedRating Green (eal)
083 @Pt.AcceptedRating Green (johnr)
084 @see ptolemy.kernel.Port
085 @see ptolemy.kernel.Relation
086 */
087public class Entity<T extends Port> extends InstantiableNamedObj {
088    /** Construct an entity in the default workspace with an empty string
089     *  as its name.
090     *  The object is added to the workspace directory.
091     *  Increment the version number of the workspace.
092     */
093    public Entity() {
094        super();
095        _portList = new NamedList(this);
096    }
097
098    /** Construct an entity in the default workspace with the given name.
099     *  If the name argument
100     *  is null, then the name is set to the empty string.
101     *  The object is added to the workspace directory.
102     *  Increment the version number of the workspace.
103     *  @param name The name of this object.
104     *  @exception IllegalActionException If the name has a period.
105     */
106    public Entity(String name) throws IllegalActionException {
107        super(name);
108        _portList = new NamedList(this);
109    }
110
111    /** Construct an entity in the given workspace with an empty string
112     *  as a name.
113     *  If the workspace argument is null, use the default workspace.
114     *  The object is added to the workspace directory.
115     *  Increment the version of the workspace.
116     *  @param workspace The workspace for synchronization and version tracking.
117     */
118    public Entity(Workspace workspace) {
119        super(workspace);
120        _portList = new NamedList(this);
121    }
122
123    /** Construct an entity in the given workspace with the given name.
124     *  If the workspace argument is null, use the default workspace.
125     *  If the name argument
126     *  is null, then the name is set to the empty string.
127     *  The object is added to the workspace directory.
128     *  Increment the version of the workspace.
129     *  @param workspace The workspace for synchronization and version tracking.
130     *  @param name The name of this object.
131     *  @exception IllegalActionException If the name has a period.
132     */
133    public Entity(Workspace workspace, String name)
134            throws IllegalActionException {
135        super(workspace, name);
136        _portList = new NamedList(this);
137    }
138
139    ///////////////////////////////////////////////////////////////////
140    ////                         public methods                    ////
141
142    /** Clone the object into the specified workspace. The new object is
143     *  <i>not</i> added to the directory of that workspace (you must do this
144     *  yourself if you want it there).
145     *  The result is a new entity with clones of the ports of the original
146     *  entity.  The ports are set to the ports of the new entity.
147     *  This method gets read access on the workspace associated with
148     *  this object.
149     *  @param workspace The workspace for the cloned object.
150     *  @exception CloneNotSupportedException If cloned ports cannot have
151     *   as their container the cloned entity (this should not occur), or
152     *   if one of the attributes cannot be cloned.
153     *  @return The new Entity.
154     */
155    @Override
156    public Object clone(Workspace workspace) throws CloneNotSupportedException {
157        try {
158            workspace().getReadAccess();
159
160            Entity newEntity = (Entity) super.clone(workspace);
161            newEntity._portList = new NamedList(newEntity); // FIXME: parameterize NamedList?
162
163            // Clone the ports.
164            Iterator<T> ports = portList().iterator();
165
166            while (ports.hasNext()) {
167                Port port = ports.next();
168                Port newPort = (Port) port.clone(workspace);
169
170                // Assume that since we are dealing with clones,
171                // exceptions won't occur normally (the original was successfully
172                // incorporated, so this one should be too).  If they do, throw an
173                // InvalidStateException.
174                try {
175                    newPort.setContainer(newEntity);
176                } catch (KernelException ex) {
177                    workspace.remove(newEntity);
178                    throw new InvalidStateException(this,
179                            "Failed to clone an Entity: " + ex.getMessage());
180                }
181            }
182
183            Class<?> myClass = getClass();
184            Field[] fields = myClass.getFields();
185
186            for (Field field : fields) {
187                try {
188                    if (field.get(newEntity) instanceof Port) {
189                        // Get the port name. Note that by convention,
190                        // this is the same as the field name. But it might
191                        // not be.
192                        String portName = ((Port) field.get(this)).getName();
193                        Port port = newEntity.getPort(portName);
194
195                        if (port == null) {
196                            throw new IllegalActionException(this,
197                                    "Could not find a port named '" + portName
198                                            + "';");
199                        }
200
201                        field.set(newEntity, port);
202                    }
203                } catch (Throwable throwable) {
204                    // CloneNotSupportedException does not have a
205                    // constructor that takes a cause argument, so we call
206                    // initCause() and then throw.
207                    CloneNotSupportedException cloneException = new CloneNotSupportedException(
208                            "Problem cloning '" + field.getName() + "'");
209                    cloneException.initCause(throwable);
210                    throw cloneException;
211                }
212            }
213
214            _cloneFixAttributeFields(newEntity);
215            return newEntity;
216        } finally {
217            workspace().doneReading();
218        }
219    }
220
221    /** Return a list of the ports that are connected to contained ports.
222     *  Ports in this entity are not included unless there is a loopback,
223     *  meaning that two distinct ports of this entity are linked to the same
224     *  relation.   The connected entities can be obtained from the ports
225     *  using getContainer().  Note that a port may be listed more than
226     *  once if there is more than one connection to it.
227     *  This method is read-synchronized on the workspace.
228     *  @return An unmodifiable list of Port objects.
229     */
230    public List<T> connectedPortList() {
231        try {
232            _workspace.getReadAccess();
233
234            // This works by constructing a linked list and returning it.
235            // That list will not be corrupted by changes
236            // in the topology.
237            // The linked list is cached for efficiency.
238            if (_workspace.getVersion() != _connectedPortsVersion) {
239                // Cache is not valid, so update it.
240                _connectedPorts = new LinkedList<T>();
241
242                Iterator<T> ports = _portList.elementList().iterator();
243
244                while (ports.hasNext()) {
245                    Port port = ports.next();
246                    _connectedPorts.addAll(port.connectedPortList());
247                }
248
249                _connectedPortsVersion = _workspace.getVersion();
250            }
251
252            return Collections.unmodifiableList(_connectedPorts);
253        } finally {
254            _workspace.doneReading();
255        }
256    }
257
258    /** Enumerate all ports that are connected to contained ports.
259     *  Ports in this entity are not included unless there is a loopback,
260     *  meaning that two distinct ports of this entity are linked to the same
261     *  relation.   The connected entities can be obtained from the ports
262     *  using getContainer().  Note that a port may be listed more than
263     *  once if there is more than one connection to it.
264     *  This method is read-synchronized on the workspace.
265     *  @deprecated Use connectedPortList() instead.
266     *  @return An enumeration of Port objects.
267     */
268    @Deprecated
269    public Enumeration connectedPorts() {
270        return Collections.enumeration(connectedPortList());
271    }
272
273    /** Notify this entity that the links to the specified port have
274     *  been altered.  The default implementation in this base class
275     *  is to do nothing, but derived classes may want to react to new
276     *  connections.
277     *  @param port The port to which connections have changed.
278
279     */
280    public void connectionsChanged(Port port) {
281    }
282
283    /** Return an iterator over contained objects. In this class, this
284     *  is simply an iterator over attributes and ports.  In derived
285     *  classes, the iterator will also traverse classes, entities,
286     *  and relations. The caller of this method should have read
287     *  access on the workspace and hold it for the duration of the
288     *  use of the iterator. Moreover, it should not modify the port
289     *  or attribute list while using the iterator or it will get a
290     *  ConcurrentModificationException.
291     *  @return An iterator over instances of NamedObj contained by this
292     *   object.
293     */
294    @Override
295    public Iterator containedObjectsIterator() {
296        return new ContainedObjectsIterator();
297    }
298
299    /** Get the attribute with the given name. The name may be compound,
300     *  with fields separated by periods, in which case the attribute
301     *  returned is (deeply) contained by a contained attribute or port.
302     *  This method is read-synchronized on the workspace.
303     *  @param name The name of the desired attribute.
304     *  @return The requested attribute if it is found, null otherwise.
305     */
306    @Override
307    public Attribute getAttribute(String name) {
308        // FindBugs states that MoMLParser.endElement() might pass
309        // null to getAttribute() because there is a chance
310        // that _currentDocName is null.
311        if (name == null) {
312            return null;
313        }
314        try {
315            _workspace.getReadAccess();
316
317            Attribute result = super.getAttribute(name);
318
319            if (result == null) {
320                // Check ports.
321                String[] subnames = _splitName(name);
322
323                if (subnames[1] != null) {
324                    Port match = getPort(subnames[0]);
325
326                    if (match != null) {
327                        result = match.getAttribute(subnames[1]);
328                    }
329                }
330            }
331
332            return result;
333        } finally {
334            _workspace.doneReading();
335        }
336    }
337
338    /** Return the port contained by this entity that has the specified name.
339     *  If there is no such port, return null.
340     *  This method is read-synchronized on the workspace.
341     *  @param name The name of the desired port.
342     *  @return A port with the given name, or null if none exists.
343     */
344    public Port getPort(String name) {
345        try {
346            _workspace.getReadAccess();
347            return (Port) _portList.get(name);
348        } finally {
349            _workspace.doneReading();
350        }
351    }
352
353    /** Enumerate the ports belonging to this entity.
354     *  The order is the order in which they became contained by this entity.
355     *  This method is read-synchronized on the workspace.
356     *  @deprecated Use portList() instead.
357     *  @return An enumeration of Port objects.
358     */
359    @Deprecated
360    public Enumeration getPorts() {
361        return Collections.enumeration(portList());
362    }
363
364    /** Get all relations that are linked to ports contained by this
365     *  entity. Note that a relation may be listed more than once.
366     *  This method is read-synchronized on the workspace.
367     *  @return An unmodifiable list of Relation objects.
368     */
369    public List linkedRelationList() {
370        try {
371            _workspace.getReadAccess();
372
373            // This method constructs a list and then enumerates it.
374            // The list is cached for efficiency.
375            if (_workspace.getVersion() != _linkedRelationsVersion) {
376                // Cache is not valid.  Update it.
377                _linkedRelations = new LinkedList();
378
379                Iterator ports = _portList.elementList().iterator();
380
381                while (ports.hasNext()) {
382                    Port port = (Port) ports.next();
383                    _linkedRelations.addAll(port.linkedRelationList());
384                }
385
386                _linkedRelationsVersion = _workspace.getVersion();
387            }
388
389            return Collections.unmodifiableList(_linkedRelations);
390        } finally {
391            _workspace.doneReading();
392        }
393    }
394
395    /** Enumerate relations that are linked to ports contained by this
396     *  entity. Note that a relation may be listed more than once.
397     *  This method is read-synchronized on the workspace.
398     *  @deprecated Use linkedRelationList() instead.
399     *  @return An enumeration of Relation objects.
400     */
401    @Deprecated
402    public Enumeration linkedRelations() {
403        return Collections.enumeration(linkedRelationList());
404    }
405
406    /** Create a new port with the specified name. Set its container
407     *  to be this entity. Derived classes should override this method
408     *  to create a subclass of Port, if they require subclasses of Port.
409     *  If the name argument is null, then the name used is an empty string.
410     *  This method is write-synchronized on the workspace, and increments
411     *  its version number.
412     *  @param name The name to assign to the newly created port.
413     *  @return The new port.
414     *  @exception IllegalActionException If the port created is not
415     *   of an acceptable class (this is a programming
416     *   error; failed to override this method in derived classes).
417     *  @exception NameDuplicationException If the entity already has a port
418     *   with the specified name.
419     */
420    public Port newPort(String name)
421            throws IllegalActionException, NameDuplicationException {
422        try {
423            _workspace.getWriteAccess();
424
425            Port port = new Port(this, name);
426            return port;
427        } finally {
428            _workspace.doneWriting();
429        }
430    }
431
432    /** Get the ports belonging to this entity.
433     *  The order is the order in which they became contained by this entity.
434     *  This method is read-synchronized on the workspace.
435     *  @return An unmodifiable list of Port objects.
436     */
437    public List<T> portList() {
438        try {
439            _workspace.getReadAccess();
440            return _portList.elementList();
441        } finally {
442            _workspace.doneReading();
443        }
444    }
445
446    /** Remove all ports by setting their container to null.
447     *  As a side effect, the ports will be unlinked from all relations.
448     *  This method is write-synchronized on the workspace, and increments
449     *  its version number.
450     */
451    public void removeAllPorts() {
452        try {
453            _workspace.getWriteAccess();
454
455            // Have to copy _portList to avoid corrupting the iterator.
456            // NOTE: Could use a ListIterator here instead.
457            NamedList portListCopy = new NamedList(_portList);
458            Iterator ports = portListCopy.elementList().iterator();
459
460            while (ports.hasNext()) {
461                Port port = (Port) ports.next();
462
463                try {
464                    port.setContainer(null);
465                } catch (KernelException ex) {
466                    // Should not be thrown.
467                    throw new InternalErrorException(
468                            "Internal error in Port constructor!"
469                                    + ex.getMessage());
470                }
471            }
472        } finally {
473            _workspace.doneWriting();
474        }
475    }
476
477    /** Specify whether this object is a class definition.
478     *  This method overrides the base class to check that if the
479     *  argument is true, then this entity contains no ports with links.
480     *  This method is write synchronized on the workspace.
481     *  @param isClass True to make this object a class definition.
482     *  @exception IllegalActionException If the argument is true and
483     *   this entity contains ports with links.
484     */
485    @Override
486    public/*final*/void setClassDefinition(boolean isClass)
487            throws IllegalActionException {
488        if (isClass && !isClassDefinition()) {
489            // Converting from an instance to a class.
490            // Check that there are no links
491            // to ports contained by this entity.
492            Iterator ports = portList().iterator();
493
494            while (ports.hasNext()) {
495                Port port = (Port) ports.next();
496
497                if (port.numLinks() > 0) {
498                    throw new IllegalActionException(this,
499                            "Cannot convert an entity to a class definition "
500                                    + "while it contains ports with links.");
501                }
502            }
503        }
504
505        super.setClassDefinition(isClass);
506    }
507
508    /** Return a name that is guaranteed to not be the name of
509     *  any contained attribute or port.  In derived classes, this should be
510     *  overridden so that the returned name is guaranteed to not conflict
511     *  with any contained object. In this implementation, the argument
512     *  is stripped of any numeric suffix, and then a numeric suffix
513     *  is appended and incremented until a name is found that does not
514     *  conflict with a contained attribute or port.
515     *  @param prefix A prefix for the name.
516     *  @return A unique name.
517     */
518    @Override
519    public String uniqueName(String prefix) {
520        if (prefix == null) {
521            prefix = "null";
522        }
523
524        prefix = _stripNumericSuffix(prefix);
525
526        String candidate = prefix;
527        int uniqueNameIndex = 2;
528
529        while (getAttribute(candidate) != null || getPort(candidate) != null) {
530            candidate = prefix + uniqueNameIndex++;
531        }
532
533        return candidate;
534    }
535
536    ///////////////////////////////////////////////////////////////////
537    ////                         protected methods                 ////
538
539    /** Add a port to this entity. This method should not be used
540     *  directly.  Call the setContainer() method of the port instead.
541     *  This method does not set
542     *  the container of the port to point to this entity.
543     *  It assumes that the port is in the same workspace as this
544     *  entity, but does not check.  The caller should check.
545     *  Derived classes should override this method if they require
546     *  a subclass of Port to throw an exception if the argument is
547     *  not of an acceptable class.
548     *  This method is <i>not</i> synchronized on the workspace, so the
549     *  caller should be.
550     *  @param port The port to add to this entity.
551     *  @exception IllegalActionException If the port has no name.
552     *  @exception NameDuplicationException If the port name collides with a
553     *   name already in the entity.
554     */
555    protected void _addPort(T port)
556            throws IllegalActionException, NameDuplicationException {
557        _portList.append(port);
558    }
559
560    /** Return a description of the object.  The level of detail depends
561     *  on the argument, which is an or-ing of the static final constants
562     *  defined in the NamedObj class.  Lines are indented according to
563     *  to the level argument using the protected method _getIndentPrefix().
564     *  Zero, one or two brackets can be specified to surround the returned
565     *  description.  If one is specified it is the the leading bracket.
566     *  This is used by derived classes that will append to the description.
567     *  Those derived classes are responsible for the closing bracket.
568     *  An argument other than 0, 1, or 2 is taken to be equivalent to 0.
569     *  This method is read-synchronized on the workspace.
570     *  @param detail The level of detail.
571     *  @param indent The amount of indenting.
572     *  @param bracket The number of surrounding brackets (0, 1, or 2).
573     *  @return A description of the object.
574     *  @exception IllegalActionException If there is a problem
575     *  accessing subcomponents of this object.
576     */
577    @Override
578    protected String _description(int detail, int indent, int bracket)
579            throws IllegalActionException {
580        try {
581            _workspace.getReadAccess();
582
583            StringBuffer result = new StringBuffer();
584
585            if (bracket == 1 || bracket == 2) {
586                result.append(super._description(detail, indent, 1));
587            } else {
588                result.append(super._description(detail, indent, 0));
589            }
590
591            if ((detail & CONTENTS) != 0 || (detail & LINKS) != 0) {
592                if (result.toString().trim().length() > 0) {
593                    result.append(" ");
594                }
595
596                result.append("ports {\n");
597
598                Iterator portLists = portList().iterator();
599
600                while (portLists.hasNext()) {
601                    Port port = (Port) portLists.next();
602                    result.append(
603                            port._description(detail, indent + 1, 2) + "\n");
604                }
605
606                result.append(_getIndentPrefix(indent) + "}");
607            }
608
609            if (bracket == 2) {
610                result.append("}");
611            }
612
613            return result.toString();
614        } finally {
615            _workspace.doneReading();
616        }
617    }
618
619    /** Write a MoML description of the contents of this object, which
620     *  in this class are the attributes plus the ports.  This method is called
621     *  by exportMoML().  Each description is indented according to the
622     *  specified depth and terminated with a newline character.
623     *  @param output The output to write to.
624     *  @param depth The depth in the hierarchy, to determine indenting.
625     *  @exception IOException If an I/O error occurs.
626     */
627    @Override
628    protected void _exportMoMLContents(Writer output, int depth)
629            throws IOException {
630        super._exportMoMLContents(output, depth);
631
632        Iterator ports = portList().iterator();
633
634        while (ports.hasNext()) {
635            Port port = (Port) ports.next();
636            port.exportMoML(output, depth);
637        }
638    }
639
640    /** Remove the specified port. This method should not be used
641     *  directly.  Call the setContainer() method of the port instead
642     *  with a null argument. The port is assumed to be contained
643     *  by this entity (otherwise, nothing happens). This
644     *  method does not alter the container of the port.
645     *  This method is <i>not</i> synchronized on the workspace, so the
646     *  caller should be.
647     *  @param port The port being removed from this entity.
648     */
649    protected void _removePort(Port port) {
650        _portList.remove(port);
651    }
652
653    /** Validate attributes deeply contained by this object if they
654     *  implement the Settable interface by calling their validate() method.
655     *  This method overrides the base class to check attributes contained
656     *  by the contained ports.
657     *  Errors that are triggered by this validation are handled by calling
658     *  handleModelError().
659     *  @param attributesValidated A HashSet of Attributes that have
660     *  already been validated.  For example, Settables that implement
661     *  the SharedSettable interface are validated only once.
662     *  @see NamedObj#handleModelError(NamedObj context, IllegalActionException exception)
663     *  @exception IllegalActionException If the superclass throws it
664     *  or if handleModelError() throws it.
665     */
666    @Override
667    protected void _validateSettables(Collection attributesValidated)
668            throws IllegalActionException {
669
670        super._validateSettables(attributesValidated);
671        Iterator ports = portList().iterator();
672        while (ports.hasNext()) {
673            Port port = (Port) ports.next();
674            if (port instanceof Settable) {
675                try {
676                    Collection validated = ((Settable) port).validate();
677                    if (validated != null) {
678                        attributesValidated.addAll(validated);
679                    }
680                    attributesValidated.add(port);
681                } catch (IllegalActionException ex) {
682                    if (!handleModelError(this, ex)) {
683                        throw ex;
684                    }
685                }
686            }
687            port.validateSettables();
688        }
689    }
690
691    ///////////////////////////////////////////////////////////////////
692    ////                         friendly variables                ////
693    // The following is package friendly so port can access it.
694
695    /** A list of Ports owned by this Entity. */
696    NamedList _portList;
697
698    ///////////////////////////////////////////////////////////////////
699    ////                         private variables                 ////
700    // Cached list of connected ports.
701    private transient LinkedList<T> _connectedPorts;
702
703    private transient long _connectedPortsVersion = -1;
704
705    // @serial Cached list of linked relations.
706    private transient LinkedList _linkedRelations;
707
708    private transient long _linkedRelationsVersion = -1;
709
710    ///////////////////////////////////////////////////////////////////
711    ////                         inner classes                     ////
712
713    /** This class is an iterator over all the contained objects
714     *  (all instances of NamedObj). In this class, the contained
715     *  objects are attributes first, then ports. In derived classes,
716     *  they include relations, and entities as well.
717     *  The user of this class should have read
718     *  access on the workspace and hold it for the duration of the
719     *  use of the iterator. Moreover, it should not modify the port
720     *  or attribute list while using the iterator or it will get a
721     *  ConcurrentModificationException.
722     */
723    protected class ContainedObjectsIterator
724            extends NamedObj.ContainedObjectsIterator {
725        /** Create an iterator over all the contained objects, which
726         *  for Entities are attributes and then ports.
727         */
728        public ContainedObjectsIterator() {
729            super();
730            _portListIterator = portList().iterator();
731        }
732
733        /** Return true if the iteration has more elements.
734         *  In this base class, this returns true if there are more
735         *  attributes or ports.
736         *  @return True if there are more attributes or ports.
737         */
738        @Override
739        public boolean hasNext() {
740            if (super.hasNext()) {
741                return true;
742            }
743            return _portListIterator.hasNext();
744        }
745
746        /** Return the next element in the iteration.
747         *  In this base class, this is the next attribute or port.
748         *  @return The next attribute or port.
749         */
750        @Override
751        public Object next() {
752            if (super.hasNext()) {
753                return super.next();
754            }
755            return _portListIterator.next();
756        }
757
758        /** The remove() method is not supported because is is not
759         *  supported in NamedObj.ContainedObjectsIterator.remove().
760         */
761        @Override
762        public void remove() {
763            // Findbugs complains about Call to unsupported method.
764            throw new UnsupportedOperationException("remove() not supported "
765                    + "because attributeList().iterator() returns a NamedList "
766                    + "that is unmodifiable");
767        }
768
769        private Iterator _portListIterator = null;
770    }
771}