/* -*- Mode: c; c-basic-offset: 2 -*-
 *
 * raptor_stringbuffer.c - Stringbuffer class for growing strings
 *
 * Copyright (C) 2003-2008, David Beckett http://www.dajobe.org/
 * Copyright (C) 2003-2004, University of Bristol, UK http://www.bristol.ac.uk/
 * 
 * This package is Free Software and part of Redland http://librdf.org/
 * 
 * It is licensed under the following three licenses as alternatives:
 *   1. GNU Lesser General Public License (LGPL) V2.1 or any newer version
 *   2. GNU General Public License (GPL) V2 or any newer version
 *   3. Apache License, V2.0 or any newer version
 * 
 * You may not use this file except in compliance with at least one of
 * the above three licenses.
 * 
 * See LICENSE.html or LICENSE.txt at the top of this package for the
 * complete terms and further detail along with the license texts for
 * the licenses in COPYING.LIB, COPYING and LICENSE-2.0.txt respectively.
 * 
 * 
 * 
 */


#ifdef HAVE_CONFIG_H
#include <raptor_config.h>
#endif

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <sys/types.h>

#ifdef HAVE_STDLIB_H
#include <stdlib.h> /* for abort() as used in errors */
#endif

/* Raptor includes */
#include "raptor2.h"
#include "raptor_internal.h"


#ifndef STANDALONE

struct raptor_stringbuffer_node_s
{
  struct raptor_stringbuffer_node_s* next;
  unsigned char *string;
  size_t length;
};
typedef struct raptor_stringbuffer_node_s raptor_stringbuffer_node;


struct raptor_stringbuffer_s
{
  /* Pointing to the first item in the list of nodes */
  raptor_stringbuffer_node* head;
  /* and the last */
  raptor_stringbuffer_node* tail;
  
  /* total length of the string */
  size_t length;

  /* frozen string if already calculated, or NULL if not present */
  unsigned char *string;
};


/* prototypes for local functions */
static int raptor_stringbuffer_append_string_common(raptor_stringbuffer* stringbuffer, const unsigned char *string, size_t length, int do_copy);


/* functions implementing the stringbuffer api */

/**
 * raptor_new_stringbuffer:
 *
 * Create a new stringbuffer.
 * 
 * Return value: pointer to a raptor_stringbuffer object or NULL on failure
 **/
raptor_stringbuffer*
raptor_new_stringbuffer(void) 
{
  raptor_stringbuffer* sb;
  
  sb = RAPTOR_CALLOC(raptor_stringbuffer*, 1, sizeof(*sb));
  return sb;
}


/**
 * raptor_free_stringbuffer:
 * @stringbuffer: stringbuffer object to destroy.
 *
 * Destroy a stringbuffer.
 * 
 **/
void
raptor_free_stringbuffer(raptor_stringbuffer *stringbuffer) 
{
  if(!stringbuffer)
    return;

  if(stringbuffer->head) {
    raptor_stringbuffer_node *node = stringbuffer->head;
  
    while(node) {
      raptor_stringbuffer_node *next = node->next;
      
      if(node->string)
        RAPTOR_FREE(char*, node->string);
      RAPTOR_FREE(raptor_stringbuffer_node, node);
      node = next;
    }
  }

  if(stringbuffer->string)
    RAPTOR_FREE(char*, stringbuffer->string);

  RAPTOR_FREE(raptor_stringbuffer, stringbuffer);
}



/**
 * raptor_stringbuffer_append_string_common:
 * @stringbuffer: raptor stringbuffer
 * @string: string
 * @length: length of string
 * @do_copy: non-0 to copy the string
 *
 * Add a string to the stringbuffer.
 *
 * INTERNAL
 *
 * If @string is NULL or @length is 0, no work is performed.
 *
 * If @do_copy is non-0, the passed-in string is copied into new memory
 * otherwise the stringbuffer becomes the owner of the string pointer
 * and will free it when the stringbuffer is destroyed.
 *
 * Return value: non-0 on failure
 **/
