odl file contains the class definitions and the relationships between classes in the system.
using odl primitives system designer can define the classes and the relationships between
classes in the system. Odl is similar to C++ in look and feel. Odl provides facilities to
inline C/C++ code along with the class and relationship definitions. This section will
explain the primitives of the odl file and how to use them


extern block allows us to write code and definitions in C/C++ language. The code and definitions
defined in the "extern" block go in to generated files as is with out any modification.

extern C|C++
    #include "complex.h"
    main (int ac, char **av)
        printf("I will go unmodified");
        return 0;

let us define a class "xyz" which contains some primitive data types and user data types as well
class xyz can use primitive data types or complex user data types as its data members. user should
include proper header files in the extern block for the definitions of the complex data types used
in the class definition.  class definition cannot use pointers or references since they dont have
any meaning in data store.

NOTE: A class definition is expanded at run time to accomodate more fields which are generated. A
C++ equivalent with the same name is generated out of an odl class and can be referred with its
name in the application code.

class xyz
        unsigned int x;
        char         y;
        short        z;
        struct complex c; ---> struct complex is defined in the header complex.h included in the extern block.

Comments are similar to c/c++ comments. ex:
//I am a C++ comment
/* I am a C comment and i can be multiline */

A relation is used to define the relationship between 2 classes. A relation primitive is similar to
C++ template instantiation. lets look at an example

relation primitive is of the form  

relation < An odl class already defined,
           Another or same odl class already defined,
           Container to hold the elements of the class of 2nd argument type,
           key to index in to the container which should be one of the fields of the class of 2nd argument
           > Name of the relation;

ClassA ------relates to ---> ClassB  , here ClassA is called lhs of the relation and ClassB as rhs.

relation < ClassA,  ClassB, list,  ClassB.key> RelationClassAClassB;

ClassA - is an odl class.
ClassB - is an odl class.
list - All the relatives should be ordered in a list fashion.
ClassB.key - key to index in to the set of relatives.

Constraint methods are called to evaluate when 2 objects are composed in to a relationship. These methods
are executed in the context of data store and should be carefully coded so that they take less number of
cycles and are fast. Methods are defined using the keyword "constraint". Each constraint method should
return bool as their return type and should take references to arguments of an lhs object and an rhs object.

The syntax of the constraint method is like below,

constraint bool
RelationClassAClassB::check_a_compatible_with_b (const ClassADsRef a, const ClassBDsRef b)
    /* write regular c/C++ code here using the input arguments */
    return true;

ClassADsRef , ClassBDsRef ------> are type definitions emitted by the odl compiler.
The logic of the constraint method can be written using normal c/c++ code.

A more real life example from an atm configuration is below

 The below definition will create a structure some thing like below

               [ port ]
               [ vpi ] <---------> [ vpi ] <-----------> [ vpi ]---------->x
                 / \
                /   \
              vci   vci
              /\     /\
             /  \   /  \
           vci vci x    x

extern "C|C++"
    #include "stdio.h"
    #include "stdlib.h"
    #define DEVICE_ATM 0x1

    unmodified_function (void)
        printf ("I will stay unmodified");

#include "dsTypes.h" //defines types which are used in the class definitions

class port
        uint32_t index;
        char     dev_type;
        char     name[32];
        uint32_t i2cid;
        char     desc[128];
        uint32_t mtu;
        uint32_t speed;
        bool     autoneg;
        uint32_t dev_flags;
        char     hwaddress[40];
        bool     admin;
        bool     oper;

class vpi
        char     vpi_name[32];
        uint8_t  vpi_type;
        uint32_t vpi_id;
        uint32_t max_vci;
        uint32_t vpi_flags;

class vci
        uint32_t vpi_id;
        uint32_t vci_id;

 * There can be at the max 32 vpis in our example system so we define the container
 * type as list in the 3rd argument.
relation <port, vpi, list, vpi::vpi_id> ResidenceRelationPortVpi;

 * vpis can be configured only on atm devices if any other devices return false.
 * portDsRef and vpiDsRef are pointers to the objects in the data store and can be used
 * as like regular c,c++ pointers.
constraint boolean
ResidenceRelationPortVpi::check_port_vpi_compat (const portDsRef port, const vpiDsRef vpi)
        return port->dev_type == DEVICE_ATM;

 * There can be at the max of 32767 vcis on each of vpi in our example system so we
 * define the container type as rbtree with the key vci_id.
relation <vpi, vci, rbtree, vci::vci_id> ResidenceRelationVpiVci;

 * Define constraint such that the id of the vci being configured is not greater than
 * the max_vci limit configured on the vpi.
constraint boolean
ResidenceRelationVpiVci::check_vpi_vci_compat (const vpiDsRef vpi, const vciDsRef vci)
        return vci->vci_id < vpi->vci_max;