From bf53de3dfa4db68627f2935e6b2144835604df3a Mon Sep 17 00:00:00 2001
From: Mike Buland <eichlan@xagasoft.com>
Date: Fri, 16 Oct 2009 16:09:02 +0000
Subject: Finally added the substream class, and added getByPath (for
 properties) and getChildByPath (for groups) to the TafGroup class.

---
 src/list.h              |  62 ++++++++++++++++++++++++---
 src/substream.cpp       | 109 ++++++++++++++++++++++++++++++++++++++++++++++++
 src/substream.h         |  63 ++++++++++++++++++++++++++++
 src/tafgroup.cpp        |  37 ++++++++++++++++
 src/tafgroup.h          |   5 +++
 src/unit/substream.unit |  52 +++++++++++++++++++++++
 src/unit/taf.unit       |  10 +++++
 7 files changed, 333 insertions(+), 5 deletions(-)
 create mode 100644 src/substream.cpp
 create mode 100644 src/substream.h
 create mode 100644 src/unit/substream.unit

diff --git a/src/list.h b/src/list.h
index c587c1a..d8c5a4a 100644
--- a/src/list.h
+++ b/src/list.h
@@ -178,11 +178,11 @@ namespace Bu
 
 	/**
 	 * Linked list template container.  This class is similar to the stl list
-	 * class except for a few minor changes.  First, it doesn't mimic a stack or
-	 * queue, use the Stack or Queue clasess for that.  Second, when const, all
-	 * members are only accessable const.  Third, erasing a location does not
-	 * invalidate the iterator, it simply points to the next valid location, or
-	 * end() if there are no more.
+	 * class except for a few minor changes.  First, when const, all
+	 * members are only accessable const.  Second, erasing a location does not
+	 * invalidate the iterator used, it simply points to the next valid
+	 * location, or end() if there are no more.  Other iterators pointing to
+	 * the deleted record will, of course, no longer be valid.
 	 *
 	 *@param value (typename) The type of data to store in your list
 	 *@param valuealloc (typename) Memory Allocator for your value type
@@ -506,6 +506,32 @@ namespace Bu
 				return *this;
 			}
 
+			iterator operator+( int iDelta )
+			{
+				iterator ret( *this );
+				for( int j = 0; j < iDelta; j++ )
+				{
+					if( ret.pLink == NULL )
+						throw Bu::ExceptionBase(
+							"Attempt to iterate past begining of list.");
+					ret.pLink = ret.pLink->pNext;
+				}
+				return ret;
+			}
+
+			iterator operator-( int iDelta )
+			{
+				iterator ret( *this );
+				for( int j = 0; j < iDelta; j++ )
+				{
+					if( ret.pLink == NULL )
+						throw Bu::ExceptionBase(
+							"Attempt to iterate past begining of list.");
+					ret.pLink = ret.pLink->pPrev;
+				}
+				return ret;
+			}
+
 			operator bool()
 			{
 				return pLink != NULL;
@@ -619,6 +645,32 @@ namespace Bu
 				return *this;
 			}
 
+			const_iterator operator+( int iDelta )
+			{
+				const_iterator ret( *this );
+				for( int j = 0; j < iDelta; j++ )
+				{
+					if( ret.pLink == NULL )
+						throw Bu::ExceptionBase(
+							"Attempt to iterate past begining of list.");
+					ret.pLink = ret.pLink->pNext;
+				}
+				return ret;
+			}
+
+			const_iterator operator-( int iDelta )
+			{
+				const_iterator ret( *this );
+				for( int j = 0; j < iDelta; j++ )
+				{
+					if( ret.pLink == NULL )
+						throw Bu::ExceptionBase(
+							"Attempt to iterate past begining of list.");
+					ret.pLink = ret.pLink->pPrev;
+				}
+				return ret;
+			}
+
 			const_iterator &operator=( const iterator &oth )
 			{
 				pLink = oth.pLink;
diff --git a/src/substream.cpp b/src/substream.cpp
new file mode 100644
index 0000000..ddb31b4
--- /dev/null
+++ b/src/substream.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2007-2008 Xagasoft, All rights reserved.
+ *
+ * This file is part of the libbu++ library and is released under the
+ * terms of the license contained in the file LICENSE.
+ */
+
+#include "bu/substream.h"
+
+Bu::SubStream::SubStream( Bu::Stream &rNext, long iSize ) :
+	Bu::Filter( rNext ),
+	iStart( 0 ),
+	iPos( 0 ),
+	iSize( iSize )
+{
+	iStart = rNext.tell();
+}
+
+Bu::SubStream::~SubStream()
+{
+}
+
+size_t Bu::SubStream::read( void *pBuf, size_t nBytes )
+{
+	if( (long)nBytes > iSize-iPos )
+		nBytes = iSize-iPos;
+	nBytes = rNext.read( pBuf, nBytes );
+	iPos += nBytes;
+	return nBytes;
+}
+
+size_t Bu::SubStream::write( const void *pBuf, size_t nBytes )
+{
+	if( (long)nBytes > iSize-iPos )
+		nBytes = iSize-iPos;
+	nBytes = rNext.write( pBuf, nBytes );
+	iPos += nBytes;
+	return nBytes;
+}
+
+void Bu::SubStream::start()
+{
+	// doesn't mean anything...
+}
+
+size_t Bu::SubStream::stop()
+{
+	// doesn't mean anything...
+	return 0;
+}
+
+void Bu::SubStream::close()
+{
+	// don't do anything?  maybe...
+}
+
+long Bu::SubStream::tell()
+{
+	return iPos;
+}
+
+void Bu::SubStream::seek( long offset )
+{
+	if( iPos+offset < 0 )
+		offset = -iPos;
+	else if( iPos+offset > iSize )
+		offset = iSize-iPos;
+	rNext.seek( offset );
+	iPos += offset;
+}
+
+void Bu::SubStream::setPos( long pos )
+{
+	if( pos < 0 )
+		pos = 0;
+	else if( pos > iSize )
+		pos = iSize;
+	iPos = pos;
+	pos += iStart;
+	rNext.setPos( pos );
+}
+
+void Bu::SubStream::setPosEnd( long pos )
+{
+	if( iSize-pos < 0 )
+		pos = 0;
+	else if( iSize-pos > iSize )
+		pos = iSize;
+	else
+		pos = iSize-pos;
+	iPos = pos;
+	rNext.setPos( iStart+pos );
+}
+
+bool Bu::SubStream::isEos()
+{
+	return rNext.isEos() || iPos == iSize;
+}
+
+bool Bu::SubStream::canRead()
+{
+	return rNext.canRead() && (iPos < iSize);
+}
+
+bool Bu::SubStream::canWrite()
+{
+	return rNext.canWrite() && (iPos < iSize);
+}
+
diff --git a/src/substream.h b/src/substream.h
new file mode 100644
index 0000000..5218493
--- /dev/null
+++ b/src/substream.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2007-2008 Xagasoft, All rights reserved.
+ *
+ * This file is part of the libbu++ library and is released under the
+ * terms of the license contained in the file LICENSE.
+ */
+
+#ifndef BU_SUB_STREAM_H
+#define BU_SUB_STREAM_H
+
+#include "bu/filter.h"
+
+namespace Bu
+{
+	/**
+	 * Creates a sub-stream of a given stream.  This allows you to read and
+	 * write safely to a section of another stream, keeping all data within
+	 * the given bounds.  The substream acts exactly like a top level stream
+	 * when you reach the bounds of either the containing stream or the
+	 * artificial bounds of the substream, except that unlike many stream types,
+	 * when writing you cannot move beyond the bounds of the substream.  Reads,
+	 * on the other hand, work exactly the same way, returning less data than
+	 * requested when the end of the stream is reached.
+	 *
+	 * The substream always begins at the current position in the base stream,
+	 * if you would like to skip some data first, simply seek.
+	 *
+	 * The substream class is safe to use with all blocking and non-blocking
+	 * base streams, including sockets, however it can have unpredictable
+	 * results when used on a buffering stream that may read more data than
+	 * requested in order to complete a request such as the buffer or bzip2
+	 * filters.
+	 */
+	class SubStream : public Bu::Filter
+	{
+	public:
+		SubStream( Bu::Stream &rNext, long iSize );
+		virtual ~SubStream();
+
+		virtual size_t read( void *pBuf, size_t nBytes );
+		virtual size_t write( const void *pBuf, size_t nBytes );
+		using Bu::Stream::write;
+
+		virtual void start();
+		virtual size_t stop();
+		virtual void close();
+		virtual long tell();
+		virtual void seek( long offset );
+		virtual void setPos( long pos );
+		virtual void setPosEnd( long pos );
+		virtual bool isEos();
+
+		virtual bool canRead();
+		virtual bool canWrite();
+
+	protected:
+		long iStart;
+		long iPos;
+		long iSize;
+	};
+};
+
+#endif
diff --git a/src/tafgroup.cpp b/src/tafgroup.cpp
index 1837bd8..9440912 100644
--- a/src/tafgroup.cpp
+++ b/src/tafgroup.cpp
@@ -162,3 +162,40 @@ const Bu::FString &Bu::TafGroup::getProperty( const Bu::FString &sName,
 	}
 }
 
