OC Tutorial

Draft: 06/01/2012
Last Revised: 06/06/2012
OC Version 2.0
Table of Contents
  1. Introduction
  2. Error Handling
  3. API Types
  4. Example 1: Print DDS with Attributes
  5. Example 2: Print Data Tree
  6. Odometers: Multi-Dimensional Array Iteration
  7. Appendix A. Example 1
  8. Appendix B. Example 2

Introduction

OC is a completely new implementation of the client side OPeNDAP data retrieval system. It has the following properties.

Version 2.0 introduces a new access model in an attempt to provide a cleaner API.

This document is intended for end users and provides a short tutorial on using the API. The API itself is documented in the file ocuserman.html. Familiarity with DAP2 concepts such as DAS, DDS, and DataDDS are assumed.

The oc API is based on the concept of tree data structures. The DDS, the DAS, the DATADDS, and the DATADDS data (aka data tree) all are represented as trees. Trees are recursive in that a given node has "subnodes" as defined by the node's edges. So, given a tree, it is possible to "traverse" it starting at its root and moving to sucessive subnodes.

Consider, for example, this DDS.

 
Dataset { 
  Structure { 
    int32 f0; 
    float64 f1; 
  } S1[2]; 
  byte v1; 
} D; 
We can represent the corresponding tree as follows, with depth levels at the left.
 
[01] Dataset D 
[02]   Structure S1[2] 
[03]    int32 f0 
[04]    float32 f1 
[05]  byte v1; 
Alternatively, in graph form, it looks like this.

The Dataset, D, is the top level node. D has two subnodes: S1 and v1. S1, in turn, has two subnodes: f0, and f1. These nodes – f0, f1, and v1 – are sometimes referred to as leaves because they have no subnodes and cannot be further traversed.

The key operations of the API consist of obtaining a reference to the root node of a tree and then "walking" around the tree by asking for the subnodes of a node, or asking for the container of a node.

Be warned, however, that walking around a data tree has some complications not present when walking around a DDS tree.

In addition to tree movement operations, there are operations to

  1. establish a link to a dataset on a server,
  2. fetch template information (e.g. the DDS and/or DAS) for that dataset,
  3. fetch data from that dataset using information from the template and, if desired, constraints on the data to be fetched.

Every time that the client invokes the oc_fetch procedure the following actions occur.

  1. A request is sent to an OPeNDAP server to obtain template information about either a DAS, a DDS, or a DATADDS.
  2. The server returns a response containing information relevant to the request. This information may be a DAS, a DDS, or a DATADDS. Recall that the DATADDS has two parts. At the front of the DATADDS packet is a modified version of the DDS. Following that DDS is the actual data.
  3. The textual part of the response is parsed to produce a tree whose nodes are of type OCnode.
  4. The data part is also converted to a data tree (see example 2).
  5. The client uses the tree walking API as illustrated in the examples to traverse the trees.

The client code can perform as many fetches as it desires with varying constraints on the fetch request. The root nodes of the resulting trees are available for client actions. When finished with a tree, the user can (and should) release the tree and have its resources reclaimed.

It should also be noted that as a rule, the DAS tree is rarely used directly. Rather, an operation called oc_merge_das is provided to merge the attributes in the DAS into some DDS tree for later access.

Error Handling

Most of the API procedures return a value of type OCerror to indicate some kind of error status. Positive valued error values map directly to the standard C errno errors. The value OC_NOERR (value 0) indicates that no error occurred. Negative error values indicate that some kind of OC error occurred. The currently defined set of values is defined in the oc.h file.

API Types

The oc API defines a set of types (some opaque) that control the operation of the interface. The primary such types are described in the following subsections.

OCtype

The file oc.h defines the type OCtype that is an enumeration that is used to classify nodes of a tree. The type defines primitive types as defined in the OPeNDAP specification and also container types corresponding to the kinds of things found in, say, the DDS or DAS: Structures or Grids, for example.

The primitive types are as follows. OC_NAT:NAT stands for "Not A Type". OC_Byte:Signed 8-bit integer. OC_Int16:Signed 16-bit integer. OC_UInt16:Unsigned 16-bit integer. OC_Int32:Signed 32-bit integer. OC_UInt32:Unsigned 32-bit integer. OC_Float32:32-bit floating point value. OC_Float64:64-bit floating point value. OC_String:A null-terminated string of UTF-8 characters. OC_URL:A null-terminated string of UTF-8 characters.

It should be noted that the type enumeration in oc.h actually defines additional primitive types that are currently unused. These are OC_Ubyte, OC_Char, OC_Int64, and OC_UInt64.

The container types are as follows.

OC_Dataset:The top-level object in a DDS. OC_Sequence:A DDS Sequence object. OC_Grid:A DDS Sequence object. OC_Structure:A DDS Sequence object. OC_Dimension:A DDS Dimension occurrence. OC_Attribute:A DAS leaf attribute. OC_Attributeset:A DAS non-leaf set of attributes. OC_Primitive:A primitive valued object, where the value typeis one of the primitive types defined in the table above.

OClink

OClink points to all the state information about an OPeNDAP URL, the server connection information (via libcurl), all of the DAS, DDS, and DATADDS root nodes, and any OCinstance data instances. Think of OClink as analogous to the C stdio FILE structure. Note especially that it is tied to a specific URL (including constraints).

OCddsnode

