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.

EXPRESSC++
INTEGERint
NUMBERdouble
REALdouble
 
BOOLEANRoseBoolean
LOGICALRoseLogical
 
STRINGRoseSTR (typedef char*)
BINARYRoseBinarySTR (typedef char*)
 
ENUMC++ enum, see below
ENTITYobject pointer
SELECTobject pointer
AGGREGATEobject 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:

  • RoseObject
    • RoseAggregate
      • RoseList
        • ListOfDouble
        • RosePtrList
          • ListOfRoseObject
          • ListOfEntityA
          • ListOfEntityB
          • ListOfEntityC
          • ...
      • RoseBag
        • BagOfDouble
        • RosePtrBag
          • BagOfRoseObject
          • ...
      • RoseSet
        • SetOfDouble
        • RosePtrSet
        • ...
      • RoseArray
        • ArrayOfDouble
        • RosePtrArray
        • ...
Aggregate Hierarchy

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;