static int
raptor_stringbuffer_append_string_common(raptor_stringbuffer* stringbuffer, 
                                         const unsigned char *string,
                                         size_t length,
                                         int do_copy)
{
  raptor_stringbuffer_node *node;

  if(!string || !length)
    return 0;
  
  node = RAPTOR_MALLOC(raptor_stringbuffer_node*, sizeof(*node));
  if(!node) {
    if(!do_copy)
      RAPTOR_FREE(char*, string);
    return 1;
  }

  if(do_copy) {
    /* Note this copy does not include the \0 character - not needed  */
    node->string = RAPTOR_MALLOC(unsigned char*, length);
    if(!node->string) {
      RAPTOR_FREE(raptor_stringbuffer_node, node);
      return 1;
    }
    memcpy(node->string, string, length);
  } else
    node->string = (unsigned char*)string;
  node->length = length;


  if(stringbuffer->tail) {
    stringbuffer->tail->next = node;
    stringbuffer->tail = node;
  } else
    stringbuffer->head = stringbuffer->tail = node;
  node->next = NULL;

  if(stringbuffer->string) {
    RAPTOR_FREE(char*, stringbuffer->string);
    stringbuffer->string = NULL;
  }
  stringbuffer->length += length;

  return 0;
}




/**
 * raptor_stringbuffer_append_counted_string:
 * @stringbuffer: raptor stringbuffer
 * @string: string
 * @length: length of string
 * @do_copy: non-0 to copy the string
 *
 * Add a counted string to the stringbuffer.
 *
 * If @string is NULL or @length is 0, no work is performed.
 *
 * If @do_copy is non-0, the passed-in string is copied into new memory
 * otherwise the stringbuffer becomes the owner of the string pointer
 * and will free it when the stringbuffer is destroyed.
 *
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_append_counted_string(raptor_stringbuffer* stringbuffer, 
                                          const unsigned char *string, size_t length,
                                          int do_copy)
{
  if(!string || !length)
    return 0;
  
  return raptor_stringbuffer_append_string_common(stringbuffer, string, length, do_copy);
}
  

/**
 * raptor_stringbuffer_append_string:
 * @stringbuffer: raptor stringbuffer
 * @string: string
 * @do_copy: non-0 to copy the string
 *
 * Add a string to the stringbuffer.
 * 
 * If @string is NULL, no work is performed.
 *
 * If @do_copy is non-0, the passed-in string is copied into new memory
 * otherwise the stringbuffer becomes the owner of the string pointer
 * and will free it when the stringbuffer is destroyed.
 *
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_append_string(raptor_stringbuffer* stringbuffer, 
                                  const unsigned char *string, int do_copy)
{
  if(!string)
    return 0;
  
  return raptor_stringbuffer_append_string_common(stringbuffer, string, strlen((const char*)string), do_copy);
}


/**
 * raptor_stringbuffer_append_decimal:
 * @stringbuffer: raptor stringbuffer
 * @integer: integer to format as decimal and add
 *
 * Add an integer in decimal to the stringbuffer.
 * 
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_append_decimal(raptor_stringbuffer* stringbuffer, 
                                   int integer)
{
  /* enough for 64 bit signed integer
   * INT64_MAX is  9223372036854775807 (19 digits) + 1 for sign 
   */
  unsigned char buf[20];
  unsigned char *p;
  int i = integer;
  size_t length = 1;
  if(integer < 0) {
    length++;
    i= -integer;
  }
  while(i /= 10)
    length++;

  p = buf+length-1;
  i = integer;
  if(i < 0)
    i= -i;
  do {
    *p-- ='0'+(i %10);
    i /= 10;
  } while(i);
  if(integer < 0)
    *p= '-';
  
  return raptor_stringbuffer_append_counted_string(stringbuffer, buf, length, 1);
}


/**
 * raptor_stringbuffer_append_stringbuffer:
 * @stringbuffer: #raptor_stringbuffer
 * @append: #raptor_stringbuffer to append
 *
 * Add a stringbuffer to the stringbuffer.
 *
 * This function removes the content from the appending stringbuffer,
 * making it empty and appends it to the supplied stringbuffer.
 *
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_append_stringbuffer(raptor_stringbuffer* stringbuffer, 
                                        raptor_stringbuffer* append)
{
  raptor_stringbuffer_node *node = append->head;

  if(!node)
    return 0;

  /* move all append nodes to stringbuffer */
  if(stringbuffer->tail) {
    stringbuffer->tail->next = node;
  } else
    stringbuffer->head = node;

  stringbuffer->tail = append->tail;

  /* adjust our length */
  stringbuffer->length += append->length;
  if(stringbuffer->string) {
    RAPTOR_FREE(char*, stringbuffer->string);
    stringbuffer->string = NULL;
  }

  /* zap append content */
  append->head = append->tail = NULL;
  append->length = 0;
  if(append->string) {
    RAPTOR_FREE(char*, append->string);
    append->string = NULL;
  }
  
  return 0;
}




