Index: trunk/psLib/src/astronomy/psMetadata.c
===================================================================
--- trunk/psLib/src/astronomy/psMetadata.c	(revision 3341)
+++ trunk/psLib/src/astronomy/psMetadata.c	(revision 3381)
@@ -12,6 +12,6 @@
 *  @author Ross Harman, MHPCC
 *
-*  @version $Revision: 1.53 $ $Name: not supported by cvs2svn $
-*  @date $Date: 2005-02-28 23:34:10 $
+*  @version $Revision: 1.54 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2005-03-07 20:58:50 $
 *
 *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
@@ -85,4 +85,13 @@
 }
 
+static void metadataIteratorFree(psMetadataIterator* iter)
+{
+    if (iter == NULL) {
+        return;
+    }
+    psFree(iter->iter);
+    regfree(iter->preg);
+}
+
 static void metadataFree(psMetadata* metadata)
 {
@@ -158,5 +167,5 @@
 
     // Set metadata item type
-    metadataItem->type = type;
+    metadataItem->type = type & PS_METADATA_TYPE_MASK;
 
     // Allocate and set metadata item name
@@ -165,5 +174,5 @@
 
     // Set metadata item value
-    switch(type) {
+    switch(metadataItem->type) {
     case PS_META_BOOL:
         metadataItem->data.B = (psBool)va_arg(argPtr, psS32);
@@ -190,4 +199,5 @@
     case PS_META_ASTROM:
     case PS_META_UNKNOWN:
+    case PS_META_MULTI:
         // Copy of input data not performed due to variability of data types
         metadataItem->data.V = psMemIncrRefCounter(va_arg(argPtr, psPtr));
@@ -223,5 +233,5 @@
 }
 
-psBool psMetadataAddItem(psMetadata *md, psMetadataItem *metadataItem, psS32 location)
+psBool psMetadataAddItem(psMetadata *md, psMetadataItem *metadataItem, psS32 location, psS32 flags)
 {
     char * key = NULL;
@@ -229,6 +239,4 @@
     psList *mdList = NULL;
     psMetadataItem *existingEntry = NULL;
-    psMetadataItem *newFolderEntry = NULL;
-    psMetadataType newType;
 
     PS_PTR_CHECK_NULL(md,NULL);
@@ -238,5 +246,4 @@
     PS_PTR_CHECK_NULL(metadataItem->name,NULL);
 
-    newType = metadataItem->type;
     mdTable = md->table;
     mdList = md->list;
@@ -245,72 +252,56 @@
     // See if key is already in table
     existingEntry = (psMetadataItem*)psHashLookup(mdTable, key);
+
+    // how the item is added to the hash depends on flags & prior existence
     if(existingEntry != NULL) {
-
-        if(existingEntry->type == PS_META_LIST) {
-
-            if(existingEntry->data.list == NULL) {
-                existingEntry->data.list = psListAlloc(NULL);
+        if ((flags & PS_META_DUPLICATE_OK) != 0) {
+            // duplicate entries allowed - add another entry.
+            if (existingEntry->type != PS_META_MULTI) {
+                // first duplicate, transfer the hash's old entry into a
+                // list of entries with the type PS_META_MULTI.
+
+                // add entry to a list and dereference the local pointer
+                psList* newList = psListAlloc(existingEntry);
+
+                existingEntry = psMetadataItemAlloc(key,
+                                                    PS_META_MULTI,
+                                                    "",
+                                                    newList);
+                psHashRemove(mdTable,key); // take out the old entry
+                psHashAdd(mdTable, key, existingEntry); // put in the new entry
+
+                // free local references of newly allocated items.
+                psFree(newList);
+                psFree(existingEntry);
+
             }
 
-            // Add leaf node to existing folder node
-            if(!psListAdd(existingEntry->data.list, PS_LIST_TAIL, metadataItem)) {
-                psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED, metadataItem->name);
+            // add to the hash's list of duplicate entries
+            if (! psListAdd(existingEntry->data.list, PS_LIST_TAIL, metadataItem) ) {
+                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_COLLECTION_FAILED,key);
                 return false;
             }
-        } else if(existingEntry->type != PS_META_LIST && newType!= PS_META_LIST) {
-
-            // Leaf node replaces another leaf. Make new folder node and add leaves.
-            newFolderEntry = psMetadataItemAlloc(key, PS_META_LIST, NULL, NULL);
-            newFolderEntry->data.list = psListAlloc(NULL);
-
-            if(!psListAdd(newFolderEntry->data.list, PS_LIST_TAIL, existingEntry)) {
-                psError(PS_ERR_UNKNOWN,false, PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED,key);
-                psFree(newFolderEntry);
-                return false;
-            }
-
-            if(!psListAdd(newFolderEntry->data.list, PS_LIST_TAIL, metadataItem)) {
-                psError(PS_ERR_UNKNOWN,false, PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED,key);
-                psFree(newFolderEntry);
-                return false;
-            }
-
-            if(!psHashRemove(mdTable, key)) {
-                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED,key);
-                psFree(newFolderEntry);
-                return false;
-            }
-
-            if(!psHashAdd(mdTable, key, newFolderEntry)) {
-                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_TABLE_FAILED,key);
-                psFree(newFolderEntry);
-                return false;
-            }
-
-            // Remove local reference to new folder node
-            psMemDecrRefCounter(newFolderEntry);
-        } else {
-
-            // Folder node replaces leaf or folder node - Put old node into new folder and remove from table
-            if(metadataItem->data.list == NULL) {
-                metadataItem->data.list = psListAlloc(NULL);
-            }
-
-            if(!psListAdd(metadataItem->data.list, PS_LIST_TAIL, existingEntry)) {
-                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_LIST_FAILED,key);
-                return false;
-            }
-
-            if(!psHashRemove(mdTable, key)) {
-                psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED,key);
-                return false;
-            }
-
+            // (added to list below)
+
+        } else if ((flags & PS_META_REPLACE) != 0) {
+            // replace entry instead of creating a duplicate entry.
+
+            // remove the existing entry from metadata
+            psListRemoveData(mdList, existingEntry);
+            psHashRemove(mdTable, key);
+
+            // treat as if new (added to list below)
             if(!psHashAdd(mdTable, key, metadataItem)) {
                 psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_TABLE_FAILED,key);
                 return false;
             }
+        } else {
+            // default is to error on duplicate entry.
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    PS_ERRORTEXT_psMetadata_DUPLICATE_NOT_ALLOWED);
+            return false;
         }
     } else {
+        // OK, this is a new item.
 
         // Node doesn't exist - Add new metadata item to metadata collection's hash
@@ -321,11 +312,7 @@
     }
 
