Source: src/binary.js

"use strict";
/**
 * @fileoverview Binary, ByteArray and ByteString classes as defined in
 * [CommonJS Binary/B](http://wiki.commonjs.org/wiki/Binary/B). This file is included in ink-strings
 * primarily to support the base64 encoding methods in {@link module:ink/strings/base64}. But they are still a valuable
 * set of tools if you need 'em.
 * @copyright Copyright 2012 Hannes Wallnoefer <hannes@helma.at>
 * @author Hannes Wallnoefer <hannes@helma.at>
 * @author Terry Weiss <me@terryweiss.net>
 * @module ink/strings/binary
 */

/**
 * The valid set of encoding supported by this library
 * @dict
 * @property {string} US-ASCII 'ascii'
 * @property {string} UTF-8 'utf8'
 * @property {string} ISO-10646-UCS-2 'ucs2'
 */
var encodings = exports.encodings = {
	'US-ASCII'        : 'ascii',
	'UTF-8'           : 'utf8',
	'ISO-10646-UCS-2' : 'ucs2'
};

/**
 * Abstract base class for {@link ByteArray} and {@link ByteString}
 *
 * @constructor
 * @abstract
 */
var Binary = exports.Binary = function () {};

/**
 * Constructs a writable and growable byte array.
 *
 * If the first argument to this constructor is a number, it specifies the
 * initial length of the ByteArray in bytes.
 *
 * Else, the argument defines the content of the ByteArray. If the argument is a
 * String, the constructor requires a second argument containing the name of the
 * String's encoding. If called without arguments, an empty ByteArray is
 * returned.
 *
 * The constructor also accepts a
 * [Node.js Buffer](http://nodejs.org/api/buffer.html).
 *
 * Also, if you pass in Number as the first argument and "false" as the second,
 * then the newly created array will not be cleared.
 *
 * @param {Binary|Array|String|Number|Buffer} contentOrLength Content or length
 * of the ByteArray.
 * @param {String=} charset The encoding name if the first argument is a
 * String.
 * @constructor
 * @augments module:ink/strings/binary~Binary
 */
var ByteArray = exports.ByteArray = function () {
	if ( !(this instanceof ByteArray) ) {
		if ( arguments.length === 0 ) {return new ByteArray();}
		if ( arguments.length === 1 ) {return new ByteArray( arguments[0] );}
		if ( arguments.length === 2 ) {return new ByteArray( arguments[0], arguments[1] );}
	}

	// ByteArray() - construct an empty byte string
	if ( arguments.length === 0 ) {
		this.buffer = new Buffer( 0 );
	}
	// ByteArray(length) - create ByteArray of specified size
	else if ( arguments.length === 1 && typeof arguments[0] === "number" ) {
		this.buffer = new Buffer( arguments[0] );
		this.buffer.fill( 0 );
	}
	// ByteArray(length) - create ByteArray of specified size, but don't clear
	else if ( arguments.length === 2 && typeof arguments[0] === "number" && typeof arguments[1] === "boolean" ) {
		this.buffer = new Buffer( arguments[0] );
		if ( arguments[1] === true ) {
			this.buffer.fill( 0 );
		}
	}
	// ByteArray(byteString or byteArray) - use the contents of byteString or
	// byteArray
	else if ( arguments.length === 1 && (arguments[0] instanceof ByteString || arguments[0] instanceof ByteArray) ) {
		var source = arguments[0];
		this.buffer = new Buffer( source.length );
		source.buffer.copy( this.buffer );
	}
	// ByteArray(arrayOfNumbers) - use the numbers in arrayOfNumbers as the bytes
	else if ( arguments.length === 1 && Array.isArray( arguments[0] ) ) {
		this.buffer = new Buffer( arguments[0] );
	}
	// ByteArray(buffer) - use contents of buffer
	else if ( arguments.length === 1 && arguments[0] instanceof Buffer ) {
		var buffer = arguments[0];
		this.buffer = new Buffer( buffer.length );
		buffer.copy( this.buffer );
	}
	// ByteArray(string, charset) - convert a string - the ByteArray will contain
	// string encoded with charset
	else if ( (arguments.length === 1 || (arguments.length === 2 && arguments[1] === undefined)) && typeof arguments[0] === "string" ) {
		this.buffer = new Buffer( arguments[0] );
	} else if ( arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "string" ) {
		this.buffer = new Buffer( arguments[0], encodings[arguments[1]] );
	} else {
		throw new Error( "Illegal arguments to ByteArray constructor" );
	}

	return this;
};