/**
 * raptor_stringbuffer_prepend_string_common:
 * @stringbuffer: raptor stringbuffer
 * @string: string
 * @length: length of string
 * @do_copy: non-0 to copy the string
 *
 * Add a string to the start of a stringbuffer.
 *
 * INTERNAL
 *
 * If do_copy is non-0, the passed-in string is copied into new memory
 * otherwise the stringbuffer becomes the owner of the string pointer
 * and will free it when the stringbuffer is destroyed.
 *
 * Return value: non-0 on failure
 **/
static int
raptor_stringbuffer_prepend_string_common(raptor_stringbuffer* stringbuffer, 
                                          const unsigned char *string, size_t length,
                                          int do_copy)
{
  raptor_stringbuffer_node *node;

  node = RAPTOR_MALLOC(raptor_stringbuffer_node*, sizeof(*node));
  if(!node)
    return 1;

  if(do_copy) {
    /* Note this copy does not include the \0 character - not needed  */
    node->string = RAPTOR_MALLOC(unsigned char*, length);
    if(!node->string) {
      RAPTOR_FREE(raptor_stringbuffer_node, node);
      return 1;
    }
    memcpy(node->string, string, length);
  } else
    node->string = (unsigned char*)string;
  node->length = length;


  node->next = stringbuffer->head;
  if(stringbuffer->head)
    stringbuffer->head = node;
  else
    stringbuffer->head = stringbuffer->tail = node;

  if(stringbuffer->string) {
    RAPTOR_FREE(char*, stringbuffer->string);
    stringbuffer->string = NULL;
  }
  stringbuffer->length += length;

  return 0;
}




/**
 * raptor_stringbuffer_prepend_counted_string:
 * @stringbuffer: raptor stringbuffer
 * @string: string
 * @length: length of string
 * @do_copy: non-0 to copy the string

 * If do_copy is non-0, the passed-in string is copied into new memory
 * otherwise the stringbuffer becomes the owner of the string pointer
 * and will free it when the stringbuffer is destroyed.
 *
 * Add a string to the start of the stringbuffer.
 *
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_prepend_counted_string(raptor_stringbuffer* stringbuffer, 
                                           const unsigned char *string, size_t length,
                                           int do_copy)
{
  return raptor_stringbuffer_prepend_string_common(stringbuffer, string, length, do_copy);
}
  

/**
 * raptor_stringbuffer_prepend_string:
 * @stringbuffer: raptor stringbuffer
 * @string: string
 * @do_copy: non-0 to copy the string
 *
 * Add a string to the start of the stringbuffer.
 * 
 * If do_copy is non-0, the passed-in string is copied into new memory
 * otherwise the stringbuffer becomes the owner of the string pointer
 * and will free it when the stringbuffer is destroyed.
 *
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_prepend_string(raptor_stringbuffer* stringbuffer, 
                                   const unsigned char *string, int do_copy)
{
  return raptor_stringbuffer_prepend_string_common(stringbuffer, string, strlen((const char*)string), do_copy);
}


/**
 * raptor_stringbuffer_length:
 * @stringbuffer: raptor stringbuffer
 *
 * Return the stringbuffer length.
 * 
 * Return value: size of stringbuffer
 **/
size_t
raptor_stringbuffer_length(raptor_stringbuffer* stringbuffer)
{
  return stringbuffer->length;
}



/**
 * raptor_stringbuffer_as_string:
 * @stringbuffer: raptor stringbuffer
 *
 * Return the stringbuffer as a C string.
 * 
 * Note: the return value is a to a shared string that the stringbuffer
 * allocates and manages.
 *
 * Return value: NULL on failure or stringbuffer is empty, otherwise
 *   a pointer to a shared copy of the string.
 **/
