/***************************************************************************
 * RMI - The RMI Project
 *
 * Copyright 2002, Patrick Powell, San Diego, CA
 *     papowell@astart.com
 * See LICENSE for conditions of use.
 * $Id: perlobj.c,v 1.8 2004/02/25 15:56:25 papowell Exp papowell $
 ***************************************************************************/


/*
 *  PERL Objects
 *  
 *  OBJ - int, long, double, void * 
 *        STR, BINSTR,  ARRAY,  HASH
 *  
 *  STR: dynamically allocated string, null terminated
 *  BINSTR: dynamically allocated string, length specified
 *  USER_ARRAY:  array of user defined elements
 *  ARRAY: array of OBJs
 *  HASH: hash of OBJS
 *  LIST: array of char *'s - i.e. char *v[] object.
 *  
 */

#include "portable.h"
#include "safemalloc.h"
#include "safestrutil.h"
#include "errormsg.h"
#include "debug.h"
#include "plp_snprintf.h"
#include "perlobj.h"

 extern int Errorcode;
#ifndef SMALLBUFFER
# define SMALLBUFFER 512
#endif
#ifndef LARGEBUFFER
# define LARGEBUFFER 10240
#endif

#ifdef DEMALLOC
#undef CHECK
#define CHECK if( dmalloc_verify(0) != DMALLOC_VERIFY_NOERROR ) exit(1);
#else
#undef CHECK
#define CHECK 
#endif

/*
 * allocate an object
 */
#undef PAIR
#ifndef _UNPROTO_
# define PAIR(X) { #X , X }
#else
# define __string(X) "X"
# define PAIR(X) { __string(X) , X }
#endif

/*
 * Type_to_str: handy debugging function
 */
 struct key_val_ {
	char *key; int value;
 };
 struct key_val_ st_obj_keys[] = {
	PAIR(OBJ_ST_INT), PAIR(OBJ_ST_DOUBLE), PAIR(OBJ_ST_PTR),
	PAIR(OBJ_ST_SHORTSTR), PAIR(OBJ_ST_STR), PAIR(OBJ_ST_BINSTR),
	{0,0},
 };
 struct key_val_ obj_keys[] = {
	PAIR(OBJ_T_NONE),
	PAIR(OBJ_T_SCALAR),
	PAIR(OBJ_T_STR), PAIR(OBJ_T_ARRAY), PAIR(OBJ_T_HASH),
	PAIR(OBJ_T_USER_ARRAY), PAIR(OBJ_T_LIST),
	{0,0},
 };

 static const char *Get_keystr_( int value, struct key_val_ *v )
{
	static char msg[32];
	while( v->key ){
		if( v->value == value ){
			return( v->key );
		}
		++v;
	}
	SNPRINTF(msg,sizeof(msg)) "Unknown type %d", value);
	return(msg);
}

const char *Type_to_str( int key )
{
	return( Get_keystr_( key, obj_keys ) );
}
const char *Subtype_to_str( int key )
{
	return( Get_keystr_( key, st_obj_keys ) );
}

/*
 * get type and subtype.  Note: this should be inlined...
 */

#ifndef INLINE_OBJ
int TYPE_OBJ( OBJ *p )
{
	return( p?p->type:OBJ_T_NONE);
}

int SUBTYPE_OBJ( OBJ *p )
{
	return( p?p->subtype:0);
}
#endif

/*
 * OBJ *NEW_OBJ( MEMPASS )
 *   - allocate new object 
 */

#ifndef INLINE_OBJ
OBJ *NEW_OBJ( OBJ *p, MEMPASS )
{
	if( p ){
		Clear_OBJ(p);
	} else {
		p = malloc_or_die( sizeof( OBJ ), MEMPASSED );
		memset(p,0,sizeof(p[0]));
	}
	return( p );
}
#endif

/*
 * Clear_OBJ( OBJ *p )
 * Clear and free all of the memory associated with this object
 */

OBJ *Clear_OBJ( OBJ *p )
{
	int i;
	OBJ *vector;
	char **list;
	HASH_ELEMENT *element;

	DEBUGFC(DUTILS4){ DUMP_OBJ("Clear_OBJ", p); }
	if( !p ){
		return p;
	} else {
		switch( p->type ){
		case OBJ_T_NONE:
			break;
		case OBJ_T_USER_ARRAY:
			if( p->info.array.free_user_obj ){
				for( i = 0; i < p->info.array.len; ++i ){
					p->info.array.free_user_obj( AT_ARRAY_OBJ(p, i) );
				}
			}
			safefree( p->info.array.value, MEMINFO );
			break;
		case OBJ_T_SCALAR:
			break;
		case OBJ_T_STR:
			switch( p->subtype ){
				default:
					Errorcode = -1; FATAL(LOG_ERR)"Clear_OBJ: type %d (%s) has bad subtype %d (%s)",
						p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
					break;
				case OBJ_ST_SHORTSTR: break;
				case OBJ_ST_STR:
				case OBJ_ST_BINSTR:
					safefree( p->info.array.value, MEMINFO );
					break;
			}
			break;
		case OBJ_T_ARRAY:
			vector = p->info.array.value;
			for( i = 0; i < p->info.array.len; ++i ){
				Clear_OBJ( &vector[i] );
			}
			safefree( p->info.array.value, MEMINFO );
			break;
		case OBJ_T_HASH:
			element = p->info.array.value;
			for( i = 0; i < p->info.array.len; ++i ){
				Clear_OBJ( &element[i].key );
				Clear_OBJ( &element[i].value );
			}
			safefree( p->info.array.value, MEMINFO );
			break;
		case OBJ_T_LIST:
			list = p->info.array.value;
			for( i = 0; i < p->info.array.len; ++i ){
				if( list[i] ) safefree(list[i], MEMINFO); list[i] = 0;
			}
			safefree( p->info.array.value, MEMINFO );
			break;
		}
		memset(p,0,sizeof(p[0]));
	}
	DEBUGF(DUTILS4)("Clear_OBJ: returning obj 0x%lx", Cast_ptr_to_long(p) );
	return( p );
}

/*
 * void FREE_OBJ( OBJ *p )
 *  Clear the object,  then free it
 */

void FREE_OBJ( OBJ *p )
{
	if( p ){
		Clear_OBJ( p );
		safefree(p, MEMINFO);
	}
}

/*
 * get the scalar values and allocate a scalar object
 */

long IVAL_OBJ( OBJ *p )
{
	long v = 0;
	if( !p || p->type == OBJ_T_NONE ) return( 0 );
	if( p->type != OBJ_T_SCALAR && p->type != OBJ_T_STR ){
		Errorcode = -1; FATAL(LOG_ERR)"IVAL_OBJ: type %d (%s) not scalar or string", p->type, Type_to_str(p->type) );
	}
	if( p->type == OBJ_T_SCALAR && p->subtype != OBJ_ST_INT ){
		To_STR_OBJ(p, MEMINFO);
		v = strtol(p->info.array.value, 0, 0);
	} else if( p->type == OBJ_T_STR ){
		v = strtol(p->info.array.value, 0, 0);
	} else {
		v = p->info.scalar.value.ival;
	}
	return( v );
}


double DVAL_OBJ( OBJ *p )
{
	double v;
	if( !p || p->type == OBJ_T_NONE ) return( 0 );
	if( p->type != OBJ_T_SCALAR && p->type != OBJ_T_STR ){
		Errorcode = -1; FATAL(LOG_ERR)"DVAL_OBJ: type %d (%s) not scalar or string", p->type, Type_to_str(p->type) );
	}
	if( p->type == OBJ_T_SCALAR && p->subtype != OBJ_ST_DOUBLE ){
		To_STR_OBJ(p, MEMINFO);
		v = strtod(p->info.array.value, 0);
	} else if( p->type == OBJ_T_STR ){
		v = strtod(p->info.array.value, 0);
	} else {
		v = p->info.scalar.value.dval;
	}
	return( v );
}

void * PVAL_OBJ( OBJ *p )
{
	void *v;
	if( !p || p->type == OBJ_T_NONE ) return( 0 );
	if( p->type != OBJ_T_SCALAR && p->type != OBJ_T_STR ){
		Errorcode = -1; FATAL(LOG_ERR)"DVAL_OBJ: type %d (%s) not scalar or string", p->type, Type_to_str(p->type) );
	}
	if( p->type == OBJ_T_SCALAR && p->subtype != OBJ_ST_PTR ){
		To_STR_OBJ(p, MEMINFO);
		v = (void *)strtol(p->info.array.value, 0, 0);
	} else if( p->type == OBJ_T_STR ){
		v = (void *)strtol(p->info.array.value, 0, 0);
	} else {
		v = p->info.scalar.value.pval;
	}
	return( v );
}

/* VARARGS2 */
#ifdef HAVE_STDARGS
 OBJ * SET_SCALAR_OBJ( MEMPASS, OBJ *p, int type, ... )
#else
# if defined(DEF)
 OBJ * SET_SCALAR_OBJ(va_alist) va_dcl
# else
 OBJ * SET_SCALAR_OBJ()
# endif
#endif
{
#ifndef HAVE_STDARGS
    OBJ *p;
	const char *file;
	int line *file;
    int type;
#endif
    VA_LOCAL_DECL
    VA_START (type);
    VA_SHIFT (file, const char *);
    VA_SHIFT (line, int);
    VA_SHIFT (p, OBJ *);
    VA_SHIFT (type, int);

	if( !p || p->type != OBJ_T_SCALAR ){
		p = NEW_OBJ( p, MEMINFO );
	}
	p->type = OBJ_T_SCALAR;
	p->subtype = type;
	p->subtype = type;
	switch( type ){
		case OBJ_ST_INT: p->info.scalar.value.ival = va_arg( ap, int ); break;
		case OBJ_ST_DOUBLE: p->info.scalar.value.dval = va_arg( ap, double ); break;
		case OBJ_ST_PTR: p->info.scalar.value.pval = va_arg( ap, void * ); break;
		default:
			Errorcode = -1; FATAL(LOG_ERR)"SET_SCALAR_OBJ: sub type %d (%s) not scalar", type, Type_to_str(type) );
			break;
	}
	VA_END;
	return( p );
}

OBJ * SET_IVAL_OBJ( OBJ *p, int v, MEMPASS )
{
	p = NEW_OBJ( p, MEMPASSED );
	p->type = OBJ_T_SCALAR;
	p->subtype = OBJ_ST_INT;
	p->info.scalar.value.ival = v;
	return(p);
}

OBJ * SET_DVAL_OBJ( OBJ *p, double v, MEMPASS )
{
	p = NEW_OBJ( p, MEMPASSED );
	p->type = OBJ_T_SCALAR;
	p->subtype = OBJ_ST_DOUBLE;
	p->info.scalar.value.dval = v;
	return(p);
}

OBJ * SET_PVAL_OBJ( OBJ *p, void *v, MEMPASS )
{
	p = NEW_OBJ( p, MEMPASSED );
	p->type = OBJ_T_SCALAR;
	p->subtype = OBJ_ST_PTR;
	p->info.scalar.value.pval = v;
	return(p);
}

/*
 * string manipulations
 */

int IS_STR_OBJ( OBJ *p )
{
	return( p && (p->type == OBJ_T_STR) );
}

/*
 * get the string point from a short string (hash) or string
 */
char * VAL_SHORTSTR_OBJ( OBJ *p )
{
	if( p && p->type == OBJ_T_STR ){
		switch( p->subtype ){
		case OBJ_ST_SHORTSTR:
			return( p->info.strval );
		default:
			return( p->info.array.value );
		}
	}
	return( 0 );
}

char * VAL_STR_OBJ( OBJ *p)
{
	if( !p ) return( 0 );
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_STR;
		p->info.array.size = 1;
	} else if( p->type == OBJ_T_SCALAR ){
		To_STR_OBJ(p, MEMINFO);
	} else if( p->type != OBJ_T_STR || p->subtype != OBJ_ST_STR ){
		Errorcode = -1; FATAL(LOG_ERR)"VAL_STR_OBJ: object %d (%s) is not OBJ_T_STR type or subtype %d (%s) is not OBJ_ST_STR",
			p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
		return( 0 );
	}
	return( p->info.array.value );
}

void * VAL_BINSTR_OBJ( OBJ *p)
{
	if( !p ) return( 0 );
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_BINSTR;
		p->info.array.size = 1;
	} else if( p->type != OBJ_T_STR || p->subtype != OBJ_ST_BINSTR ){
		Errorcode = -1; FATAL(LOG_ERR)"VAL_BINSTR_OBJ: object %d (%s) not OBJ_T_STR type or subtype %d (%s) not OBJ_ST_BINSTR",
			p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
		return( 0 );
	}
	return( (void *)p->info.array.value );
}

int LEN_STR_OBJ( OBJ *p)
{
	if( !p ) return( 0 );
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_STR;
		p->info.array.size = 1;
	} else if( p->type != OBJ_T_STR && p->type != OBJ_ST_STR ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_STR_OBJ: object %d (%s) not OBJ_T_STR type or subtype %d (%s) not OBJ_ST_STR",
			p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
		return( 0 );
	}
	return( safestrlen( p->info.array.value ) );
}

int LEN_BINSTR_OBJ( OBJ *p)
{
	if( !p ) return( 0 );
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_BINSTR;
		p->info.array.size = 1;
	} else if( p->type != OBJ_T_STR && p->type != OBJ_ST_BINSTR ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_STR_OBJ: object %d (%s) not OBJ_T_STR type or subtype %d (%s) not OBJ_ST_BINSTR",
			p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
		return( 0 );
	}
	return( p->info.array.len );
}

