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 in iopcmeta/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