unsigned char *
raptor_stringbuffer_as_string(raptor_stringbuffer* stringbuffer)
{
  raptor_stringbuffer_node *node;
  unsigned char *p;
  
  if(!stringbuffer->length)
    return NULL;
  if(stringbuffer->string)
    return stringbuffer->string;

  stringbuffer->string = RAPTOR_MALLOC(unsigned char*, stringbuffer->length + 1);
  if(!stringbuffer->string)
    return NULL;

  node = stringbuffer->head;
  p = stringbuffer->string;
  while(node) {
    memcpy(p, node->string, node->length);
    p+= node->length;
    node = node->next;
  }
  *p='\0';
  return stringbuffer->string;
}


/**
 * raptor_stringbuffer_copy_to_string:
 * @stringbuffer: raptor stringbuffer
 * @string: output string
 * @length: size of output string
 *
 * Copy the stringbuffer into a string.
 * 
 * Copies the underlying string to a pre-allocated buffer.  The
 * output string is always '\0' terminated.
 *
 * Return value: non-0 on failure such as stringbuffer is empty, buffer is too small
 **/
int
raptor_stringbuffer_copy_to_string(raptor_stringbuffer* stringbuffer,
                                   unsigned char *string, size_t length)
{
  raptor_stringbuffer_node *node;
  unsigned char *p;
  
  if(!string || length < 1)
    return 1;

  if(!stringbuffer->length)
    return 0;

  p = string;
  for(node = stringbuffer->head; node; node = node->next) {
    if(node->length > length) {
      p[-1]='\0';
      return 1;
    }
    memcpy(p, node->string, node->length);
    p+= node->length;
    length-= node->length;
  }
  *p='\0';
  return 0;
}



/**
 * raptor_stringbuffer_append_hexadecimal:
 * @stringbuffer: raptor stringbuffer
 * @hex: integer to format
 *
 * Add an integer formatted in hexdecimal (base 16) to the stringbuffer.
 * 
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_append_hexadecimal(raptor_stringbuffer* stringbuffer, 
                                       int hex)
{
  unsigned char buf[2];
  
  if(hex < 0 || hex > 0xF)
     return 1;

  *buf = (hex < 10) ? ('0' + hex) : ('A' + hex - 10);
  buf[1] = '\0';

  return raptor_stringbuffer_append_counted_string(stringbuffer, buf, 1, 1);
}


/* RFC3986 Unreserved */
#define IS_URI_UNRESERVED(c) ( (c >= 'A' && c <= 'F') || \
                               (c >= 'a' && c <= 'f') || \
                               (c >= '0' && c <= '9') || \
                               (c == '-' || c == '.' || c == '_' || c == '~') )
#define IS_URI_SAFE(c) (IS_URI_UNRESERVED(c))
    

/**
 * raptor_stringbuffer_append_uri_escaped_counted_string:
 * @sb: raptor stringbuffer
 * @string: string
 * @length: length of string
 * @space_is_plus: if non-0, escape spaces as '+' otherwise percent-encode them
 *
 * Add a URI-escaped version of @string to the stringbuffer.
 *
 * If @string is NULL or @length is 0, no work is performed.
 *
 * Return value: non-0 on failure
 **/
int
raptor_stringbuffer_append_uri_escaped_counted_string(raptor_stringbuffer* sb,
                                                      const char* string,
                                                      size_t length,
                                                      int space_is_plus)
{
  unsigned int i;
  unsigned char buf[2];
  buf[1] = '\0';

  if(!string || !length)
    return 0;
  
  for(i = 0; i < length; i++) {
    int c = string[i];
    if(!c)
      break;
    
    if(IS_URI_SAFE(c)) {
      *buf = c;

      if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1))
        return 1;
    } else if (c == ' ' && space_is_plus) {
      *buf = '+';

      if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1))
        return 1;
    } else {
      *buf = '%';
      if(raptor_stringbuffer_append_counted_string(sb, buf, 1, 1))
        return 1;

      if(raptor_stringbuffer_append_hexadecimal(sb, (c & 0xf0) >> 4))
        return 1;

      if(raptor_stringbuffer_append_hexadecimal(sb, (c & 0x0f)))
        return 1;
    }
  }

  return 0;
}


#endif



#ifdef STANDALONE

/* one more prototype */
int main(int argc, char *argv[]);