ByteArray.prototype = new Binary();


Object.defineProperty( ByteArray.prototype, 'length', {
	get : function () {
		return this.buffer.length;
	},
	set : function ( length ) {
		var buffer = new Buffer( length );
		length = Math.min( this.buffer.length, length );
		this.buffer.copy( buffer, 0, 0, length );
		buffer.fill( 0, length );
		this.buffer = buffer;
	}
} );

/**
 * Constructs an immutable byte string.
 *
 * If the first argument is a String, the constructor requires a second argument
 * containing the name of the String's encoding. If called without arguments, an
 * empty ByteString is returned.
 *
 * The constructor also accepts a
 * [Node.js Buffer](http://nodejs.org/api/buffer.html).
 *
 * @param {Binary|Array|String|Buffer} content The content of the ByteString.
 * @param {String=} charset The encoding name if the first argument is a String.
 * @constructor
 * @augments module:ink/strings/binary~Binary
 */
var ByteString = exports.ByteString = function () {
	if ( !(this instanceof ByteString) ) {
		if ( arguments.length === 0 ) {return new ByteString();}
		if ( arguments.length === 1 ) {return new ByteString( arguments[0] );}
		if ( arguments.length === 2 ) {return new ByteString( arguments[0], arguments[1] );}
	}

	// ByteString() - construct an empty byte string
	if ( arguments.length === 0 ) {
		this.buffer = new Buffer( 0 );
	}
	// ByteString(byteString) - returns byteString, which is immutable
	else if ( arguments.length === 1 && arguments[0] instanceof ByteString ) {
		return arguments[0];
	}
	// ByteString(byteArray) - use the contents of byteArray
	else if ( arguments.length === 1 && arguments[0] instanceof ByteArray ) {
		var source = arguments[0];
		this.buffer = new Buffer( source.length );
		source.buffer.copy( this.buffer );
	}
	// ByteString(buffer) - use contents of buffer
	else if ( arguments.length === 1 && arguments[0] instanceof Buffer ) {
		var buffer = arguments[0];
		this.buffer = new Buffer( buffer.length );
		buffer.copy( this.buffer );
	}
	// ByteString(arrayOfNumbers) - use the numbers in arrayOfNumbers as the bytes
	else if ( arguments.length === 1 && Array.isArray( arguments[0] ) ) {
		this.buffer = new Buffer( arguments[0] );
	}
	// ByteString(string, charset) - convert a string - the ByteString will
	// contain string encoded with charset
	else if ( (arguments.length === 1 || (arguments.length === 2 && arguments[1] === undefined)) && typeof arguments[0] === "string" ) {
		this.buffer = new Buffer( arguments[0] );
	} else if ( arguments.length === 2 && typeof arguments[0] === "string" && typeof arguments[1] === "string" ) {
		this.buffer = new Buffer( arguments[0], encodings[arguments[1]] );
	} else {
		throw new Error( "Illegal arguments to ByteString constructor" + JSON.stringify( arguments ) );
	}

	return this;
};

ByteString.prototype = new Binary();

/**
 * Returns the length of the byte string
 * @type {integer}
 * @memberof module:ink/strings~ByteString
 * @name length
 */
Object.defineProperty( ByteString.prototype, 'length', {
	get : function () {
		return this.buffer.length;
	},
	set : function () {}
} );