Each DAS, DDS, DATADDS, and DATADDS data part is converted (parsed) internally into a tree. The nodes of the tree are of opaque type OCddsnode. It is termed "opaque" because its internal memory representation is hidden. Calling it a ddsnode is somewhat of a misnomer since it also is used for DAS tree nodes.

OCdatanode

The OCdatanode opaque type represents a node in a data tree.

Example 1: Print DDS with Attributes

This program shows how to walk a DDS tree and print it out in a pseudo-DDS format. The complete program is shown in Appendix A. Here, the program is broken into into pieces for detailed explanation with unimportant parts elided.

Link and Tree Creation

/*Example 1*/
01 int 
02 main(int argc, char **argv) 
03 {
04   char* fileurl = NULL;
05   OClink link;
06   OCddsnode ddsroot, dasroot;
07  
08   if(argv[1] == NULL) {
09     fprintf(stderr,"no file url specified\n");
10     exit(1);
11   }
12 
13   fileurl = strdup(argv[1]);
14   check_err(oc_open(fileurl,&link));
15   check_err(oc_fetch(link,NULL,OCDAS,0,&dasroot));
16   check_err(oc_fetch(link,NULL,OCDDS,0,&ddsroot));
17   check_err(oc_merge_das(link,dasroot,ddsroot));
18 
19   /* Walk and output the DDS */
20   generatedds(link, ddsroot, 0);
21   exit(0);
22 }
23 
Lines 4-6 declare some variables for use in subsequent code.

Line 14 opens a connection to the DAP server as specified by the URL that is the command line argument. The instance, of type OClink is stored in the variable named link.

Line 15 uses the link to fetch the DAS for the data source specified by the URL. The DAS is fetched, parsed, and its root node is returned in the dasroot variable (of type OCddsnode).

Line 16 uses the link to fetch the DDS for the data source specified by the URL. The DDS is fetched, parsed, and its root node is returned in the ddsroot variable (of type OCddsnode).

Line 17 "merges" the DAS into the DDS. It annotates the DDS nodes with attributes taken from the DAS. The merge algorithm is somewhat convoluted to match the original lib-nc library. It will not be explained in this document.

Line 20 initiates traversal by calling a recursive procedure to traverse the tree and print out the nodes of the tree. The last argument, the "0" indicates the depth we are at in the tree. This will be used to control indenting.

For all the "oc_XXX" calls, an error code is returned and checked by the check_err function. If it is an error rather than OC_NOERR, then the program will fail at that point.

Tree Traversal Part 1

 
01 static void
02 generatedds(OClink link, OCddsnode node, int depth)
03 {
04   size_t i,rank,nattr,nsubnodes;
05   OCtype octype, atomtype;
06   OCddsnode container;
07   OCddsnode* dimids;
08   size_t size;
09   char tmp[1024];
10   char id1[1024];
11   char* name;
12 
13   indent(depth);
14   /* get all info about the node */
15   check_err(oc_dds_properties(link,node,&name,&octype,&atomtype,&container,
16             &rank,&nsubnodes,&nattr));
17 
Line 14 establishes the indent level based on the tree depth. This is purely to produce a nicer looking output.

Line 17 starts by obtaining all known information about that current node. The information is obtained by passing in pointers to variables into which to store the node information. Specifically, this includes the following.

Tree Traversal: Print Leaves

 
01   if(octype == OC_Atomic) {
02     OCddsnode dimids[OC_MAX_DIMENSIONS];
03     printf("%s %s", oc_typetostring(atomtype),name);
04     /* dump dim info*/
05     if(rank > 0) {
06       check_err(oc_dds_dimensions(link,node,dimids));
07       for(i=0;i<rank;i++) {
08         OCddsnode dim = dimids[i];
09         char* dimname = NULL;
10         check_err(oc_dimension_properties(link,dim,&size,&dimname));
11         if(dimname == NULL)
12           printf("[%lu]",(unsigned long)size);
13         else
14           printf("[%s=%lu]",dimname,(unsigned long)size);
15         if(dimname) free(dimname);
16       }
17     }
18     printf(";\n");
19     generateddsattributes(link,node,depth+1);
If the instance is referencing a leaf node (line 1), then we print out a line of this form (properly indented).
 
{type name} {field name} {dimensions} ;
The type name and field name are printed in line 3.

In order to print the dimensions, we need to get references to the dimension nodes associated with the field. Space for the dimension nodes is pre-allocated in line 2. Alternately one could have invoked malloc to dynamically allocate the space. The dimension nodes are obtained in line 5.

In lines 6-15, we iterate over each dimension node, obtain its name and size (line 9), and print it out. Since a dimension may be anonymous (i.e. has no name), we must be prepared to print the dimension with a name (line 13) or without (line 11).

As the last order of business (line 17), we print out any attributes associated with the leaf node. This procedure will be discussed later.

Tree Traversal: Print Containers

The remaining case to print is when the instance is referencing a container (non-leaf) node.
 
01    } else { /* Must be a container */
02      OCddsnode field;
03      switch (octype) {
04      case OC_Dataset:
05      case OC_Structure:
06      case OC_Sequence:
07        printf("%s {\n",oc_typetostring(octype));
08        for(i=0;i<nsubnodes;i++) {
09          /* Get the i'th field (i.e. subnode) of this container */
10          check_err(oc_dds_ithfield(link,node,i,&field));
11          generatedds(link,field,depth+1);
12        }
13        indent(depth); printf("} %s",name);
14        /* print structure dimensions, if any */
15        if(rank > 0 && octype == OC_Structure) {
16          OCddsnode dimids[OC_MAX_DIMENSIONS];
17          check_err(oc_dds_dimensions(link,node,dimids));
18          for(i=0;i<rank;i++) {
19            char* dimname = NULL;
20            OCddsnode dim = dimids[i];
21            check_err(oc_dimension_properties(link,dim,&size,&dimname));
22            printf("[");
23            if(dimname != NULL) printf("%s=",dimname);
24            printf("%lu]",(unsigned long)size);
25            if(dimname) free(dimname);
26         }
27       }
28       printf("\n",name);
29       break;
30     case OC_Grid: 
31       printf("%s {\n",oc_typetostring(octype)); 
32       /* Get the Array field (i.e. subnode zer0) of this Grid */ 
33       indent(depth); printf("Array:\n"); 
34       check_err(oc_dds_ithfield(link,node,0,&field)); 
35       generatedds(link,field,depth+2); 
36       /* Print the map fields */ 
37       indent(depth); printf("Maps:\n"); 
38       for(i=1;i<nsubnodes;i++) { 
39         check_err(oc_dds_ithfield(link,node,1,&field)); 
40         generatedds(link,field,depth+2); 
41       } 
42       indent(depth); printf("} %s\n",name); 
43       break; 
44     } 
45   } 
46   generateddsattributes(link,node,depth+1); 
47   oc_dds_free(link,node);
48   if(name) free(name); 
49 }
50 
This is where this procedure goes recursive. The idea is to print out the header of this container (e.g. "Structure {") (line 7) with proper indentation and then recursively print out the fields of the structure.