int
main(int argc, char *argv[]) 
{
  const char *program = raptor_basename(argv[0]);
#define TEST_ITEMS_COUNT 9
  const char *items[TEST_ITEMS_COUNT] = { "the", "quick" ,"brown", "fox", "jumps", "over", "the", "lazy", "dog" };
  const char *items_string = "thequickbrownfoxjumpsoverthelazydog";  
  const size_t items_len = 35;
  const char *test_integer_string = "abcd";
#define TEST_INTEGERS_COUNT 7
  const int test_integers[TEST_INTEGERS_COUNT]={ 0, 1, -1, 11, 1234, 12345, -12345 };
  const char *test_integer_results[TEST_INTEGERS_COUNT]={ "abcd0", "abcd1", "abcd-1", "abcd11", "abcd1234", "abcd12345", "abcd-12345" };
  raptor_stringbuffer *sb;
  unsigned char *str;
  size_t len;
  int i = 0;
  raptor_stringbuffer *sb1, *sb2;
#define TEST_APPEND_COUNT 2
  const char *test_append_results[TEST_APPEND_COUNT]={ "thebrownjumpsthedog", "quickfoxoverlazy" };
  const char *test_append_results_total="thebrownjumpsthedogquickfoxoverlazy";
#define COPY_STRING_BUFFER_SIZE 100
  unsigned char *copy_string;
  
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
  fprintf(stderr, "%s: Creating string buffer\n", program);
#endif

  /* test appending */

  sb = raptor_new_stringbuffer();
  if(!sb) {
    fprintf(stderr, "%s: Failed to create string buffer\n", program);
    exit(1);
  }

  for(i = 0; i < TEST_ITEMS_COUNT; i++) {
    int rc;
    len = strlen(items[i]);

#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
    fprintf(stderr, "%s: Adding string buffer item '%s'\n", program, items[i]);
#endif
  
    rc = raptor_stringbuffer_append_counted_string(sb, (unsigned char*)items[i], len, 1);
    if(rc) {
      fprintf(stderr, "%s: Adding string buffer item %d '%s' failed, returning error %d\n",
              program, i, items[i], rc);
      exit(1);
    }
  }

  len = raptor_stringbuffer_length(sb);
  if(len != items_len) {
    fprintf(stderr, "%s: string buffer len is %d, expected %d\n", program,
            (int)len, (int)items_len);
    exit(1);
  }

  str = raptor_stringbuffer_as_string(sb);
  if(strcmp((const char*)str, items_string)) {
    fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n",
            program, str, items_string);
    exit(1);
  }

  raptor_free_stringbuffer(sb);

  
  /* test prepending */

#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
  fprintf(stderr, "%s: Creating string buffer\n", program);
#endif

  sb = raptor_new_stringbuffer();
  if(!sb) {
    fprintf(stderr, "%s: Failed to create string buffer\n", program);
    exit(1);
  }

  for(i = TEST_ITEMS_COUNT-1; i>=0 ; i--) {
    int rc;
    len = strlen(items[i]);

#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
    fprintf(stderr, "%s: Prepending string buffer item '%s'\n", program, items[i]);
#endif
  
    rc = raptor_stringbuffer_prepend_counted_string(sb, (unsigned char*)items[i], len, 1);
    if(rc) {
      fprintf(stderr, "%s: Prepending string buffer item %d '%s' failed, returning error %d\n",
              program, i, items[i], rc);
      exit(1);
    }
  }

  len = raptor_stringbuffer_length(sb);
  if(len != items_len) {
    fprintf(stderr, "%s: string buffer len is %d, expected %d\n", program,
            (int)len, (int)items_len);
    exit(1);
  }

  str = raptor_stringbuffer_as_string(sb);
  if(strcmp((const char*)str, items_string)) {
    fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n",
            program, str, items_string);
    exit(1);
  }


  /* test adding integers */

  for(i = 0; i < TEST_INTEGERS_COUNT; i++) {
    raptor_stringbuffer *isb = raptor_new_stringbuffer();
    if(!isb) {
      fprintf(stderr, "%s: Failed to create string buffer\n", program);
      exit(1);
    }
    
    raptor_stringbuffer_append_string(isb, 
                                      (const unsigned char*)test_integer_string, 1);

#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
    fprintf(stderr, "%s: Adding decimal integer %d to buffer\n", program, test_integers[i]);
#endif

    raptor_stringbuffer_append_decimal(isb, test_integers[i]);

    str = raptor_stringbuffer_as_string(isb);
    if(strcmp((const char*)str, test_integer_results[i])) {
      fprintf(stderr, "%s: string buffer contains '%s', expected '%s'\n",
              program, str, test_integer_results[i]);
      exit(1);
    }
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
    fprintf(stderr, "%s: Freeing string buffer\n", program);
#endif
    raptor_free_stringbuffer(isb);
  }


