5.1 Overview
The ROSE library provides a functions for applications to traverse, search, or otherwise locate objects in memory. Since a RoseDesign is the basic unit of I/O for STEP data, most of these services operate within the scope of a single design.
Some functions search for objects based upon particular criterion. Applications can take advantage of the EXPRESS usedin() function, objects organized by type, named objects, STEP entity identifiers from Part 21 files, ROSE universal object identifiers (OIDS), and objects marked as the "root" of a design. In addition, applications can use design cursors and special marking functions to simplify traversals. Each service is described in the following sections.
C++ is a strongly typed language, so pointer type casting is a major concern when working with objects returned by the traversal functions. Since traversal operations must handle all types of objects, most function return values are typed as RoseObject pointers. These very general pointers must often be cast to an appropriate type before using them. Section 5.2 describes the casting services provided by the ROSE library.
5.2 Pointer Type Casting
Since C++ is a strongly typed language, an application may be required to cast pointers to appropriate types before using them. Since traversal operations often return values typed as general RoseObject pointers, an application must cast these pointers to a more specific type before using them. Unfortunately, C++ support for casting within inheritance hierarchies is limited. C++ allows casting from a subclass to a superclass, but does not always allow casting from superclass down to a more specific class. Even when it is allowed, the compiler cannot check to make sure that the cast is valid. Figure 5.1 shows the casting capabilities of C++.
The following code fragments show some C++ casts. If cartesian_point had a virtual base class (needed for multiple inheritance), the last cast would not be allowed by the compiler:
RoseObject * obj;
cartesian_point * pt = pnew cartesian_point;
obj = pt; /* valid, automatic specific to general */
obj = (RoseObject *) pt; /* valid, explicit specific to general*/
pt = obj; /* invalid, automatic general to specific */
pt = (cartesian_point *) obj; /* Disallowed if class has virtual base
classes. Otherwise allowed but validity
unchecked by compiler. */
The ROSE_CAST() macro allows the full range of casting, even on classes with virtual base classes. It uses the ROSE type information to provide run-time type checking. The full range of casting is shown in Figure 5.2.
The ROSE_CAST() macro takes the name of a type and a pointer value. It returns a pointer value of the appropriate type, with offsets adjusted accordingly to account for virtual base classes. The following example shows how to cast the results of a query down to a more specific type.
RoseCursor objects;
RoseObject * obj;
objects.traverse (design);
objects.domain (ROSE_DOMAIN (cartesian_point));
while (obj = objects.next())
{
cartesian_point * pt = ROSE_CAST (cartesian_point, obj);
...
}
The RoseCursor::next() function returns a RoseObject pointer. Since we have told the cursor to only return cartesian points, we are confident that object is an instance of the more specific cartesian_point class. We use ROSE_CAST() to cast the pointer from RoseObject* to cartesian_point* .
If we tried to cast an object that was not a cartesian_point , the cast would return a null pointer and issue a warning. If we were not sure of the object type, we could check first using the RoseObject::isa() function.
5.3 EXPRESS Usedin Function
The EXPRESS usedin() function is an important navigational mechanism. This function searches a data set for references to a particular object. Given an object and description of the attribute relation to look for, this function searches the data set for all instances that refer to the specified object through the given attribute relation. The structure of the STEP information models make this operation very valuable to an application programmer.
The primary design goal of a STEP APs is to document the structure of product information and relationships in a clear and unambiguous manner. Unfortunately, the clearest way to document a relationship is not always the way an application programmer would prefer for easy access.
An information modeler must capture existence and cardinality constraints of relationships. As an example, let us consider a one-to-many relationships, such as an assembly that has some parts. We would like to capture the knowledge that assemblies have associated simple parts, no two assemblies share parts, and no parts can exist without a assembly. One model for this is to have an "assembly" object refer to its "part" objects. The schema below shows this model.
ENTITY assembly;
contents : SET [1:?] OF part;
END_ENTITY;
ENTITY part;
END_ENTITY;
Unfortunately, while this model matches the way we might wish to traverse the structure -- from assembly to part -- it does not completely model the constraints. We could construct stand-alone parts or parts that are owned by more than one assembly. Of course, we might argue that a disciplined user would not do this, but the goal of information modeling is to capture constraints within the model, not to rely on auxiliary knowledge. A better model would be:
ENTITY assembly;
END_ENTITY;
ENTITY part;
owner : assembly;
END_ENTITY;
This is much better, since a part must have one and only one owner. Unfortunately, we have no direct navigation path from assembly to part. The EXPRESS usedin() function lets us perform a query to get that information:
ENTITY assembly;
DERIVE
contents : SET [1:?] OF part := USEDIN (SELF, 'part.OWNER');
END_ENTITY;
ENTITY part;
owner : assembly;
END_ENTITY;
And in fact, the usedin() function is similar to constructing an impromptu inverse attribute. The schema would be better written as:
ENTITY assembly;
INVERSE
contents : SET [1:?] OF part FOR owner;
END_ENTITY;
ENTITY part;
owner : assembly;
END_ENTITY;
This pattern appears throughout the STEP information models. Consequently, application programmers may need the usedin() function. This is available through RoseObject::usedin(). When called on an object, this function will search for references in the design that owns the object.
The usedin() function takes several arguments. The first is a RoseDomain data dictionary object which describes the type of object to search. If this is null, all entity instances ( RoseStructure objects) within the design will be searched. The second argument is a RoseAttribute object describing the attribute to examine. If this is null, all attributes of an object will be examined. Finally, the function expects a RoseAggregate object to pass back the pointers to the matching objects. If this is null, a ListOfRoseObject will be created by the function. It is the programmer's responsibility to free this object when it is no longer needed. The example below shows how one might use usedin() with the assembly schema.
unsigned i, sz;
ListOfpart contents;
assembly * aobj
RoseDomain * search_domain;
RoseAttribute * search_att;
search_domain = ROSE_DOMAIN (part);
search_att = search_domain-> findTypeAttribute ("owner");
aobj-> usedin (search_domain, search_att, &contents);
for (i=0, sz=contents.size(); i<sz; i++)
{
/* loop over the part objects */
}
Note the stack-based "contents" list in this example. Since we only need a temporary, non-persistent list, we chose to create the object on the stack, rather than creating it on the heap with new or pnew . This is a useful technique because the C++ compiler will allocate and deallocate the object when control flow enters and leaves the scope of the declaration. This is only useful for transient, non-persistent objects. Persistent objects must always be created on the heap with pnew .
An application can accumulate objects found by several different calls into a single list. If the aggregate passed to usedin() already has objects in it, the function will append the objects it finds to the aggregate.
5.4 Traversing Objects Within a Design
The ROSE library contains several cursor classes for quick traversals over the objects within a design. They traverse a design "in place" using the internal object index, rather than by building a list of pointers.
Cursors
The RoseCursor class can be used to traverse all objects in a design, or just objects of a specific type. The easiest way to use a cursor is to create it on the stack. This way, the C++ compiler can take care of allocating and deallocating the object.
The RoseCursor::traverse() function specifies a design to traverse. By default, a cursor examines all data objects in a design. Advanced functions are available to select header objects, system objects and others.
The cursors can select objects of a particular type using the RoseCursor::domain() function. This sets the cursor so that it returns all objects of the type, including all subtypes. You can also set the cursor to traverse objects of exactly one type (with no subtypes) using the exact() function. If no types are specified, the cursor will return all objects in a design.
The RoseCursor::next() and previous() functions iterate over the objects. When no more objects remain, these functions return null. The RoseCursor::rewind() and fastforward() functions reset the cursor for subsequent passes.
The following example traverses all of the Point objects in a design. The next() function returns objects as a RoseObject pointer, so we must use the ROSE_CAST() macro to cast the return value down to a more specific pointer type.
RoseCursor objects;
RoseObject * obj;
objects.traverse (design);
objects.domain (ROSE_DOMAIN (Point));
while (obj = objects.next())
{
Point * pt = ROSE_CAST (Point, obj);
...
}
This returns all of the Point objects as well as any subtypes, such as cartesian points. The body of the loop casts the object to the specific C++ type before operating on it. If we did not want instances of any subtypes, we could use RoseCursor::exact() instead of the domain() function:
RoseCursor objects;
objects.traverse (design);
objects.exact (ROSE_DOMAIN (Point));
while (obj = objects.next())
{
Point * pt = ROSE_CAST (Point, obj);
...
}
This example only returns Point objects. Instances of subtypes, such as a cartesian point class, would not be returned by the cursor. Cursors can also use a filter function to decide which types to return. See RoseCursor::type_filter() for more information on this capability.
Marking Objects During Traversal
Many traversal algorithms require some way to mark objects as "visited." This is used to detect circular references or to avoid unnecessary search. The RoseInterface class provides a way to mark persistent objects.
Before marking an object, you must first indicate that you will be traversing objects with RoseInterface::beginTraversal(). This grabs a "marker" that will be used when you visit objects. When finished, you must release the marker with the RoseInterface::endTraversal(). These begin/end blocks cannot be nested. You must close one traversal before starting a new one. Note that some system functions, such as save() and copy() make use of this facility, so you can not call them during a traversal. The RoseInterface::isTraversing() function tests if a begin/end block is in effect.
ROSE.beginTraversal();
. . .
ROSE.endTraversal();
Within the traversal block, you can mark objects with RoseObject::visit(). The RoseObject::wasVisited() function tests if an object was visited. The example below shows a simple traversal using a RoseCursor object. We make sure that each object was not already visited, then we visit it and do some operation.
RoseCursor objs;
RoseObject * obj;
. . .
ROSE.beginTraversal();
while (obj = objs.next())
{
if (!obj-> wasVisited())
{
obj-> visit();
/* do other things */
}
}
ROSE.endTraversal();
The check with wasVisited() in the above example is not really needed. If we were making a single pass through the objects, we probably would not encounter objects that were already marked. However, if we made several passes, or were using multiple cursors, we might encounter such a case. The RoseCursor::nextMarked() / nextUnmarked() functions are useful in such a case. Rewriting the previous example using this function:
RoseCursor objs;
RoseObject * obj;
. . .
ROSE.beginTraversal();
while (obj = objs.nextUnmarked())
{
obj-> visit();
/* do other things */
}
ROSE.endTraversal();
This version is a little simpler and easier to understand. It is important to note that although traversal blocks cannot be nested, it is possible to have several cursors active within a single traversal block. The restriction is on the marking of objects, not on the traversal of them.
Design Sections
The objects within a RoseDesign are divided into several groups called design sections. Each design may contain several sections, one or more for the data objects, one for physical file header objects, and one for internal system objects. These sections correspond to the sections of a Part 21 physical file and are represented by instances of the RoseDesignSection class.
The RoseDesign::sections() function returns a linked list of RoseDesignSection objects belonging to the design. The list can be traversed using the RoseDesignSection::next() function.
When a design is written as a ROSE working form file all three types of sections are preserved. STEP physical files only preserve the header and data sections. The system section, which contains name table and other information is not preserved. The three types of sections are:
- Data Section -- Contains normal STEP data objects can be found. This corresponds to the DATA section of a Part 21 file. A design may have more than one data section when using multiple data section Part 21 files. Refer to AP Interoperability Features for more information about multiple data sections.
- Header Section -- This is where the Part 21 HEADER objects can be found. This can be accessed with the RoseDesign::header_section() function.
- System Section -- This contains objects specific to ROSE working form files, such as the ROSE name index, and associated database information. The system section is never written to a STEP Part 21 file, but it is written to ROSE working form files. This can be accessed with the RoseDesign::system_section() function.
Each design has a default section. Object created with pnew or pnewIn(RoseDesign*) will be placed in the default section. The default section is usually the first data section in the design, but it can be set to one of the others. This can be accessed with the RoseDesign::dflt_section() function. Objects can be created in a specific section using the pnewIn(RoseDesignSection*) operator.
RoseDesign * d;
Point * pt;
pt = pnew Point; // in dflt section of current design
pt = pnewIn (d) Point; // in dflt section of design 'd'
pt = pnewIn (d-> dflt_section()) Point; // in default section
pt = pnewIn (d-> system_section()) Point; // in system section
The RoseCursors understand and can take advantage of the design sections. The RoseCursor::traverse() function accepts individual sections as well as designs. To find the number of objects in a section, use the RoseDesignSection::size() function.
Objects can be moved between design sections using the rose_move_to_section() function. Moving an object to a null section makes it non-persistent, just like moving an object to a null design.
5.5 Backpointers
The references in STEP Application Protocols often point is a direction that is inconvenient for application programmers to use. Thus, the ROSE library includes a feature to compute the back pointers to simplify object traversal.
Before using back pointers, you must call the rose_compute_backptrs() function, passing the RoseDesign for which you want the back pointers computed. This traverses all instances within the design and builds a list of backpointers on each instance. When you are finished, call rose_release_backptrs() to free the memory used to hold the back pointers.
Once the backpointers have been computed, you can retrieve them for any instance by calling rose_get_backptrs() on the object you need the back pointers to. This returns a RoseBackptrs object which can then be used to find all the references to the target object.
For example, you can use the following code to find all the instances that refer to the object obj .
RoseDesign *des = obj->design();
rose_compute_backptrs(des);
RoseBackptrs *bp = rose_get_backptrs(obj);
for (unsigned i=0; i<bp->size(); i++) {
RoseObject * ref = bp->get(i);
/* Ref points to obj -- go something with it here */
}
You can also use a RoseBackptrCursor to limit the scope of a search to a particular attribute. For example this can be used to efficiently compute inverse attributes.
RoseObject * product;
RoseAttribute * att = des->findDomain("product_definition_formation")
->findTypeAttribute("of_product");
RoseBackPtrCursor cur;
cur.traverse(product);
cur.attribute(att);
RoseObject *pdf)
while (pdf = cur.next()) {
/* Do somethig with the product_definition_formation in pd */
}
5.6 Reference Counting
If you only need to know whether an object is referenced or not, a full set of backpointers is not necessary. The ROSE library can compute reference counts for objects in a more space efficient way than computing backpointer lists.
The procedure is similar to the backpointer functions described above. An application computes the reference counts for a design, then processes the objects using the counts, then finally releases the counts and cleans up the space.
Use rose_compute_refcount() to initializes the counts and rose_release_refcount() to clean up afterwards. Applications can then use rose_refcount() in the middle to test a particular object. The example below looks for unreferenced objects in a design. Note that this examines all types of objects: entities, selects, and aggregates.
RoseDesign * design;
RoseObject * obj;
RoseCusrsor objs;
rose_compute_refcount(design);
objs.traverse(design);
while (obj=objs.next()) {
if (!rose_refcount(obj)) printf ("Unreferenced obj!\n");
}
rose_release_refcount(design);
If you modify the objects after computing the reference counts, the counts are not automatically updated. However there are a number of functions that you can use to maintain the reference counts when adding or deleting objects.
The rose_refcount_inc() and rose_refcount_dec() functions increment or decrement the count of an individual object and return the new count value. If you want to set the reference count to a specific value, the rose_refcount_put() function can do so.
If you are deleting an object, you can decrement the reference counts of the objects referred to by its attributes using the rose_refcount_dec_atts() function. This only goes one level deep, but the rose_refcount_recursive_dec_atts() function continues to recursively decrement attributes of a sub-object if it becomes unreferenced.
Using this function, you could write a function to cleanly delete a tree of objects as shown below. Objects would only be deleted if they were not referenced elsewhere in the data.
ListOfRoseObject objs;
unsigned i, sz;
rose_refcount_recursive_dec_atts(obj, &objs);
for (i=0, sz=objs.size(); i<sz; i++) {
rose_move_to_trash (objs[i]);
}
rose_move_to_trash (obj);
5.7 Searching for Objects by Type
The RoseDesign class has several findObjects() functions to search a design and fill an aggregate with objects matching some type criteria. These functions are just wrappers around RoseCursor , so the recommended approach is to just use a cursor directly as described in Traversing Objects Within a Design
The aggregate for return values is handled in the same way as with the usedin() function. Pointers to the matching objects are appended to the aggregate. If an aggregate is not provided, a ListOfRoseObject will be created and the programmer must delete this list when it is no longer needed.
The search criteria come from a combination of arguments. All of the functions examine the type of the return aggregate and return objects matching that type (or its subtypes). For example, if a function were passed a ListOfPoint , only objects of the C++ class Point or its subclasses would be put into the list. Several of the variants also take a RoseDomain object. In this case objects must match both the EXPRESS type given by the domain and the C++ class restrictions implied by the return list.
If the number of objects found is expected to be large, you may be better off writing a traversal loop as outlined in Section 5.4. Traversing a design with a cursor does not require allocating a large aggregate, so it is far more space efficient than calling findObjects() and iterating over the results. Constructing a list with findObjects() may still be useful, however, in cases where you iterate many times over the same set of objects.
The RoseDesign::findObjects() function takes a list and possibly a domain object:
RoseAggregate * RoseDesign::findObjects ( RoseDomain * domain /* may be omitted */ RoseAggregate * return_list );
The following example shows the use of the function to find all cartesian_point objects in a design called "drawing." Note the use of an aggregate on the stack. The ListOfcartesian_point access functions insure the correct pointer type, so additional casting is not needed.
unsigned i, sz;
List (cartesian_point) points;
RoseDesign * d;
d->findObjects (&points);
for (i=0, sz=points.size(); i<sz; i++)
{
/* loop over the points with points-> get(i) */
}
In this example, the list will be filled with all objects that match the list type. If our design contained a cartesian_point subclass, such as anchored_point , these instances would also be returned. To find only anchored_points , we would pass in a ListOfanchored_point , or we could put an additional type constraint on the findObjects() call, as in the example below:
unsigned i, sz;
List (cartesian_point) points;
d->findObjects (ROSE_DOMAIN(anchored_point), &points);
for (i=0, sz=points.size(); i<sz; i++)
{
/* loop over the points with points-> get(i) */
}
5.8 Searching for Individual Objects
The ROSE library provides several indexing mechanisms for marking individual objects. Except as noted, these mechanisms are useful only while the objects are stored as ROSE working form files. STEP Part 21 files do not support this extra index information. The three main indices associate objects with names, universal object identifiers (OIDS), and STEP Part 21 entity identifiers.
STEP Part 21 Entity Identifiers (EIDS)
The entity identifiers for objects that have been read in from a Part 21 physical file are available to an application program. The entity identifiers are the #number tags that are associated with each entity instance in a physical file. These identifiers are not meant to be persistent and may change each time the file is written.
There is currently no search function for these identifiers, although a simple linear search could be written using the traversal mechanisms in Section 5.4. The entity identifiers are accessed through the RoseObject::entity_id() function.
unsigned long RoseObject::entity_id();
Note that entity identifiers are only available for entities ( RoseStructure objects) that are read from STEP physical files. This field will be zero for aggregates and selects read from physical files, as well as all objects read in from ROSE working form files. All entities are renumbered when written back out to disk.
Objects by Name
Every RoseDesign object maintains an index of name/object pairs. Applications can use this name index to tag and locate specific objects. The name index is preserved as long as a design is written to ROSE working form files, but is lost when a design is written to a STEP Part 21 file. This mechanism is used extensively to find definitions within the ROSE compiled schema files.
An object can have many different names, but a name can be associated with only one object. The RoseDesign::addName() function associates an object with a name. Names can be removed with RoseDesign::removeName() . The name index is implemented as a DictionaryOfRoseObject object returned by RoseDesign::nameTable() . Refer to RoseDictionary for more on dictionaries.
Objects can be searched for with RoseDesign::findObject() . These functions return a RoseObject pointer, so an application must use the ROSE_CAST() macro to convert it to a more specific type.
cartesian_point * t;
. . .
ROSE.addName ("top", t);
. . .
t = ROSE_CAST (cartesian_point,ROSE.findObject ("top"));
The RoseInterface::findObjectInWorkspace() function will search all designs in memory. If many designs have an object with the name, the function will return the first match. The order in which the designs are searched is not specified.
Root Object
A design can have a top-level "root" object. If an application needs a one starting object for traversals, it can mark that object as the "root" of a data set with RoseDesign::rootObject() function. The access functions return a RoseObject pointer, so an application must use the ROSE_CAST() macro to convert it to a more specific type.
Picture * pict;
. . .
ROSE.setRootObject (pict);
pict = ROSE_CAST (Picture, ROSE.rootObject ());
Only one root object is allowed per design. The "root" marker is preserved as long as a design is written to ROSE working form files, but is lost when a design is written to a STEP Part 21 file.
ROSE Universal Object Identifiers (OIDS)
ROSE Object Identifiers ( RoseOIDs ) are 160 bit (20 byte) quantities that uniquely identify an object across space and time. These identifiers allow the ROSE system to locate objects across inter-file references. Every persistent object has a RoseOID that can be retrieved with the RoseObject::oid() function. Because of the structure of these identifiers, we can use a shorthand 32 bit representation for them while in main memory. This is the value that is passed to functions.
These identifiers are only preserved by ROSE working form files. When a STEP Part 21 file is read, the objects are assigned new RoseOIDs . Part 21 files must be entirely self contained, or must use a higher semantic mechanism for resolving object references.
RoseOIDs are used by ROSE internals, but may not be particularly useful to application developers. The RoseDesign::findObject() function can search for an object with a particular RoseOID . In addition, the RoseInterface::findObjectInWorkspace() function will search all designs in memory and return the first match. It is possible for two objects in different designs to have the same OID if they were copied from the same object. The order in which the designs are searched is not specified.