The fields of the container must be obtained one by one by obtaining the node for each field in turn (line 10). Then this procedure is called recursively (line 11) to print out the field.

After the loop over the fields, it is time to print out the container's name (line 13) with proper indentation.

If the container was a Structure, then it may have dimensions, so they must be printed out (lines 15-27). The code is similar to that shown previously.

In order to print a Grid, we must distinguish the Array variable (field 0) from the Map variables (fields 1 to nsubnodes). This is done in lines 30-43.

Again (line 46), any attributes associated with the container must be printed out.

Finally (line 47), we must free the previously allocated name and (line 48) tell the oc library we no longer will use this node.

Tree Traversal: Print Attributes

In the code above, the procedure generateddsattributes was called to print out the attributes associated with a leaf or a container. That procedure is discussed here.
 
01 static void
02 generateddsattributes(OClink link, OCddsnode node, int depth)
03 {
04   size_t i,j;
05   size_t nattrs;
06   char tmp[128];
07   char* aname;
08   char* name;
09   OCtype atomictype;
10   size_t nvalues;
11   char id1[1024];
12 
13   check_err(oc_dds_attr_count(link,node,&nattrs));
14   check_err(oc_dds_name(link,node,&name));
15   check_err(oc_dds_name(link,node,&name));
16   if(nattrs > 0) {
17     for(i=0;i<nattrs;i++) {
18       char** values;
19       check_err(oc_dds_attr(link,node,i,NULL,NULL,&nvalues,NULL));
20       values = (char**)malloc(sizeof(char*)*nvalues);
21       if(values == NULL) check_err(OC_ENOMEM);
22       check_err(oc_dds_attr(link,node,i,&aname,&atomictype,
23                             &nvalues,values));
24       indent(depth); printf("%s %s:%s = ",
25             oc_typetostring(atomictype),name,aname);
26       for(j=0;j<nvalues;j++) {
27         if(j > 0) printf(", ");
28         printf("%s",values[j]);
29       }
30       printf(";\n"); 
31       free(aname); 
32       oc_reclaim_strings(nvalues,values);
33       free(values); 
34     } 
35   }
36   free(name); 
37 }
38 
This procedure is given the node whose attributes are to be printed as an argument.

The processing steps are as follows.

  1. Obtain the number of attributes associated with this node (line 13) and the name of the node (line 14).

  2. Loop over the index of the attributes (0 through nattrs) (line 17).

  3. Obtain (line 19) the number of values in the attribute value vector (remember each attribute is a vector of strings). This represents a common cliche when we need to allocate memory but we need to know how much to allocate. We first ask for the count only (line 19), then allocate (line 20), then check for out-of-memory (line 21).

  4. Obtain (line 22-23) all of the following pieces of information about the attribute.

  5. print out "{atomictype} {nodename} : {attributname} = " (lines 24-25).

  6. print out the vector of values (lines 26-29). Since they are strings, we can just print them out, however it would be better to validate the values to make sure they match the atomic type of the attribute.
  7. Terminate the line (line 30).

  8. free allocated space (lines 31-33).

Tree Traversal: Miscellaneous Procedures

A couple of utility procedures are defined here. Hopefully, their functionality is obvious.
 
01 static void
02 check_err(int stat)
03 {
04   if(stat == OC_NOERR) return;
05   fprintf(stderr,"error status returned: (%d) %s\n",
06           stat,ocerrstring(stat));
07   fflush(stdout); fflush(stderr);
08   exit(1);
09 }
10 
11 static void
12 indent(int depth)
13 {
14   while(depth-- > 0) printf("  ");
15 }

Releasing datanodes when unneeded

It is desirable for the client code to tell the oc library when it is finished with a OCddsnode reference so the oc library can reclaim it. This action is performed using the oc_dds_free API procedure.

This is an optimization in the sense that the code will work correctly if unused OCddsnode references are not freed. However, this may have impacts on memory efficiency and so it is a desirable action.

Example 2: Print Data Tree