/**
 * Converts the String to a mutable ByteArray using the specified encoding.
 * @param {string} str The string to convert
 * @param {String} charset the name of the string encoding. Defaults to 'UTF-8'
 * @returns {ByteArray} a ByteArray representing the string
 */
exports.toByteArray = function ( str, charset ) {
	charset = charset || 'UTF-8';
	return new ByteArray( String( str ), charset );
};

/**
 * Converts the String to an immutable ByteString using the specified encoding.
 * @param {string} str The string to convert
 * @param {String} charset the name of the string encoding. Defaults to 'UTF-8'
 * @returns {ByteString} a ByteString representing the string
 */
exports.toByteString = function ( str, charset ) {
	charset = charset || 'UTF-8';
	return new ByteString( String( str ), charset );
};

/**
 * Reverses the content of the ByteArray in-place
 *
 * @returns {ByteArray} this ByteArray with its elements reversed
 */
ByteArray.prototype.reverse = function () {
	// "limit" is halfway, rounded down. "top" is the last index.
	var limit = Math.floor( this.length / 2 ),
		top = this.length - 1;

	// swap each pair of bytes, up to the halfway point
	for ( var i = 0; i < limit; i++ ) {
		var tmp = this.buffer[i];
		this.buffer[i] = this.buffer[top - i];
		this.buffer[top - i] = tmp;
	}

	return this;
};

/**
 * Sorts the content of the ByteArray in-place.
 * @private
 * @param {Function} comparator the function to compare entries
 * @returns {ByteArray} this ByteArray with its elements sorted
 */
var numericCompareFunction = function ( o1, o2 ) {
	return o1 - o2;
};

/**
 * Sorts a ByteArray.
 * @param {function(byte)=} compareFunction A function to be executed against each element in the ByteArray
 *      If not present, the bytes are compared numerically. Otherwise
 *      it works like a regular JS array sorter
 *
 */
ByteArray.prototype.sort = function ( compareFunction ) {
	// TODO: inefficient, optimize
	var array = this.toArray();

	if ( arguments.length ) {array.sort( compareFunction );}
	else {array.sort( numericCompareFunction );}

	for ( var i = 0; i < array.length; i++ ) {this.set( i, array[i] );}
};

/**
 * Apply a function for each element in the ByteArray.
 *
 * @param {function(byte)} fn the function to call for each element
 * @param {Object=} thisObj optional this-object for callback
 */
ByteArray.prototype.forEach = function ( callback, thisObject ) {
	for ( var i = 0, length = this.length; i < length; i++ ) {callback.apply( thisObject, [this.get( i ), i, this] );}
};

/**
 * Return a ByteArray containing the elements of this ByteArray for which the
 * callback function returns true.
 *
 * @param {function(byte)} callback the filter function
 * @param {Object=} thisObj optional this-object for callback
 * @returns {ByteArray} a new ByteArray
 */
ByteArray.prototype.filter = function ( callback, thisObject ) {
	var result = new ByteArray( this.length );
	for ( var i = 0, length = this.length; i < length; i++ ) {
		var value = this.get( i );
		if ( callback.apply( thisObject, [value, i, this] ) ) {result.push( value );}
	}
	return result;
};

/**
 * Tests whether some element in the array passes the test implemented by the
 * provided function.
 *
 * @param {function(byte)} callback the callback function
 * @param {Object=} thisObj optional this-object for callback
 * @returns {Boolean} true if at least one invocation of callback returns true
 */
ByteArray.prototype.some = function ( callback, thisObject ) {
	for ( var i = 0, length = this.length; i < length; i++ ) {if ( callback.apply( thisObject, [this.get( i ), i, this] ) ) {return true;}}
	return false;
};

/**
 * Tests whether all elements in the array pass the test implemented by the
 * provided function.
 *
 * @param {function(byte)} callback the callback function
 * @param {Object=} thisObj optional this-object for callback
 * @returns {Boolean} true if every invocation of callback returns true
 */