OBJ * SET_STR_OBJ(OBJ *p, const char *str, MEMPASS)
{
	int len = safestrlen(str);
	DEBUGF(DUTILS4)("SET_STR_OBJ: from %s, line %d, str='%s', obj 0x%lx",
		file, line, str, Cast_ptr_to_long(p) );
	if( !str ) str = "";
	if( !p || p->type != OBJ_T_STR || p->subtype != OBJ_ST_STR ){
		p = NEW_OBJ( p, MEMPASSED );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_STR;
		p->info.array.size = 1;
	}
	SET_MAXLEN_STR_OBJ( p, len, MEMPASSED );
	memmove( p->info.array.value, str, len+1);
	return( p );
}

/*
 * set the short key strings for the HASH object
 */

OBJ * SET_SHORTSTR_OBJ(OBJ *p, const char *str, MEMPASS)
{
	int len;
	if( !p || str == 0 || p->type != OBJ_T_STR
				|| (p->subtype != OBJ_ST_SHORTSTR) ){
		p = NEW_OBJ( p, MEMPASSED );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_SHORTSTR;
		p->info.array.size = 1;
	}
	if( !str ) str = "";
	len = safestrlen(str);
	if( len < (int)sizeof(p->info.strval) ){
		memmove( p->info.strval, str, len+1);
	} else { 
		p->subtype = OBJ_ST_STR;
		SET_MAXLEN_STR_OBJ( p, len, MEMPASSED );
		memmove( p->info.array.value, str, len+1);
	}
	return( p );
}

/*
 *  void SET_MAXLEN_STR_OBJ( OBJ *p, int maxlen)
 *    allocate space long enough for the specified string length.
 *    you may need to change the string type as well
 */

OBJ * SET_MAXLEN_STR_OBJ( OBJ *p, int maxlen, MEMPASS )
{
	if( !p || p->type != OBJ_T_STR || p->subtype != OBJ_ST_STR ){
		p = NEW_OBJ( p, MEMPASSED );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_STR;
		p->info.array.size = 1;
	}
	DEBUGF(DUTILS4)("SET_MAXLEN_STR_OBJ: obj 0x%lx, want %d, maxlen %d",
		Cast_ptr_to_long(p), maxlen, p->info.array.maxlen );
	if( p->info.array.maxlen <= maxlen ){
		maxlen += STR_PAD;
		DEBUGF(DUTILS4)("SET_MAXLEN_STR_OBJ: alloc maxlen %d", maxlen );
		p->info.array.value = realloc_or_die( p->info.array.value, maxlen+1, MEMPASSED); 
		p->info.array.maxlen = maxlen;
	}
	return( p );
}

int GET_MAXLEN_STR_OBJ( OBJ *p )
{
	if( !p ){
		/* Errorcode = -1; FATAL(LOG_ERR)"GET_MAXLEN_STR_OBJ: NULL object"); */
	} else if( p->type == OBJ_T_NONE  ){
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_STR;
		p->info.array.size = 1;
	} else if( p->type != OBJ_T_STR && p->type != OBJ_T_STR ){
		Errorcode = -1; FATAL(LOG_ERR)"GET_MAXLEN_STR_OBJ: object %d (%s) not OBJ_T_STR or subtype %d (%s) is not OBJ_T_STR",
		p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
		return(0);
	}
	return( p->info.array.maxlen );
}

OBJ *SET_MAXLEN_BINSTR_OBJ( OBJ *p, int maxlen, MEMPASS)
{
	if( !p || p->type != OBJ_T_STR || p->subtype != OBJ_ST_BINSTR ){
		p = NEW_OBJ( p, MEMPASSED );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_BINSTR;
		p->info.array.size = 1;
	}
	DEBUGF(DUTILS4)("SET_MAXLEN_BINSTR_OBJ: want maxlen %d, current maxlen %d", maxlen, p->info.array.maxlen );
	if( p->info.array.maxlen <= maxlen ){
		p->info.array.value = realloc_or_die( p->info.array.value, maxlen+1, MEMPASSED); 
		p->info.array.maxlen = maxlen;
	}
	return( p );
}

OBJ *SET_LEN_BINSTR_OBJ( OBJ *p, int len, MEMPASS)
{
	p = SET_MAXLEN_BINSTR_OBJ(p, len, MEMPASSED);
	p->info.array.len = len;
	return( p );
}

int GET_MAXLEN_BINSTR_OBJ( OBJ *p, int maxlen)
{
	int len;

	if( !p ) return(0);
	if( p->type != OBJ_T_STR && p->subtype != OBJ_ST_BINSTR ){
		Errorcode = -1; FATAL(LOG_ERR)"GET_MAXLEN_BINSTR_OBJ: object %d (%s) not OBJ_T_STR or subtype %d (%s) is not OBJ_T_BINSTR",
		p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
		return(0);
	}
	len = p->info.array.maxlen;
	return( len );
}

OBJ * SET_BINSTR_OBJ( OBJ *p, const char *str, int len, MEMPASS )
{
	if( !p || p->type != OBJ_T_STR || p->subtype != OBJ_ST_BINSTR ){
		p = NEW_OBJ( p, MEMPASSED );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_BINSTR;
	}
	SET_MAXLEN_BINSTR_OBJ(p,len,MEMPASSED);
	if( len ){
		if( !str ){
			Errorcode = -1; FATAL(LOG_ERR)"SET_BINSTR_OBJ: no str value" );
			return( 0 );
		}
		memmove( p->info.array.value, str, len );
	}
	p->info.array.len = len;
	return( p );
}

/*
 * APPEND_STR_OBJ( OBJ *p, char *v1, char *v2, ... , (char *)0 )
 *   - append the strings to the STR_OBJ p
 */

/* VARARGS2 */
#ifdef HAVE_STDARGS
 OBJ * APPEND_STR_OBJ( MEMPASS,  OBJ *p, ... )
#else
# if defined(DEF)
 OBJ * APPEND_STR_OBJ(va_alist) va_dcl
# else
 OBJ * APPEND_STR_OBJ()
# endif
#endif
{
	char *str;
#ifndef HAVE_STDARGS
    OBJ *p;
	const char *file;
	int line;
#endif
    VA_LOCAL_DECL
    VA_START (p);
    VA_SHIFT (file, char *);
    VA_SHIFT (line, int);
    VA_SHIFT (p, OBJ *);
	if( !p || p->type != OBJ_T_STR || p->subtype != OBJ_ST_STR ){
		p = NEW_OBJ( p, MEMINFO );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_STR;
		p->info.array.size = 1;
		SET_STR_OBJ(p,"",MEMINFO);
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "APPEND_STR_OBJ: before", p); }
	while( (str = va_arg(ap, char *)) ){
		int len = safestrlen(str);
		if( len ){
			int olen = LEN_STR_OBJ( p );
			SET_MAXLEN_STR_OBJ( p, olen+len, MEMINFO );
			memmove(((char *)p->info.array.value)+olen, str, len+1);
		}
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "APPEND_STR_OBJ: after", p); }

	VA_END;
	return( p );
}


/*
 * PREFIX_STR_OBJ( OBJ *p, char *v1, char *v2, ... , (char *)0 )
 *   - prefix the strings to the STR_OBJ p
 */

/* VARARGS2 */
#ifdef HAVE_STDARGS
 OBJ * PREFIX_STR_OBJ( MEMPASS,  OBJ *p, ... )
#else
# if defined(DEF)
 OBJ * PREFIX_STR_OBJ(va_alist) va_dcl
# else
 OBJ * PREFIX_STR_OBJ()
# endif
#endif
{
	char *str;
#ifndef HAVE_STDARGS
    OBJ *p;
	const char *file;
	int line;
#endif
    VA_LOCAL_DECL
    VA_START (p);
    VA_SHIFT (file, char *);
    VA_SHIFT (line, int);
    VA_SHIFT (p, OBJ *);
	if( !p || p->type != OBJ_T_STR || p->subtype != OBJ_ST_STR ){
		p = NEW_OBJ( p, MEMINFO );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_STR;
		p->info.array.size = 1;
		SET_STR_OBJ(p,"",MEMINFO);
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "PREFIX_STR_OBJ: before", p); }
	while( (str = va_arg(ap, char *)) ){
		int len = safestrlen(str);
		if( len ){
			int olen = safestrlen( p->info.array.value );
			SET_MAXLEN_STR_OBJ( p, olen+len, MEMINFO );
			*((char *)(p->info.array.value)+olen) = 0;
			memmove((char *)(p->info.array.value)+len, p->info.array.value, olen+1 );
			memmove(p->info.array.value, str, len);
		}
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "PREFIX_STR_OBJ: after", p); }

	VA_END;
	return( p );
}


/*
 * APPEND_BINSTR_OBJ( OBJ *p, char *v1,int len1, char *v2, int len2,... , (char *)0 )
 *   - append the strings to the STR_OBJ p
 */


/* VARARGS2 */
#ifdef HAVE_STDARGS
 OBJ * APPEND_BINSTR_OBJ(  OBJ *p, ... )
#else
# if defined(DEF)
 OBJ * APPEND_BINSTR_OBJ(va_alist) va_dcl
# else
 OBJ * APPEND_BINSTR_OBJ()
# endif
#endif
{
	char *str;
#ifndef HAVE_STDARGS
    OBJ *p;
#endif
    VA_LOCAL_DECL
    VA_START (p);
    VA_SHIFT (p, OBJ *);
	if( !p || p->type != OBJ_T_STR || p->subtype != OBJ_ST_BINSTR ){
		p = NEW_OBJ( p, MEMINFO );
		p->type = OBJ_T_STR;
		p->subtype = OBJ_ST_BINSTR;
		p->info.array.size = 1;
	}
	while( (str = va_arg(ap, char *)) ){
		int len = va_arg(ap, int);
		if( len ){
			int olen = p->info.array.len;
			int nlen = olen + len;
			SET_MAXLEN_BINSTR_OBJ( p, nlen, MEMINFO );
			memmove((char *)(p->info.array.value)+olen, str, len+1);
			p->info.array.len = nlen;
		}
	}

	VA_END;
	return( p );
}

OBJ * NEW_STR_OBJ( const char *str, MEMPASS )
{
	return( SET_STR_OBJ(0, str, MEMPASSED ));
}

OBJ * NEW_BINSTR_OBJ( void *str, int len, MEMPASS )
{
	return( SET_BINSTR_OBJ(0, str, len, MEMPASSED) );
}

/*
 * array manipulations
 */

int LEN_ARRAY_OBJ( OBJ *p )
{
	if( !p ){
		return(0);
	}
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_ARRAY;
		p->info.array.size = sizeof( p[0] );
	} else if( p->type != OBJ_T_ARRAY && p->type != OBJ_T_USER_ARRAY ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_ARRAY or OBJ_T_USER_ARRAY", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	return( p->info.array.len );
}

void * AT_ARRAY_OBJ( OBJ *p, int indexv )
{
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"AT_ARRAY_OBJ: NULL OBJECT");
		return( 0 );
	}
	if( p->type != OBJ_T_ARRAY
		&& p->type != OBJ_T_USER_ARRAY
		&& p->type != OBJ_T_LIST){
		Errorcode = -1; FATAL(LOG_ERR)"AT_ARRAY_OBJ: object %d (%s) not OBJ_T_ARRAY, OBJ_T_USER_ARRAY or OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	if( indexv >= p->info.array.len ){
		Errorcode = -1; FATAL(LOG_ERR)"AT_ARRAY_OBJ: index %d >= array len %d",
			indexv, p->info.array.len );
		return( 0 );
	}
	if( p->info.array.value == 0 ){
		LOGMSG(LOG_ERR)"AT_ARRAY_OBJ: LOGIC ERROR - index %d, len %d, maxlen %d, no value obj 0x%lx",
		indexv, p->info.array.len, p->info.array.maxlen, Cast_ptr_to_long(p));
		abort();
		return( 0 );
	}
	return( (OBJ *)((char *)p->info.array.value + p->info.array.size*indexv) );
}

OBJ * EXTEND_ARRAY_OBJ( OBJ *p, int len, int size, MEMPASS )
{
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"EXTEND_ARRAY_OBJ: NULL object");
		return(0);
	}
	if( p->type == OBJ_T_NONE ){
		if( !size ){
			p->type = OBJ_T_ARRAY;
			p->info.array.size = sizeof( p[0] );
		} else if( size ){
			p->type = OBJ_T_USER_ARRAY;
			p->info.array.size = size;
		}
	}
	switch( p->type ){
		case OBJ_T_ARRAY: case OBJ_T_USER_ARRAY: case OBJ_T_LIST:
			break;
		default:
			Errorcode = -1; FATAL(LOG_ERR)"EXTEND_ARRAY_OBJ: object %d (%s) not OBJ_T_ARRAY or OBJ_T_USER_ARRAY", p->type, Type_to_str(p->type) );
			return( 0 );
	}
	if( p->info.array.size == 0 ){
		Errorcode = -1; FATAL(LOG_ERR)"EXTEND_ARRAY_OBJ: object %d (%s) has 0 size", p->type, Type_to_str(p->type) );
	}

	if( len >= p->info.array.maxlen ){
		int extra = 0;
		int oldlen = p->info.array.len;
		switch( p->type ){
			case OBJ_T_USER_ARRAY: extra = USER_ARRAY_PAD; break;
			case OBJ_T_ARRAY: extra = ARRAY_PAD; break;
			case OBJ_T_LIST: extra = LIST_PAD; break;
		}
		p->info.array.maxlen = len+extra;
		p->info.array.value = realloc_or_die( p->info.array.value, (p->info.array.maxlen + 1) *p->info.array.size, MEMPASSED );
		memset( (char *)(p->info.array.value) + p->info.array.size *oldlen,
			0, (p->info.array.maxlen - oldlen + 1) * p->info.array.size );
	}
	p->info.array.len = len;
	return p;
}