This program shows how to walk the data part of a DATADDS tree and print it out in a pseudo-DDS format. The complete program is shown in Appendix B. Here, the program is broken into pieces for explanatory purposes.

As mentioned in the introduction, walking the data tree is more complex than walking a DDS. Consider this DDS.

 
Dataset {
  Structure {
    Structure {
      int32 f1[2];
    } S2[3];
  } S1[2];
} D;
Note the following. This rather ugly graph shows the situation.

A nested box graph shows the situation a little more clearly.

This is the key fact: each node in the DDS tree may have multiple instances in the data tree. This makes traversal more complex.

Note also that this is the situation with Sequences as well. A Sequence instance may have an arbitrary number of associated records, where a record is an instance of that Sequence.

Note also that every instance maps uniquely to a node in the DDS. In effect, the DDS nodes serve as "templates" for the instances in the data tree.

Data Tree Traversal Model

Before showing a program to traverse a data tree, it is important to describe the general approach and point out the differences from walking a relatively simple DDS tree.

As with a DDS tree, we start by getting a reference to the root of the data tree. of the data tree. The term "instance" (of type OCdatanode) is used to remind the user that a data tree is being walked.

Given an instrance, we can do the following things.

  1. Select a specific instance of, say, a Sequence or dimensioned Structure.

  2. Select a field instance from a container instance.

The API procedures for walking an instance tree all begin with oc_data_.

Data Tree Traversal Example Code

For our example, we will suppose we are given a function "process" that we wish to apply to all leaf instances in a given tree Our goal is to walk the tree in depth-first order. The following procedure performs this walk. It is adapted from the octest.c program.

In order to walk the data tree, we define three functions: FC, FF, and FL. FC walks the instances of a container and FF walks the fields of a container instances. They are mutually recursive. FL walks the leaves and invokes "process" on each element of the leaf.

This program assumes the following.

  1. FC is initially called with the root instance of some data tree.
  2. A FAIL function exists to check the return codes on API calls and stop with an error message if the return code is other than OC_NOERR. The check_err procedure in the previous example could serve for this.
  3. A set of odometer procedures exist to iterate over all the possible values of a set of dimensions. Example odometer code is shown later in this section.
  4. Instance of OCdatanode are free'd when no longer needed. See previous discussion about free'ing OCddsnode references.

Function FC

/*Example 2*/
01 static OCerror
02 FC(OClink link, OCdatanode instance)
03 {
04   int i;
05   OCddsnode node;
06   size_t rank;
07   OCtype octype;
08   size_t nsubnodes;
09 
10   /* Get the OCddsnode object associated with the current instance */
11   FAIL(oc_data_ddsnode(link,instance,&node));
12   /* Obtain some information about the node */
13   FAIL(oc_dds_octype(link,node,&octype));
14   FAIL(oc_dds_rank(link,node,&rank));
15   FAIL(oc_dds_nsubnodes(link,node,&nsubnodes));
16 
The processing proceeds as follows.

  1. Line 11 – Obtain the "template" node for this instance. Remember that every instance has a unique template node from the corresponding DDS.

  2. Lines 14-16 – Similar to the code in example 1, obtain various pieces of information about the "template" node.
If this is a dimensioned Structure, then we need to walk over all its elements.
 
01   if(octype == OC_Structure && rank > 0) {
02     size_t dimsizes[OC_MAX_DIMENSIONS];
03     size_t indices[OC_MAX_DIMENSIONS]; /* records current indices
04                                            using the odometer code */
05     /* walk all the elements of the dimensioned Structure */
06     /* Obtain the dimension sizes of the Structure (via the template node) */
07     FAIL(oc_dds_dimensionsizes(link,node,dimsizes));
08     /* Initialize the odometer indices to zero */
09     for(i=0;i<rank;i++) indices[i] = 0;
10     /* Iterate over all elements of the array of Structures */
11     while(odom_more(rank,dimsizes,indices)) {
12       OCdatanode arrayelement;
13       /* Get the instance of the current array element */
14       FAIL(oc_data_ithelement(link,instance,indices,&arrayelement));
15       FAIL(FC(link,arrayelement)); /* recurse to visit this structure array instance */
16       /* No longer need this array element instance */
17       FAIL(oc_data_free(link,arrayelement));
18       odom_next(rank,dimsizes,indices);
19     }
The processing proceeds as follows.

  1. Line 7 – Obtain the sizes of the Structure's dimensions from the template node.

  2. Line 9 – Initialize the set of indices to start at element [0]...[0].

  3. Lines 11 and 18 – Iterate over all the possible elements of the Structure (see the odometer code in the odometer section).

  4. Line 14 – Get the instance associated with the chosen element as specified by the indices array.

  5. Line 15 – Since we have a chosen instance of the Structure, it is time to invoke FF to iterate over the field instances of that chosen Structure instance.

  6. Line 17 – Release this array element instance as it is no longer needed.
The other case we need to look at is if the container node is a Sequence. In that case, we need to walk over all its record instances.
 
01   } else if(octype == OC_Sequence) {
02     /* walk each record of the sequence. We have two choices,
03        one is to pre-compute the number of records using
04        oc_data_recordcount and the other is to just iterate
05        until we get OC_EINDEX error return. We use this latter approach.
06     */
07     for(i=0;;i++) {
08       OCdatanode recordinstance;
09       OCerror stat = oc_data_ithrecord(link,instance,i,&recordinstance);
10       if(stat != OC_NOERR) {
11         if(stat == OC_EINDEX)
12           break; /* done */
13         else FAIL(stat);
14       }
15       FF(link,recordinstance); /* Recurse */
16       FAIL(oc_data_free(link,recordinstance));
17     }
18   } else { /* Singleton Container: Grid or Dataset */
19     FAIL(FF(link,instance)); /* Recurse */
20   }
21   return OC_NOERR;
22 }
23 
The processing proceeds as follows.
  1. Line 1 – Test for the sequence case.

  2. Line 7 – Do an unbounded loop to generate record indices.

  3. Line 9 – Get the instance associated with the i'th record.

  4. Lines 10-14 – If the attempt to get the ith record failed, and its return code is OC_EINDEX, then that means we have exhausted the set of records and we can exit the loop.

  5. Line 15 – Since we have a chosen record instance, it is time to invoke FF to iterate over the field instances of that record.

  6. Line 16 – Release this record instance as it is no longer needed.