ByteArray.prototype.every = function ( callback, thisObject ) {
	for ( var i = 0, length = this._length; i < length; i++ ) {if ( !callback.apply( thisObject, [this.get( i ), i, this] ) ) { return false;}}
	return true;
};

/**
 * Returns a new ByteArray whose content is the result of calling the provided
 * function with every element of the original ByteArray
 *
 * @param {function(byte)} callback the callback
 * @param {Object=} thisObj optional this-object for callback
 * @returns {ByteArray} a new ByteArray
 */
ByteArray.prototype.map = function ( callback, thisObject ) {
	var result = new ByteArray( this.length );
	for ( var i = 0, length = this.length; i < length; i++ ) {result.set( i, callback.apply( thisObject, [this.get( i ), i, this] ) );}
	return result;
};

/**
 * Apply a function to each element in this ByteArray as to reduce its content
 * to a single value.
 *
 * @param {function(byte)} callback the function to call with each element of the
 * ByteArray
 * @param {*=} initialValue optional argument to be used as the first argument to the
 * first call to the callback
 * @returns {*} the return value of the last callback invocation
 * @see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/reduce
 */
ByteArray.prototype.reduce = function ( callback, initialValue ) {
	var value = initialValue;
	for ( var i = 0, length = this.length; i < length; i++ ) {value = callback( value, this.get( i ), i, this );}
	return value;
};

/**
 * Apply a function to each element in this ByteArray starting at the last
 * element as to reduce its content to a single value.
 *
 * @param {function(byte)} callback the function to call with each element of the
 * ByteArray
 * @param {*=} initialValue optional argument to be used as the first argument to the
 * first call to the callback
 * @returns {*} the return value of the last callback invocation
 * @see ByteArray.prototype.reduce
 * @see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/reduceRight
 */
ByteArray.prototype.reduceRight = function ( callback, initialValue ) {
	var value = initialValue;
	for ( var i = this.length - 1; i > 0; i-- ) {value = callback( value, this.get( i ), i, this );}
	return value;
};

/**
 * Removes the last element from an array and returns that element.
 *
 * @returns {Number}
 */
ByteArray.prototype.pop = function () {
	if ( this.length === 0 ) {return undefined;}

	var last = this.buffer[this.length - 1];
	// TODO sort this out as it's very expensive, maybe somehow use
	// https://github.com/substack/node-buffers? alternatively could just
	// deprecate this
	this.length--;

	return last;
};

/**
 * Appends the given elements and returns the new length of the array.
 *
 * @param {...Number} num... one or more numbers to append
 * @returns {Number} the new length of the ByteArray
 */
ByteArray.prototype.push = function () {
	var length, newLength = this.length += length = arguments.length;
	try {
		for ( var i = 0; i < length; i++ ) {this.set( newLength - length + i, arguments[i] );}
	} catch ( e ) {
		this.length -= length;
		throw e;
	}
	return newLength;
};

/**
 * Removes the first element from the ByteArray and returns that element. This
 * method changes the length of the ByteArray
 *
 * @returns {Number} the removed first element
 */
ByteArray.prototype.shift = function () {
	if ( this.length === 0 ) { return undefined;}
	var first = this.buffer[0];
	var buffer = new Buffer( this.length - 1 );
	this.buffer.copy( buffer, 0, 1 );
	this.buffer = buffer;
	return first;
};

/**
 * Adds one or more elements to the beginning of the ByteArray and returns its
 * new length.
 *
 * @param {...Number} num... one or more numbers to append
 * @returns {Number} the new length of the ByteArray
 */
ByteArray.prototype.unshift = function () {
	var copy = this.slice();
	this.length = 0;
	try {
		this.push.apply( this, arguments );
		this.push.apply( this, copy.toArray() );
		return this.length;
	} catch ( e ) {
		this.length = copy.length;
		copy.buffer.copy( this.buffer );
		throw e;
	}
};