-    // Add items to metadata collection's list, even if they have the same metadata item names. Folder nodes
-    // (PS_META_LIST metadata items) are not added, since the metadata list is flat.
-    if(metadataItem->type != PS_META_LIST) {
-        if(!psListAdd(mdList, location, metadataItem)) {
-            psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_COLLECTION_FAILED,key);
-            return false;
-        }
+    if(!psListAdd(mdList, location, metadataItem)) {
+        psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_COLLECTION_FAILED,key);
+        return false;
     }
 
@@ -333,15 +320,24 @@
 }
 
-psBool psMetadataAdd(psMetadata *md, psS32 where, const char *name,
-                     psMetadataType type, const char *comment, ...)
+psBool psMetadataAdd(psMetadata *md, psS32 location, const char *name,
+                     psS32 type, const char *comment, ...)
 {
     va_list argPtr;
+
+    va_start(argPtr, comment);
+    psBool result = psMetadataAddV(md,location,name,type,comment,argPtr);
+    va_end(argPtr);
+
+    return result;
+}
+
+psBool psMetadataAddV(psMetadata *md, psS32 location, const char *name,
+                      psS32 type, const char *comment, va_list list)
+{
     psMetadataItem* metadataItem = NULL;
 
-    va_start(argPtr, comment);
-    metadataItem = psMetadataItemAllocV(name, type, comment, argPtr);
-    va_end(argPtr);
-
-    if (!psMetadataAddItem(md, metadataItem, where)) {
+    metadataItem = psMetadataItemAllocV(name, type & PS_METADATA_TYPE_MASK, comment, list);
+
+    if (!psMetadataAddItem(md, metadataItem, location, type & PS_METADATA_FLAGS_MASK)) {
         psError(PS_ERR_UNKNOWN,false,PS_ERRORTEXT_psMetadata_ADD_FAILED);
         psFree(metadataItem);
@@ -373,51 +369,72 @@
 psBool psMetadataRemove(psMetadata *md, psS32 where, const char *key)
 {
-    psList* mdList = NULL;
-    psHash* mdTable = NULL;
-    psMetadataItem* entry = NULL;
-
-
     PS_PTR_CHECK_NULL(md,NULL);
+
+    PS_PTR_CHECK_NULL(md->list,NULL);
+    psList* mdList = md->list;
+
     PS_PTR_CHECK_NULL(md->table,NULL);
-    PS_PTR_CHECK_NULL(md->list,NULL);
-
-    mdList = md->list;
-    mdTable = md->table;
+    psHash* mdTable = md->table;
 
     // Select removal by key or index
     if (key != NULL) {
-
         // Remove by key name
-        entry = (psMetadataItem*)psHashLookup(mdTable, key);
+        psMetadataItem* entry = psHashLookup(mdTable,key);
         if (entry == NULL) {
-            psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_FIND_FAILED, key);
+            psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED, key);
             return false;
         }
-
+        if (entry->type == PS_META_MULTI) {
+            psMetadataItem* listItem;
+            psListIterator* iter = psListIteratorAlloc(
+                                       entry->data.list,
+                                       PS_LIST_HEAD,true);
+            while ((listItem=psListGetAndIncrement(iter)) != NULL) {
+                psListRemoveData(mdList, listItem);
+            }
+            psFree(iter);
+            psHashRemove(mdTable,key);
+
+        } else {
+            psListRemoveData(mdList, entry);
+            psHashRemove(mdTable, key);
+        }
     } else {
-
         // Remove by index
-        entry = psListGet(mdList, where);
+        psMetadataItem* entry = psListGet(mdList, where);
         if (entry == NULL) {
             psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_FIND_INDEX_FAILED, where);
             return false;
         }
-
         key = entry->name;
-        if(key == NULL) {
+
+        if (key == NULL) {
             psError(PS_ERR_BAD_PARAMETER_VALUE, true, PS_ERRORTEXT_psMetadata_REMOVE_LIST_INDEX_FAILED, where);
             return false;
         }
-    }
-
-    if (!psListRemoveData(mdList, entry)) {
-        psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_LIST_FAILED, key);
-        return false;
-    }
-
-    // Remove entry from metadata collection's table
-    if (!psHashRemove(mdTable, key)) {
-        psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED, key);
-        return false;
+
+        psMetadataItem* tableItem = psHashLookup(mdTable, key);
+        if (tableItem == NULL) {
+            psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED, key);
+            return false;
+        }
+
+        if (tableItem->type == PS_META_MULTI) {
+            // multiple entries with same key, remove just the specified one
+            psListRemoveData(tableItem->data.list, entry);
+            if (psListGet(tableItem->data.list,PS_LIST_HEAD) == NULL) {
+                // list is empty, so let's clear the whole entry now.
+                if (!psHashRemove(mdTable, key)) {
+                    psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED, key);
+                    return false;
+                }
+            }
+        } else {
+            if (!psHashRemove(mdTable, key)) {
+                psError(PS_ERR_UNKNOWN, false, PS_ERRORTEXT_psMetadata_REMOVE_TABLE_FAILED, key);
+                return false;
+            }
+        }
+        psListRemove(mdList, where);
     }
 