The last case to consider (line 19) is that we have a container that only has a single instance (all other cases). We then need to just invoke FF on that instance.

Function FF

 
01 static OCerror
02 FF(OClink link, OCdatanode instance)
03 {
04   int i;
05   OCddsnode node;
06   size_t rank;
07   OCtype octype;
08   size_t nsubnodes;
09 
10   /* Get the OCddsnode object associated with the current instance */
11   FAIL(oc_data_ddsnode(link,instance,&node));
12   /* Obtain some information about the node */
13   FAIL(oc_dds_octype(link,node,&octype));
14   FAIL(oc_dds_rank(link,node,&rank));
15   FAIL(oc_dds_nsubnodes(link,node,&nsubnodes));
16 
The initial code for FF is similar to that of FC; it gets the instance's template node and some info about the template node.

FF needs to walk the field instances of the instance argument.

 
01   if(octype == OC_Atomic) {
02     return FL(link,instance);
03   } else {/* Instance is a container */
04     /* For all these cases, we want to visit the fields in turn. */
05     for(i=0;i<nsubnodes;i++) {
06       OCdatanode fieldinstance;
07       /* get a new instance node for the i'th field */
08       FAIL(oc_data_ithfield(link,instance,i,&fieldinstance));
09       FAIL(FC(link,fieldinstance)); /* recurse */
10       FAIL(oc_data_free(link,fieldinstance));
11     }
12   }
13   return OC_NOERR;
14 }
15 
Processing of a container instance proceeds as follows.

  1. Lines 1-2 – If this instance is an atomic leaf instance, then invoke the FL function.

  2. Line 5 – Otherwise, it is a container instance and we need to invoke FC on each field instance.

  3. Line 8 – Obtain an instance for the i'th field.

  4. Lines 9 – Invoke FC recursively on this field instance.

  5. Lines 10 – Since we no longer need that field instance, release it.

Function FL

Processing of leaf instances requires somewhat different processing than a DDS leaf.
 
01 static OCerror
02 FL(OClink link, OCdatanode instance)
03 {
04   int i;
05   OCddsnode node;
06   size_t rank;
07   OCtype octype,atomtype;
08   size_t elemsize;
09   size_t memsize;
10   char* memory;
11   size_t count;
12   size_t dimsizes[OC_MAX_DIMENSIONS];
13   size_t indices[OC_MAX_DIMENSIONS]; /* records current indices
14                                         using the odometer code */
15 
16   /* Get the OCddsnode object associated with the current instance */
17   FAIL(oc_data_ddsnode(link,instance,&node));
18   /* Obtain some information about the node */
19   FAIL(oc_dds_octype(link,node,&octype));
20   FAIL(oc_dds_atomictype(link,node,&atomtype));
21   FAIL(oc_dds_rank(link,node,&rank));
22   elemsize = oc_typesize(atomtype); /* get memory size of each primitive value */
23 
The initial code for FL is similar to that of FC; it gets the instance's template node and some info about the template node. At line 24, we obtain the in-memory size of the atomic type: sizeof(int) for int32, sizeof(char*) for string, etc.
 
01   if(rank == 0) {/* Scalar case */
02     memory = calloc(elemsize,1); /* reading only one value */
03     /* read the scalar */
04     FAIL(oc_data_read(link,instance,NULL,NULL,elemsize,memory));
05     count = 1;
In the scalar case (line 1), we allocate memory for a single instance of the atomic type. We then invoke the oc_data_read procedure to actually pull out the data from the instance into memory. The two NULLs are used because reading a scalar ignores those arguments.
 
01   } else {/* rank > 0 */
02     size_t offset;
03     /* Initialize the odometer indices to zero */
04     odom_init(rank,indices);
05     /* Obtain the dimension sizes */
06     FAIL(oc_dds_dimensionsizes(link,node,dimsizes));
07     /* Compute the cross product */
08     for(count=1,i=0;i<rank;i++) count *= dimsizes[i];
09     /* Use the cross product to allocate enough memory */
10     memsize = elemsize*count;
11     memory = calloc(memsize,1); /* allocate the memory */
12     if(memory == NULL)
13       return OC_ENOMEM;
14     /* This code also has a conditional to show extracting
15        the data piece by piece versus all at once
16     */
17     {
18 #ifdef ALLATONCE
19       /* Read whole leaf at one time */
20       /* the indices variable should be all zeros at this point */
21       FAIL(oc_data_read(link,instance,indices,dimsizes,memsize,memory));
22 #else
23       size_t offset;
24       size_t one[OC_MAX_DIMENSIONS];
25       /* Initialize the read-by-one counts */
26       for(i=0;i<rank;i++) one[i]=1;
27       /* Read the data item by item using odometer approach */ 
28       for(offset=0;odom_more(rank,dimsizes,indices);offset+=elemsize) { 
29         FAIL(oc_data_read(link,instance,indices,one,elemsize,memory+offset)); 
30         odom_next(rank,dimsizes,indices);
31       } 
32 #endif 
33    }
34   }
35   process(atomtype,rank,count,memory,elemsize);
36   if(atomtype == OC_String || atomtype == OC_URL)
37     oc_reclaim_strings(count,(char**)memory);
38   free(memory); 
39   return OC_NOERR; 
40 }
41 
This code has two alternatives: (1) we read item by item using an odometer, or (2) we read all the elements at once.