OBJ * TRUNC_ARRAY_OBJ( OBJ *p, int len, MEMPASS )
{
	if( !p || p->type == OBJ_T_NONE ){
		p = NEW_OBJ(p, MEMPASSED);
		p->type = OBJ_T_ARRAY;
		p->info.array.size = sizeof( p[0] );
	} else if( p->type != OBJ_T_ARRAY && p->type != OBJ_T_USER_ARRAY ){
		Errorcode = -1; FATAL(LOG_ERR)"TRUNC_ARRAY_OBJ: object %d (%s) not OBJ_T_ARRAY or OBJ_T_USER_ARRAY", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	if( p->info.array.size == 0 ){
		Errorcode = -1; FATAL(LOG_ERR)"TRUNC_ARRAY_OBJ: object %d (%s) size zero", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	while( p->info.array.len > len ){
		void *d;
		--p->info.array.len;
		d = ((char *)(p->info.array.value) + p->info.array.size*p->info.array.len);
		if( p->type == OBJ_T_USER_ARRAY ){
			if( p->info.array.free_user_obj ){
				p->info.array.free_user_obj(d);
			}
			memset( d, 0, p->info.array.size);
		} else {
			Clear_OBJ((OBJ *)d);
		}
	}
	return p;
}

OBJ *SHIFT_DOWN_ARRAY_OBJ( OBJ *p, MEMPASS )
{
	int len;
	OBJ *s, *t;
	if( !p || p->type == OBJ_T_NONE ){
		p = NEW_OBJ(p,MEMPASSED);
		p->type = OBJ_T_ARRAY;
		p->info.array.size = sizeof( p[0] );
	}
	len = LEN_ARRAY_OBJ(p);
	if( len+1 >= p->info.array.maxlen ){
		p = EXTEND_ARRAY_OBJ( p, len+1, 0, MEMPASSED );
		p->info.array.len = len;
	}
	s = AT_ARRAY_OBJ(p,0);
	if( len ){
		/* move the whole array down */
		t = AT_ARRAY_OBJ(p,1);
		memmove(t,s,len * p->info.array.size);
	}
	memset(s,0, p->info.array.size);
	++p->info.array.len;
	/*DUMP_OBJ("SHIFT_DOWN: after", p);*/
	return(p);
}


OBJ *ROTATE_DOWN_ARRAY_OBJ( OBJ *p, MEMPASS )
{
	int len;
	OBJ *s, *t;
	if( !p || p->type == OBJ_T_NONE ){
		p = NEW_OBJ( p, MEMPASSED );
		p->type = OBJ_T_ARRAY;
		p->info.array.size = sizeof( p[0] );
	}
	len = LEN_ARRAY_OBJ(p);
	/* shift the whole array down */
	if( len+1 > p->info.array.maxlen ){
		p = EXTEND_ARRAY_OBJ( p, len+1, 0, MEMPASSED );
		p->info.array.len = len;
	}
	if( len > 1 ){
		s = AT_ARRAY_OBJ(p,0);
		t = AT_ARRAY_OBJ(p,1);
		memmove(t,s,len * p->info.array.size);
		t = AT_ARRAY_OBJ(p,len);
		memmove(s,t,p->info.array.size);
		memset(t,0,p->info.array.size);
	}
	/*DUMP_OBJ("ROTATE_DOWN: after", p);*/
	return(p);
}

OBJ * NEW_ARRAY_OBJ( OBJ *p, MEMPASS)
{
	p = NEW_OBJ( p, MEMPASSED );
	p->type = OBJ_T_ARRAY;
	p->info.array.size = sizeof( p[0] );
	return(p);
}

OBJ * NEW_USER_ARRAY_OBJ( OBJ *p, int size, obj_proc_free freeit,
	obj_proc_dump dumpit, obj_proc_copy copyit, MEMPASS )
{
	if( size == 0 ){
		Errorcode = -1; FATAL(LOG_ERR)"NEW_USER_ARRAY_OBJ: size zero");
		return( 0 );
	}
	p = NEW_OBJ(p, MEMPASSED);
	p->type = OBJ_T_USER_ARRAY;
	p->info.array.size = size;
	p->info.array.free_user_obj = freeit;
	p->info.array.dump_user_obj = dumpit;
	p->info.array.copy_user_obj = copyit;
	return(p);
}

/*
 * sizes
 */
int GET_SIZE_ARRAY_OBJ( OBJ *p )
{
	int size = 0;
	if( p ) switch( p->type ){
		case OBJ_T_ARRAY:
		case OBJ_T_USER_ARRAY:
			size = p->info.array.size;
			break;
		case OBJ_T_STR:
			size = 1;
			break;
	}
	return( size );
}

/*
 * list manipulations
 */

OBJ * NEW_LIST_OBJ( OBJ *p, MEMPASS)
{
	p = NEW_OBJ( p, MEMPASSED );
	p->type = OBJ_T_LIST;
	p->info.array.size = sizeof( char * );
	return(p);
}

int LEN_LIST_OBJ( OBJ *p )
{
	if( !p ){
		return(0);
	}
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_LIST;
		p->info.array.size = sizeof( char *);
	}
	if( p->type != OBJ_T_LIST ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	return( p->info.array.len );
}

char ** GET_LIST_OBJ( OBJ *p )
{
	if( !p ){
		/*Errorcode = -1; FATAL(LOG_ERR)"GET_ENTRY_LIST_OBJ: NULL object");*/
		return(0);
	}
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_LIST;
		p->info.array.size = sizeof( char * );
	}
	if( p->type != OBJ_T_LIST ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	return( p->info.array.value );
}
char * GET_ENTRY_LIST_OBJ( OBJ *p, int indexv )
{
	char **sp;
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"GET_ENTRY_LIST_OBJ: NULL object");
		return(0);
	}
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_LIST;
		p->info.array.size = sizeof( char * );
	}
	if( p->type != OBJ_T_LIST ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	sp = AT_ARRAY_OBJ( p, indexv );
	if( sp ){
		return( *sp );
	}
	return( 0 );
}

OBJ * SET_ENTRY_LIST_OBJ( OBJ *p, int indexv, const char *s, MEMPASS )
{
	char **sp;
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"SET_ENTRY_LIST_OBJ: NULL object");
		return(0);
	}
	if( p && p->type == OBJ_T_NONE ){
		p->type = OBJ_T_LIST;
		p->info.array.size = sizeof( char * );
	}
	if( p->type != OBJ_T_LIST ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	sp = AT_ARRAY_OBJ( p, indexv );
	*sp = safestrdup(s, MEMPASSED);
	return( p );
}

OBJ * APPEND_LIST_OBJ( OBJ *p, const char *s, MEMPASS )
{
	int len;
	char **sp;
	if( !p || p->type == OBJ_T_NONE ){
		p = NEW_LIST_OBJ( p, MEMPASSED );
	}
	if( p->type != OBJ_T_LIST ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	len = p->info.array.len;
	if( len >= p->info.array.maxlen ){
		EXTEND_LIST_OBJ(p,len+1,MEMPASSED);
	}
	p->info.array.len = len+1;
	sp = AT_ARRAY_OBJ( p, len );
	*sp = safestrdup(s, MEMPASSED );
	/*DEBUGFC(DUTILS4){ DUMP_OBJ(  "APPEND_LIST_OBJ: ", p ); }*/
	return( p );
}

OBJ * APPEND_LIST_BIN_OBJ( OBJ *p, const char *s, int valuelen, MEMPASS )
{
	int len;
	char **sp;
	char *newval;
	if( !p || p->type == OBJ_T_NONE ){
		p = NEW_LIST_OBJ( p, MEMPASSED );
	}
	if( p->type != OBJ_T_LIST ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	len = p->info.array.len;
	if( len >= p->info.array.maxlen ){
		EXTEND_LIST_OBJ(p,len+1,MEMPASSED);
	}
	p->info.array.len = len+1;
	sp = AT_ARRAY_OBJ( p, len );
	newval = malloc_or_die(valuelen, MEMPASSED);
	memmove( newval, s, valuelen );
	*sp = newval;
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "APPEND_LIST_OBJ: ", p ); }
	return( p );
}
OBJ * EXTEND_LIST_OBJ( OBJ *p, int len, MEMPASS )
{
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"EXTEND_LIST_OBJ: NULL object");
		return(0);
	}
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_LIST;
		p->info.array.size = sizeof( char * );
	}
	if( p->type != OBJ_T_LIST ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_ARRAY_OBJ: object %d (%s) not OBJ_T_LIST", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	return( EXTEND_ARRAY_OBJ( p, len, 0, MEMPASSED ) );
}

/*
 * hash manipulations
 */

int LEN_HASH_OBJ( OBJ *p )
{
	if( !p ) return( 0 );
	if( p->type == OBJ_T_NONE ){
		p->type = OBJ_T_HASH;
		p->info.array.size = sizeof( p[0] );
	}
	if( p->type != OBJ_T_HASH  ){
		Errorcode = -1; FATAL(LOG_ERR)"LEN_HASH_OBJ: object %d (%s) not OBJ_T_HASH", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	return( p->info.array.len );
}

OBJ * KEY_HASH_OBJ( OBJ *p, int indexv )
{
	HASH_ELEMENT *element;
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"KEY_HASH_OBJ: NULL object");
		return(0);
	}
	if( p->type != OBJ_T_HASH  ){
		Errorcode = -1; FATAL(LOG_ERR)"KEY_HASH_OBJ: object %d (%s) not OBJ_T_HASH", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	if( indexv < 0 || indexv >= p->info.array.len ){
		Errorcode = -1; FATAL(LOG_ERR)"KEY_HASH_OBJ: object %d (%s) len %d, indexv %d out of bounds",
			p->type, Type_to_str(p->type), p->info.array.len, indexv );
		return( 0 );
	}
	element = p->info.array.value;
	return( &(element[indexv].key) );
}


OBJ * VALUE_HASH_OBJ( OBJ *p, int indexv )
{
	HASH_ELEMENT *element;
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"VALUE_HASH_OBJ: NULL object");
		return(0);
	}
	if( p->type != OBJ_T_HASH  ){
		Errorcode = -1; FATAL(LOG_ERR)"VALUE_HASH_OBJ: object %d (%s) not OBJ_T_HASH", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	if( indexv < 0 || indexv >= p->info.array.len ){
		Errorcode = -1; FATAL(LOG_ERR)"VALUE_HASH_OBJ: object %d (%s) len %d, indexv %d out of bounds",
			p->type, Type_to_str(p->type), p->info.array.len, indexv );
		return( 0 );
	}
	element = p->info.array.value;
	return( &(element[indexv].value) );
}

