Reflection and object persistence features will be presented on the following classes. We will start with a simple class hierarchy with which we are already familiar from the the section called “Database mapping requirements”. To present the provided reflection mechanism and later the database mapping, we need to define some test classes. Additional features will be added later.
// We use the reflection library configuration #include "iopcmeta/iopcMetaHeaders.h" #include <string> using namespace std; class Person : public iopc::Object { public: EString name; EShort age; }; class Employee: public Person { private: EInt salary; public: Employee() {}; Employee(string name, short int age, int salary) { this->name = name; this->age = age; this->salary = salary; } void setSalary(int salary) { this->salary = salary; }; int getSalary() { return salary; }; friend class iopc::TypeDesc<Employee>; }; class Student : public Person { public: EString studcardid; }; class PhdStudent : public Student { public: EShort scholarship; };
Classes discoverable by the IOPC 2 reflection must conform to the following rules:
The classes must be defined in header files. These headers will be included by the generated IH files containing type descriptions (see the section called “iopcsp”).
The classes must derive directly or indirectly from the
Object
defined iniopcmeta/object.h
.The classes must contain a non-parameterised constructor (implicit or explicit). A constructor with all default parameters is also acceptable.
If the classes contain any non-public members, they must declare the associated type description as friend - see the
Employee
class definition. For more information, refer to the section called “Usage transparency differences from IOPC”.
Attribute definitions use enhanced data types (see the section called “Enhanced data types”). This will come in handy later in sections discussing object persistence and querying.
To see how to use the reflection interface, we will provide a set of examples which demonstrate its features. The code should be placed between the init
and shutdown
calls which we presented in the first example.
Following example lists all data types that are recognised by the reflection mechanism.
const map<string, const Type*>& types = TypeManager::getTypesMap(); cout << "Registered types:" << endl; for( map<string, const Type*>::const_iterator it = types.begin(); it != types.end(); it++) { cout << (*it).first << endl; }
When compiled using the supplied makefile/compile script or using the managed-build Eclipse project and when run, the program will print a list of types registered by the TypeManager
singleton (see the section called “The metamodel”).
::Employee iopc::EBool ::Person iopc::EChar ::PhdStudent iopc::EDouble ::Student iopc::EFloat ::bool iopc::EInt ::char iopc::ELDouble ::double iopc::ELong ::float iopc::ESChar ::int iopc::EShort ::long iopc::EString ::long double iopc::EUChar ::short iopc::EUInt ::signed char iopc::EULong ::unsigned char iopc::EUShort ::unsigned int iopc::EWCharT ::unsigned long iopc::EWString ::unsigned short iopc::Object ::wchar_t std::string std::wstring
The types can be divided into three categories:
Built-in C++ numeric types, STL string types.
Enhanced data types which correspond to the basic types and string types.
All descendants of the
Object
class which is also included and can be inspected using the reflection. Such types are classified as complex data types in the IOPC 2 library.
All the listed types can be used as attribute types. Attributes of other types are ignored[32]. Complex type attributes cannot be however persisted by the object persistence layer (iopclib) at the moment. In this case, complex type attributes must be marked as transient using the attribute metadata db.transient
. See Appendix C, Metadata overview.
The programmer can use the reflection for more complex tasks, as it is shown in following examples.
First, we obtain an object which represents a type description. See the section called “The metamodel classes”.
const Type& t = TypeManager::getType("::Person");
The TypeManager::getType()
call searches in the catalogue of registered types for the type of given name and returns its description. The type description is obtained as a reference to a Type
object representing the requested type. This class provides many methods that can be used to introspect the structure of the associated type. The type description can be also obtained in a static way:
const Type& t = TypeDesc<Person>::getType()
Having the Type
reference we can start examinig the Person
class by obtaining a list of its parents:
const Type::ParentsList& parents = t.getParents(); for(Type::ParentsIterator it = parents.begin(); it != parents.end(); it++) { cout << (*it)->getQualifiedName() << endl; }
If we are not using multiple inheritance, the following method can be used:
cout << t.getFirstParent().getQualifiedName() << endl;
The example prints:
iopc::Object
Similarly, we list the child types of the Person
class.
const Type::ChildrenList& children = t.getChildren(); for(Type::ChildrenIterator it = children.begin(); it != children.end(); it++) { cout << (*it)->getQualifiedName() << endl; }
We get the following output:
::Employee ::Student
If we want to obtain even indirect parents or children, we have to use the Type::getAllParents()
or Type::getAllChildren()
methods instead.
Attributes can be listed using the following code:
const Type::AttributesMap& attributes = t.getAttributes(); for(Type::AttributesIterator it = attributes.begin(); it != attributes.end(); it++) { const Attribute& attr = it->second; switch (attr.getVisibility()) { case Attribute::VISIBILITY_PUBLIC: cout << "public "; break; case Attribute::VISIBILITY_PRIVATE: cout << "private "; break; case Attribute::VISIBILITY_PROTECTED: cout << "protected "; break; } cout << it->second.getType().getQualifiedName() << " " << it->first << endl; }
The example iterates over a map whose keys are attribute names and values are Attribute
class instances. Using Attribute
methods we can obtain attribute names, their data types (which are again described by the familiar Type
interface) and determine if the attributes were declared as private, protected or public. In a similar way, inherited attributes can be listed using the Type::getInheritedAttributes()
method. The previous code yields the following output:
public iopc::EString name public iopc::EShort age
Using reflection we can also create objects or manipulate with values of their attributes (regardless of their visibility). In the following example we create a new instance of the Employee
class and initialise values of all of its attributes.
Employee* e = dynamic_cast<Employee*>( TypeManager::getType("::Employee").newObjectInstance()); *((EString*)e->getAttributeValue("name").getValue()) = "Ola Nordmann"; *((EShort*)e->getAttributeValue("age").getValue()) = 45; *((EInt*)e->getAttributeValue("salary").getValue()) = 60000; cout << e->name << " " << e->age << " " << e->getSalary() << endl;
The Object::getAttributeValue()
method returns an instance of the AttributeValue
which allows us to get or set values of the attribute it represents. Using its getValue()
method we obtain a void pointer to the requested field in the Employee
instance. By casting it to the actual attribute data type we can manipulate with its value. Output of the example is:
Ola Nordmann 45 60000
Attribute values can be iterated similarly as attributes. The list of attribute values can be obtained using the Object::getAttributeValues()
method:
const Object::AttributeValuesMap& attrVals = e->getAttributeValues(); for(Object::AttributeValuesIterator it = attrVals.begin(); it != attrVals.end(); it++) { const AttributeValue& val = it->second; cout << it->first << ": "; if (val.getAttribute().getType().getDataTypeClass() == Type::IOPC_TYPECLASS_ENHANCED) { cout << ((EnhancedTypeBase*)val.getValue())->toString() << endl; } }
In the example we take advantage of the enhanced data types and of the Object::toString()
method they implement. For regular objects it prints just an address on which they reside in the memory. It is however overridden by the enhanced data type implementations so that the returned string contains a value they represent. Output of the example is:
age: 45 name: Ola Nordmann salary: 60000
Types recognised by the reflection are categorised into several groups called type classes. They help to easily determine their "nature":
IOPC_TYPECLASS_SIMPLE - built-in C++ numeric types
IOPC_TYPECLASS_STRING - STL strings.
IOPC_TYPECLASS_COMPLEX - class types, descendants of
Object
. Excluding the enhanced data types.IOPC_TYPECLASS_ENHANCED - enhanced data types.
Last thing we may need from the reflection interface is the ability to compare the types. For example, we may want to ask if some type is descendant of another type or if it represents the same type. The following example illustrates how such comparisons can be done in IOPC 2.
const Type& t1 = TypeDesc<Person>::getType(); const Type& t2 = TypeDesc<Student>::getType(); if (t1 == t2) { cout << "t1 is t2" << endl; } if (t1 != t2) { cout << "t1 is not t2" << endl; } if (t1.isTypeOf(t2)) { cout << "t1 is t2 or t1 is descendant of t2" << endl; } if (t2.isTypeOf(t1)) { cout << "t1 is t2 or t2 is descendant of t1" << endl; } if (t1.is<Student>()) { cout << "t1 is of type Student" << endl; } if (t2.is<Student>()) { cout << "t2 is of type Student" << endl; } if (t1.isTypeOf<Person>()) { cout << "t1 is of type Person or its descendant" << endl; } if (t2.isTypeOf<Person>()) { cout << "t2 is of type Person or its descendant" << endl; }
Output of the example is:
t1 is not t2 t1 is t2 or t2 is descendant of t1 t2 is of type Student t1 is of type Person or its descendant t2 is of type Person or its descendant