+const Bu::TafGroup *Bu::TafGroup::getChildByPath(
+		const Bu::FString &sPath ) const
+{
+	return getChildByPath( sPath.split('/') );
+}
+
+const Bu::TafGroup *Bu::TafGroup::getChildByPath( Bu::StrList lPath ) const
+{
+	const Bu::TafGroup *cur = this;
+
+	for( Bu::StrList::const_iterator i = lPath.begin(); i; i++ )
+	{
+		cur = cur->getChild( *i );
+	}
+
+	return cur;
+}
+
+const Bu::FString &Bu::TafGroup::getByPath( const Bu::FString &sPath ) const
+{
+	return getByPath( sPath.split('/') );
+}
+
+const Bu::FString &Bu::TafGroup::getByPath( Bu::StrList lPath ) const
+{
+	const Bu::TafGroup *cur = this;
+
+	for( Bu::StrList::const_iterator i = lPath.begin(); i; i++ )
+	{
+		if( !(i+1) )
+			break;
+		cur = cur->getChild( *i );
+	}
+
+	return cur->getProperty( lPath.last() );
+}
+
diff --git a/src/tafgroup.h b/src/tafgroup.h
index 6a50d11..f2df669 100644
--- a/src/tafgroup.h
+++ b/src/tafgroup.h
@@ -16,6 +16,7 @@
 
 namespace Bu
 {
+	typedef Bu::List<Bu::FString> StrList;
 	class TafProperty;
 	class TafComment;
 	/**
@@ -53,6 +54,10 @@ namespace Bu
 		TafProperty *addProperty(
 			const Bu::FString &sName, const Bu::FString &sValue );
 		const NodeList &getChildren() const;
+		const TafGroup *getChildByPath( const Bu::FString &sPath ) const;
+		const TafGroup *getChildByPath( StrList lPath ) const;
+		const Bu::FString &getByPath( const Bu::FString &sPath ) const;
+		const Bu::FString &getByPath( StrList lPath ) const;
 
 	private:
 		Bu::FString sName;
diff --git a/src/unit/substream.unit b/src/unit/substream.unit
new file mode 100644
index 0000000..ef6c70b
--- /dev/null
+++ b/src/unit/substream.unit
@@ -0,0 +1,52 @@
+// vim: syntax=cpp
+/*
+ * Copyright (C) 2007-2008 Xagasoft, All rights reserved.
+ *
+ * This file is part of the libbu++ library and is released under the
+ * terms of the license contained in the file LICENSE.
+ */
+
+#include "bu/membuf.h"
+#include "bu/substream.h"
+
+{=Init}
+
+{%testRead01}
+{
+	Bu::MemBuf mb("abcdefghijklmnopqrstuvwxyz");
+	mb.seek( 4 );
+	Bu::SubStream ss( mb, 10 );
+	unitTest( ss.readLine() == "efghijklmn" );
+}
+
+{%testRead02}
+{
+	Bu::MemBuf mb("abcdefghijklmnopqrstuvwxyz");
+	mb.seek( 4 );
+	Bu::SubStream ss( mb, 10 );
+	char buf[8];
+	size_t iRead = ss.read( buf, 8 );
+	unitTest( iRead == 8 );
+	unitTest( strncmp( buf, "efghijkl", 8 ) == 0 );
+	unitTest( !ss.isEos() );
+	iRead = ss.read( buf, 8 );
+	unitTest( iRead == 2 );
+	unitTest( strncmp( buf, "mn", 2 ) == 0 );
+	unitTest( ss.isEos() );
+}
+
+{%testRead03}
+{
+	Bu::MemBuf mb("abcdefghijklmnopqrstuvwxyz");
+	mb.seek( 20 );
+	Bu::SubStream ss( mb, 10 );
+	char buf[8];
+	size_t iRead = ss.read( buf, 8 );
+	unitTest( iRead == 6 );
+	unitTest( strncmp( buf, "uvwxyz", 6 ) == 0 );
+	unitTest( ss.isEos() );
+	iRead = ss.read( buf, 8 );
+	unitTest( iRead == 0 );
+	unitTest( ss.isEos() );
+}
+
diff --git a/src/unit/taf.unit b/src/unit/taf.unit
index eeddd53..a9329fe 100644
--- a/src/unit/taf.unit
+++ b/src/unit/taf.unit
@@ -109,3 +109,13 @@
 		// Woot
 	}
 }
+
+{%bypath1}
+{
+	Bu::MemBuf mb("{outer: \"Hello=\" {inner: {final: test=hi} } }");
+	Bu::TafReader tr( mb );
+	const Bu::TafGroup *g = tr.readGroup();
+	unitTest( g->getChildByPath("inner/final")->getProperty("test") == "hi" );
+	unitTest( g->getByPath("inner/final/test") == "hi" );
+}
+
-- 
cgit v1.2.3