/* A CompositeEntity is a cluster in a clustered graph. Copyright (c) 1997-2010 The Regents of the University of California. All rights reserved. Permission is hereby granted, without written agreement and without license or royalty fees, to use, copy, modify, and distribute this software and its documentation for any purpose, provided that the above copyright notice and the following two paragraphs appear in all copies of this software. IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY */ package ptolemy.kernel; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import ptolemy.kernel.attributes.VersionAttribute; import ptolemy.kernel.util.Attribute; import ptolemy.kernel.util.DecoratedAttributes; import ptolemy.kernel.util.IllegalActionException; import ptolemy.kernel.util.InternalErrorException; import ptolemy.kernel.util.KernelException; import ptolemy.kernel.util.NameDuplicationException; import ptolemy.kernel.util.Nameable; import ptolemy.kernel.util.NamedList; import ptolemy.kernel.util.NamedObj; import ptolemy.kernel.util.Settable; import ptolemy.kernel.util.Workspace; import ptolemy.util.StringUtilities; /////////////////////////////////////////////////////////////////// //// CompositeEntity /** A CompositeEntity is a cluster in a clustered graph. I.e., it is a non-atomic entity, in that it can contain other entities and relations. It supports transparent ports, where, in effect, the port of a contained entity is represented by a port of this entity. Methods that "deeply" traverse the topology see right through transparent ports. It may be opaque, in which case its ports are opaque and methods that "deeply" traverse the topology do not see through them. For instance, deepEntityList() returns the opaque entities directly or indirectly contained by this entity.
To add an entity or relation to this composite, call its setContainer() method with this composite as an argument. To remove it, call its setContainer() method with a null argument (or another container). The entity must be an instance of ComponentEntity and the relation of ComponentRelation or an exception is thrown. Derived classes may further constrain these to subclasses. To do that, they should override the protected methods _addEntity() and _addRelation() and the public member newRelation().
A CompositeEntity may be contained by another CompositeEntity. To set that up, call the setContainer() method of the inside entity. Derived classes may further constrain the container to be a subclass of CompositeEntity. To do this, they should override setContainer() to throw an exception. Recursive containment structures, where an entity directly or indirectly contains itself, are disallowed, and an exception is thrown on an attempt to set up such a structure.
A CompositeEntity can contain instances of ComponentPort. By default these ports will be transparent, although subclasses of CompositeEntity can make them opaque by overriding the isOpaque() method to return true. Derived classes may further constrain the ports to a subclass of ComponentPort. To do this, they should override the public method newPort() to create a port of the appropriate subclass, and the protected method _addPort() to throw an exception if its argument is a port that is not of the appropriate subclass.
Since contained entities implement the
{@link ptolemy.kernel.util.Instantiable} interface,
some may be class definitions. If an entity is a class definition,
then it is not included in the lists returned by
{@link #entityList()}, {@link #entityList(Class)},
{@link #deepEntityList()}, and {@link #allAtomicEntityList()}.
Correspondingly, if it is not a class definition, then it is not
included in the list returned by {@link #classDefinitionList()}.
Contained class definitions are nonetheless required to have names
distinct from contained entities that are not class definitions,
and the method {@link #getEntity(String)} will return either
a class definition or an entity that is not a class definition,
as long as the name matches. Note that contained entities that
are class definitions cannot be connected to other entities.
Moreover, they cannot be deleted as long as there are either
subclasses or instances present.
@author John S. Davis II, Edward A. Lee, contributor: Christopher Brooks
@version $Id: CompositeEntity.java 59167 2010-09-21 17:08:02Z cxh $
@since Ptolemy II 0.2
@Pt.ProposedRating Green (eal)
@Pt.AcceptedRating Green (hyzheng)
*/
public class CompositeEntity extends ComponentEntity {
/** Construct an entity in the default workspace with an empty string
* as its name. Add the entity to the workspace directory.
* Increment the version number of the workspace.
*/
public CompositeEntity() {
super();
_addIcon();
}
/** Construct an entity in the specified workspace with an empty
* string as a name. You can then change the name with setName().
* If the workspace argument is null, then use the default workspace.
* Add the entity to the workspace directory.
* Increment the version number of the workspace.
* @param workspace The workspace that will list the entity.
*/
public CompositeEntity(Workspace workspace) {
super(workspace);
_addIcon();
}
/** Create an object with a name and a container.
* The container argument must not be null, or a
* NullPointerException will be thrown. This entity will use the
* workspace of the container for synchronization and version counts.
* If the name argument is null, then the name is set to the empty string.
* Increment the version of the workspace.
* @param container The container entity.
* @param name The name of the entity.
* @exception IllegalActionException If the container is incompatible
* with this entity.
* @exception NameDuplicationException If the name coincides with
* an entity already in the container.
*/
public CompositeEntity(CompositeEntity container, String name)
throws IllegalActionException, NameDuplicationException {
super(container, name);
_addIcon();
}
///////////////////////////////////////////////////////////////////
//// public methods ////
/** Return a list that consists of all the atomic entities in a model.
* This method differs from {@link #deepEntityList()} in that
* this method looks inside opaque entities, whereas deepEntityList()
* does not. The returned list does not include any entities that
* are class definitions.
* @return a List of all atomic entities in the model.
*/
public List allAtomicEntityList() {
// We don't use an Iterator here so that we can modify the list
// rather than having both an Iterator and a result list.
//
// Note:
// deepEntityList() should be renamed to deepOpaqueEntityList()
// allAtomicEntityList() to deepAtomicEntityList()
// However, the change would require a fair amount of work.
//LinkedList entities = (LinkedList) deepEntityList();
List entities = deepEntityList();
for (int i = 0; i < entities.size(); i++) {
Object entity = entities.get(i);
if (entity instanceof CompositeEntity) {
// Remove the composite actor and add its containees.
entities.remove(i);
// Note that removing an element from the list causes
// the indices of later elements to shift forward by 1.
// We reduce the index i by one to match the index in
// the list.
i--;
entities.addAll(((CompositeEntity) entity)
.allAtomicEntityList());
}
}
return entities;
}
/** Return a list that consists of all the non-opaque composite entities in a
* model. This method differs from allAtomicEntityList() in that
* this method returns CompositeEntities and
* allAtomicEntityList() returns atomic entities. This method
* differs from {@link #deepEntityList()} in that this method
* returns only CompositeEntities, whereas deepEntityList()
* returns ComponentEntities. The returned list of this method
* does not include any entities that are class definitions.
* @return a List of all Composite entities in the model.
*/
public List allCompositeEntityList() {
try {
_workspace.getReadAccess();
LinkedList result = new LinkedList();
// This might be called from within a superclass constructor,
// in which case there are no contained entities yet.
if (_containedEntities != null) {
Iterator entities = _containedEntities.elementList().iterator();
while (entities.hasNext()) {
ComponentEntity entity = (ComponentEntity) entities.next();
if (/*!entity.isClassDefinition()&& */!entity.isOpaque() /*entity instanceof CompositeEntity*/) {
result.add(entity);
result.addAll(((CompositeEntity) entity)
.allCompositeEntityList());
}
}
}
return result;
} finally {
_workspace.doneReading();
}
}
/** Allow or disallow connections that are created using the connect()
* method to cross levels of the hierarchy.
* The default is that such connections are disallowed.
* Generally it is a bad idea to allow level-crossing
* connections, since it breaks modularity. This loss of modularity
* means, among other things, that this composite cannot be cloned.
* Nonetheless, this capability is provided for the benefit of users
* that feel they just must have it, and who are willing to sacrifice
* clonability and modularity.
* @param boole True to allow level-crossing connections.
*/
public void allowLevelCrossingConnect(boolean boole) {
_levelCrossingConnectAllowed = boole;
}
/** List the contained class definitions
* in the order they were added
* (using their setContainer() method). The returned list does
* not include any entities that are not class definitions.
* The returned list is static in the sense
* that it is not affected by any subsequent additions or removals
* of class definitions.
* This method is read-synchronized on the workspace.
* @return A list of ComponentEntity objects.
* @see #entityList()
*/
public List classDefinitionList() {
try {
_workspace.getReadAccess();
if (_workspace.getVersion() == _classDefinitionListVersion) {
return _classDefinitionListCache;
}
List result = new LinkedList();
// This might be called from within a superclass constructor,
// in which case there are no contained entities yet.
if (_containedEntities != null) {
Iterator entities = _containedEntities.elementList().iterator();
while (entities.hasNext()) {
ComponentEntity entity = (ComponentEntity) entities.next();
if (entity.isClassDefinition()) {
result.add(entity);
}
}
_classDefinitionListCache = result;
_classDefinitionListVersion = _workspace.getVersion();
}
return result;
} finally {
_workspace.doneReading();
}
}
/** Clone the object into the specified workspace. The new object is
* not added to the directory of that workspace (you must do this
* yourself if you want it there).
* This method gets read access on the workspace associated with
* this object.
* @param workspace The workspace for the cloned object.
* @exception CloneNotSupportedException If one of the attributes
* cannot be cloned.
* @return A new CompositeEntity.
*/
public Object clone(Workspace workspace) throws CloneNotSupportedException {
try {
workspace().getReadAccess();
// NOTE: The following assumes we will not do an exportMoML()
// at the same time we are doing a clone(). Since clone() is used
// to instantiate objects, this seems safe. But the field below
// is shared with exportMoML().
_levelCrossingLinks = new LinkedList Note that if this method is being called many times, then
* it may be more efficient to use
* {@link #connect(ComponentPort, ComponentPort, String)}
* instead of this method because this method calls
* {@link #uniqueName(String)} each time, which
* searches the object for attributes, ports, entities and relations
* that may match a candidate unique name.
*
* @param port1 The first port to connect.
* @param port2 The second port to connect.
* @return The ComponentRelation that is created to connect port1 and
* port2.
* @exception IllegalActionException If one of the arguments is null, or
* if a disallowed level-crossing connection would result.
*/
public ComponentRelation connect(ComponentPort port1, ComponentPort port2)
throws IllegalActionException {
try {
return connect(port1, port2, uniqueName("_R"));
} catch (NameDuplicationException ex) {
// This exception should not be thrown.
throw new InternalErrorException(this, ex,
"Internal error in CompositeEntity.connect() method!");
}
}
/** Create a new relation with the specified name and use it to
* connect two ports. Level-crossing connections are not permitted
* unless allowLevelCrossingConnect() has been called with a true
* argument. Note that is rarely a good idea to permit level crossing
* connections, since they break modularity and cloning.
* A reference to the newly created alias relation is returned.
* To remove the relation, call its setContainer() method with a null
* argument. This method is write-synchronized on the workspace
* and increments its version number.
* @param port1 The first port to connect.
* @param port2 The second port to connect.
* @param relationName The name of the new relation.
* @return The ComponentRelation that is created to connect port1 and
* port2.
* @exception IllegalActionException If one of the arguments is null, or
* if a disallowed level-crossing connection would result, or if the two
* ports are not in the same workspace as this entity.
* @exception NameDuplicationException If there is already a relation with
* the specified name in this entity.
*/
public ComponentRelation connect(ComponentPort port1, ComponentPort port2,
String relationName) throws IllegalActionException,
NameDuplicationException {
if ((port1 == null) || (port2 == null)) {
throw new IllegalActionException(this,
"Attempt to connect null port.");
}
if ((port1.workspace() != port2.workspace())
|| (port1.workspace() != _workspace)) {
throw new IllegalActionException(port1, port2,
"Cannot connect ports because workspaces are different.");
}
try {
_workspace.getWriteAccess();
ComponentRelation ar = newRelation(relationName);
if (_levelCrossingConnectAllowed) {
port1.liberalLink(ar);
} else {
port1.link(ar);
}
// Have to catch the exception to restore the original state.
try {
if (_levelCrossingConnectAllowed) {
port2.liberalLink(ar);
} else {
port2.link(ar);
}
} catch (IllegalActionException ex) {
port1.unlink(ar);
throw ex;
}
return ar;
} finally {
_workspace.doneWriting();
}
}
/** Return an iterator over contained objects. In this class,
* this is an iterator over attributes, ports, classes,
* entities, and relations.
* @return An iterator over instances of NamedObj contained by this
* object.
*/
public Iterator containedObjectsIterator() {
return new ContainedObjectsIterator();
}
/** List the opaque entities that are directly or indirectly
* contained by this entity. The list will be empty if there
* are no such contained entities. This list does not include
* class definitions nor anything contained by them.
* This method is read-synchronized on the workspace.
* @return A list of opaque ComponentEntity objects.
* @see #classDefinitionList()
* @see #allAtomicEntityList()
*/
public List deepOpaqueEntityList() {
try {
_workspace.getReadAccess();
List results = new ArrayList();
_deepOpaqueEntityList(results);
return results;
} finally {
_workspace.doneReading();
}
}
/** List the opaque entities that are directly or indirectly
* contained by this entity. The list will be empty if there
* are no such contained entities. This list does not include
* class definitions nor anything contained by them.
* This method is read-synchronized on the workspace.
* @return A list of opaque ComponentEntity objects.
* @see #classDefinitionList()
* @see #allAtomicEntityList()
*/
public List deepEntityList() {
try {
_workspace.getReadAccess();
LinkedList result = new LinkedList();
// This might be called from within a superclass constructor,
// in which case there are no contained entities yet.
if (_containedEntities != null) {
Iterator entities = _containedEntities.elementList().iterator();
while (entities.hasNext()) {
ComponentEntity entity = (ComponentEntity) entities.next();
if (!entity.isClassDefinition()) {
if (entity.isOpaque()) {
result.add(entity);
} else {
result.addAll(((CompositeEntity) entity)
.deepEntityList());
}
}
}
}
return result;
} finally {
_workspace.doneReading();
}
}
/** Return a set with the relations that are directly or indirectly
* contained by this entity. The set will be empty if there
* are no such contained relations.
* This method is read-synchronized on the workspace.
* @return A set of ComponentRelation objects.
*/
public Set
* If the filter argument is null, then return all the links that this
* composite is responsible for (i.e., apply no filtering). If the
* argument is an empty collection, then return none of the links. The
* links that this entity is responsible for are the inside links of
* its ports, and links on ports contained by contained entities.
*
* If any link is found where both ends of the link are inherited objects,
* then that link is not exported. It is assumed that the base class
* will export that link. For this purpose, a port of a contained
* entity is deemed to be an inherited object if it is itself a class
* element and its container is an inherited object.
* @param depth The depth below the MoML export in the hierarchy.
* @param filter A collection of ports, parameters, and entities, or
* null to apply no filtering.
* @return A string that describes the links present in the
* filter.
* @exception IOException If an I/O error occurs.
*/
public String exportLinks(int depth, Collection filter) throws IOException {
// To get the ordering right,
// we read the links from the ports, not from the relations.
StringBuffer result = new StringBuffer();
// First, produce the inside links on contained ports.
Iterator ports = portList().iterator();
while (ports.hasNext()) {
ComponentPort port = (ComponentPort) ports.next();
// Skip the port if it is not persistent.
if (port != null && !port.isPersistent()) {
continue;
}
Iterator relations = port.insideRelationList().iterator();
// The following variables are used to determine whether to
// specify the index of the link explicitly, or to leave
// it implicit.
int index = -1;
boolean useIndex = false;
while (relations.hasNext()) {
index++;
ComponentRelation relation = (ComponentRelation) relations
.next();
// Skip the relation if it is not persistent.
if (relation != null && !relation.isPersistent()) {
continue;
}
if (relation == null) {
// Gap in the links. The next link has to use an
// explicit index.
useIndex = true;
continue;
}
// If both ends of the link are inherited objects, then
// suppress the export. This depends on the level of export
// because if both ends of the link are implied, then the
// link is implied.
if (_commonImplier(relation, depth, port, depth)) {
continue;
}
// Apply filter.
if ((filter == null)
|| (filter.contains(relation) && (filter.contains(port) || filter
.contains(port.getContainer())))) {
// If the relation is not persistent, then do not export the link.
if (relation != null && !relation.isPersistent()) {
continue;
}
// In order to support level-crossing links, consider the
// possibility that the relation is not contained by this.
String relationName;
if (relation.getContainer() == this) {
relationName = relation.getName();
} else {
if (deepContains(relation)) {
// NOTE: This used to export the full name, but the
// relative name is sufficient.
relationName = relation.getName(this);
} else {
// Can't export the link here since when the
// MoML file is re-read there is no assurance that
// the relation exists when the link is to be
// created. Need to delegate to the least common
// container.
_recordLevelCrossingLink(port, relation, null,
index);
continue;
}
}
String escapedPortName = StringUtilities.escapeForXML(port.getName());
String escapedRelationName = StringUtilities.escapeForXML(relationName);
if (useIndex) {
useIndex = false;
result.append(_getIndentPrefix(depth) + "\n");
} else {
result.append(_getIndentPrefix(depth) + "\n");
}
}
}
}
// Next, produce the links on ports contained by contained entities.
Iterator entities = entityList().iterator();
while (entities.hasNext()) {
ComponentEntity entity = (ComponentEntity) entities.next();
// Skip the entity if it is not persistent.
if (entity != null && !entity.isPersistent()) {
continue;
}
ports = entity.portList().iterator();
while (ports.hasNext()) {
ComponentPort port = (ComponentPort) ports.next();
// Skip the port if it is not persistent.
if (port != null && !port.isPersistent()) {
continue;
}
Iterator relations = port.linkedRelationList().iterator();
// The following variables are used to determine whether to
// specify the index of the link explicitly, or to leave
// it implicit.
int index = -1;
boolean useIndex = false;
while (relations.hasNext()) {
index++;
ComponentRelation relation = (ComponentRelation) relations
.next();
// Skip the relation if it is not persistent.
if (relation != null && !relation.isPersistent()) {
continue;
}
if (relation == null) {
// Gap in the links. The next link has to use an
// explicit index.
useIndex = true;
continue;
}
// If both ends of the link are inherited objects, then
// suppress the export. This depends on the level of export
// because if both ends of the link are implied, then the
// link is implied. Note that we need for both the port
// to be implied and the port's container to share a
// common implier with the relation. We know that the port
// is contained within its container, so we don't have to
// check it separately for a common implier.
if (port.getDerivedLevel() <= (depth + 1)
&& _commonImplier(relation, depth, port
.getContainer(), depth)) {
continue;
}
// Used to have the previous logic here, skipping the link export,
// instead of the above.
// But careful! It may be that the both the relation and
// the port are derived, but not from the same object.
// This can happen with level-crossing links.
// Check that the container above at which these two objects
// are implied is the same container.
// EAL 6/6/09
/*
int relationLevel = relation.getDerivedLevel();
int portLevel = port.getDerivedLevel();
if ((relationLevel <= depth)
&& (portLevel <= (depth + 1))
&& ((port.getContainer()).getDerivedLevel() <= depth)) {
continue;
}
*/
// Apply filter.
if ((filter == null)
|| (filter.contains(relation) && (filter
.contains(port) || filter.contains(port
.getContainer())))) {
// If the relation is not persistent, then do not export the link.
if (relation != null && !relation.isPersistent()) {
continue;
}
// In order to support level-crossing links,
// consider the possibility that the relation
// is not contained by this.
String relationName;
if (relation.getContainer() == this) {
relationName = relation.getName();
} else {
if (deepContains(relation)) {
// NOTE: This used to export the full name, but the
// relative name is sufficient.
relationName = relation.getName(this);
} else {
// Can't export the link here since when the
// MoML file is re-read there is no assurance that
// the relation exists when the link is to be
// created. Need to delegate to the least common
// container.
_recordLevelCrossingLink(port, relation, null,
index);
continue;
}
}
// Escape any < character that occurs in name.
// setName(String).
String escapedName = StringUtilities.escapeForXML(entity.getName());
String escapedPortName = StringUtilities.escapeForXML(port.getName());
String escapedRelationName = StringUtilities.escapeForXML(relationName);
if (useIndex) {
useIndex = false;
result.append(_getIndentPrefix(depth)
+ "\n");
} else {
result.append(_getIndentPrefix(depth)
+ "\n");
}
}
}
}
}
// Finally, produce the links that are between contained
// relations only. Slight trickiness here: Both relations
// on either side of a link have links to each other,
// but we only want to represent one of the links.
// It doesn't matter which one. We do this by accumulating
// a set of visited relations.
Set visitedRelations = new HashSet();
Iterator relations = relationList().iterator();
while (relations.hasNext()) {
ComponentRelation relation = (ComponentRelation) relations.next();
visitedRelations.add(relation);
// Skip the relation if it is not persistent.
if (relation != null && !relation.isPersistent()) {
continue;
}
Iterator portsAndRelations = relation.linkedObjectsList()
.iterator();
while (portsAndRelations.hasNext()) {
Object portOrRelation = portsAndRelations.next();
if (portOrRelation instanceof Relation) {
Relation otherRelation = (Relation) portOrRelation;
// Skip the relation if it is not persistent.
if (otherRelation != null && !otherRelation.isPersistent()) {
continue;
}
// If we have visited the other relation already, then
// we have already represented the link. Skip this.
if (visitedRelations.contains(otherRelation)) {
continue;
}
// If both ends of the link are inherited objects, then
// suppress the export. This depends on the level of export
// because if both ends of the link are implied, then the
// link is implied.
if (_commonImplier(relation, depth, otherRelation, depth)) {
continue;
}
// Apply filter.
if ((filter == null)
|| (filter.contains(relation) && filter
.contains(otherRelation))) {
// In order to support level-crossing links, consider the
// possibility that the relation is not contained by this.
String relationName;
if (relation.getContainer() == this) {
relationName = relation.getName();
} else {
if (deepContains(relation)) {
// NOTE: This used to export the full name, but the
// relative name is sufficient.
relationName = relation.getName(this);
} else {
// Can't export the link here since when the
// MoML file is re-read there is no assurance that
// the relation exists when the link is to be
// created. Need to delegate to the least common
// container.
_recordLevelCrossingLink(null, relation,
otherRelation, 0);
continue;
}
}
String otherRelationName;
if (otherRelation.getContainer() == this) {
otherRelationName = otherRelation.getName();
} else {
// Can't export the link here since when the
// MoML file is re-read there is no assurance that
// the relation exists when the link is to be
// created. Need to delegate to the least common
// container.
_recordLevelCrossingLink(null, relation,
otherRelation, 0);
continue;
}
result.append(_getIndentPrefix(depth)
+ "\n");
}
}
}
}
return result.toString();
}
/** Override the base class to initialize a data structure that can
* capture and then export level-crossing links deeply contained
* structure within. Otherwise, this delegates to the base
* class to do all the work.
* @param output The output stream to write to.
* @param depth The depth in the hierarchy, to determine indenting.
* @param name The name to use in the exported MoML.
* @exception IOException If an I/O error occurs.
* @see ptolemy.kernel.util.MoMLExportable
*/
public void exportMoML(Writer output, int depth, String name)
throws IOException {
try {
_levelCrossingLinks = new LinkedList Note that this method should be called judiciously from when
* the CompositeEntity is large. The reason is that this method
* searches for matching attributes, ports, classes, entities
* and relations, which can result in slow performance.
*
* @param prefix A prefix for the name.
* @return A unique name.
*/
public String uniqueName(String prefix) {
if (prefix == null) {
prefix = "null";
}
prefix = _stripNumericSuffix(prefix);
String candidate = prefix;
// NOTE: The list returned by getPrototypeList() has
// length equal to the number of containers of this object
// that return non-null to getParent(). That number is
// assured to be at least one greater than the corresponding
// number for any of the parents returned by getParent().
// Hence, we can use that number to minimize the likelyhood
// of inadvertent capture.
try {
int depth = getPrototypeList().size();
if (depth > 0) {
prefix = prefix + "_" + depth + "_";
}
} catch (IllegalActionException e) {
// Derivation invariant is not satisified.
throw new InternalErrorException(e);
}
// FIXME: because we start with 2 each time, then if
// we are calling this method many times we will need
// to search the CompositeEntity for matching
// attributes, ports, entities and releations.
// This will have poor behaviour for large CompositeEntities.
// However, if we cached the uniqueNameIndex, then
// it would tend to increase over time, which would be
// unusual if we created a relation (_R2), deleted it
// and created another relation, which would get the name
// _R3, instead of _R2.
int uniqueNameIndex = 2;
while ((getAttribute(candidate) != null)
|| (getPort(candidate) != null)
|| (getEntity(candidate) != null)
|| (getRelation(candidate) != null)) {
candidate = prefix + uniqueNameIndex++;
}
return candidate;
}
///////////////////////////////////////////////////////////////////
//// protected methods ////
/** Add an entity or class definition to this container. This method
* should not be used directly. Call the setContainer() method of
* the entity instead. This method does not set
* the container of the entity to point to this composite entity.
* It assumes that the entity is in the same workspace as this
* container, but does not check. The caller should check.
* Derived classes may override this method to constrain the
* the entity to a subclass of ComponentEntity.
* This method is not synchronized on the workspace, so the
* caller should be.
* @param entity Entity to contain.
* @exception IllegalActionException If the entity has no name, or the
* action would result in a recursive containment structure.
* @exception NameDuplicationException If the name collides with a name
* already in the entity.
*/
protected void _addEntity(ComponentEntity entity)
throws IllegalActionException, NameDuplicationException {
if (entity.deepContains(this)) {
throw new IllegalActionException(entity, this,
"Attempt to construct recursive containment");
}
_containedEntities.append(entity);
}
/** Add a relation to this container. This method should not be used
* directly. Call the setContainer() method of the relation instead.
* This method does not set
* the container of the relation to refer to this container.
* This method is not synchronized on the workspace, so the
* caller should be.
* @param relation Relation to contain.
* @exception IllegalActionException If the relation has no name.
* @exception NameDuplicationException If the name collides with a name
* already on the contained relations list.
*/
protected void _addRelation(ComponentRelation relation)
throws IllegalActionException, NameDuplicationException {
_containedRelations.append(relation);
}
/** Adjust the deferrals in this object. This method should
* be called on any newly created object that is created by
* cloning. While cloning, parent relations are set to null.
* That is, no object in the clone has a parent. This method
* identifies the correct parent for any object in the clone.
* To do this, it uses the class name. Specifically, if this
* object has a class name that refers to a class in scope,
* then it replaces the current parent with that object.
* To look for a class in scope, we go up the hierarchy, but
* no more times than the return value of getDerivedLevel().
* The reason for this is that if the class from which this
* object is defined is above that level, then we do not want
* to establish a parent relationship with that class. This
* object is implied, and the parent relationship of the object
* from which it is implied is sufficient.
*
* Derived classes that contain other objects should recursively
* call this method on contained objects.
* @exception IllegalActionException If the class found in scope
* cannot be set.
*/
protected void _adjustDeferrals() throws IllegalActionException {
super._adjustDeferrals();
Iterator containedClasses = lazyClassDefinitionList().iterator();
while (containedClasses.hasNext()) {
NamedObj containedObject = (NamedObj) containedClasses.next();
if (containedObject instanceof ComponentEntity) {
((ComponentEntity) containedObject)._adjustDeferrals();
}
}
Iterator containedEntities = lazyEntityList().iterator();
while (containedEntities.hasNext()) {
NamedObj containedObject = (NamedObj) containedEntities.next();
if (containedObject instanceof ComponentEntity) {
((ComponentEntity) containedObject)._adjustDeferrals();
}
}
}
/** List the opaque entities that are directly or indirectly
* contained by this entity. The list will be empty if there
* are no such contained entities. This list does not include
* class definitions nor anything contained by them.
* This method is not read-synchronized on the workspace,
* its caller should be read-synchronized.
* @param result The list of opaque ComponentEntity objects.
* @see #classDefinitionList()
* @see #allAtomicEntityList()
* @see #deepEntityList()
*/
protected void _deepOpaqueEntityList(List result) {
// This might be called from within a superclass constructor,
// in which case there are no contained entities yet.
if (_containedEntities != null) {
Iterator entities = _containedEntities.elementList().iterator();
while (entities.hasNext()) {
ComponentEntity entity = (ComponentEntity) entities.next();
if (!entity.isClassDefinition()) {
if (entity.isOpaque()) {
result.add(entity);
} else {
((CompositeEntity) entity)
._deepOpaqueEntityList(result);
}
}
}
}
}
/** Return a description of the object. The level of detail depends
* on the argument, which is an or-ing of the static final constants
* defined in the NamedObj class. Lines are indented according to
* to the level argument using the protected method _getIndentPrefix().
* Zero, one or two brackets can be specified to surround the returned
* description. If one is specified it is the the leading bracket.
* This is used by derived classes that will append to the description.
* Those derived classes are responsible for the closing bracket.
* An argument other than 0, 1, or 2 is taken to be equivalent to 0.
* This method is read-synchronized on the workspace.
* @param detail The level of detail.
* @param indent The amount of indenting.
* @param bracket The number of surrounding brackets (0, 1, or 2).
* @return A description of the object.
* @exception IllegalActionException
*/
protected String _description(int detail, int indent, int bracket)
throws IllegalActionException {
try {
_workspace.getReadAccess();
StringBuffer result = new StringBuffer();
if ((bracket == 1) || (bracket == 2)) {
result.append(super._description(detail, indent, 1));
} else {
result.append(super._description(detail, indent, 0));
}
if ((detail & CONTENTS) != 0) {
if (result.toString().trim().length() > 0) {
result.append(" ");
}
result.append("classes {\n");
Iterator classes = classDefinitionList().iterator();
while (classes.hasNext()) {
ComponentEntity entity = (ComponentEntity) classes.next();
result.append(entity._description(detail, indent + 1, 2)
+ "\n");
}
result.append(_getIndentPrefix(indent) + "} entities {\n");
Iterator entities = entityList().iterator();
while (entities.hasNext()) {
ComponentEntity entity = (ComponentEntity) entities.next();
result.append(entity._description(detail, indent + 1, 2)
+ "\n");
}
result.append(_getIndentPrefix(indent) + "} relations {\n");
Iterator relations = relationList().iterator();
while (relations.hasNext()) {
Relation relation = (Relation) relations.next();
result.append(relation._description(detail, indent + 1, 2)
+ "\n");
}
result.append(_getIndentPrefix(indent) + "}");
}
if (bracket == 2) {
result.append("}");
}
return result.toString();
} finally {
_workspace.doneReading();
}
}
/** Write a MoML description of the contents of this object, which
* in this class are the attributes, ports, contained relations,
* and contained entities, plus all links. The links are written
* in an order that respects the ordering in ports, but not necessarily
* the ordering in relations. This method is called
* by exportMoML(). Each description is indented according to the
* specified depth and terminated with a newline character.
* @param output The output to write to.
* @param depth The depth in the hierarchy, to determine indenting.
* @exception IOException If an I/O error occurs.
*/
protected void _exportMoMLContents(Writer output, int depth)
throws IOException {
if ((depth == 1) && (getContainer() == null)) {
if (getAttribute("_createdBy") == null) {
// If there is no _createdBy attribute, then add one.
output.write(_getIndentPrefix(depth)
+ "