OBJ * GET_HASH_OBJ( OBJ *p, const char *key )
{
	DEBUGF(DUTILS4)( "GET_HASH_OBJ: OBJ 0x%lx, KEY '%s' \n", Cast_ptr_to_long(p), key);
	if( !p || p->type == OBJ_T_NONE || !key ){
		return(0);
	}
	if( p->type != OBJ_T_HASH  ){
		Errorcode = -1; FATAL(LOG_ERR)"GET_HASH_OBJ: object %d (%s) not OBJ_T_HASH", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	if( p->info.array.len == 0 ) return(0);
	/* now we do a binary search */
	{
		int bottom = 0;
		int top = p->info.array.len-1;
		HASH_ELEMENT *element = p->info.array.value;
		while( bottom <= top ){
			int middle = (bottom + top)/2;
			char *s = VAL_SHORTSTR_OBJ(&element[middle].key);
			int cmp = safestrcmp( key, s );
			DEBUGF(DUTILS4)( "GET_HASH_OBJ: KEY '%s' bottom %d, middle %d, top %d, comparing '%s', cmp %d\n",
				key, bottom, middle, top, s, cmp );
			if( cmp < 0 ){
				top = middle-1;
			} else if( cmp > 0 ){
				bottom = middle+1;
			} else {
				void *ptr = &(element[middle].value);
				DEBUGF(DUTILS4)( "GET_HASH_OBJ: OBJ 0x%lx, KEY '%s' FOUND 0x%lx\n",
					Cast_ptr_to_long(p), key, Cast_ptr_to_long(ptr));
				return( ptr );
			}
		}
	}
	return( 0 );
}

OBJ * SET_HASH_OBJ( OBJ *p, const char *key, OBJ *value, MEMPASS )
{
	int bottom, top;
	int middle = 0, cmp = -1;
	int count = 0;
	char *s;
	HASH_ELEMENT *element;

	if( !p || p->type == OBJ_T_NONE  ){
		p = NEW_OBJ(p, MEMPASSED);
		p->type = OBJ_T_HASH;
		p->info.array.size = sizeof( element[0] );
	} else if( p->type != OBJ_T_HASH  ){
		Errorcode = -1; FATAL(LOG_ERR)"SET_HASH_OBJ: object %d (%s) not OBJ_T_HASH", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	if( key == 0 ){
		Errorcode = -1; FATAL(LOG_ERR)"SET_HASH_OBJ: object %d (%s) - searching for NULL key", p->type, Type_to_str(p->type) );
		return( 0 );
	}
	/* now we do a binary search */
	bottom = 0;
	top = p->info.array.len-1;
	element = p->info.array.value;
	DEBUGF(DUTILS4)("SET_HASH_OBJ: START '%s' bottom %d, top %d, len %d, maxlen %d\n",
			key, bottom, top, p->info.array.len, p->info.array.maxlen );
	s = 0;
	while( bottom <= top ){
		middle = (bottom + top)/2;
		s = VAL_SHORTSTR_OBJ(&element[middle].key);
		cmp = safestrcmp( key, s );
		DEBUGF(DUTILS4)("SET_HASH_OBJ: KEY '%s' bottom %d, middle %d, top %d, compare '%s', cmp %d\n",
			key, bottom, middle, top,s, cmp );
		if( cmp < 0 ){
			top = middle-1;
		} else if( cmp > 0 ){
			bottom = middle+1;
		} else {
			break;
		}
	}
	/* if cmp < 0, we find the place where it goes to > 0 */
	DEBUGF(DUTILS4)( "SET_HASH_OBJ: FINAL '%s' bottom %d, middle %d, top %d, compare '%s', cmp %d\n",
			key, bottom, middle, top, element?s:0, cmp );
	if( cmp ){
		if( p->info.array.len >= p->info.array.maxlen ){
			int oldmax = p->info.array.maxlen;
			p->info.array.maxlen += 1 + HASH_PAD;
			p->info.array.value = element = realloc_or_die( p->info.array.value,
				sizeof(element[0]) * p->info.array.maxlen, MEMPASSED );
			memset(&element[oldmax], 0, sizeof(element[0])*(p->info.array.maxlen-oldmax));
		}
		DEBUGFC(DUTILS4){ SHORT_DUMP_OBJ(  "SET_HASH_OBJ: after extension", p ); }
		if( cmp < 0 ){
			/* we need to move middle to end up one position */
		} else if( cmp > 0 ){
			/* we need to move middle+1 to end up one position */
			++middle;
		}
		count = p->info.array.len - middle;
		DEBUGF(DUTILS4)( "SET_HASH_OBJ: ADJUST middle %d, count %d\n", middle, count );
		if( count ){
			memmove( &element[middle+1], &element[middle], sizeof(element[0]) * count );
		}
		++p->info.array.len;
		memset( &element[middle], 0, sizeof( element[0] ) );
		SET_SHORTSTR_OBJ( &element[middle].key, key, MEMPASSED );
	}
	{
		void *ptr = &(element[middle].value);
		DEBUGF(DUTILS4)( "SET_HASH_OBJ: OBJ 0x%lx, KEY '%s' FOUND 0x%lx\n",
		Cast_ptr_to_long(p), key, Cast_ptr_to_long(ptr));
		COPY_OBJ( ptr, value, MEMPASSED );
		return( ptr );
	}
}

/*
 * OBJ *SET_HASH_STR_OBJ( OBJ *p, const char *key, const char *str )
 *
 *  - set a hash value to a string
 */

OBJ *SET_HASH_STR_OBJ( OBJ *p, const char *key, const char *str, MEMPASS )
{
	OBJ *v = 0;
	if( str ){
		v = GET_HASH_OBJ( p, key );
		if( !v ){
			v = SET_HASH_OBJ( p, key, 0, MEMPASSED );
		}
		SET_STR_OBJ( v, str, MEMPASSED );
	} else {
		REMOVE_HASH_OBJ( p, key );
	}
	return(v);
}


/*
 * char *GET_HASH_STR_OBJ( OBJ *p, const char *key )
 *
 *  - get a string from a hash
 */

char *GET_HASH_STR_OBJ( OBJ *p, const char *key, MEMPASS )
{
	char *s = 0;
	OBJ *v = GET_HASH_OBJ( p, key );
	if( v ){
		To_STR_CONV(v, MEMPASSED);
		s = VAL_STR_OBJ( v );
	}
	return(s);
}


/*
 * char *To_STR_CONV( OBJ *p )
 * convert to string in place
 */
char *To_STR_CONV( OBJ *p, MEMPASS )
{
	char buffer[SMALLBUFFER];
	if( p ){
		switch( p->type ){
		case OBJ_T_NONE:
			break;
		case OBJ_T_SCALAR:
			switch( p->subtype ){
			case OBJ_ST_INT:
				SNPRINTF(buffer,sizeof(buffer)) "%ld", IVAL_OBJ(p) );
				SET_STR_OBJ(p,buffer, MEMPASSED);
				break;
			case OBJ_ST_DOUBLE:
				SNPRINTF(buffer,sizeof(buffer)) "%f", DVAL_OBJ(p) );
				SET_STR_OBJ(p,buffer, MEMPASSED);
				break;
			case OBJ_ST_PTR:
				SNPRINTF(buffer,sizeof(buffer)) "0x%lx", Cast_ptr_to_long(PVAL_OBJ(p)) );
				SET_STR_OBJ(p,buffer, MEMPASSED);
				break;
			}
			break;
		case OBJ_T_STR:
			break;
		default:
			Errorcode = -1; FATAL(LOG_ERR)"To_STR_CONV: cannot convert object %d (%s)", p->type, Type_to_str(p->type) );
			break;
		}
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "To_STR_CONV", p ); }
	return( VAL_STR_OBJ(p) );
}


/*
 * char *To_IVAL_CONV( OBJ *p )
 * convert to string, return value
 */
long To_IVAL_CONV( OBJ *p, MEMPASS )
{
	long n = 0;
	if( p ){
		switch( p->type ){
		case OBJ_T_NONE:
			break;
		case OBJ_T_SCALAR:
			switch( p->subtype ){
			case OBJ_ST_INT:
				n = IVAL_OBJ(p);
				break;
			case OBJ_ST_DOUBLE:
				n = DVAL_OBJ(p);
				break;
			case OBJ_ST_PTR:
				n = Cast_ptr_to_long(PVAL_OBJ(p));
				break;
			}
			break;
		case OBJ_T_STR:
			n = strtol(VAL_STR_OBJ(p), 0, 0);
			break;
		default:
			Errorcode = -1; FATAL(LOG_ERR)"To_IVAL_CONV: cannot convert object %d (%s)", p->type, Type_to_str(p->type) );
			break;
		}
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "To_IVAL_CONV", p ); }
	return( n );
}

/*
 * OBJ *SET_HASH_IVAL_OBJ( OBJ *p, const char *key, int val )
 *
 *  - set a hash value to an integer
 */

OBJ *SET_HASH_IVAL_OBJ( OBJ *p, const char *key, int val, MEMPASS )
{
	OBJ *v = SET_HASH_OBJ( p, key, 0, MEMPASSED );
	SET_IVAL_OBJ( v, val, MEMPASSED );
	return(v);
}


/*
 * int GET_HASH_IVAL_OBJ( OBJ *p, const char *key )
 *
 *  - get a string from a hash
 */

int GET_HASH_IVAL_OBJ( OBJ *p, const char *key )
{
	int n= 0;
	OBJ *v = GET_HASH_OBJ( p, key );
	if( v ){
		n = IVAL_OBJ(v);
	}
	return(n);
}

/*
 * OBJ *SET_HASH_DVAL_OBJ( OBJ *p, const char *key, int val )
 *
 *  - set a hash value to an integer
 */

OBJ *SET_HASH_DVAL_OBJ( OBJ *p, const char *key, double val, MEMPASS )
{
	OBJ *v = SET_HASH_OBJ( p, key, 0, MEMPASSED );
	SET_DVAL_OBJ( v, val, MEMPASSED );
	return(v);
}


/*
 * int GET_HASH_DVAL_OBJ( OBJ *p, const char *key )
 *
 *  - get a string from a hash
 */

double GET_HASH_DVAL_OBJ( OBJ *p, const char *key )
{
	double n= 0;
	OBJ *v = GET_HASH_OBJ( p, key );
	if( v ){
		n = DVAL_OBJ(v);
	}
	return(n);
}


/*
 * OBJ *SET_HASH_PVAL_OBJ( OBJ *p, const char *key, void * val )
 *
 *  - set a hash value to a pointer
 */

OBJ *SET_HASH_PVAL_OBJ( OBJ *p, const char *key, void * val, MEMPASS )
{
	OBJ *v = SET_HASH_OBJ( p, key, 0, MEMPASSED );
	SET_PVAL_OBJ( v, val, MEMPASSED );
	return(v);
}


/*
 * void * GET_HASH_PVAL_OBJ( OBJ *p, const char *key )
 *
 *  - get a pointer from a hash
 */

void * GET_HASH_PVAL_OBJ( OBJ *p, const char *key )
{
	void *n = 0;
	OBJ *v = GET_HASH_OBJ( p, key );
	if( v ){
		n = PVAL_OBJ( v );
	}
	return(n);
}

void REMOVE_HASH_OBJ( OBJ *p, const char *key )
{
	DEBUGF(DUTILS4)( "REMOVE_HASH_OBJ: OBJ 0x%lx, KEY '%s' \n", Cast_ptr_to_long(p), key);
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"REMOVE_HASH_OBJ: NULL object");
		return;
	}
	if( p->type == OBJ_T_NONE  ){
		p->type = OBJ_T_HASH;
	} else if( p->type != OBJ_T_HASH  ){
		Errorcode = -1; FATAL(LOG_ERR)"REMOVE_HASH_OBJ: object %d (%s) not OBJ_T_HASH", p->type, Type_to_str(p->type) );
		return;
	}
	if( key == 0 ){
		Errorcode = -1; FATAL(LOG_ERR)"REMOVE_HASH_OBJ: object %d (%s) - searching for NULL key", p->type, Type_to_str(p->type) );
		return;
	}
	if( p->info.array.len == 0 ) return;
	/* now we do a binary search */
	{
		int count;
		int bottom = 0;
		int top = p->info.array.len-1;
		HASH_ELEMENT *element = p->info.array.value;
		while( bottom <= top ){
			int middle = (bottom + top)/2;
			char *s = VAL_SHORTSTR_OBJ(&element[middle].key);
			int cmp = safestrcmp( key, s );
			DEBUGF(DUTILS4)(
"REMOVE_HASH_OBJ: KEY '%s' bottom %d, middle %d, top %d, comparing '%s', cmp %d\n",
				key, bottom, middle, top, s, cmp );
			if( cmp < 0 ){
				top = middle-1;
			} else if( cmp > 0 ){
				bottom = middle+1;
			} else {
				count = p->info.array.len - middle - 1;
				DEBUGF(DUTILS4)( "REMOVE_HASH_OBJ: FOUND '%s' middle %d, count %d\n",
					s, middle, count );
				/* we remove this element */
				Clear_OBJ(&element[middle].key);
				Clear_OBJ(&element[middle].value);
				if( count ){
					memmove( &element[middle], &element[middle+1], sizeof(element[0]) *count );
				}
				--p->info.array.len;
				break;
			}
		}
	}
}

OBJ * NEW_HASH_OBJ(OBJ *p, MEMPASS)
{
	p = NEW_OBJ( p, MEMPASSED );
	p->type = OBJ_T_HASH;
	p->info.array.size = sizeof( HASH_ELEMENT );
	return(p);
}

/*
 * object manipulations
 */

OBJ * COPY_OBJ( OBJ *dest, OBJ *p, MEMPASS )
{
	HASH_ELEMENT *newelement, *oldelement;
	int i, len;

	DEBUGFC(DUTILS4){ DUMP_OBJ(  "COPY_OBJ - in", p ); }
	if( !p ){
		return(p);
	}
	dest = NEW_OBJ(dest,MEMPASSED);
	dest->type = p->type;
	dest->subtype = p->subtype;
	switch( p->type ){
	case OBJ_T_NONE:
		break;
	case OBJ_T_SCALAR:
		memmove( dest, p, sizeof(p[0]) );
		break;
	case OBJ_T_STR:
		dest->info.array.size = p->info.array.size;
		switch( p->subtype ){
			case OBJ_ST_SHORTSTR:
				SET_SHORTSTR_OBJ(dest,VAL_SHORTSTR_OBJ(p), MEMPASSED );
				break;
			case OBJ_ST_STR:
				SET_STR_OBJ(dest,VAL_STR_OBJ(p), MEMPASSED );
				break;
			case OBJ_ST_BINSTR:
				SET_BINSTR_OBJ(dest,VAL_BINSTR_OBJ(p), LEN_BINSTR_OBJ(p), MEMPASSED );
				break;
			default:
			Errorcode = -1; FATAL(LOG_ERR)"COPY_OBJ: unknonwn OBJ_T_STR subtype %d (%s)", p->subtype, Subtype_to_str(p->subtype) );
			break;
		}
		break;

	case OBJ_T_LIST:
	case OBJ_T_ARRAY:
	case OBJ_T_USER_ARRAY:
		/* copy all of the information */
		dest->info.array.size = p->info.array.size;
		dest->info.array.free_user_obj = p->info.array.free_user_obj;
		dest->info.array.dump_user_obj = p->info.array.dump_user_obj;
		dest->info.array.copy_user_obj = p->info.array.copy_user_obj;
		/* allocate the storage area */

		if( p->type == OBJ_T_LIST ){
			EXTEND_LIST_OBJ( dest, p->info.array.len, MEMPASSED );
		} else if( p->info.array.len ){
			EXTEND_ARRAY_OBJ( dest, p->info.array.len, 0, MEMPASSED );
		}
		for( i = 0; i < p->info.array.len; ++i ){
			void *s = AT_ARRAY_OBJ(p,i);
			void *d = AT_ARRAY_OBJ(dest,i);
			switch( p->type ){
			case OBJ_T_LIST:
				{
					char *t = ((char **)s)[0];
					*((char **)d) = safestrdup(t,MEMPASSED);
				}
				break;
			case OBJ_T_ARRAY:
				COPY_OBJ(d,s,MEMPASSED);
				break;
			case OBJ_T_USER_ARRAY:
				if( dest->info.array.copy_user_obj ){
					(dest->info.array.copy_user_obj)(d,s,MEMPASSED);
				} else {
					memmove(d,s,dest->info.array.size);
				}
				break;
			}
		}
		break;

	case OBJ_T_HASH:
		dest->info.array.size = sizeof(newelement[0]);
		len = dest->info.array.len = p->info.array.len;
		dest->info.array.maxlen = len + HASH_PAD;
		dest->info.array.value = realloc_or_die( dest->info.array.value,
				sizeof(newelement[0]) * dest->info.array.maxlen, MEMPASSED );
		memset(dest->info.array.value, 0, sizeof(newelement[0])*(dest->info.array.maxlen));
		if( len ){
			newelement = dest->info.array.value;
			oldelement = p->info.array.value;
			for( i = 0; i < len; ++i ){
				SET_SHORTSTR_OBJ(&newelement[i].key, VAL_SHORTSTR_OBJ(&oldelement[i].key), MEMPASSED);
				COPY_OBJ(&newelement[i].value, &oldelement[i].value, MEMPASSED);
			}
		}
		break;
	default:
		Errorcode = -1; FATAL(LOG_ERR)"COPY_OBJ: unknonwn object %d (%s)", p->type, Type_to_str(p->type) );
		break;
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "COPY_OBJ - out", dest ); }
	return( dest );
}