#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
    fprintf(stderr, "%s: Creating two stringbuffers to join\n", program);
#endif

  sb1 = raptor_new_stringbuffer();
  if(!sb1) {
    fprintf(stderr, "%s: Failed to create string buffer\n", program);
    exit(1);
  }
  sb2 = raptor_new_stringbuffer();
  if(!sb2) {
    fprintf(stderr, "%s: Failed to create string buffer\n", program);
    exit(1);
  }

  for(i = 0; i < TEST_ITEMS_COUNT; i++) {
    raptor_stringbuffer *sbx;
    int rc;
    len = strlen(items[i]);

    sbx = (i % 2) ? sb2 : sb1;
    rc = raptor_stringbuffer_append_counted_string(sbx, (unsigned char*)items[i], len, 1);
    if(rc) {
      fprintf(stderr, "%s: Adding string buffer item %d '%s' failed, returning error %d\n",
              program, i, items[i], rc);
      exit(1);
    }
  }

  if(1) {
    int rc;

    rc = raptor_stringbuffer_append_counted_string(sb1, (unsigned char*)"X", 0, 1);
    if(rc) {
      fprintf(stderr, "%s: Adding 0-length counted string failed, returning error %d\n",
              program, rc);
      exit(1);
    }
    rc = raptor_stringbuffer_append_string(sb1, NULL, 1);
    if(rc) {
      fprintf(stderr, "%s: Adding NULL string failed, returning error %d\n",
              program, rc);
      exit(1);
    }
  }
  
  str = raptor_stringbuffer_as_string(sb1);
  if(strcmp((const char*)str, test_append_results[0])) {
    fprintf(stderr, "%s: string buffer sb1 contains '%s', expected '%s'\n",
            program, str, test_append_results[0]);
    exit(1);
  }
  str = raptor_stringbuffer_as_string(sb2);
  if(strcmp((const char*)str, test_append_results[1])) {
    fprintf(stderr, "%s: string buffer sb2 contains '%s', expected '%s'\n",
            program, str, test_append_results[1]);
    exit(1);
  }

#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
    fprintf(stderr, "%s: Appended two stringbuffers\n", program);
#endif

  if(raptor_stringbuffer_append_stringbuffer(sb1, sb2)) {
    fprintf(stderr, "%s: Failed to append string buffer\n", program);
    exit(1);
  }

  str = raptor_stringbuffer_as_string(sb1);
  if(strcmp((const char*)str, test_append_results_total)) {
    fprintf(stderr, "%s: appended string buffer contains '%s', expected '%s'\n",
            program, str, test_append_results_total);
    exit(1);
  }
  
  len = raptor_stringbuffer_length(sb2);
  if(len) {
    fprintf(stderr, "%s: appended string buffer is length %d, not empty'\n",
            program, (int)len);
    exit(1);
  }


#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
  fprintf(stderr, "%s: Copying string buffer to string\n", program);
#endif

  copy_string = (unsigned char*)malloc(COPY_STRING_BUFFER_SIZE);
  if(raptor_stringbuffer_copy_to_string(sb1, copy_string, COPY_STRING_BUFFER_SIZE)) {
    fprintf(stderr, "%s: copying string buffer to string failed\n",
            program);
    exit(1);
  }
  if(strcmp((const char*)copy_string, test_append_results_total)) {
    fprintf(stderr, "%s: copied string buffer contains '%s', expected '%s'\n",
            program, copy_string, test_append_results_total);
    exit(1);
  }
  free(copy_string);
  
  
#if defined(RAPTOR_DEBUG) && RAPTOR_DEBUG > 1
  fprintf(stderr, "%s: Freeing string buffers\n", program);
#endif
  raptor_free_stringbuffer(sb1);
  raptor_free_stringbuffer(sb2);
  raptor_free_stringbuffer(sb);

  /* keep gcc -Wall happy */
  return(0);
}

#endif
