Overview
ROSE applications create and manipulate EXPRESS-defined data as C++ objects. This section describes the C++ classes for different EXPRESS types, show how to create and delete objects, how to get and put values using EXPRESS data dictionary calls Finally, we discuss helper objects, called managers, that hold auxiliary information.
EXPRESS-defined C++ Objects
EXPRESS-defined data is managed in memory as instances of the RoseObject class. Subclasses handle each kind of definitions. The RoseStructure class handles ENTITY data, RoseUnion handles SELECT types, and RoseAggregate handles the different List, Set, and Bag types.
Objects that are owned by a RoseDesign are considered persistent and will be written to secondary storage when the design is saved. An object may belong to at most one design at any given time. The RoseObject::design() function returns the owner of an object. You can also use RoseObject::isPersistent() to test if an object has an owner.
RoseObject * obj; if (obj-> isPersistent()) { /* obj is persistent */ } if (obj-> design() != 0) { /* obj is persistent */ }
Every object has an EXPRESS schema definition, called its domain. The domain() function gives the domain for an object and the ROSE_DOMAIN() macro gives the domain for a class. This is discussed in detail in EXPRESS Data Dictionary.
RoseObject * obj; RoseObject * tmp; RoseDomain * dom = obj->domain(); /* data dictionary def */ RoseCursor objs; objs.traverse(obj->design()); /* look at design that owns obj */ objs.domain(ROSE_DOMAIN(Point)); /* look at all Point objects */ while ((tmp=objs.next()) != 0) { /* tmp contains a Point object */ }
Each data object is also associated with C++ runtime type information, called its rosetype. This is discussed in detail in C++ Type Information. The ROSE_TYPE() macro returns the C++ runtime type information for a generated C++ class.
Entity Classes
Each EXPRESS ENTITY is a subclass of the RoseStructure C++ class. The C++ inheritance following the EXPRESS as closely as possible, with root entities as an immediate subclass of RoseStructure. If an entity has multiple supertypes, the C++ class has multiple superclasses. The C++ classes use virtual base classes to accommodate multiple inheritance.
Each class has get and put functions for each of its normal stored attributes. Handling of attribute types and values is discussed below. Functions for DERIVE and INVERSE attributes are not generated, nor are WHERE or UNIQUE clauses.
-- EXPRESS definition ENTITY Point; x : REAL; y : REAL; END_ENTITY; // C++ generated code class Point : virtual public RoseStructure { /* Attribute get/put functions */ double x(); void x (double ax); double y(); void y (double ay); Point (); /* Default constructor */ };
The default constructor for each class. This constructor sets each object and string attribute to null, integer and reals to zero, booleans and logicals to false.
Consult Entity Classes for more information on the access and update functions for entity types.
EXPRESS AND/OR entity types are handled as C++ classes that inherit from each of the multiple types in the complex entity. They have no new attributes, and are specified in the working set files as described by Working Sets and Best-Fit Classes.
Attribute Types and Values
Get and put functions use the following C++ types for passing values. Attributes with an ENTITY, SELECT, or aggregate type use a pointer to a previously created data object. The EXPRESS simple types (integer, real, boolean, enums, and strings) are all copied into their attribute.
Each data type also has a NULL value
defined that will be written to a Part 21 file as the no value
symbol $
. The null value for objects and strings is
just a NULL pointer.
EXPRESS | C++ |
---|---|
INTEGER | int |
NUMBER | double |
REAL | double |
BOOLEAN | RoseBoolean |
LOGICAL | RoseLogical |
STRING | RoseSTR (typedef char* ) |
BINARY | RoseBinarySTR (typedef char* ) |
ENUM | C++ enum, see below |
ENTITY | object pointer |
SELECT | object pointer |
AGGREGATE | object pointer |
STRING values are UTF-8 encoded, null-terminated C
strings. Put functions a copy the string and the string
returned by a Get function should be treated as read-only.
The library converts between UTF-8 in memory and the \X2\
or \X4\
encoding used in Part 21 files. The library will
also convert Part 21 strings containing ISO 8859 \S\
notation characters into UTF-8 strings in memory. All functions that
take filenames expect UTF-8 strings for wide character filenames.
Unix applications should already be using UTF-8 strings. On
Windows, programs can use MultiByteToWideChar()
and WideCharToMultiByte() with the CP_UTF8
flag to
convert between wchar_t
and UTF-8 strings where
needed.
BOOLEAN and LOGICAL types use a ROSE_TRUE
value
defined to be one and a zero ROSE_FALSE
value.
The ROSE_UNKNOWN
is a nonzero value, so it tests
as not false which matches EXPRESS semantics.
BINARY is also a char* stored in the string encoding used by the Part 21 file format. We provide the RoseBinaryObject helper class to handle bit and byte level access to the data.
Select Classes
Each EXPRESS SELECT is an immediate subclass of the RoseUnion C++ class. There is no extra inheritance. A select is a strongly-typed union that holds a single value.
Each class has a get, put, and test function for kind of value the select can hold. The functions are named based on the type of the value, with an underscore prefix to avoid type name conflicts. An instance can only hold one value at a time and putting a new value will replace the previous one.
-- EXPRESS definition TYPE Shape = SELECT (Point, Circle, Line); END_TYPE; // C++ generated code class Shape : public RoseUnion { public: /* Attribute get/put/test functions */ Point * _Point(); void _Point (Point * val); RoseBoolean is_Point(); Circle * _Circle(); void _Circle (Circle * val); RoseBoolean is_Circle(); Line * _Line(); void _Line (Text * val); RoseBoolean is_Line(); Shape (); /* Default constructor */ };
In STEP models have many nested selects that only contain object values. Rather than use the generated test functions, we recommend using the rose_get_nested_object() and rose_put_nested_object() functions to work directly with the underlying contents of the selects.
In the example below, product_definition_shape has a definition attribute that is usually a product_definition, but is wrapped in two layers of select objects.
-- Nested EXPRESS selects, characterized_item is also a select */ TYPE characterized_definition = SELECT (characterized_item, characterized_object, characterized_product_definition, shape_definition); TYPE characterized_product_definition = SELECT (product_definition, product_definition_occurrence, product_definition_relationship, product_definition_relationship_relationship); END_TYPE; // C++ example stp_product_definition_shape * pds; // Dig through characterized_definition, and a nested // characterized_item or characterized_product_definition // to get what it contains RoseObject * ref = rose_get_nested_object(pds->definition()); if (ref->isa(ROSE_DOMAIN(stp_product_definition)) { stp_product_definition * pd = ROSE_CAST(stp_product_definition,ref); } else { // some other type of entity, do something else }
Aggregate Classes
Each EXPRESS aggregate becomes a subclass of
RoseAggregate. There are separate
subtypes for lists, sets, bags, and arrays. Most of the classes will
handle object types so they will be subtypes of RosePtrList
,
RosePtrSet
, etc.
A class named <agg>Of<type>
is generated
for entities, selects, enumerations, and nested aggregates.
Aggregates for the various primitive types are already present in the
ROSE library. For example, LIST OF LIST OF LIST OF XYZ will
produce three
classes: ListOfListOfListOfXYZ
, ListOfListOfXYZ
,
and ListOfXYZ
All of the aggregates are implemented as simple arrays, but EXPRESS declares the following semantics for them:
- List — Has ordered elements, expands and contracts, no null elements.
- Bag — Unordered elements, expands and contracts, no null elements.
- Set — Unordered elements, no duplicates. Expands and contracts, no null elements.
- Array — Ordered and fixed size. This is the only aggregate in which null values are allowed.
All aggregates have the same basic access functions:
first(); /* return first element */ last(); /* return last element */ operator[] (index); /* return element at index */ get (index); /* same */
The update functions differ from aggregate to aggregate. All of the aggregates support a put() function, but some support additional functions:
put (value, index); /* all */ add (value); /* only lists, sets, and bags */ addIfAbsent (value); /* only lists, sets, and bags */ insert(value, index); /* only lists */ removeAt (index); /* all */ removeValue (value); /* all */ emptyYourself(); /* all */
In addition, there are functions for searching and controlling the aggregates:
size(); size (newsize); /* set size - only arrays */ capacity(); capacity (newcap); unsigned find (value); RoseBoolean contains (value);
Enumerations
Each EXPRESS ENUMERATION has a matching C++ enum
. An
EXPRESS enumeration is a separate name space, while C++ uses one name
space across all enumerators. To avoid conflicts, the name of the enum
is prepended to each value, separated by an underscore. Consider the
EXPRESS:
SCHEMA example_schema; TYPE colorType = ENUMERATION OF (red, green, blue); END_TYPE; END_SCHEMA;
The C++ enum will be:
enum colorType { colorType_NULL = NULL_ENUM, colorType_red = 0, colorType_green, colorType_blue };
Each enum also has a strongly-typed null value, which is equivalent
to the ROSE_NULL_ENUM
constant.
Creating Objects
The ROSE library has several ways to create objects. When EXPRESS
C++ classes are available, you can use the new operator to
create an instance that is not owned by
any RoseDesign.
The pnew and pnewIn() operators
create persistent
objects that are owned by
a RoseDesign, so they can be saved to a STEP file.
The pnew operator creates the object in
the current design
while pnewIn() takes the design pointer as an argument.
The pnew and pnewIn() operators can only be used with
classes derived from RoseObject.
Point * p1 = new Point; /* non-persistent point */ Point * p2 = pnew Point; /* persistent point in current design */ RoseDesign * d = new RoseDesign (picture); Point * p3 = pnewIn (d) Point; /* persistent point in picture */
Late-bound applications that do not have EXPRESS classes use the RoseDesign::pnewInstance() function to create persistent objects using the EXPRESS data dictionary. The functions take a type name or a RoseDomain object which describes a type.
RoseDesign * d = new RoseDesign (picture); RoseObject * obj; RoseDomain * pnt_domain = d-> findDomain (point); obj = d-> pnewInstance (point); /* new point in design */ obj = d-> pnewInstance (pnt_domain); /* same */
Deleting Objects
When an object is no longer needed, it can be removed in two
different ways. A persistent object can be moved to a special
trashcan
design, then deleted later when the trash is
emptied
. Alternatively, any object can be deleted
immediately using the C++ delete operator.
The advantage of the trashcan approach is that it can eliminate
dangling references
to deleted objects. When the trashcan
is emptied, all of the persistent objects in memory are examined and
references to any non-persistent or trashed objects are set to
null. When an object is deleted using the C++ delete operator,
no reference checking is done; other objects may hold pointers to
freed memory. Such dangling references could cause crashes or other
such problems elsewhere in the application, particularly when writing
designs to secondary storage.
Objects can be moved to the trash with the rose_move_to_trash() function. If an application wishes to do something fancy, it can access the trash design directly using the rose_trash() function.
When it is time to reclaim memory, call rose_empty_trash(). This traverses every object in memory, nulling pointers to nonpersistent or trashed objects. Then it deletes all of the trashed objects.
/* Trashcan delete functionality */ extern void rose_move_to_trash (RoseObject * obj); extern void rose_empty_trash(); extern RoseDesign * rose_trash(); /* trash design */ /* example */ RoseDesign * d; Point * p_pnt = pnewIn(d) Point; rose_move_to_trash (p_pnt); /* moves from design, to trash */ rose_empty_trash(); /* clears bad pointers, deletes obj */
The C++ delete operator works on both persistent and non-persistent objects. When a persistent object is deleted, it is removed from its owner RoseDesign. The ROSE library does not check for dangling references from other objects, so the application should remove any references to the purged object before deleting it.
RoseDesign * d; Point * np_pnt = new Point; Point * p_pnt = pnewIn(d) Point; delete np_pnt; /* normal C++ deletion */ delete p_pnt; /* removes from design, then normal C++ deletion */
The delete operator has no effect when passed a null pointer.
Moving and Copying Objects
Persistent objects belong to only one design at a time, although
they may be moved between designs
with rose_move_to_design()
or RoseObject::move(). The
rose_move_to_design() function moves a single object,
while RoseObject::move() is a layer on top that can deep
move
a tree of objects. There is also
a rose_move_to_section()
function for moving between RoseDesignSections. This is discussed
in Design Sections.
These functions can make persistent objects non-persistent and vice versa. When a non-persistent object is moved to a design, it will gain an EXPRESS data dictionary definition and possibly a RoseOID. If an object is made non-persistent by moving it to null, it will lose its OID and EXPRESS definition. If the object was read into memory as a best-fit class, any extra attributes that do not match C++ attributes will be lost. While an object is persistent, these attributes are available through the late-bound access and update functions.
If a persistent object is simply moved from one design to another, it does not lose its OID, EXPRESS definition, or extra attribute information.
RoseDesign * d1 = new RoseDesign (picture); RoseDesign * d2 = new RoseDesign (gadget); Point * np_pnt = new Point; Point * p_pnt = pnew Point; /* owned by default design */ np_pnt-> move (d1); /* make persistent, owned by d1 */ p_pnt-> move (NULL); /* make non-persistent */ rose_move_to_design (np_pnt, d1); /* same operations using */ rose_move_to_design (p_pnt, NULL); /* rose_move_to_design */ p_pnt = pnewIn (d1) Point; /* new object, owned by d1 */ p_pnt-> move (d2); /* now owned by d2 */
The RoseObject::copy()
function duplicates an object in the same design or in a different
design. By default, attribute values of the new object are copies of
the originals. This means, for example, that a copy of a circle
object would refer to the same center point object as the original.
It is possible to create a deep copy
of the circle; i.e.,
a copy of the circle, and a copy of the objects the circle refers
to.
RoseDesign * d; Circle * c1 = pnewIn(d) Circle; Point * p1 = pnewIn(d) Point; c1-> radius (5.9); c1-> center (p1); /* shallow copy: c2 radius is 5.9, and center is p1 */ RoseDesign * new_design = new RoseDesign (gadget); Circle * c2 = ROSE_CAST (Circle,c1->copy(new_design));
For a deep copy,
specify a depth argument to the copy
operation. This signifies how many levels of references to follow. A
value of zero indicates no references; i.e shallow copy.
A
value of one follows one level; i.e. the center point. A value of two
would copy any objects that the center point referenced.
Circle * c2 = ROSE_CAST (Circle,c1->copy(new_design, 1)); /* deep copy: c2 radius is 5.9, and center is a copy of p1 */
Data Dictionary Based Access and Update
The early-bound generated get and put functions are convenient and benefit from strong compiler type checking. However, an application can also use the late-bound data-dictionary access and update functions defined by RoseObject.
The RoseObject class defines get and put functions for each value type. The parameters differ slightly depending upon whether the object being interrogated is an entity, select, or aggregate. The basic form of the put function is:
put<type> (value, attribute); /* entity */ put<type> (value, att_for_type); /* select */ put<type> (value, index); /* aggregate */
The basic form of the get function is
value = get<type> (attribute); /* entity */ value = get<type> (att_for_type); /* select */ value = get<type> (index); /* aggregate */
The get and put functions for entities accept the name of an
attribute as well as the RoseAttribute data dictionary object
that describes the attribute. The functions for selects are similar,
but each attribute represents a type allowed by the select. To avoid
name conflicts, the attributes of a select are have the same name as
their associated type, but are prefixed by an underscore. For
example, to set a select object to hold a floating point value 5.0 of
type length_measure,
one would use:
obj-> putDouble (5.0,_length_measure);
The get and put functions for aggregates accept unsigned integer index. If an application tries to put a value beyond the end of an aggregate, the aggregate will be resized and the value will be appended. Regardless of how much larger the index is, the aggregate will only be expanded by one element. So given a list with six elements, the following calls all have the same effect:
/* given list with 6 elements */ list-> putString (foo, 7); /* expands list by one element */ list-> putString (foo, 7000); /* expands list by one element */
The put and get functions for the different types are as shown below.
putObject (RoseObject* ...); RoseObject* getObject (...); putInteger (int ...); int getInteger (...); putDouble (double ...); double getDouble (...); putString (const char * ...); const char * getString (...); putBinary (const char * ...); const char * getBinary (...); putBoolean (RoseBoolean ...); RoseBoolean getBoolean (...); putLogical (RoseLogical ...); RoseLogical getLogical (...);
The putObject() and getObject() functions expect or return a RoseObject pointer. Since RoseObject is the superclass of all STEP data objects, these two functions can be used to assign and retrieve entity objects, aggregate objects, and select objects.
There are no special functions for enumeration types. Instead, use
the putString() and getString() functions. The integer
access and update functions will also work, but should be avoided for
clarity reasons. The application should assign the string name of the
enumerator. For example, if we have an enumerator that defined
elements for made
and bought
:
TYPE make_or_buy = ENUMERATION OF (made, bought); END_TYPE; ENTITY part; source: make_or_buy; . . . END_ENTITY;
We might see the following code in early and late-bound applications. Note that make_or_buy_made is an element of the C++ enum make_or_buy that would be generated by the EXPRESS compiler:
partobj-> putString (made,source); /* late-bound */ partobj-> source (make_or_buy_made); /* early bound */
Manager Objects
Managers annotate a RoseObject with extra information. Your applications can define RoseManager subclasses for storing translation results, reverse-pointers, and other cached values. Information in managers will not be written to a Part 21 file.
An object can have many different managers, but it can only have one of a given type. A manager can be associated with any data object and can be used by late-bound code without generated C++ classes.
The ROSE library adds managers to hold database information such as overflow attributes and external references. Both persistent and non-persistent objects can have managers.
Defining a Manager Class
Declare a subtype of RoseManager with DECLARE/IMPLEMENT macros for a unique type identifier and optional find() and make() helper functions. Include any other member variables or functions that you need. Below is a subclass that holds a single count variable.
/* in your header file */ class CountManager : public RoseManager { public: unsigned long m_count; CountManager() { m_count=0; } ROSE_DECLARE_MANAGER_COMMON(); ROSE_DECLARE_MANAGER_FIND(CountManager); ROSE_DECLARE_MANAGER_MAKE(CountManager); }; /* In your cxx file */ ROSE_IMPLEMENT_MANAGER_COMMON (CountManager) ROSE_IMPLEMENT_MANAGER_FIND (CountManager) ROSE_IMPLEMENT_MANAGER_MAKE (CountManager)
Finding and Using Managers
To add a manager to an object, create a instance of the manager on the heap (with new) then RoseObject::add_manager() to associate it with an object.
The macros in the class above define a CountManager::type() static function that returns a unique class identifer. To see if an object is associated with a manager of a given type, call RoseObject::find_manager() and pass in this type()value. Similarly, call RoseObject::remove_manager() to find and delete a manager of a particular type.
Usually code will check to see if a manager is present and then add one if it is not as shown in the code below:
cartesian_point * pt; // find a previously added manager on the point. If none // is present, then add a new one CountManager * mgr = CountManager::make(pt); // use or change a count value on the manager mgr-> m_count++;
When a RoseObject is deleted, all managers attached to it are also deleted. We can delete one manager explicitly as shown below:
/* remove and delete any CountManager on the object */ pt-> remove_manager (CountManager::type());
The ROSE_MGR_UNKNOWN value signifies an unknown manager type. Several other RoseManagerType values are reserved for system-defined manager classes.
/* The RoseManagerType is for identifying manager classes. */ typedef unsigned RoseManagerType; #define ROSE_MGR_UNKNOWN (RoseManagerType) 0;