/*
 * convert OBJ to a string object
 */

void To_STR_OBJ( OBJ *p, MEMPASS )
{
	char buffer[SMALLBUFFER];
	buffer[0] = 0;
	if( !p ){
		Errorcode = -1; FATAL(LOG_ERR)"To_STR_OBJ: 0 object pointer");
	}
	switch( p->type ){
	case OBJ_T_NONE:
		SET_STR_OBJ(p,"", MEMPASSED);
		break;
	case OBJ_T_SCALAR:
		switch( p->subtype ){
		case OBJ_ST_INT:
			SNPRINTF(buffer,sizeof(buffer)) "%ld", IVAL_OBJ(p) );
			SET_STR_OBJ(p,buffer, MEMPASSED);
			break;
		case OBJ_ST_DOUBLE:
			SNPRINTF(buffer,sizeof(buffer)) "%f", DVAL_OBJ(p) );
			SET_STR_OBJ(p,buffer, MEMPASSED);
			break;
		case OBJ_ST_PTR:
			SNPRINTF(buffer,sizeof(buffer)) "0x%lx", Cast_ptr_to_long(PVAL_OBJ(p)) );
			SET_STR_OBJ(p,buffer, MEMPASSED);
			break;
		}
		break;
	case OBJ_T_STR:
		switch( p->subtype ){
		case OBJ_ST_STR:
			break;
		case OBJ_ST_SHORTSTR:
			safestrncpy(buffer,VAL_SHORTSTR_OBJ(p));
			SET_STR_OBJ(p,buffer, MEMPASSED);
			break;
		}
		break;
	default:
		Errorcode = -1; FATAL(LOG_ERR)"To_STR_OBJ: cannot convert object %d (%s)", p->type, Type_to_str(p->type) );
		break;
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ(  "To_STR_OBJ", p ); }
}

void DUMP_OBJ( const char *header, OBJ *p)
{
	REAL_DUMP_OBJ( 1, "",  header, p);
	
}
void SHORT_DUMP_OBJ( const char *header, OBJ *p)
{
	REAL_DUMP_OBJ( 0, "",  header, p);
}


void REAL_DUMP_OBJ( int verbose, const char *indent, const char *header, OBJ *p)
{
	char buffer[LARGEBUFFER], *s;
	char newindent[SMALLBUFFER];
	HASH_ELEMENT *element;
	int len, i, n, extra;

	buffer[0] = 0;
	len = 0;
	if( header == 0 ){ header = "????"; };
	if( indent == 0 ){ indent = "....."; };
	SNPRINTF(newindent, sizeof(newindent)) "%s ", indent);
	if( verbose ){
		SNPRINTF( buffer+len, sizeof(buffer)-len) "%s%s - OBJ 0x%lx (TYPE %s, SUBTYPE %s)",
			indent, header, Cast_ptr_to_long(p),
			p?Type_to_str(p->type):"",
			p?Subtype_to_str(p->subtype):""
			 );
	} else {
		SNPRINTF( buffer+len, sizeof(buffer)-len) "%s%s - %s%s",
			indent, header,  p?Type_to_str(p->type):"", p?"":" <NULL>" );
	}
	header = " ";
	len = strlen(buffer);
	if( p ){
		switch( p->type ){
		case OBJ_T_NONE:
			if( verbose ){
				SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d, maxlen %d, size %d, value 0x%lx",
				p->info.array.len, p->info.array.maxlen, p->info.array.size, Cast_ptr_to_long(p->info.array.value) );
			} else {
				SNPRINTF( buffer+len, sizeof(buffer)-len) "");
			}
			LOGDEBUG("%s",buffer);
			break;
		case OBJ_T_USER_ARRAY:
			SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d, maxlen %d, size %d, value 0x%lx",
				p->info.array.len, p->info.array.maxlen, p->info.array.size, Cast_ptr_to_long(p->info.array.value) );
			LOGDEBUG("%s",buffer);
			if( p->info.array.dump_user_obj ){
				for( i = 0; i < p->info.array.len; ++i ){
					SNPRINTF( buffer, sizeof(buffer)) "[%d] ",i );
					(p->info.array.dump_user_obj)( verbose, newindent,
						buffer, AT_ARRAY_OBJ( p, i ) );
				}
			}
			break;
		case OBJ_T_SCALAR:
			switch( p->subtype ){
			case OBJ_ST_INT:
				SNPRINTF( buffer+len, sizeof(buffer)-len) " = %ld (0x%lx)", IVAL_OBJ(p), IVAL_OBJ(p) );
				LOGDEBUG("%s",buffer);
				break;
			case OBJ_ST_DOUBLE:
				SNPRINTF( buffer+len, sizeof(buffer)-len) " = %f", DVAL_OBJ(p) );
				LOGDEBUG("%s",buffer);
				break;
			case OBJ_ST_PTR:
				SNPRINTF( buffer+len, sizeof(buffer)-len) " = 0x%lx", Cast_ptr_to_long(PVAL_OBJ(p)));
				LOGDEBUG("%s",buffer);
				break;
			}
			break;
		case OBJ_T_STR:
			switch( p->subtype ){
			case OBJ_ST_SHORTSTR:
			case OBJ_ST_STR:
				s = VAL_SHORTSTR_OBJ(p);
				n = GET_MAXLEN_STR_OBJ(p); 
				if( verbose ){
					SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d, maxlen %d, value 0x%lx '%s'",
					safestrlen(s), n, Cast_ptr_to_long(s), s );
				} else {
					SNPRINTF( buffer+len, sizeof(buffer)-len) " '%s'", s );
				}
				LOGDEBUG("%s",buffer);
				break;
			case OBJ_ST_BINSTR:
				if( verbose ){
					SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d, maxlen %d, value 0x%lx",
					p->info.array.len, p->info.array.maxlen, Cast_ptr_to_long( p->info.array.value) );
				} else {
					SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d",
					p->info.array.len );
				}
				LOGDEBUG("%s",buffer);
				buffer[0] = 0;
				if( p->info.array.value ){
					for( i = 0; i < p->info.array.len; ++i ){
						if( !( i%16 ) ) {
							if( i ){
								len = strlen(buffer);
								SNPRINTF( buffer+len, sizeof(buffer)-len) "\n" );
							}
							len = strlen(buffer);
							SNPRINTF( buffer+len, sizeof(buffer)-len) "%s%s[0x%02x] ", indent,
								i?"":header, i?"":" ", i );
						}
						len = strlen(buffer);
						SNPRINTF(buffer+len, sizeof(buffer)-len) " %02x", ((unsigned char *)p->info.array.value)[i] );
					}
					LOGDEBUG("%s",  buffer );
				}
				break;
			}
			break;
		case OBJ_T_ARRAY:
		case OBJ_T_LIST:
			extra = (p->type == OBJ_T_LIST)?1:0;
			if( verbose ){
				SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d, maxlen %d, size %d, value 0x%lx",
				p->info.array.len, p->info.array.maxlen,
				p->info.array.size, Cast_ptr_to_long( p->info.array.value) );
			} else {
				SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d",
					p->info.array.len );
			}
			LOGDEBUG("%s",buffer);
			buffer[0] = 0;
			for( i = 0; i < p->info.array.len; ++i ){
				if( p->type == OBJ_T_ARRAY ){
					SNPRINTF( buffer, sizeof(buffer)) "[%d] ",i );
					REAL_DUMP_OBJ(verbose, newindent, buffer, &((OBJ *)(p->info.array.value))[i]);
				} else if( p->type == OBJ_T_LIST ){
					int len;
					char *s = 0;
					SNPRINTF( buffer, sizeof(buffer)) "%s[%d] ",newindent, i );
					len = strlen(buffer);
					if( p->info.array.value) s = ((char **)(p->info.array.value))[i];
					if( verbose ){
						SNPRINTF( buffer+len, sizeof(buffer)-len) " STR 0x%lx '%s' ",Cast_ptr_to_long(s), s );
					} else {
						SNPRINTF( buffer+len, sizeof(buffer)-len) " '%s' ", s );
					}
					LOGDEBUG("%s",buffer);
				}
			}
			if( extra ){
				SNPRINTF( buffer, sizeof(buffer)) "%s[%d] ",newindent, i );
				if( p->type == OBJ_T_LIST ){
					int len = strlen(buffer);
					char *s = 0;
					if( p->info.array.value) s = ((char **)(p->info.array.value))[i];
					SNPRINTF( buffer+len, sizeof(buffer)-len) " 0x%lx", Cast_ptr_to_long(s) );
					LOGDEBUG("%s",buffer);
				}
			}
			break;
	 	case OBJ_T_HASH:
			if( verbose ){
			SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d, maxlen %d, value 0x%lx",
				p->info.array.len, p->info.array.maxlen, Cast_ptr_to_long( p->info.array.value) );
			} else {
			SNPRINTF( buffer+len, sizeof(buffer)-len) " len %d",
				p->info.array.len );
			}
			LOGDEBUG("%s",buffer);
			buffer[0] = 0;
			element = p->info.array.value;
			for( i = 0; i < p->info.array.len; ++i ){
				if( verbose ){
				SNPRINTF( buffer, sizeof(buffer)) "[%d] key ",i );
				REAL_DUMP_OBJ(verbose, newindent, buffer, &element[i].key);
				SNPRINTF( buffer, sizeof(buffer)) "     value ");
				REAL_DUMP_OBJ(verbose, newindent, buffer, &element[i].value);
				} else {
					SNPRINTF( buffer, sizeof(buffer)) "[%d] HASH key '%s' ",i,
						VAL_SHORTSTR_OBJ(&element[i].key) );
					REAL_DUMP_OBJ(verbose, newindent, buffer, &element[i].value);
				}
			}
			break;
		default:
			SNPRINTF( buffer+len, sizeof(buffer)-len) " UNKNOWN OBJ TYPE" );
			LOGDEBUG("%s",buffer);
		}
	} else {
		LOGDEBUG("%s",  buffer );
	}
}

/*
 * OBJ * Read_fd_STR_OBJ( OBJ **p, int fd, char *errstr, int errlen )
 *   Read a file into the STR OBJ *p.
 *   Returns:  < 0  if error, errstr set to error message
 *             >= 0 if no error
 */

int Read_fd_STR_OBJ( OBJ **p_ptr, const char *filename, int fd, char *errstr, int errlen, MEMPASS )
{
	int n;
	OBJ *p = *p_ptr;
	char buffer[LARGEBUFFER];
	*p_ptr = p = SET_STR_OBJ(p, "", MEMPASSED);
	while( (n = read( fd, buffer,sizeof(buffer)-1)) > 0 ){
		buffer[n] = 0;
		APPEND_STR_OBJ( MEMPASSED, p, buffer, 0 );
	}
	if( n < 0 ){
		SNPRINTF(errstr,errlen)"Read_fd_STR_OBJ: error reading fd %d, file '%s' - %s",
			fd, filename, strerror(errno) );
	}
	return( n );
}


/*
 * int Read_file_STR_OBJ( OBJ **p, const char *file, char *errstr, int errlen, MEMPASS )
 *   Read a file into the STR OBJ p.
 *   Returns:  < 0, 0 if no error, errstr set to error message
 *             p - string object
 */

int Read_file_STR_OBJ( OBJ **p_ptr, const char *filename, char *errstr, int errlen, MEMPASS )
{
	int n, fd = open( filename, O_RDONLY, 0 );
	if( fd < 0 ){
		SNPRINTF(errstr,errlen)"Read_file_STR_OBJ: error opening file '%s' - '%s'",
			file, strerror(errno) );
		return( -1 );
	}
	n = Read_fd_STR_OBJ( p_ptr, filename, fd, errstr, errlen, MEMPASSED );
	close(fd);
	return( n );
}

/*
 * OBJ *Split_STR_OBJ( OBJ *p, int nomod, char *str,
 *   int type, const char *linesep, char *escape,
 *       int commentchar, int trim,
 *   const char *keysep, int do_append, int lowercase, int flagvalues, int unesc )
 *
 *  Split a string up into lines or a hash.  The 'str' value will
 *   be modified in this process.  If nomodify is nonzero, use
 *   a copy of the string.
 * 
 *
 * If type == OBJ_T_ARRAY, then we return an array of lines
 *    type == OBJ_T_HASH, then we return a hash of the values
 *
 *  For OBJ_ARRAY:
 *
 *  Split the input on all characters in 'linesep'.
 *   If the character in linesep is also in escape and is prefaced by
 *   \ (i.e. - it is escaped), then do not split on it.
 *
 *  If trim != 0 then \<newline> is replaced by blanks, and then
 *   leading and trailing whitespace and blank lines are removed along
 *   with CR characters.
 *
 *  If commentchar != 0, i.e. - commentchar = '#',
 *     then remove all text following '#' to the end of a line.
 *     Note that this is done after trimming a line.
 *
 *
 *  For OBJ_HASH:
 *    We do all of the splitting,  and then for each line, we use the
 *  characters in 'keysep' to spit each line into 'key<sep>value'
 *  pairs, and then use 'key' as a hash key.
 *
 *  If do_append is nonzero, then we let +<keysep> append the value
 *    to the exisiting value and -<keysep> prefix the value with
 *    <do_append> as the separator if needed
 *
 *  If the lowercase is nonzero, then lowercase the key
 *
 *  If the flagvalues is nonzero and a key does not have a value,
 *    then set the value to {key=>1};
 *    if it has the form 'key@' then set it to {key=>0}.
 *
 *  If the 'unesc' value is nonzero, then we URL unescape the value
 * 
 *   Example:  split up a file of the form
 *      '  line1  \\n  line2\n line3' -> ['line1     line2', 'line3']
 *  Split( p, "...", OBJ_ARRAY, "\n", "\n", '#', 1, 0, 0)
 *   Example:  split up a line of the form below into hash entries:
 *        v1=p1:v1=p2:  v3=p3  :  = {v1=>p1, v3=>p3 }
 *  Split( p, "...", OBJ_HASH, ":", ":", / comment / 0, / trim / 1,
 *         / keysep / "=", 1,)
 */