/**
 * Changes the content of the ByteArray, adding new elements while removing old
 * elements.
 *
 * @param {Number} index the index at which to start changing the ByteArray
 * @param {Number} howMany The number of elements to remove at the given
 * position
 * @param {...Number} elements... the new elements to add at the given position
 */
ByteArray.prototype.splice = function ( index, howMany ) {
	if ( index === undefined ) {return;}
	if ( index < 0 ) {index += this.length;}
	if ( howMany === undefined ) {howMany = this.length - index;}
	var end = index + howMany;
	var remove = this.slice( index, end );
	var keep = this.slice( end );
	var inject = Array.prototype.slice.call( arguments, 2 );
	this.length = index;
	this.push.apply( this, inject );
	this.push.apply( this, keep.toArray() );
	return remove;
};

/**
 * Copy a range of bytes between start and stop from this object to another
 * ByteArray at the given target offset.
 *
 * @param {Number} start
 * @param {Number} end
 * @param {ByteArray} target
 * @param {Number} targetOffset
 * @name ByteArray.prototype.copy
 * @function
 */
ByteArray.prototype.copy = function ( start, end, target, targetOffset ) {
	//TODO validate parameters
	target.length = targetOffset + (end - start);
	this.buffer.copy( target.buffer, targetOffset, start, end );
};

/**
 * The length in bytes. This property is writable. Setting it to a value higher
 * than the current value fills the new slots with 0, setting it to a lower
 * value truncates the byte array.
 *
 * @type Number
 * @name ByteArray.prototype.length
 */

/**
 * Returns a new ByteArray containing a portion of this ByteArray.
 *
 * @param {Number} begin Zero-based index at which to begin extraction. As a
 * negative index, begin indicates an offset from the end of the sequence.
 * @param {Number} end Zero-based index at which to end extraction. slice
 * extracts up to but not including end. As a negative index, end indicates an
 * offset from the end of the sequence. If end is omitted, slice extracts to the
 * end of the sequence.
 * @returns {ByteArray} a new ByteArray
 */
ByteArray.prototype.slice = function () {
	return new ByteArray( ByteString.prototype.slice.apply( this, arguments ) );
};

/**
 * Returns a ByteArray composed of itself concatenated with the given
 * ByteString, ByteArray, and Array values.
 *
 * @param {Binary|Array} arg... one or more elements to concatenate
 * @returns {ByteArray} a new ByteArray
 */
ByteArray.prototype.concat = function () {
	var components = [this],
		totalLength = this.length;

	for ( var i = 0; i < arguments.length; i++ ) {
		var component = Array.isArray( arguments[i] ) ? arguments[i] : [arguments[i]];

		for ( var j = 0; j < component.length; j++ ) {
			var subcomponent = component[j];
			if ( !(subcomponent instanceof ByteString) && !(subcomponent instanceof ByteArray) ) {throw "Arguments to ByteArray.concat() must be ByteStrings, ByteArrays, or Arrays of those.";}

			components.push( subcomponent );
			totalLength += subcomponent.length;
		}
	}

	var result = new ByteArray( totalLength ),
		offset = 0;

	components.forEach( function ( component ) {
		component.buffer.copy( result.buffer, offset );
		offset += component.length;
	} );

	return result;
};

/**
 * Returns a ByteArray
 * @return {ByteArray}
 */
ByteArray.prototype.toByteArray = function () {
	return new ByteArray( this.buffer );
};

/**
 *Returns a ByteString
 * @return {ByteString}
 */
ByteArray.prototype.toByteString = function () {
	return new ByteString( this.buffer );
};

/**
 * Returns an array containing the bytes as numbers.
 *
 * @name ByteArray.prototype.toArray
 * @function
 * @returns {array.<byte>}
 */

/**
 * Returns a String representation of the ByteArray.
 *
 * @param {string=} charset The charset to use to create the string
@return {string}
 */
ByteArray.prototype.toString = function ( charset ) {
	if ( charset ) {return this.decodeToString( charset );}

	return "[ByteArray " + this.length + "]";
};