In either case, we will need a starting point of a set of indices all with the value zero (line 4). We also need to get the dimension sizes (line 7) and we compute the cross-product of the dimension sizes (line 9). The cross-product tells us the total number of elements in the array.

We use the total number of elements to allocate enough memory (lines 12-15).

Now, assuming we want to read the whole atomic leaf instance at once, we invoke oc_data_read procedure (line 22) to actually pull out the data from the instance into memory. We tell it the starting set of indices (all zeros), the dimension sizes, the memory in which to store the data, and the amount of memory allocated.

Instead of reading the leaf instance all-at-once, we can read one-by-one by using an odometer to iterate over the elements of the atomic leaf array. The processing goes as follows in this case.

  1. Line 28-29 – We create an edge vector (named "ones") to read a single element at a time.

  2. Lines 31 and 33 – We iterate over all possible index vectors. As part of this, we need to keep an offset into the memory variable indicating where to store each element as we read it. Every time we read an element, we increment the offset appropriately (last part of line 31).

  3. Line 32 – We read a single element starting at the current odometer position.

  4. Line 33 – We increment the odometer to move to the next element.

However the contents of the leaf is read, the "process" function is invoked to process the data in some format (line 37);

Finally (lines 38-40), we reclaim allocated memory.

An example Process Function

The operation of the process function is arbitrary, but a simple example follows that just prints out the memory contents.
 
01 static void
02 process(OCtype atomtype, size_t rank, size_t nelements, char* memory, size_t elemsize)
03 {
04   int i;
05   char* p;
06 
07   printf("%s",oc_typetostring(atomtype));
08   if(rank > 0)
09     printf("[%d]",(int)nelements);
10   printf("=",(int)nelements);
11 
12   for(p=memory,i=0;i 0) printf(",");
14     switch (atomtype) {
15     case OC_Byte:
16       printf("%hhu",*p);
17       break;
18     case OC_Int16:
19       printf("%hd",*(short*)p);
20       break;
21     case OC_UInt16:
22       printf("%hu",*(unsigned short*)p);
23       break;
24     case OC_Int32:
25       printf("%d",*(int*)p);
26       break;
27     case OC_UInt32:
28       printf("%u",*(unsigned int*)p);
29       break;
30     case OC_Float32:
31       printf("%g",*(float*)p);
32       break;
33     case OC_Float64:
34       printf("%g",*(double*)p);
35       break;
36     case OC_String: case OC_URL:
37       printf("\"%s\"",*(char**)p);
38       break;
39     default:
40       break;
41     }
42   }
43   printf("\n");
44 }
45 

Main

 
01 int
02 main(int argc, char **argv)
03 {
04   char* fileurl = NULL;
05   OClink link;
06   OCddsnode dataddsroot, dasroot;
07   OCdatanode rootinstance;
08 
09   if(argv[1] == NULL) {
10     fprintf(stderr,"no file url specified\n");
11     exit(1);
12   }
13   fileurl = strdup(argv[1]);
14   FAIL(oc_open(fileurl,&link));
15   FAIL(oc_fetch(link,NULL,OCDAS,0,&dasroot));
16   FAIL(oc_fetch(link,NULL,OCDATADDS,0,&dataddsroot));
17   FAIL(oc_merge_das(link,dasroot,dataddsroot));
18   /* Get the root instance */
19   FAIL(oc_data_getroot(link,dataddsroot,&rootinstance));
20  
21   /* apply FC to the instance tree to start because we know
22      that the root is a Dataset => container */
23   FC(link, rootinstance);
24   oc_root_free(link,dasroot);
25   oc_root_free(link,dataddsroot); /* Also frees the associated data tree */
26   exit(0); 
27 } 
28 

Lines 4-7 declare some OCddsnode variables for use in subsequent code.

Line 15 opens a connection to the DAP server as specified by the URL that is the command line argument. The instance, of type OClink is stored in the variable named link.

Line 16 uses the link to fetch the DAS for the data source specified by the URL. The DAS is fetched, parsed, and its root node is returned in the dasroot variable (of type OCddsnode).

Line 17 uses the link to fetch the DataDDS for the data source specified by the URL. Recall that this will fetch both the DDS part and the instance part. The DDS part is fetched, parsed, and its root node is returned in the dataddsroot variable (of type OCddsnode).

Line 18 "merges" the DAS into the DataDDS. It annotates the DataDDS nodes with attributes taken from the DAS. The merge algorithm is somewhat convoluted, so it will not be explained in this document.

Line 21 obtains an instance reference to the root of the data part of the DataDDS. The reference is store in the rootinstance variable.

Line 25 invokes the FC function. FC is called because by definition, the root instance refers to a Dataset instance, which by definition is not dimensioned.

Finally, lines 27-28 free up the original DAS and DDS trees.