OBJ *Split_STR_OBJ( OBJ *p, int nomod, char *str, 
    int type, const char *linesep, const char *escape,
        int commentchar, int trim,
	char *keysep, int do_append, int lc, int flagvalues, int value_urlunesc, MEMPASS )
{
	OBJ *v, *strduplicate = 0;
	char *s, *t, *end, *value, *start;
	int count, len, c, doesc;

	if( !p || p->type != type ){
		p = NEW_OBJ(p, MEMPASSED);
		if( type == OBJ_T_ARRAY ){
			p = NEW_ARRAY_OBJ( p, MEMPASSED );
		} else if( type == OBJ_T_HASH ){
			p = NEW_HASH_OBJ( p, MEMPASSED );
		} else if( type == OBJ_T_LIST ){
			p = NEW_LIST_OBJ( p, MEMPASSED );
		}
	}
	if( nomod ){
		strduplicate = SET_STR_OBJ( strduplicate, str, MEMINFO );
		str = VAL_STR_OBJ(strduplicate);
	}
	if( type == OBJ_T_ARRAY || type == OBJ_T_LIST ){
		count = 1;
		len = p->info.array.len;
		if( linesep ) for( s = str; s && (s = strpbrk(s,linesep)); ++s ) ++count;
		DEBUGF(DUTILS4)( "Split_STR_OBJ: count %d, len %d, maxlen %d",
			count, len, p->info.array.maxlen );
		/* set up array */
		p =  EXTEND_ARRAY_OBJ( p, len+count, 0, MEMPASSED );
		p->info.array.len = len;
	}
	/* now we split the string up and process the lines */
	for( s = str; (start = s) && cval(s); s = end ){
  again:
		/* look for an unescaped end of line */
		c = 0;
		if( (end = safestrpbrk(s,linesep)) ){
			c = cval(end); 
			*end++ = 0;
		}
		DEBUGFC(DUTILS4){
			char b[256];
			SNPRINTF(b,sizeof(b)-6) "%s", start );
			if( start && safestrlen(b) != safestrlen(start) ) safestrncat(b,"...");
			DEBUGF(DUTILS4)( "Split_STR_OBJ: line '%s'", b );
		}
		/* now remove stuff after comments */
		if( commentchar && (t = safestrchr(s,commentchar)) ){
			while( s != t
				&& safestrchr( escape, cval(t) )
				&& cval(end-1) != '\\' ){
				t = safestrchr(t+1,commentchar);
			}
			while( t && t < end ){
				*t++ = 0;
			}
		}
		/* now we check to see if we have an escaped line */
		if( end-s > 1 && end[-2] == '\\' ){
			/* backup */
			--end;
			*end = c;
			/* whitespace is a special case */
			if( isspace(c) ){
				*end = ' ';
			}
			memmove(end-1,end,strlen(end)+1);
			s = end;
			goto again;
		}
		s = start;
		if( trim ){
			while( isspace(cval(s)) ) ++s;
			for( len = strlen(s); len > 0 && isspace(cval(s+len-1)); --len );
			s[len] = 0;
		}
		if( trim && !cval(s) ){
			continue;
		}
		DEBUGF(DUTILS4)( "Split_STR_OBJ: result '%s'", s );
		if( type == OBJ_T_LIST ){
			APPEND_LIST_OBJ(p,s,MEMINFO);
		} else if( type == OBJ_T_ARRAY ){
			len = p->info.array.len;
			p =  EXTEND_ARRAY_OBJ( p, len+1, 0, MEMPASSED );
			v = AT_ARRAY_OBJ( p,len );
			SET_STR_OBJ(v,s, MEMPASSED);
		} else if( type == OBJ_T_HASH ){
			int append = 0, prefix = 0;
			doesc = 1;
			if( (value = safestrpbrk(s,keysep)) ){
				*value++ = 0;
				while( isspace(cval(value)) ) ++value;
				if( do_append ){
					len = strlen( s );
					if( len > 0 ){
						int c = cval(s+len-1);
						if( c == '-' ){ prefix = 1; s[len-1] = 0; }
						else if( c == '+' ){ append = 1; s[len-1] = 0; }
					}
				}
				for( len = strlen( s ); len > 0 && isspace(cval(s+len-1)); --len);
				s[len] = 0;
			} else {
				if( flagvalues ){
					value = "1";
					len = strlen( s );
					if( cval(s+len-1) == '@' ){
						s[len-1] = 0;
						value = "0";
					}
				}
				doesc = 0;
			}
			if( lc ) lowercase(s);
			if( value_urlunesc && doesc ) URLUnEscStr( value );
			if( !ISNULL(s) ){
				DEBUGF(DUTILS4)( "Split_STR_OBJ: key '%s', value '%s', append %d, prefix '%d'", s, value, append, prefix );
				if( (append || prefix) ){
					OBJ *obj = GET_HASH_OBJ(p, s);
					if( !obj ){
						SET_HASH_STR_OBJ(p,s,value, MEMPASSED);
					} else {
						char m[2];
						m[0] = do_append;
						m[1] = 0;
						if( prefix ){
							PREFIX_STR_OBJ(MEMINFO,obj,value,m, 0);
						} else if( append ){
							APPEND_STR_OBJ(MEMINFO,obj,m,value, 0);
						}
					}
					DEBUGF(DUTILS4)( "Split_STR_OBJ: append/prefix result '%s'", VAL_STR_OBJ(obj) );
				} else {
					SET_HASH_STR_OBJ(p,s,value, MEMPASSED);
				}
			}
		}
	}
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ( "Split_STR_OBJ - done", p ); }
	FREE_OBJ( strduplicate ); strduplicate = 0;
	return(p);
}

/*
 * OBJ *SemiURLEsc( OBJ *p, const char *str )
 *   Semi URL Escape a string to the STR_OBJ p
 *   if p is 0, allocate a new string
 *  Note: the str cannot be p's value
 */

int NeedURLEsc( int c, int full )
{
	if( full ){
		return( !isprint(c) || (!isalnum(c) && !strchr("_-", c )) );
	}
	return( !isprint(c) || strchr( "?+,;:'%\\\"", c ) );
}

/*
 * int NeedURLEscStr( const char *s, int *extra, int full )
 *   Check to see if Semi URL Escape is needed
 * return: 0 - not needed, != needed, extra set to extra length
 */

int NeedURLEscStr( const char *s, int *extra, int full )
{
	int c, v = 0, count = 0;

	DEBUGF(DUTILS4)("NeedURLEscStr: '%s'", s );
	if( ISNULL(s) ) return (0);
	for( ; (c = cval(s)); ++s ){
		if( full ){
			if( NeedURLEsc(c,full) ){
				v = 1;
				if( c != ' '){
					count += 2;
				}
			}
		} else {
			if( NeedURLEsc(c, full) ){
				v = 1; count += 2;
			}
		}
		DEBUGF(DUTILS4)("NeedURLEscStr: full %d, check '%c' = %d, count %d", full, c , v, count );
	}
	if( extra ) *extra = count;
	return(v);
}

OBJ *URLEsc_OBJ( OBJ *p, const char *instr, int full, MEMPASS )
{
	int c, extra = 0;
	char *str;
	p = SET_STR_OBJ(p, instr, MEMPASSED);
	DEBUGF(DUTILS4)("URLEsc_OBJ: full %d, str '%s'", full, instr );
	if( NeedURLEscStr( instr, &extra, full ) ){
		DEBUGF(DUTILS4)("URLEsc: extra %d, len %d" , extra, strlen(instr) );
		if( extra ){
			SET_MAXLEN_STR_OBJ( p, extra + strlen(instr), MEMPASSED );
		}
		for( str = VAL_STR_OBJ(p); (c = cval(instr)); ++instr ){
			if( full && c == ' ' ){
				*str++ = '+';
			} else if( NeedURLEsc( c, full ) ){
				SNPRINTF(str,4)"%%%02x", c );
				str += strlen(str);
			} else {
				*str++ = c;
			}
		}
		if( str ) *str = 0;
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ("URLEsc_OBJ: result", p ); }
	return( p );
}

void URLUnEscStr( char *s )
{
	char *t;
	int c;
	if( !ISNULL(s) ){
		for( t = s; (c = cval(s)); ++s ){
			if( c == '+' ){
				*t++ = ' ';
			} else if( c != '%' ){
				*t++ = c;
			} else {
				char val[3];
				++s;
				val[0] = *s;
				++s;
				val[1] = *s;
				val[2] = 0;
				*t++ = strtol(val,0,16);
			}
		}
		*t = 0;
	}
}

OBJ *URLUnEsc_OBJ( OBJ *p )
{
	DEBUGFC(DUTILS4){ DUMP_OBJ("URLUnEsc: ", p ); }
	if( !p ) return (0);
	if( p->type != OBJ_T_STR || p->subtype != OBJ_ST_STR ){
		Errorcode = -1; FATAL(LOG_ERR)"URLUnEsc: object %d (%s) not OBJ_T_STR or subtype %d (%s) not OBJ_T_STR",
			p->type, Type_to_str(p->type), p->subtype, Subtype_to_str(p->subtype) );
		return(0);
	}
	URLUnEscStr(VAL_STR_OBJ(p));
	return( p );
}

/*
 * general backslash escape
 */

int NeedBackslashEsc( char *str, char *escape )
{
	return( safestrpbrk( str, escape ) != 0);
}

int NeedBackslashEscStr( const char *s, int *extra, char *escape )
{
	int c, v = 0, count = 0;
	DEBUGF(DUTILS4)("NeedBackslashEscStr: '%s'", s );
	if( ISNULL(s) ) return (0);
	for( ; (c = cval(s)); ++s ){
		if( safestrchr( escape, c ) ){
			v = 1; count += 1;
		}
		DEBUGF(DUTILS4)("NeedBackslashEscStr: check '%c' = %d, count %d", c , v, count );
	}
	if( extra ) *extra = count;
	return(v);
}

/*
 * Backslash escape a string
 */


OBJ *BackslashEsc_OBJ( OBJ *p, const char *instr, char *escape, MEMPASS )
{
	int c, extra = 0;
	char *str;
	p = SET_STR_OBJ(p, instr, MEMPASSED);
	DEBUGF(DUTILS4)("BackslashEsc: str '%s', escape '%s'", instr, escape );
	if( NeedBackslashEscStr( instr, &extra, escape ) ){
		DEBUGF(DUTILS4)("BackslashEsc: extra %d, len %d" , extra, strlen(instr) );
		if( extra ){
			SET_MAXLEN_STR_OBJ( p, extra + strlen(instr), MEMPASSED );
		}
		for( str = VAL_STR_OBJ(p); (c = cval(instr)); ++instr ){
			if( safestrchr( escape, c ) ){
				*str++ = '\\';
			}
			*str++ = c;
		}
		if( str ) *str = 0;
	}
	DEBUGFC(DUTILS4){ DUMP_OBJ("BackslashEsc: result", p ); }
	return( p );
}

/****************************************************************************************
 * convert a hash to a list.
 *  The general format is:  key=value
 ****************************************************************************************/

OBJ *Hash_to_LIST_OBJ( OBJ *list, OBJ *hash, char *sep, int urlesc, int full,
	char *backslash_esc, MEMPASS )
{
	int hashindex, hashcount;
	char *key, *value;
	OBJ *esc_str = 0, *backslash_str = 0, *append_str = 0;

	DEBUGFC(DUTILS3){ SHORT_DUMP_OBJ("Hash_to_LIST - input", hash ); }
	if( !sep ) sep = "";
	for( hashindex = 0, hashcount = LEN_HASH_OBJ(hash);
		hashindex < hashcount; ++hashindex ){
		key = VAL_SHORTSTR_OBJ( KEY_HASH_OBJ(hash,hashindex) );
		value = VAL_STR_OBJ( VALUE_HASH_OBJ(hash,hashindex) );
		DEBUGF(DUTILS3)("Hash_to_list: [%d] '%s' = '%s'", hashindex,key, value );
		if( urlesc ){
			esc_str = URLEsc_OBJ( esc_str, value, full, MEMPASSED );
			DEBUGF(DUTILS3)("Hash_to_list: URL escape '%s' -> '%s'", value, VAL_STR_OBJ(esc_str) );
			value = VAL_STR_OBJ( esc_str );
		}
		if( backslash_esc ){
			backslash_str = BackslashEsc_OBJ( backslash_str, value, backslash_esc, MEMPASSED );
			DEBUGF(DUTILS3)("Hash_to_list: Backslash escape '%s' -> '%s'", value, VAL_STR_OBJ(backslash_str) );
			value = VAL_STR_OBJ( backslash_str );
		}
		append_str = SET_STR_OBJ( append_str, key, MEMPASSED );
		APPEND_STR_OBJ( MEMPASSED, append_str, sep, value, 0 );
		DEBUGF(DUTILS3)("Hash_to_list: final '%s' = '%s'  -> '%s'", key, value, VAL_STR_OBJ(append_str) );
		list = APPEND_LIST_OBJ( list, VAL_STR_OBJ(append_str), MEMPASSED );
	}
	FREE_OBJ(esc_str); esc_str = 0;
	FREE_OBJ(backslash_str); backslash_str = 0;
	FREE_OBJ(append_str); append_str = 0;
	DEBUGFC(DUTILS3){ SHORT_DUMP_OBJ("Hash_to_LIST - output", list ); }
	return( list );
}