/**
 * Returns the ByteArray decoded to a String using the given encoding
 *
 * @param {String} encoding the name of the encoding to use
 * @name ByteArray.prototype.decodeToString
 * @function
 * @return {String}
 */

/**
 * Returns the index of the first occurrence of sequence (a Number or a
 * ByteString or ByteArray of any length) or -1 if none was found. If start
 * and/or stop are specified, only elements between the indexes start and stop
 * are searched.
 *
 * @param {Number|Binary} sequence the number or binary to look for
 * @param {Number} start optional index position at which to start searching
 * @param {Number} stop optional index position at which to stop searching
 * @returns {Number} the index of the first occurrence of sequence, or -1
 * @name ByteArray.prototype.indexOf
 * @function
 *
 */

/**
 * Returns the index of the last occurrence of sequence (a Number or a
 * ByteString or ByteArray of any length) or -1 if none was found. If start
 * and/or stop are specified, only elements between the indexes start and stop
 * are searched.
 *
 * @param {Number|Binary} sequence the number or binary to look for
 * @param {Number} start optional index position at which to start searching
 * @param {Number} stop optional index position at which to stop searching
 * @returns {Number} the index of the last occurrence of sequence, or -1
 * @name ByteArray.prototype.lastIndexOf
 * @function
 */

/**
 * Split at delimiter, which can by a Number, a ByteString, a ByteArray or an
 * Array of the prior (containing multiple delimiters, i.e., "split at any of
 * these delimiters"). Delimiters can have arbitrary size.
 *
 * @param {Number|Binary} delimiter one or more delimiter items
 * @param {Object} options optional object parameter with the following optional
 * properties:
 * <ul>
 * <li>count - Maximum number of elements (ignoring delimiters) to return. The
 * last returned element may contain delimiters.</li>
 * <li>includeDelimiter - Whether the delimiter should be included in the
 * result.</li>
 * </ul>
@return {array.<ByteArray>}
 */
ByteArray.prototype.split = function () {
	var components = ByteString.prototype.split.apply( this.toByteString(), arguments );

	// convert ByteStrings to ByteArrays
	for ( var i = 0; i < components.length; i++ ) {
		components[i] = new ByteArray( components[i] );
	}

	return components;
};

/**
 * Returns a byte for byte copy of this immutable ByteString as a mutable
 * ByteArray.
 *
@return {ByteArray}
 */
ByteString.prototype.toByteArray = function () {
	return new ByteArray( this );
};

/**
 * Returns this ByteString itself.
 *
 @return {ByteString}
 */
ByteString.prototype.toByteString = function () {
	return this;
};

/**
 * Returns an array containing the bytes as numbers.

 * @param {String} charset optional the name of the string encoding
 * @return {array}
 */
Binary.prototype.toArray = function ( charset ) {
	if ( charset ) {
		return Array.prototype.map.call( this.buffer.toString( encodings[charset] ), function ( x ) {
			return x.charCodeAt( 0 );
		} );
	} else {
		return Array.prototype.slice.call( this.buffer, 0 );
	}
};

/**
 * Returns a debug representation such as `"[ByteString 10]"` where 10 is the
 * length of this ByteString.
 *
 @return {string}
 */
ByteString.prototype.toString = function () {
	return '[ByteString ' + this.buffer.length + ']';
};

/**
 * Returns this ByteString as string, decoded using the given charset.
 *
 * @name ByteString.prototype.decodeToString
 * @param {String=} charset the name of the string encoding
    @return {string}
 */
Binary.prototype.decodeToString = function ( charset ) {
	return this.buffer.toString( encodings[charset] );
};

/**
 * Returns the index of the first occurrence of sequence (a Number or a
 * ByteString or ByteArray of any length), or -1 if none was found. If start
 * and/or stop are specified, only elements between the indexes start and stop
 * are searched.
 *
 * @param {Number|Binary} sequence the number or binary to look for
 * @param {Number} start optional index position at which to start searching
 * @param {Number} stop optional index position at which to stop searching
 * @returns {Number} the index of the first occurrence of sequence, or -1

 */