Odometers: Multi-Dimensional Array Iteration

The above code refers to the concept of an odometer. The problem to be solved is to produce, in some order, all possible combinations of values for a set of dimensions.

For example, suppose we have the set of dimensions [2][3][2]. We need to generate the following set of dimension values.

 
0,0,0
0,0,1
0,1,0
0,1,1
0,2,0
0,2,1
1,0,0
1,0,1
1,1,0
1,1,1
1,2,0
1,2,1
This order, where the rightmost index varies the fastest, is often called row-major order. It is that used by C/C++. Fortran uses column-major order, where the leftmost index varies the fastest. The oc library assumes row-major order.

The odometer is represented by a vector of indices (of size 3 in the example immediately above).

We need two procedures.

 
01 static void odom_init(size_t rank, size_t* indices)
02 {
03   int i;
04   for(i=0;i<rank;i++) indices[i] = 0;
05 }
06 
The odom_init procedure initializes our vector of indices all to zero.

 
01 static int odom_more(size_t rank, size_t* dimsizes, size_t* indices)
02 {
03   return (indices[0] < dimsizes[0] ? 1 : 0);
04 }
05 
The odom_more procedure returns 0 if we have covered all the possible combinations, and 1 otherwise (line 3). There is more as long as the first index (indices[0]) has not reached its maximum value.
01 static void odom_next(size_t rank, size_t* dimsizes, size_t* indices)
02 {
03   int i;
04   for(i=rank-1;i>=0;i--) {
05     indices[i]++;
06     if(indices[i] < dimsizes[i]) break;
07     if(i > 0) indices[i] = 0;
08   }
09 }
10 
The odom_next procedure moves to the next index element. The next set of indices is established by incrementing each index by one starting at the leftmost dimension. If at any point, the i'th index has not overflowed (i.e. reached its maximum), then we can stop. Otherwise, reset the i'th index to 0 and move to the left. In order for odom_more to work, indices[0] is not reset when it overflows.

Appendix A. Example 1
 
 int 
 main(int argc, char **argv) 
 {
   char* fileurl = NULL;
   OClink link;
   OCddsnode ddsroot, dasroot;
  
   if(argv[1] == NULL) {
     fprintf(stderr,"no file url specified\n");
     exit(1);
   }
 
   fileurl = strdup(argv[1]);
   check_err(oc_open(fileurl,&link));
   check_err(oc_fetch(link,NULL,OCDAS,0,&dasroot));
   check_err(oc_fetch(link,NULL,OCDDS,0,&ddsroot));
   check_err(oc_merge_das(link,dasroot,ddsroot));
 
   /* Walk and output the DDS */
   generatedds(link, ddsroot, 0);
   exit(0);
 }
 
 static void
 generatedds(OClink link, OCddsnode node, int depth)
 {
   size_t i,rank,nattr,nsubnodes;
   OCtype octype, atomtype;
   OCddsnode container;
   OCddsnode* dimids;
   size_t size;
   char tmp[1024];
   char id1[1024];
   char* name;
 
   indent(depth);
   /* get all info about the node */
   check_err(oc_dds_properties(link,node,&name,&octype,&atomtype,&container,
             &rank,&nsubnodes,&nattr));
 
   if(octype == OC_Atomic) {
     OCddsnode dimids[OC_MAX_DIMENSIONS];
     printf("%s %s", oc_typetostring(atomtype),name);
     /* dump dim info*/
     if(rank > 0) {
       check_err(oc_dds_dimensions(link,node,dimids));
       for(i=0;i<rank;i++) {
         OCddsnode dim = dimids[i];
         char* dimname = NULL;
         check_err(oc_dimension_properties(link,dim,&size,&dimname));
         if(dimname == NULL)
           printf("[%lu]",(unsigned long)size);
         else
           printf("[%s=%lu]",dimname,(unsigned long)size);
         if(dimname) free(dimname);
       }
     }
     printf(";\n");
     generateddsattributes(link,node,depth+1);
    } else { /* Must be a container */
      OCddsnode field;
      switch (octype) {
      case OC_Dataset:
      case OC_Structure:
      case OC_Sequence:
        printf("%s {\n",oc_typetostring(octype));
        for(i=0;i<nsubnodes;i++) {
          /* Get the i'th field (i.e. subnode) of this container */
          check_err(oc_dds_ithfield(link,node,i,&field));
          generatedds(link,field,depth+1);
        }
        indent(depth); printf("} %s",name);
        /* print structure dimensions, if any */
        if(rank > 0 && octype == OC_Structure) {
          OCddsnode dimids[OC_MAX_DIMENSIONS];
          check_err(oc_dds_dimensions(link,node,dimids));
          for(i=0;i<rank;i++) {
            char* dimname = NULL;
            OCddsnode dim = dimids[i];
            check_err(oc_dimension_properties(link,dim,&size,&dimname));
            printf("[");
            if(dimname != NULL) printf("%s=",dimname);
            printf("%lu]",(unsigned long)size);
            if(dimname) free(dimname);
         }
       }
       printf("\n",name);
       break;
     case OC_Grid: 
       printf("%s {\n",oc_typetostring(octype)); 
       /* Get the Array field (i.e. subnode zer0) of this Grid */ 
       indent(depth); printf("Array:\n"); 
       check_err(oc_dds_ithfield(link,node,0,&field)); 
       generatedds(link,field,depth+2); 
       /* Print the map fields */ 
       indent(depth); printf("Maps:\n"); 
       for(i=1;i<nsubnodes;i++) { 
         check_err(oc_dds_ithfield(link,node,1,&field)); 
         generatedds(link,field,depth+2); 
       } 
       indent(depth); printf("} %s\n",name); 
       break; 
     } 
   } 
   generateddsattributes(link,node,depth+1); 
   oc_dds_free(link,node);
   if(name) free(name); 
 }
 
 static void
 generateddsattributes(OClink link, OCddsnode node, int depth)
 {
   size_t i,j;
   size_t nattrs;
   char tmp[128];
   char* aname;
   char* name;
   OCtype atomictype;
   size_t nvalues;
   char id1[1024];
 
   check_err(oc_dds_attr_count(link,node,&nattrs));
   check_err(oc_dds_name(link,node,&name));
   check_err(oc_dds_name(link,node,&name));
   if(nattrs > 0) {
     for(i=0;i<nattrs;i++) {
       char** values;
       check_err(oc_dds_attr(link,node,i,NULL,NULL,&nvalues,NULL));
       values = (char**)malloc(sizeof(char*)*nvalues);
       if(values == NULL) check_err(OC_ENOMEM);
       check_err(oc_dds_attr(link,node,i,&aname,&atomictype,
                             &nvalues,values));
       indent(depth); printf("%s %s:%s = ",
             oc_typetostring(atomictype),name,aname);
       for(j=0;j<nvalues;j++) {
         if(j > 0) printf(", ");
         printf("%s",values[j]);
       }
       printf(";\n"); 
       free(aname); 
       oc_reclaim_strings(nvalues,values);
       free(values); 
     } 
   }
   free(name); 
 }
 
 static void
 check_err(int stat)
 {
   if(stat == OC_NOERR) return;
   fprintf(stderr,"error status returned: (%d) %s\n",
           stat,ocerrstring(stat));
   fflush(stdout); fflush(stderr);
   exit(1);
 }
 
 static void
 indent(int depth)
 {
   while(depth-- > 0) printf("  ");
 }