/*************************************************
 * hash and list output format
 *************************************************/

OBJ *Join_list_OBJ( OBJ *str, OBJ *list, char *prefix, char *suffix, MEMPASS )
{
	int listindex, listcount;
	char *s;

	for( listindex = 0, listcount = LEN_LIST_OBJ(list);
		listindex < listcount; ++listindex ){
		if( (s = GET_ENTRY_LIST_OBJ(list,listindex)) ){
			str = APPEND_STR_OBJ( MEMPASSED, str, prefix?prefix:"", s, suffix, 0 );
		}
	}
	return( str );
}

/**************************************************
 * hash of string values to a string
 *
 * OBJ *Hash_to_str_OBJ( OBJ *str, OBJ *hash, char *prefix, char *suffix,
 *		int urlesc, int full, char *backslash_esc )
 *  hash - convert this to '<prefix>key=value<suffix>' list
 *  prefix, suffix - for start and end of entries
 *    urlesc, full - if urlesc is set, do URLescape on values, full sets level 
 *    backslash_esc - list of chars to backslash escape
 **************************************************/

OBJ *Hash_to_STR_OBJ( OBJ *str, OBJ *hash, char *prefix, char *suffix,
	char *sep, int urlesc, int full, char *backslash_esc, MEMPASS )
{
	OBJ *list = Hash_to_LIST_OBJ( 0, hash, sep, urlesc, full, backslash_esc, MEMPASSED );
	str = Join_list_OBJ( str, list, prefix, suffix, MEMPASSED );
	FREE_OBJ( list ); list = 0;
	return( str );
}

/*
 * int Read_file_list_OBJ( 
 *   int required  - 0, files are not required
 *          != 0, files must be present
 *   char *filelist - a whitespace, comma, colon, semicolon
 *      (FILESEP) separated list of files or filters.
 *   char *directories - look in these directories if specified
 *  
 *	char *sep, int sort, char *keysep, int uniq, int trim, int marker )
 *  read the model information from these files
 *  we use Split_STR_OBJ:
 *    OBJ *Split_STR_OBJ( OBJ *p, int nomod, char *str,
 *    int type, const char *linesep, const char *escape,
 *    int commentchar, int trim,
 *    char *keysep, int lc, int flagvalues, int unesc, MEMPASS )
 *
 *  Note: this is used to read in the various configuration files.
 *    It really is a general purpose beast that will generate LISTS or
 *    HASH values.
 *
 * We assume that we have a list of files that contain
 *   configuration or other information.  The files in this
 *   list cannot be filters.
 * By default,  the configuration file information consists of
 *   lines of the following format:
 *      #  --- comment to end of line
 *         xxxxx  -> 'xxxx', i.e. ignore leading and trailing whitespace
 * If you have hash values,  then these are of the form:
 *      xx, xx@,  xx \s*value, xx\s*=\s*value.  We are brutal and will
 *  assume that if you pass a string in, then we can destroy it.
 * This means that you better make sure to use a copy of the string.
 */

/*
 * OBJ *Split_file_LIST_OBJ( OBJ *list, char *filelist )
 *  Split a list of file names - destroys filelist
 *  This is a handy utility function for splitting up file lists.
 */

OBJ *Split_file_LIST_OBJ( OBJ *list, char *filelist )
{
	DEBUGF(DUTILS2)("Split_file_LIST_OBJ: input '%s'", filelist);
	list = Split_STR_OBJ( /*p*/list, /*nomod*/1, /*str*/ filelist,
		/*type*/OBJ_T_LIST, /*linesep*/FILESEP, /*escape*/FILESEP,
		/*comment*/ 0, /*trim*/1, /*keysep*/0,/*do_append*/0, /*lc*/0,
		/*flagvalues*/ 0, /*value_urlunesc*/0, MEMINFO );
	DEBUGFC(DUTILS2){ DUMP_OBJ("Split_file_LIST_OBJ:", list); }
	return( list );
} 

/*
 * The top level function to implement Read_file_list_OBJ.
 *
 * We assume that we have a list of files that contain
 *   configuration or other information.  The files in this
 *   list cannot be filters.
 * By default,  the configuration file information consists of
 *   lines of the following format:
 *      #  --- comment to end of line
 *         xxxxx  -> 'xxxx', i.e. ignore leading and trailing whitespace
 * If you have hash values,  then these are of the form:
 *      xx, xx@,  xx \s*value, xx\s*=\s*value
 */

int Read_file_list_OBJ( OBJ *output,
	const char *filenames, char *directories, int required,
	int doinclude, int depth, int maxdepth,
    int type, const char *linesep, const char *escape,
    int commentchar, int trim,
    char *keysep, int do_append, int lc, int flagvalues, int unesc,
	char *errmsg, int errlen, MEMPASS )

{
	OBJ *liststr = 0; /* filelist image */
	OBJ *filelist = 0; /* split up list */
	OBJ *filelines = 0; /* lines in file */
	OBJ *fileimage = 0; /* image of file */
	OBJ *includefile = 0; /* included file */
	OBJ *dirs = 0; /* included file */
	char path[MAXPATHLEN];
	struct stat statb;
	int status = 0, listlen, listindex, linecount, lineno, c, found, includecount, iline;
	char *filename, *s, *t;

	DEBUGF(DUTILS3)("Read_file_list_OBJ: '%s', doinclude %d, directories '%s'",
		filenames, doinclude, directories );
	
	errmsg[0] = 0;
	if( doinclude && depth > maxdepth ){
		SNPRINTF(errmsg, errlen)
			"recursion depth %d exceeds maximum %d for file '%s'",
			depth, maxdepth, filenames );
		status = -1;
		goto done;
	}
	liststr = SET_STR_OBJ( liststr, filenames, MEMINFO );
	filelist = Split_file_LIST_OBJ( 0, VAL_STR_OBJ(liststr));
	listlen = LEN_LIST_OBJ( filelist );
	/* work our way down the list of files */
	dirs = Split_STR_OBJ( /*p*/dirs,/*nomod*/1, /*str*/ directories,
		/*type*/ OBJ_T_LIST,LISTSEP, LISTSEP,
		0, 1, 0, 0, 0,
		0, /*value_urlunesc*/0, MEMINFO );
	for( listindex = 0; listindex < listlen; ++listindex ){
		filename = GET_ENTRY_LIST_OBJ( filelist, listindex);
		/* check to see if it is there, and then get the image */
		if( cval(filename) != '/' && LEN_LIST_OBJ(dirs) ){
			int dirindex, dirlen, found;
			found = 0;
			for( dirindex = 0, dirlen = LEN_LIST_OBJ(dirs);
				dirindex < dirlen; ++dirindex ){
				int len;
				safestrncpy(path,GET_ENTRY_LIST_OBJ(dirs,dirindex));
				len = safestrlen(path);
				if( cval(path+len-1) != '/' ){
					SNPRINTF(path+len,sizeof(path)-len) "%s", "/" );
					len = safestrlen(path);
				}
				SNPRINTF(path+len,sizeof(path)-len) "%s", filename );
				DEBUGF(DUTILS3)("Read_file_list_OBJ: checking '%s'", path );
				if( (found = !stat( path, &statb )) ){
					break;
				}
			}
			if( !found ){
				if( required ){
					SNPRINTF(errmsg, errlen)
						"cannot stat required file '%s' in '%s'", filename,
						directories );
					status = -1;
					goto done;
				} else {
					continue;
				}
			}
		} else {
			safestrncpy(path,filename);
			if( stat( path, &statb ) == -1 ){
				if( required ){
					SNPRINTF(errmsg, errlen)
						"cannot stat required file '%s'", filename );
					status = -1;
					goto done;
				}
				continue;
			}
		}
		if( (status = Read_file_STR_OBJ( &fileimage, path, errmsg, errlen, MEMINFO)) < 0 ){
			goto done;
		}
		/* if we do not do includes, then we can be brutal */
		if( !doinclude ){
			output = Split_STR_OBJ( /*p*/output,/*nomod*/0,
				/*str*/ VAL_STR_OBJ(fileimage),
				type, linesep, escape,
				commentchar, trim, keysep, do_append, lc,
				flagvalues, /*value_urlunesc*/unesc, MEMINFO );
		} else {
			/* we must process the file line by line */
			filelines = Split_STR_OBJ( /*p*/filelines, /*nomod*/0,
				/*str*/ VAL_STR_OBJ(fileimage),
				OBJ_T_LIST, linesep, escape,
				commentchar, trim, keysep, do_append, lc,
				flagvalues, /*value_urlunesc*/unesc, MEMINFO );
			for( lineno = 0, linecount = LEN_LIST_OBJ( filelines );
					lineno < linecount; ++lineno ){
				/* check each line for an include */
				s = GET_ENTRY_LIST_OBJ( filelines, lineno );
				if( !s ) continue;
				found = 0;
				if( (t = safestrpbrk( s, WHITESPACE )) ){
					c = *t; *t = 0;
					found = !safestrcasecmp( s, "include" );
					*t = c;
				}
				/* if it is one, then we do a recursive call to
				 * read the list of files.  This is very simple
				 */
				if( found ){
					DEBUGF(DUTILS3)("Read_file_list_OBJ: include '%s'", t );
					includefile = NEW_LIST_OBJ( includefile, MEMINFO );
					status = Read_file_list_OBJ(
						/*output*/includefile,
						/*filenames*/t,/*directories*/directories,/*required*/1,
						doinclude, depth+1, maxdepth,
						/*type*/OBJ_T_LIST, linesep, escape,
						commentchar, trim, keysep, do_append, lc,
						flagvalues, /*value_urlunesc*/unesc, errmsg, errlen, MEMINFO );
					if( status < 0 ){
						int len = strlen( errmsg );
						SNPRINTF(errmsg+len,errlen-len) " from '%s'", filename );
						goto done;
					}
					for( iline = 0, includecount = LEN_LIST_OBJ(includefile);
						iline < includecount; ++iline ){
						s = GET_ENTRY_LIST_OBJ( includefile, iline );
						if( type == OBJ_T_LIST ){
							output = APPEND_LIST_OBJ( output, s, MEMINFO );
						} else if( type == OBJ_T_HASH ){
							output = Split_STR_OBJ( /*p*/output, /*nomod*/0, /*str*/ s,
								type, linesep, escape,
								commentchar, trim, keysep, do_append, lc,
								flagvalues, /*value_urlunesc*/unesc, MEMINFO );
						}
					}
				} else {
					if( type == OBJ_T_LIST ){
						output = APPEND_LIST_OBJ( output, s, MEMINFO );
					} else if( type == OBJ_T_HASH ){
						output = Split_STR_OBJ( /*p*/output, /*nomod*/0, /*str*/ s,
							type, linesep, escape,
							commentchar, trim, keysep, do_append, lc,
							flagvalues, /*value_urlunesc*/unesc, MEMINFO );
					}
				}
			}
		}
	}

 done:
	DEBUGFC(DUTILS1){ DUMP_OBJ("Read_file_list_OBJ:", output); }
	FREE_OBJ(liststr); liststr = 0;
	FREE_OBJ(filelist); filelist = 0;
	FREE_OBJ(filelines); filelines = 0;
	FREE_OBJ(fileimage); fileimage = 0;
	FREE_OBJ(includefile); includefile = 0;
	return( status );
}

/*
 * convert object to string representation
 *  hash = \{
 *           [string => obj][,string => obj]*
 *         \} 
 *  array = \[ [obj][ , obj]* \]
 *  string = "...." OR '....' OR [a-zA-Z0-9_\-]+
 *           backslash escape  \\, ' or " in string
 */

OBJ *Obj_to_STR_OBJ( OBJ *str, OBJ *obj, MEMPASS )
{
	str = Obj_obj_to_STR_OBJ(str,obj,"",MEMPASSED);
	return( str );
}


/*
 *  Str_obj_to_STR_OBJ - we append a string value
 *    to the string.  If none (NULL string) do not append
 *    anything.
 */
 static char safestr[] =
	"abcdefghijklmnopqrstuvwxyz"
	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	"0123456789" "-_";

OBJ *Str_to_STR_OBJ( OBJ *str, char *s, MEMPASS )
{
	OBJ *escstr = 0;
	char *t;
	int c = 0;

	DEBUGF(DUTILS1)("Str_to_STR_OBJ - in '%s'", s);
	if( s ){
		for( t = s; (c = cval(t)) && strchr(safestr,c); ++t );
		DEBUGF(DUTILS1)("Str_to_STR_OBJ - escape at '%s'", t);
		if( c || !cval(s) ){
			escstr = BackslashEsc_OBJ(escstr,s,"\"\\", MEMPASSED );
			escstr = PREFIX_STR_OBJ(MEMPASSED,escstr,"\"",0);
			escstr = APPEND_STR_OBJ(MEMPASSED,escstr,"\"",0);
			s = VAL_STR_OBJ(escstr);
		}
		DEBUGF(DUTILS1)("Str_to_STR_OBJ - setting '%s'", s);
		str = APPEND_STR_OBJ(MEMPASSED,str,s,0);
	}
	FREE_OBJ(escstr); escstr = 0;
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Str_to_STR_OBJ - out", str); }
	return( str );
}