Binary.prototype.indexOf = function ( byteValue, start, stop ) {
	// HACK: use ByteString's slice since we know we won't be modifying result
	var array = ByteString.prototype.slice.apply( this, [start, stop] ).toArray(),
		result = array.indexOf( byteValue );
	return (result < 0) ? -1 : result + (start || 0);
};

/**
 * Returns the index of the last occurrence of sequence (a Number or a
 * ByteString or ByteArray of any length) or -1 if none was found. If start
 * and/or stop are specified, only elements between the indexes start and stop
 * are searched.
 *
 * @param {Number|Binary} sequence the number or binary to look for
 * @param {Number} start optional index position at which to start searching
 * @param {Number} stop optional index position at which to stop searching
 * @returns {Number} the index of the last occurrence of sequence, or -1

 */
Binary.prototype.lastIndexOf = function ( byteValue, start, stop ) {
	// HACK: use ByteString's slice since we know we won't be modifying result
	var array = ByteString.prototype.slice.apply( this, [start, stop] ).toArray(),
		result = array.lastIndexOf( byteValue );
	return (result < 0) ? -1 : result + (start || 0);
};

/**
 * Returns the byte at the given offset as a ByteString.
 *

 * @param {Number} offset
 * @returns {Number}
   @function
 */
ByteString.prototype.byteAt = ByteString.prototype.charAt = function ( offset ) {
	if ( offset < 0 || offset >= this.buffer.length ) {return new ByteString();}
	return new ByteString( [this.buffer[offset]] );
};

/**
 * Returns the byte at the given offset as a ByteString.
 *

 * @param {Number} offset
 * @returns {Number}
 @method
 */
ByteArray.prototype.byteAt = function ( offset ) {
	if ( offset < 0 || offset >= this.buffer.length ) {return new ByteArray();}
	return new ByteArray( [this.buffer[offset]] );
};

/**
 * Returns the byte at the given offset.
 *

 * @param {Number} offset
 * @returns {ByteString}
    @method
 */
Binary.prototype.get = ByteString.prototype.charCodeAt = function ( offset ) {
	return this.buffer[offset];
};

ByteArray.prototype.set = function ( offset, value ) {
	this.buffer[offset] = value;
};

/**
 * Copy a range of bytes between start and stop from this ByteString to a target
 * ByteArray at the given targetStart offset.
 *
 * @param {Number} start
 * @param {Number} end
 * @param {ByteArray} target
 * @param {Number} targetStart

 */
ByteString.prototype.copy = function ( start, end, target, targetOffset ) {
	//TODO validate parameters
	target.length = targetOffset + (end - start);
	this.buffer.copy( target.buffer, targetOffset, start, end );
};

/**
 * Split at delimiter, which can by a Number, a ByteString, a ByteArray or an
 * Array of the prior (containing multiple delimiters, i.e., "split at any of
 * these delimiters"). Delimiters can have arbitrary size.
 *
 * @param {Number|Binary} delimiter one or more delimiter items
 * @param {Object} options optional object parameter with the following optional
 * properties:
 * <ul>
 * <li>count - Maximum number of elements (ignoring delimiters) to return. The
 * last returned element may contain delimiters.</li>
 * <li>includeDelimiter - Whether the delimiter should be included in the
 * result.</li>
 * </ul>
@return {array}
 */