Appendix B. Example 2
 
 static OCerror
 FC(OClink link, OCdatanode instance)
 {
   int i;
   OCddsnode node;
   size_t rank;
   OCtype octype;
   size_t nsubnodes;
 
   /* Get the OCddsnode object associated with the current instance */
   FAIL(oc_data_ddsnode(link,instance,&node));
   /* Obtain some information about the node */
   FAIL(oc_dds_octype(link,node,&octype));
   FAIL(oc_dds_rank(link,node,&rank));
   FAIL(oc_dds_nsubnodes(link,node,&nsubnodes));
 
   if(octype == OC_Structure && rank > 0) {
     size_t dimsizes[OC_MAX_DIMENSIONS];
     size_t indices[OC_MAX_DIMENSIONS]; /* records current indices
                                            using the odometer code */
     /* walk all the elements of the dimensioned Structure */
     /* Obtain the dimension sizes of the Structure (via the template node) */
     FAIL(oc_dds_dimensionsizes(link,node,dimsizes));
     /* Initialize the odometer indices to zero */
     for(i=0;i 0 */
     size_t offset;
     /* Initialize the odometer indices to zero */
     odom_init(rank,indices);
     /* Obtain the dimension sizes */
     FAIL(oc_dds_dimensionsizes(link,node,dimsizes));
     /* Compute the cross product */
     for(count=1,i=0;i 0)
     printf("[%d]",(int)nelements);
   printf("=",(int)nelements);
 
   for(p=memory,i=0;i 0) printf(",");
     switch (atomtype) {
     case OC_Byte:
       printf("%hhu",*p);
       break;
     case OC_Int16:
       printf("%hd",*(short*)p);
       break;
     case OC_UInt16:
       printf("%hu",*(unsigned short*)p);
       break;
     case OC_Int32:
       printf("%d",*(int*)p);
       break;
     case OC_UInt32:
       printf("%u",*(unsigned int*)p);
       break;
     case OC_Float32:
       printf("%g",*(float*)p);
       break;
     case OC_Float64:
       printf("%g",*(double*)p);
       break;
     case OC_String: case OC_URL:
       printf("\"%s\"",*(char**)p);
       break;
     default:
       break;
     }
   }
   printf("\n");
 }
 
 int
 main(int argc, char **argv)
 {
   char* fileurl = NULL;
   OClink link;
   OCddsnode dataddsroot, dasroot;
   OCdatanode rootinstance;
 
   if(argv[1] == NULL) {
     fprintf(stderr,"no file url specified\n");
     exit(1);
   }
   fileurl = strdup(argv[1]);
   FAIL(oc_open(fileurl,&link));
   FAIL(oc_fetch(link,NULL,OCDAS,0,&dasroot));
   FAIL(oc_fetch(link,NULL,OCDATADDS,0,&dataddsroot));
   FAIL(oc_merge_das(link,dasroot,dataddsroot));
   /* Get the root instance */
   FAIL(oc_data_getroot(link,dataddsroot,&rootinstance));
  
   /* apply FC to the instance tree to start because we know
      that the root is a Dataset => container */
   FC(link, rootinstance);
   oc_root_free(link,dasroot);
   oc_root_free(link,dataddsroot); /* Also frees the associated data tree */
   exit(0); 
 } 
 
 static void odom_init(size_t rank, size_t* indices)
 {
   int i;
   for(i=0;i=0;i--) {
     indices[i]++;
     if(indices[i] < dimsizes[i]) break;
     if(i > 0) indices[i] = 0;
   }
 }