OBJ *Str_obj_to_STR_OBJ( OBJ *str, OBJ *obj, MEMPASS )
{
	char *s = VAL_SHORTSTR_OBJ(obj);
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Str_obj_to_STR_OBJ - in", obj); }
	str =  Str_to_STR_OBJ( str, s, MEMPASSED );
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Str_obj_to_STR_OBJ - out", str); }
	return( str );
}

/*
 * Hash_obj_to_STR_OBJ( OBJ*str, OBJ *hash, char *prefix, MEMPASS )
 *   we generate
 *     ... {
 *  <prefix>key => <value>
 *       value gets prefix' '
 */

OBJ *Hash_obj_to_STR_OBJ( OBJ *str, OBJ *obj, char *prefix, MEMPASS )
{
	OBJ *p = 0;
	char *s;
	int i;
	p = SET_STR_OBJ(p,"",MEMINFO);
	APPEND_STR_OBJ(MEMPASSED,p,prefix," ");
	s = VAL_STR_OBJ(p);

	DEBUGF(DUTILS1)("Hash_obj_to_STR_OBJ: new prefix '%s'", s );

	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Hash_obj_to_STR_OBJ - in", obj); }
	str = APPEND_STR_OBJ(MEMPASSED,str,"{",0);
	for( i = 0; i < LEN_HASH_OBJ(obj); ++i ){
		char *key = VAL_SHORTSTR_OBJ(KEY_HASH_OBJ(obj,i));
		OBJ *value = VALUE_HASH_OBJ(obj,i);
		if( i ){
			APPEND_STR_OBJ(MEMINFO,str,",",0);
		}
		APPEND_STR_OBJ(MEMINFO,str,"\n", s,0);
		Str_to_STR_OBJ( str, key, MEMPASSED );
		APPEND_STR_OBJ(MEMINFO,str,"=>",0);
		Obj_obj_to_STR_OBJ( str, value, s, MEMPASSED );
	}
	APPEND_STR_OBJ(MEMPASSED,str,"\n", s, "}",0);
	FREE_OBJ(p); p = 0;
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Hash_obj_to_STR_OBJ - out", str); }
	return( str );
}

/*
 * Array_obj_to_STR_OBJ( OBJ*str, OBJ *array, char *prefix, MEMPASS )
 *   we generate
 *     ... {
 *  <prefix>key => <value>
 *       value gets prefix' '
 */

OBJ *Array_obj_to_STR_OBJ( OBJ *str, OBJ *obj, char *prefix, MEMPASS )
{
	OBJ *p = 0;
	char *s;
	int i;
	p = SET_STR_OBJ(p,"",MEMINFO);
	APPEND_STR_OBJ(MEMPASSED,p,prefix," ");
	s = VAL_STR_OBJ(p);

	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Array_obj_to_STR_OBJ - in", obj); }
	str = APPEND_STR_OBJ(MEMPASSED,str,"[",0);
	for( i = 0; i < LEN_ARRAY_OBJ(obj); ++i ){
		OBJ *value = AT_ARRAY_OBJ(obj,i);
		if( i ){
			APPEND_STR_OBJ(MEMINFO,str,",",0);
		}
		APPEND_STR_OBJ(MEMINFO,str,"\n", s,0);
		Obj_obj_to_STR_OBJ( str, value, s, MEMPASSED );
	}
	APPEND_STR_OBJ(MEMPASSED,str,"\n", s, "]",0);
	FREE_OBJ(p); p = 0;
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Array_obj_to_STR_OBJ - out", str); }
	return( str );
}

/*
 * List_obj_to_STR_OBJ( OBJ*str, OBJ *array, char *prefix, MEMPASS )
 *   we generate
 *     ... {
 *  <prefix>key => <value>
 *       value gets prefix' '
 */

OBJ *List_obj_to_STR_OBJ( OBJ *str, OBJ *obj, char *prefix, MEMPASS )
{
	OBJ *p = 0;
	char *s;
	int i;
	p = SET_STR_OBJ(p,"",MEMINFO);
	APPEND_STR_OBJ(MEMPASSED,p,prefix," ");
	s = VAL_STR_OBJ(p);

	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("List_obj_to_STR_OBJ - in", obj); }
	str = APPEND_STR_OBJ(MEMPASSED,str,"[",0);
	for( i = 0; i < LEN_LIST_OBJ(obj); ++i ){
		char *value = GET_ENTRY_LIST_OBJ(obj,i);
		if( i ){
			APPEND_STR_OBJ(MEMINFO,str,",",0);
		}
		APPEND_STR_OBJ(MEMINFO,str,"\n", s,0);
		Str_to_STR_OBJ( str, value, MEMPASSED );
	}
	APPEND_STR_OBJ(MEMPASSED,str,"\n", s, "]",0);
	FREE_OBJ(p); p = 0;
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("List_obj_to_STR_OBJ - out", str); }
	return( str );
}

OBJ *Obj_obj_to_STR_OBJ( OBJ *str, OBJ *obj, char *prefix, MEMPASS )
{
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Obj_obj_to_STR_OBJ - in", obj); }
	if( obj ){
		switch( TYPE_OBJ(obj) ){
		case OBJ_T_HASH:
			str = Hash_obj_to_STR_OBJ( str, obj, prefix, MEMPASSED );
			break;
		case OBJ_T_ARRAY:
			str = Array_obj_to_STR_OBJ( str, obj, prefix, MEMPASSED );
			break;
		case OBJ_T_LIST:
			str = List_obj_to_STR_OBJ( str, obj, prefix, MEMPASSED );
			break;
		case OBJ_T_STR:
			switch( SUBTYPE_OBJ(obj) ){
			case OBJ_ST_STR: case OBJ_ST_SHORTSTR:
				str = Str_obj_to_STR_OBJ( str, obj, MEMPASSED );
				break;
			default:
				Errorcode = -1; FATAL(LOG_ERR)"Obj_obj_to_STR_OBJ: type %d (%s) subtype %d (%s) not supported",
						obj->type, Type_to_str(obj->type), obj->subtype, Subtype_to_str(obj->subtype) );
				break;
			}
			break;
		case OBJ_T_SCALAR:
			To_STR_OBJ(obj, MEMINFO);
			str = Str_obj_to_STR_OBJ( str, obj, MEMPASSED );
			break;

		default:
			Errorcode = -1; FATAL(LOG_ERR)"Obj_obj_to_STR_OBJ: type %d (%s) subtype %d (%s) not supported",
						obj->type, Type_to_str(obj->type), obj->subtype, Subtype_to_str(obj->subtype) );
			break;
		}
	}
	DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Obj_obj_to_STR_OBJ - out", str); }
	return( str );
}

/*
 * convert a string to an object
 *  - inverse of hash to str functions
 *  done by recursive descent parser
 *   - get token - we find the next input token
 *     return: EOF, STR, {, }, [, ], SEP (, or =>)
 */
#define T_EOF 0
#define T_ERR 1
#define T_STR 2
#define T_NAME 3
#define T_SEP ','

 static char *input;

char *Parse_token_to_str( int c )
{
	static char msg[32];
	switch( c ){
	case T_EOF: safestrncpy(msg,"T_EOF"); break;
	case T_ERR: safestrncpy(msg,"T_ERR"); break;
	case T_STR: safestrncpy(msg,"T_STR"); break;
	case T_NAME: safestrncpy(msg,"T_NAME"); break;
	default: SNPRINTF( msg,sizeof(msg)) "%d (%c)", c, isprint(c)?c:'?');
	}
	return(msg);
}

void Init_parse( char *str )
{
	input = str;
}

char *Get_parse_position( void )
{
	return( input );
}

int Get_parse_token( OBJ *str )
{
	char *s, *t;
	int c, len;
	if( ISNULL(input) ){
		return( T_EOF );
	}
	while( (c = cval(input)) && isspace(c) ) ++input;
	if( !c ) return( T_EOF );
	DEBUGFC(DUTILS1){ 
		char bm[32];
		safestrncpy(bm,input);
		DEBUGF(DUTILS1)("Get_parse_token: at '%s'", bm ); 
	}
	
	switch(c){
		case '{': case '}':
		case '[': case ']':
		case ',':
			++input;
			DEBUGF(DUTILS1)("Get_parse_token: token '%c'", c ); 
			return(c);
		case '=':
			if( cval(input+1) == '>' ){
				input += 2;
				c= ',';
			} else {
				return( T_ERR );
			}
			DEBUGF(DUTILS1)("Get_parse_token: token '%c'", c ); 
			return(c);
		case '"':
		case '\'':
			if( !cval(input+1) ) return( T_ERR );
			++input;
			s = input; 
			while( (s = strchr(s,c)) && cval(s-1) == '\\' ) ++s;
			if( !s ) return(T_ERR);
			len = s - input;
			SET_MAXLEN_STR_OBJ( str, len+1, MEMINFO );
			t = VAL_STR_OBJ(str);
			memmove(t,input,len);
			t[len] = 0;
			input = ++s;
			DEBUGF(DUTILS1)("Get_parse_token: STR '%s'", VAL_STR_OBJ(str) ); 
			return( T_STR );
		default:
			s = input; 
			if( !strchr(safestr,c) ) return( T_ERR );
			while( (c = cval(s)) && strchr(safestr,c) ) ++s;
			len = s - input;
			SET_MAXLEN_STR_OBJ( str, len+1, MEMINFO );
			t = VAL_STR_OBJ(str);
			memmove(t,input,len);
			t[len] = 0;
			input = s;
			DEBUGF(DUTILS1)("Get_parse_token: NAME '%s'", VAL_STR_OBJ(str) ); 
			return( T_NAME );
	}
}

/*
 * Parse an object represenation
 *   - we update the hash object
 *   - we return the token we cannot handle
 */

int Parse_hash_obj_str( OBJ *hash )
{
	int c;
	OBJ *name = SET_STR_OBJ(0,"",MEMINFO);
	OBJ *value;
	
	while( 1 ){
		c = Get_parse_token( name );
		DEBUGF(DUTILS1)("Parse_hash_obj_str: starting token %s",Parse_token_to_str(c) );
		if( c != T_STR && c != T_NAME ){
			break;
		}
		DEBUGF(DUTILS1)("Parse_hash_obj_str: name '%s'", VAL_STR_OBJ(name) );
		value = SET_HASH_OBJ(hash,VAL_STR_OBJ(name),0,MEMINFO);
		c = Get_parse_token(name);
		if( c != ',' ) break;
		c = Parse_obj_obj_str( &value );
		DEBUGFC(DUTILS1){SHORT_DUMP_OBJ("Parse_hash_obj_str: found", hash ); }
		if( c != ',' ) break;
	}
	FREE_OBJ(name); name = 0;
	DEBUGF(DUTILS1)("Parse_hash_obj_str: return token %s",Parse_token_to_str(c) );
	return( c );
}

int Parse_array_obj_str( OBJ *array )
{
	int c, n;
	OBJ *value = 0, *p = 0;
	
	while( 1 ){
		value = 0;
		DEBUGF(DUTILS1)("Parse_array_obj_str: starting");
		c = Parse_obj_obj_str( &value );
		DEBUGFC(DUTILS1){ SHORT_DUMP_OBJ("Parse_array_obj_str: value",value); }
		if( value || c == ',' ){
			n = LEN_ARRAY_OBJ(array);
			EXTEND_ARRAY_OBJ(array,n+1,0,MEMINFO);
			/* this is nasty, but it saves COPY_OBJ */
			if( value ){
				p = AT_ARRAY_OBJ(array,n);
				memmove(p,value,sizeof(p[0]));
				memset(value,0,sizeof(value[0]));
			}
			FREE_OBJ(value); value = 0;
		}
		if( c != ',' ) break;
	}
	DEBUGFC(DUTILS1){SHORT_DUMP_OBJ("Parse_array_obj_str: out", array ); }
	DEBUGF(DUTILS1)("Parse_hash_obj_str: return token %s",Parse_token_to_str(c) );
	return( c );
}

int Parse_obj_obj_str( OBJ **objp )
{
	int c;
	OBJ *name = SET_STR_OBJ(0,"",MEMINFO);
	c = Get_parse_token(name);
	DEBUGF(DUTILS1)("Parse_obj_obj_str: starting token %s",Parse_token_to_str(c) );
	if( c == T_STR || c == T_NAME ){
		if( *objp ){
			SET_STR_OBJ( *objp, VAL_STR_OBJ(name), MEMINFO );
		} else {
			*objp = name;
			name = 0;
		}
		c = Get_parse_token(name);
	} else if( c == '[' ){
		name = NEW_ARRAY_OBJ(name,MEMINFO);
		c = Parse_array_obj_str( name );
		if( c != ']' ){
			c = T_ERR;
		} else {
			*objp = name;
			name = 0;
			c = Get_parse_token(name);
		}
	} else if( c == '{' ){
		name = NEW_HASH_OBJ(name,MEMINFO);
		c = Parse_hash_obj_str( name );
		if( c != '}' ){
			c = T_ERR;
		} else {
			*objp = name;
			name = 0;
			c = Get_parse_token(name);
		}
	}
	if( name ) FREE_OBJ(name); name = 0;
	DEBUGFC(DUTILS1){SHORT_DUMP_OBJ("Parse_obj_obj_str: out", *objp ); }
	DEBUGF(DUTILS1)("Parse_obj_obj_str: return token %s",Parse_token_to_str(c) );
	return( c );
}

char *Parse_err_msg( void )
{
	char mb[32];
	static char msg[64];
	SNPRINTF(mb,sizeof(mb)) "%s", ISNULL(input)?"<EOF>":input);
	SNPRINTF(msg,sizeof(msg)) "parse error at '%s'", mb );
	return( msg );
}