ByteString.prototype.split = function ( delimiters, options ) {
	// taken from https://github.com/kriskowal/narwhal-lib/
	options = options || {};

	var includeDelimiter = options.includeDelimiter || false;

	// standardize delimiters into an array of ByteStrings:
	if ( !Array.isArray( delimiters ) ) {delimiters = [delimiters];}

	delimiters = delimiters.map( function ( delimiter ) {
		if ( typeof delimiter === "number" ) {
			delimiter = [delimiter];
		}
		return new ByteString( delimiter );
	} );

	var components = [],
		startOffset = 0,
		currentOffset = 0;

	// loop until there's no more bytes to consume
	bytes_loop: while ( currentOffset < this.length ) {

		// try each delimiter until we find a match
		delimiters_loop: for ( var i = 0; i < delimiters.length; i++ ) {
			var d = delimiters[i];
			if ( !d.length ) {
				currentOffset = this.length;
				continue bytes_loop;
			}
			for ( var j = 0; j < d.length; j++ ) {

				// reached the end of the bytes, OR bytes not equal
				if ( currentOffset + j > this.length || this.buffer[currentOffset + j] !== d.buffer[j] ) {

					continue delimiters_loop;
				}
			}

			// push the part before the delimiter
			components.push( this.slice( startOffset, currentOffset ) );

			// optionally push the delimiter
			if ( includeDelimiter ) {components.push( this.slice( currentOffset, currentOffset + d.length ) );}

			// reset the offsets
			startOffset = currentOffset = currentOffset + d.length;

			continue bytes_loop;
		}

		// if there was no match, increment currentOffset to try the next one
		currentOffset++;
	}

	// push the remaining part, if any
	if ( currentOffset > startOffset ) {components.push( this.slice( startOffset, currentOffset ) );}

	return components;
};

/**
 * Returns a new ByteString containing a portion of this ByteString.
 *
 * @param {Number} begin Zero-based index at which to begin extraction. As a
 * negative index, begin indicates an offset from the end of the sequence.
 * @param {Number} end Zero-based index at which to end extraction. slice
 * extracts up to but not including end. As a negative index, end indicates an
 * offset from the end of the sequence. If end is omitted, slice extracts to the
 * end of the sequence.
 * @returns {ByteString} a new ByteString

 */
ByteString.prototype.slice = function ( begin, end ) {
	if ( !begin || typeof begin !== "number" ) {begin = 0;}
	var length = this.buffer.length;
	if ( begin < 0 ) {begin += length;}
	if ( !end ) {end = length;}
	if ( typeof end !== "number" ) {end = begin;}
	if ( end < 0 ) {end += length;}
	if ( begin < 0 ) {begin = 0;}
	if ( end < begin ) {end = begin;}
	if ( begin > length ) {begin = length;}
	if ( end > length ) {end = length;}
	var buffer = new Buffer( end - begin );
	this.buffer.copy( buffer, 0, begin, end );
	return new ByteString( buffer );
};

/**
 * Returns a ByteString composed of itself concatenated with the given
 * ByteString, ByteArray, and Array values.
 *
 * @param {Binary|Array} arg... one or more elements to concatenate
 * @returns {ByteString} a new ByteString

 */
ByteString.prototype.concat = function ( arg ) {
	var components = [this],
		totalLength = this.length;

	for ( var i = 0; i < arguments.length; i++ ) {
		var component = Array.isArray( arguments[i] ) ? arguments[i] : [arguments[i]];

		for ( var j = 0; j < component.length; j++ ) {
			var subcomponent = component[j];
			if ( !(subcomponent instanceof ByteString) && !(subcomponent instanceof ByteArray) ) {throw "Arguments to ByteString.concat() must be ByteStrings, ByteArrays, or Arrays of those.";}

			components.push( subcomponent );
			totalLength += subcomponent.length;
		}
	}

	var result = new Buffer( totalLength ),
		offset = 0;

	components.forEach( function ( component ) {
		component.buffer.copy( result, offset );
		offset += component.length;
	} );

	return new ByteString( result );
};
See NOTICE.md for licenses
ink-strings Copyright contributers to ink-strings, base64, Helma, RingoJS, sprintf projects.
Documentation generated by JSDoc 3.2.0-dev on Sun Jun 09 2013 11:56:07 GMT-0400 (EDT) using the DocStrap template.