/*-------------------------------------------------------------------------
 *
 * stringinfo.c
 *
 * StringInfo provides an indefinitely-extensible string data type.
 * It can be used to buffer either ordinary C strings (null-terminated text)
 * or arbitrary binary data.  All storage is allocated with palloc().
 *
 * Portions Copyright (c) 1996-2002, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *	  $Id: stringinfo.c,v 1.32 2002/09/04 20:31:18 momjian Exp $
 *
 *-------------------------------------------------------------------------
 */


#include "postgres.h"
#include "lib/stringinfo.h"

/*
 * makeStringInfo
 *
 * Create an empty 'StringInfoData' & return a pointer to it.
 */
StringInfo
makeStringInfo(void)
{
	StringInfo	res;

	res = (StringInfo) palloc(sizeof(StringInfoData));

	initStringInfo(res);

	return res;
}

/*
 * initStringInfo
 *
 * Initialize a StringInfoData struct (with previously undefined contents)
 * to describe an empty string.
 */
void
initStringInfo(StringInfo str)
{
	int			size = 256;		/* initial default buffer size */

	str->data = (char *) palloc(size);
	str->maxlen = size;
	str->len = 0;
	str->data[0] = '\0';
}

/*
 * enlargeStringInfo
 *
 * Internal routine: make sure there is enough space for 'needed' more bytes
 * ('needed' does not include the terminating null).
 *
 * NB: because we use repalloc() to enlarge the buffer, the string buffer
 * will remain allocated in the same memory context that was current when
 * initStringInfo was called, even if another context is now current.
 * This is the desired and indeed critical behavior!
 */
static void
enlargeStringInfo(StringInfo str, int needed)
{
	int			newlen;

	needed += str->len + 1;		/* total space required now */
	if (needed <= str->maxlen)
		return;					/* got enough space already */

	/*
	 * We don't want to allocate just a little more space with each
	 * append; for efficiency, double the buffer size each time it
	 * overflows. Actually, we might need to more than double it if
	 * 'needed' is big...
	 */
	newlen = 2 * str->maxlen;
	while (needed > newlen)
		newlen = 2 * newlen;

	str->data = (char *) repalloc(str->data, newlen);

	str->maxlen = newlen;
}

/*
 * appendStringInfo
 *
 * Format text data under the control of fmt (an sprintf-like format string)
 * and append it to whatever is already in str.  More space is allocated
 * to str if necessary.  This is sort of like a combination of sprintf and
 * strcat.
 */
void
appendStringInfo(StringInfo str, const char *fmt,...)
{
	va_list		args;
	int			avail,
				nprinted;

	Assert(str != NULL);

	for (;;)
	{
		/*
		 * Try to format the given string into the available space; but if
		 * there's hardly any space, don't bother trying, just fall
		 * through to enlarge the buffer first.
		 */
		avail = str->maxlen - str->len - 1;
		if (avail > 16)
		{
			/*
			 * Assert check here is to catch buggy vsnprintf that overruns
			 * the specified buffer length.  Solaris 7 in 64-bit mode is
			 * an example of a platform with such a bug.
			 */
#ifdef USE_ASSERT_CHECKING
			str->data[str->maxlen - 1] = '\0';
#endif

			va_start(args, fmt);
			nprinted = vsnprintf(str->data + str->len, avail,
								 fmt, args);
			va_end(args);

			Assert(str->data[str->maxlen - 1] == '\0');

			/*
			 * Note: some versions of vsnprintf return the number of chars
			 * actually stored, but at least one returns -1 on failure. Be
			 * conservative about believing whether the print worked.
			 */
			if (nprinted >= 0 && nprinted < avail - 1)
			{
				/* Success.  Note nprinted does not include trailing null. */
				str->len += nprinted;
				break;
			}
		}
		/* Double the buffer size and try again. */
		enlargeStringInfo(str, str->maxlen);
	}
}

/*------------------------
 * appendStringInfoChar
 * Append a single byte to str.
 * Like appendStringInfo(str, "%c", ch) but much faster.
 */
void
appendStringInfoChar(StringInfo str, char ch)
{
	/* Make more room if needed */
	if (str->len + 1 >= str->maxlen)
		enlargeStringInfo(str, 1);

	/* OK, append the character */
	str->data[str->len] = ch;
	str->len++;
	str->data[str->len] = '\0';
}

/*
 * appendBinaryStringInfo
 *
 * Append arbitrary binary data to a StringInfo, allocating more space
 * if necessary.
 */
void
appendBinaryStringInfo(StringInfo str, const char *data, int datalen)
{
	Assert(str != NULL);

	/* Make more room if needed */
	enlargeStringInfo(str, datalen);

	/* OK, append the data */
	memcpy(str->data + str->len, data, datalen);
	str->len += datalen;

	/*
	 * Keep a trailing null in place, even though it's probably useless
	 * for binary data...
	 */
	str->data[str->len] = '\0';
}