@@ -455,4 +472,8 @@
         }
         return NULL;
+    }
+    if (metadataItem->type == PS_META_MULTI) {
+        // if multiple keys found, use the first.
+        metadataItem = (psMetadataItem*)((metadataItem->data.list)->head);
     }
 
@@ -486,4 +507,8 @@
         } \
         return 0; \
+    } \
+    if (metadataItem->type == PS_META_MULTI) { \
+        /* if multiple keys found, use the first. */ \
+        metadataItem = (psMetadataItem*)((metadataItem->data.list)->head); \
     } \
     \
@@ -535,2 +560,145 @@
     return entry;
 }
+
+psMetadataIterator* psMetadataIteratorAlloc(psMetadata* md,
+        int location,
+        const char* regex)
+{
+    PS_PTR_CHECK_NULL(md,NULL);
+    PS_PTR_CHECK_NULL(md->list,NULL);
+
+    psMetadataIterator* newIter = psAlloc(sizeof(psMetadataIterator));
+    newIter->preg = NULL;
+    newIter->iter = NULL;
+
+    // Set deallocator
+    p_psMemSetDeallocator(newIter, (psFreeFcn) metadataIteratorFree);
+
+    if (regex == NULL) {
+        newIter->iter = psListIteratorAlloc(md->list, location, false);
+        return newIter;
+    } else {
+        int regRtn = regcomp(newIter->preg,regex,0);
+        if (regRtn != 0) {
+            char errMsg[256];
+            regerror(regRtn, newIter->preg, errMsg, 256);
+            regfree(newIter->preg);
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                    PS_ERRORTEXT_psMetadata_REGEX_INVALID,
+                    errMsg);
+            psFree(newIter);
+            return NULL;
+        }
+    }
+
+    psMetadataIteratorSet(newIter, location); // XXX: do we error if no match is found?
+
+    return newIter;
+}
+
+psBool psMetadataIteratorSet(psMetadataIterator* iterator,
+                             int location)
+{
+    int match;
+    psMetadataItem* cursor;
+
+    PS_PTR_CHECK_NULL(iterator,NULL);
+
+    psListIterator* iter = iterator->iter;
+    PS_PTR_CHECK_NULL(iterator->iter,NULL);
+
+    regex_t* preg = iterator->preg;
+
+    // handle trivial case where no regex subsetting is required.
+    if (preg == NULL) {
+        return psListIteratorSet(iter,location);
+    }
+
+    if (location < 0) {
+        // match from the tail
+        match = 0;
+        psListIteratorSet(iter,PS_LIST_TAIL);
+        while ( (cursor=(psMetadataItem*)iter->cursor) != NULL) {
+            if (regexec(preg, cursor->name, 0, NULL, 0) == 0) {
+                // this key is a match
+                match--;
+                if (match == location) {
+                    break;
+                }
+            }
+            (void)psListGetAndDecrement(iter);
+        }
+        return (match == location);
+    }
+
+    // find the n-th match from the head
+    match = -1;
+    psListIteratorSet(iter,PS_LIST_HEAD);
+    while ( (cursor=(psMetadataItem*)iter->cursor) != NULL) {
+        if (regexec(preg, cursor->name, 0, NULL, 0) == 0) {
+            // this key is a match
+            match++;
+            if (match == location) {
+                break;
+            }
+        }
+        (void)psListGetAndIncrement(iter);
+    }
+    return (match == location);
+}
+
+psMetadataItem* psMetadataGetAndIncrement(psMetadataIterator* iterator)
+{
+    psMetadataItem* oldValue;
+
+    PS_PTR_CHECK_NULL(iterator,NULL);
+
+    psListIterator* iter = iterator->iter;
+    PS_PTR_CHECK_NULL(iterator->iter,NULL);
+
+    regex_t* preg = iterator->preg;
+
+    // handle trivial case where no regex subsetting is required.
+    if (preg == NULL) {
+        return (psMetadataItem*)psListGetAndIncrement(iter);
+    }
+
+    oldValue = (psMetadataItem*)iter->cursor;
+
+    while (psListGetAndIncrement(iter) != NULL) {
+        if (iter->cursor != NULL &&
+                regexec(preg, ((psMetadataItem*)iter->cursor)->name, 0, NULL, 0) == 0) {
+            // this key is a match
+            break;
+        }
+    }
+    return oldValue;
+}
+
+psMetadataItem* psMetadataGetAndDecrement(psMetadataIterator* iterator)
+{
+    psMetadataItem* oldValue;
+
+    PS_PTR_CHECK_NULL(iterator,NULL);
+
+    psListIterator* iter = iterator->iter;
+    PS_PTR_CHECK_NULL(iterator->iter,NULL);
+
+    regex_t* preg = iterator->preg;
+
+    // handle trivial case where no regex subsetting is required.
+    if (preg == NULL) {
+        return (psMetadataItem*)psListGetAndDecrement(iter);
+    }
+
+    oldValue = (psMetadataItem*)iter->cursor;
+
+    while (psListGetAndDecrement(iter) != NULL) {
+        if (iter->cursor != NULL &&
+                regexec(preg, ((psMetadataItem*)iter->cursor)->name, 0, NULL, 0) == 0) {
+            // this key is a match
+            break;
+        }
+    }
+    return oldValue;
+}
