/**********************************************************************************
* $URL: https://source.sakaiproject.org/svn/kernel/trunk/kernel-util/src/main/java/org/sakaiproject/util/Blob.java $
* $Id: Blob.java 63297 2009-06-04 12:44:44Z david.horwitz@uct.ac.za $
***********************************************************************************
*
* Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008 Sakai Foundation
*
* Licensed under the Educational Community License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.opensource.org/licenses/ECL-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
**********************************************************************************/
package org.sakaiproject.util;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.Enumeration;
import java.util.NoSuchElementException;
/**
* A blob is a Binary Large OBject.
* The Blob uses a linked list structure internally so it is
* not susceptible to running out of memory when growing. This can
* be a problem with very large Vectors -- when they grow, a new, larger
* internal array is created and elements copied over. Thus, for that
* short amount of time, there are two copies of the array. That, plus
* the added overhead of storing bytes in object form, make the Blob the
* preferrable method for storing raw data.
* <P>Since it uses ints as indicies, a single Blob
* can hold up to Integer.MAX_VALUE (2,147,483,647) bytes.
* Could be modified to use longs and then could hold up to
* Long.MAX_VALUE (9.2233e18) bytes if your computer had that much storage. :^)
* <P>This is my 'pet' class.
* @author T. Gee
*
*/
public class Blob implements Cloneable, Serializable {
/**
*
*/
private static final long serialVersionUID = 3832623997476484914L;
/**
* The default internal data storage node size.
*/
public static final int NODE_SIZE = 512;
// THE FOLLOWING THREE FIELDS ARE TRANSIENT BECAUSE WE DO THE
// SERIALIZATION OURSELVES. WE DO THIS BECAUSE OF JAVA'S
// LIMITED STACK SIZE FOR SERIALIZATION.
// The head of the linked list of data nodes.
protected transient BlobNode head;
// The current tail of the linked list of data nodes.
protected transient BlobNode tail;
// The current size of the Blob.
// It is possible to compute this, but it is stored
// for convience.
protected transient int size;
// The actual internal data storage node size.
protected int nodeSize;
// Current Node -- set when seek() is called
protected transient BlobNode curr;
// For the internal enumeration methods
protected transient BlobNode enumerationNode = null;
protected transient int enumerationPos = 0;
/**
* An inclusive between function (for chars).
* @param test The char to test.
* @param low The lower bound.
* @param high The upper bound.
* @return true if test is between low and high (inclusive),
* false otherwise.
*
*/
protected static final boolean between(char test, char low, char high) {
return ((test >= low) && (test <= high));
}
/**
* An inclusive between function (for ints).
* @param test The number to test.
* @param low The lower bound.
* @param high The upper bound.
* @return true if test is between low and high (inclusive),
* false otherwise.
*
*/
public static final boolean between(int test, int low, int high) {
return ((test >= low) && (test <= high));
}
/**
* Returns a hex representation of a byte.
* @param b The byte to convert to hex.
* @return The 2-digit hex value of the supplied byte.
*
*/
public static final String toHex(byte b) {
char ret[] = new char[2];
ret[0] = hexDigit((b >>> 4) & (byte)0x0F);
ret[1] = hexDigit((b >>> 0) & (byte)0x0F);
return new String(ret);
}
/**
* Returns the hex representation of a short.
* @param s The short to convert to hex.
* @return The 4-digit hex value of the supplied short.
*
*/
public static final String toHex(short s) {
StringBuilder sb = new StringBuilder(5);
sb.append(toHex((byte)(s >>> 8)));
sb.append(' ');
sb.append(toHex((byte)(s >>> 0)));
return sb.toString();
}
/**
* Returns the hex representation of an int.
* @param i The int to convert to hex.
* @return The 8-digit hex value of the supplied int.
*
*/
public static final String toHex(int i) {
StringBuilder sb = new StringBuilder(11);
sb.append(toHex((byte)(i >>> 24)));
sb.append(' ');
sb.append(toHex((byte)(i >>> 16)));
sb.append(' ');
sb.append(toHex((byte)(i >>> 8)));
sb.append(' ');
sb.append(toHex((byte)(i >>> 0)));
return sb.toString();
}
/**
* Returns the hex representation of an long.
* @param l The long to convert to hex.
* @return The 16-digit hex value of the supplied long.
*
*/
public static final String toHex(long l) {
StringBuilder sb = new StringBuilder(11);
sb.append(toHex((byte)(l >>> 56)));
sb.append(' ');
sb.append(toHex((byte)(l >>> 48)));
sb.append(' ');
sb.append(toHex((byte)(l >>> 40)));
sb.append(' ');
sb.append(toHex((byte)(l >>> 32)));
sb.append(' ');
sb.append(toHex((byte)(l >>> 24)));
sb.append(' ');
sb.append(toHex((byte)(l >>> 16)));
sb.append(' ');
sb.append(toHex((byte)(l >>> 8)));
sb.append(' ');
sb.append(toHex((byte)(l >>> 0)));
return sb.toString();
}
/**
* Returns the hex representation of the characters of a String.
* @param s The String to convert to hex.
* @return A String where each character of the original string
* is given as a space seperated sequence of hex values.
*
*/
public static final String toHex(String s) {
StringBuilder sb = new StringBuilder();
char chars[] = s.toCharArray();
for (int x = 0 ; x < chars.length ; x++) {
sb.append(toHex((byte)chars[x]));
if (x != (chars.length - 1)) {
sb.append(' ');
}
}
return sb.toString();
}
/**
* Returns the hex digit cooresponding to a number between 0 and 15.
* @param i The number to get the hex digit for.
* @return The hex digit cooresponding to that number.
* @exception java.lang.IllegalArgumentException If supplied digit
* is not between 0 and 15 inclusive.
*
*/
public static final char hexDigit(int i) {
switch (i) {
case 0:
return '0';
case 1:
return '1';
case 2:
return '2';
case 3:
return '3';
case 4:
return '4';
case 5:
return '5';
case 6:
return '6';
case 7:
return '7';
case 8:
return '8';
case 9:
return '9';
case 10:
return 'A';
case 11:
return 'B';
case 12:
return 'C';
case 13:
return 'D';
case 14:
return 'E';
case 15:
return 'F';
}
throw new IllegalArgumentException("Invalid digit:" + i);
}
/**
* Returns a string of a specified number of a specified character.
* @param n The number of characters to create in the return String.
* @param c The character to create.
* @return A String of the requested number of the requested character.
*
*/
public static final String strstr(int n, char c) {
StringBuilder ret = new StringBuilder(n);
for (int x = 0 ; x < n ; x++) {
ret.append(c);
}
return ret.toString();
}
/**
* Returns a string of a specified number of spaces.
* @param n The number of spaces to create.
* @return A String of the requested number of spaces.
* @see #strstr
*
*/
public static final String spaces(int n) {
return strstr(n, ' ');
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//
// CONSTRUCTORS
//
/**
* Creates a new, empty Blob.
* Uses default internal node size.
*
*/
public Blob() {
this(NODE_SIZE);
}
/**
* Creates a new, empty Blob and specifies the default internal
* node size.
* @param growSize The number of bytes to allocate for a new node
* in the internal data storage structure.
*
*/
public Blob(int nodeSize) {
this.nodeSize = nodeSize;
// Create a new, empty head node
head = new BlobNode(nodeSize);
tail = head;
size = 0;
}
/**
* Creates a new blob that is a copy of an existing blob.
* @param b The Blob to copy.
*
*/
public Blob(Blob b) {
this();
append(b);
}
/**
* Creates a new blob and initialized it with a byte array.
* @param arr A byte array to copy entirely into the new Blob.
*
*/
public Blob(byte arr[]) {
this(arr, 0, arr.length);
}
/**
* Creates a new blob and initialized it with a byte array.
* @param arr A byte array to copy into the new Blob.
* @param startPos The location to start extracting bytes from.
* @param len The length of the array to copy in.
*
*/
public Blob(byte arr[], int startPos, int len) {
this();
append(arr, startPos, len);
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//
// EXTERNAL METHODS
//
//
// Append Methods
//
/**
* Appends a byte to the end of the Blob.
* @param b The byte to add.
*
*/
public synchronized void append(byte b) {
// Ensure the capacity of the current tail node
if (tail.freespace() == 0) {
appendNode(nodeSize);
}
// Add the byte to the end node
tail.data[tail.size] = b;
tail.size++;
// incriment our total.
size++;
}
/**
* Appends one byte (in the form of a char) to the end of the Blob.
* Strips the high byte off.
* @param c The character to add.
*
*/
public synchronized void append(char c) {
append(false, c);
}
/**
* Appends one or two bytes (in the form of a char) to the end
* of the Blob.
* Can optionally add the high byte or not.
* @param addHighByte 'true' to add the character's high byte to the
* Blob; 'false' to strip it.
* @param c The character to add.
*
*/
public synchronized void append(boolean addHighByte, char c) {
if (addHighByte) {
// Byte 1
append((byte)(c >> 8));
}
// Byte 2
append((byte)(c >> 0));
}
/**
* Appends two bytes (in the form of a short) to the end of the Blob.
* @param s The short integer to add.
*
*/
public synchronized void append(short s) {
// Byte 1
append((byte)(s >> 8));
// Byte 2
append((byte)(s >> 0));
}
/**
* Appends four bytes (in the form of an int) to the end of the Blob.
* @param i The integer to add.
*
*/
public synchronized void append(int i) {
// Byte 1
append((byte)(i >> 24));
// Byte 2
append((byte)(i >> 16));
// Byte 3
append((byte)(i >> 8));
// Byte 4
append((byte)(i >> 0));
}
/**
* Appends eight bytes (in the form of a long) to the end of the Blob.
* @param l The long integer to add.
*
*/
public synchronized void append(long l) {
// Byte 1
append((byte)(l >> 56));
// Byte 2
append((byte)(l >> 48));
// Byte 3
append((byte)(l >> 40));
// Byte 4
append((byte)(l >> 32));
// Byte 5
append((byte)(l >> 24));
// Byte 6
append((byte)(l >> 16));
// Byte 7
append((byte)(l >> 8));
// Byte 8
append((byte)(l >> 0));
}
/**
* Appends four bytes (in the form of a float) to the end of the Blob.
* @param f The float to add.
*
*/
public synchronized void append(float f) {
// Append the integer created from the bytes of this float
append(Float.floatToIntBits(f));
}
/**
* Appends eight bytes (in the form of a double) to the end of the Blob.
* @param d The double to add.
*
*/
public synchronized void append(double d) {
// Append the long created from the bytes of this double
append(Double.doubleToLongBits(d));
}
/**
* Appends bytes from a String to the end of the Blob.
* Strips the high bytes from the characters.
* @param s The String to add.
*
*/
public synchronized void append(String s) {
append(false, s);
}
/**
* Appends bytes from a String to the end of the Blob.
* Can optionally add the high bytes from the characters or not.
* @param addHighByte 'true' to add the characters' high byte to the
* Blob; 'false' to strip it.
* @param s The String to add.
*
*/
public synchronized void append(boolean addHighByte, String s) {
for (int x = 0 ; x < s.length() ; x++) {
append(addHighByte, s.charAt(x));
}
}
/**
* Appends an entire byte array to the end of the Blob.
* @param arr The array to add bytes from.
*
*/
public synchronized void append(byte arr[]) {
append(arr, 0, arr.length);
}
/**
* Appends a byte array to the end of the Blob.
* @param arr The array to add bytes from.
* @param startPos The position to start the byte extraction
* from the array at.
* @param len The number of bytes to read from the array.
*
*/
public synchronized void append(byte arr[], int startPos, int len) {
// If the current tail node has enuff storage for this
// new addition, use it, otherwise, append a new node.
if (tail.freespace() < len) {
BlobNode oldTail = tail;
appendNode(Math.max(len, nodeSize));
// If old tail node was empty, we'll eliminate it
if (oldTail.size == 0) {
BlobNode bn = findBefore(oldTail);
if (bn == null) {
// oldTail == head
head = tail;
} else {
bn.next = tail;
}
} // endif
} // endif
System.arraycopy(arr, startPos, tail.data, tail.size, len);
tail.size += len;
size += len;
} // end append()
/**
* Appends the bytes from a Blob to the end of the Blob.
* @param b The Blob to draw bytes from.
*
*/
public synchronized void append(Blob b) {
BlobNode bn;
boolean setHead = false;
// We're going to clone the target Blob's nodes and
// tack them on the end.
// if our current node is empty, we'll back up one before appending
if (tail.size == 0) {
bn = findBefore(tail);
if (bn == null) {
// tail == head
setHead = true;
} else {
tail = bn;
tail.next = null;
}
}
// Now append all of the target's nodes
bn = b.head;
if (setHead) {
// set the head node
// (the head node now is empty)
head = (BlobNode)bn.clone();
tail = head;
size = head.size;
// we'll start with the next node down the line
// (since we just used the first one)
bn = bn.next;
}
while (bn != null) {
tail.next = (BlobNode)bn.clone();
tail = tail.next;
size += tail.size;
bn = bn.next;
}
} // end append()
//
// Data insert Methods
//
/**
* Inserts a byte into the blob at the position pos.
* Everything else is moved back.
* @param pos The postition to add the byte at (0 -> beginning).
* @param b The byte to add.
* @see Blob#insertBytes
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
public synchronized void insertByte(int pos, byte b) {
byte arr[] = new byte[1];
arr[0] = b;
insertBytes(pos, arr, 0, 1);
} // end insertByte()
/**
* Inserts a byte into the blob at the position pos.
* Everything else is moved back.
* @param pos The postition to begin adding the bytes at (0 -> beginning).
* @param arr The array of bytes to add from.
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
public synchronized void insertBytes(int pos, byte arr[]) {
this.insertBytes(pos, arr, 0, arr.length);
}
/**
* Inserts a byte into the blob at the position pos.
* Everything else is moved back.
* @param pos The postition to begin adding the bytes at (0 -> beginning).
* @param arr The array of bytes to add from.
* @param startPos The position to begin byte copy from.
* @param len The number of bytes to add from the array.
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
public synchronized void insertBytes(int pos, byte arr[],
int startPos, int len) {
// are we just appending?
if (pos == size) {
append(arr, startPos, len);
return;
}
// Is the position valid?
if (!between(pos, 0, (size - 1))) {
throw new IndexOutOfBoundsException();
} // endif
// Find the correct node and index into that node
int currIndex = seek(pos);
// Do we have enough space in the current node for the
// new information
if (curr.freespace() >= len) {
// Insert the info into the current node
// Move the old stuff back
System.arraycopy(curr.data, currIndex,
curr.data, (currIndex + len),
(curr.size - currIndex));
// Copy in the new stuff
System.arraycopy(arr, startPos, curr.data, currIndex, len);
// Set the new size
this.curr.size += len;
} else {
// not enough room in the inn.
// erect a barn, errr, a new node for it.
BlobNode newNode = new BlobNode(nodeSize, arr, startPos, len);
BlobNode before = findBefore(curr);
// Where do we stick it?
if (currIndex == 0) {
// put it before the current node
if (before == null) {
// inserting before head
newNode.next = head;
head = newNode;
} else {
// inserting in middle
newNode.next = before.next;
before.next = newNode;
}
} else {
// We'll have to split the current node
BlobNode a = new BlobNode(Math.max(nodeSize, currIndex));
BlobNode b = new BlobNode(Math.max(nodeSize,
(curr.size - currIndex)));
// Copy the data
a.size = currIndex;
System.arraycopy(curr.data, 0, a.data, 0, a.size);
b.size = curr.size - currIndex;
System.arraycopy(curr.data, currIndex, b.data, 0, b.size);
// Set up the links.
b.next = (before == null) ? head : before.next;
newNode.next = b;
a.next = newNode;
if (before == null) {
head = a;
} else {
before.next = a;
}
} // endif
} // endif
// adjust size
size += len;
} // end insertBytes()
//
// Data remove methods
//
/**
* Truncates the Blob to the specified position.
* The Blob will have the given number of bytes left.
* @param len The size to truncate the Blob to.
* @exception java.lang.IndexOutOfBoundsException If len is
* outside range of Blob.
*
*/
public synchronized void truncate(int len) {
// Anything to do?
if (len == size) {
return;
}
// Is the data within bounds?
if (!between(len, 0, size)) {
throw new IndexOutOfBoundsException();
}
// find the correct node and truncate that node
// to the required number of bytes to make the
// whole thing work.
// We have to do the seek() and truncate() on different
// lines as seek() sets 'curr'..
int currIndex = seek(len);
curr.size = currIndex;
// Set the new size;
size = len;
// set the next pointer of this node to null
curr.next = null;
tail = curr;
} // end truncate()
/**
* Removes a byte from the Blob at the requested position.
* Everything else is moved up.
* @param pos The position to remove a byte from (0 -> beginning).
* @see Blob#removeBytes
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
public synchronized void removeByte(int pos) {
removeBytes(pos, 1);
} // end removeByte()
/**
* Removes a number of bytes from the Blob at the requested position.
* Everything else is moved up.
* @param pos The position to remove the bytes from (0 -> beginning).
* @param len The number of bytes to remove.
* @exception java.lang.IndexOutOfBoundsException If pos and len are
* outside range of Blob.
*
*/
public synchronized void removeBytes(int pos, int len) {
// Is the data within bounds?
if (!(between(pos, 0, (size - 1)) &&
between((pos + len), 0, size))) {
throw new IndexOutOfBoundsException();
}
BlobNode startNode, endNode;
int startPos, endPos;
// Get the starting and ending locations
startPos = seek(pos);
startNode = curr;
endPos = seek(pos + len);
endNode = curr;
if (startNode == endNode) {
// Just removing stuff from one node
// move the stuff up and adjust length
System.arraycopy(curr.data, endPos,
curr.data, startPos, (curr.size - endPos));
curr.size -= len;
} else {
// Removing stuff across several nodes
// Create a new node to hold the info
BlobNode newNode =
new BlobNode(Math.max(nodeSize,
(startPos + (endNode.size - endPos))));
// Move stuff into this new node
System.arraycopy(startNode.data, 0,
newNode.data, 0, startPos);
System.arraycopy(endNode.data, endPos,
newNode.data, startPos, (endNode.size - endPos));
newNode.size = (startPos + 1) + (endNode.size - endPos);
// update the pointers
newNode.next = endNode.next;
// Find the node PREVIOUS to the startNode
BlobNode before = findBefore(startNode);
if (before == null) {
head = newNode;
} else {
before.next = newNode;
}
} // endif
// Set the new size
size -= len;
} // end removeBytes()
//
// Data Retrieval methods
//
/**
* Gets the bytes of the blob as a byte array.
* @return An array of the bytes that compose the Blob.
*
*/
public byte[] getBytes() {
return getBytes(0, size);
}
/**
* Returns a subset of the bytes of the blob as a byte array.
* @param start start index to begin draw (included in get).
* @param len number of bytes to extract.
* @return An array of the bytes that compose the Blob.
* @exception java.lang.IndexOutOfBoundsException If start and len are
* outside range of Blob.
*
*/
public synchronized byte[] getBytes(int start, int len) {
// Special case
if ((start == 0) && (len == 0)) {
return new byte[0];
}
// Is the data within bounds?
if (!(between(start, 0, (size - 1)) &&
between((start + len), 0, size))) {
throw new IndexOutOfBoundsException();
}
byte ret[] = new byte[len];
int bytesCopied = 0;
int copyFromThis = 0;
int currIndex = seek(start);
while (bytesCopied < len) {
copyFromThis = Math.min((curr.size - currIndex),
(len - bytesCopied));
System.arraycopy(curr.data, currIndex,
ret, bytesCopied, copyFromThis);
bytesCopied += copyFromThis;
curr = curr.next;
currIndex = 0;
} // endwhile
return ret;
} // end getBytes()
/**
* Returns the byte at a specific location.
* @param pos The position to from which to return the byte
* (0 -> beginning).
* @return The byte at requested position.
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
public synchronized byte byteAt(int pos) {
// Is the data within bounds?
if (!between(pos, 0, (size - 1))) {
throw new IndexOutOfBoundsException();
}
// Find the correct node and index into that node
int currIndex = seek(pos);
// Get the byte
return curr.data[currIndex];
}
/**
* Returns a character reconstructed from one byte at a specific location.
* Sets the high byte in the character to 0x0.
* @param pos The position to get the byte to reconstuct the character
* (0 -> beginning).
* @return The reconstructed character.
* @exception java.lang.IndexOutOfBoundsException If the requested
* char is outside the Blob.
*
*/
public synchronized char charAt(int pos) {
return charAt(false, pos);
} // end charAt()
/**
* Returns a character reconstructed from one or two bytes at a
* specific location.
* @param useHighByte 'true' to use two bytes (one for the high byte)
* to reconstruct a character; 'false' to use one byte and set the
* high byte to 0x0.
* @param pos The position to get the byte(s) to reconstuct the character
* (0 -> beginning).
* @return The reconstructed character.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized char charAt(boolean useHighByte, int pos) {
// Is the data within bounds?
if (!between(pos, 0, (size - (useHighByte ? 2 : 1)))) {
throw new IndexOutOfBoundsException();
}
// Return character
char ret;
if (useHighByte) {
// get a short and cast it as a char.
ret = (char)shortAt(pos);
} else {
// get a byte and cast it as a char.
ret = (char)byteAt(pos);
} // endif
return ret;
} // end charAt()
/**
* Returns a short int reconstructed from two bytes at a specific
* location.
* @param pos The position to get the bytes to reconstuct the short
* (0 -> beginning).
* @return The reconstructed short.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized short shortAt(int pos) {
// Is the data within bounds?
if (!between(pos, 0, (size - 2))) {
throw new IndexOutOfBoundsException();
}
// Return value
short ret = 0x0, temp;
// Find proper location
beginEnumeration(pos);
for (int x = 0 ; x < 2 ; x++) {
// get next byte
// We shift the input byte up all the way to the high
// byte and then back down to the req'd position because
// java won't let us cast an a byte as a larger integer
// type without propigating the sign bit thruout the
// higher bytes. This ain't what we need so we jump
// thru some hoops to avoid it.
temp = (short)((nextByte() << (1 * 8)) >>> (1 * 8));
ret |= (temp << (8 * (1 - x)));
} // endfor
return ret;
} // end shortAt()
/**
* Returns an int reconstructed from four bytes at a specific
* location.
* @param pos The position to get the bytes to reconstuct the int
* (0 -> beginning).
* @return The reconstructed int.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized int intAt(int pos) {
// Is the data within bounds?
if (!between(pos, 0, (size - 4))) {
throw new IndexOutOfBoundsException();
}
// Return value
int ret = 0x0, temp;
// Find proper location
beginEnumeration(pos);
for (int x = 0 ; x < 4 ; x++) {
// get next byte
// We shift the input byte up all the way to the high
// byte and then back down to the req'd position because
// java won't let us cast an a byte as a larger integer
// type without propigating the sign bit thruout the
// higher bytes. This ain't what we need so we jump
// thru some hoops to avoid it.
temp = ((int)nextByte() << (3 * 8)) >>> (3 * 8);
ret |= (temp << (8 * (3 - x)));
} // endfor
return ret;
} // end intAt()
/**
* Returns a long int reconstructed from eight bytes at a specific
* location.
* @param pos The position to get the bytes to reconstuct the long
* (0 -> beginning).
* @return The reconstructed long.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized long longAt(int pos) {
// Is the data within bounds?
if (!between(pos, 0, (size - 8))) {
throw new IndexOutOfBoundsException();
}
// Return value
long ret = 0x0, temp;
// Find proper location
beginEnumeration(pos);
for (int x = 0 ; x < 8 ; x++) {
// get next byte
// We shift the input byte up all the way to the high
// byte and then back down to the req'd position because
// java won't let us cast an a byte as a larger integer
// type without propigating the sign bit thruout the
// higher bytes. This ain't what we need so we jump
// thru some hoops to avoid it.
temp = ((long)nextByte() << (7 * 8)) >>> (7 * 8);
ret |= (temp << (8 * (7 - x)));
} // endfor
return ret;
} // end longAt()
/**
* Returns a floating point number reconstructed from four bytes
* at a specific
* location.
* @param pos The position to get the bytes to reconstuct the float
* (0 -> beginning).
* @return The reconstructed float.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
protected synchronized float floatAt(int pos) {
return Float.intBitsToFloat(intAt(pos));
} // end floatAt()
/**
* Returns a double precision floating point number reconstructed
* from eight bytes at a specific
* @param pos The position to get the bytes to reconstuct the double
* (0 -> beginning).
* @return The reconstructed double.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized double doubleAt(int pos) {
return Double.longBitsToDouble(longAt(pos));
} // end floatAt()
/**
* Returns a String reconstructed from the bytes of the Blob.
* Sets the high byte in each character to 0x0.
* @return The reconstructed String.
*
*/
public synchronized String getString() {
return getString(false, 0, size);
} // end getString()
/**
* Returns a String reconstructed from bytes at a specific location.
* Sets the high byte in each character to 0x0.
* @param pos The position to start retrieving the bytes to reconstuct
* the String (0 -> beginning).
* @param len The length of the to-be-returned String.
* @return The reconstructed String.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized String getString(int pos, int len) {
return getString(false, pos, len);
} // end getString()
/**
* Returns a String reconstructed from bytes at a specific location.
* @param useHighByte 'true' to use two bytes (one for the high byte)
* to reconstruct each character; 'false' to use one byte and set the
* high byte to 0x0.
* @param pos The position to start retrieving the bytes to reconstuct
* the String (0 -> beginning).
* @param len The length of the to-be-returned String.
* @return The reconstructed String.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized String getString(boolean useHighByte,
int pos, int len) {
// Special case
if ((pos == 0) && (len == 0)) {
return "";
}
// Is the data within bounds?
if (!(between(pos, 0, (size - 1)) &&
between((pos + (len * (useHighByte ? 2 : 1))),
0, size))) {
throw new IndexOutOfBoundsException();
}
// Return String
StringBuilder ret = new StringBuilder(len);
// hold character
char c;
// Find proper location
beginEnumeration(pos);
for (int x = 0 ; x < len ; x++) {
if (useHighByte) {
// Use both bytes
// high byte
c = (char)(nextByte() << 8);
// low byte
c |= nextByte();
} else {
// just get one (low) byte
c = (char)nextByte();
} // endif
// Add the new character
ret.append(c);
} // endfor
return ret.toString();
} // end getString()
/**
* Clones this Blob.
* The resulting Blob will have the same data as the first.
* (altho possibly not the same internal configuration).
*
*/
public synchronized Object clone() {
// We can just call our getBlob method to do this.
return getBlob(0, size);
}
/**
* Returns a new Blob drawn from bytes at a specific location.
* @param pos The position to start retrieving the bytes to build
* the new Blob (0 -> beginning).
* @param len The number of bytes to put into the new Blob.
* @return The newly constructed Blob.
* @exception java.lang.IndexOutOfBoundsException If the requested
* bytes are outside the Blob.
*
*/
public synchronized Blob getBlob(int pos, int len) {
// cop out and use our other methods. Speed be damned -- this
// isn't that important anyway.
// We *should* just go thru and clone the appropiate
// BlobNodes and just modifiy the ones on the end....
return new Blob(getBytes(pos, len));
} // end getBlob()
//
// Misc Methods
//
/**
* Returns an enumeration of the bytes in this Blob.
* The objects returned from the calls to nextElement are
* of type Byte.
* @param pos The location from which to begin enumerating bytes.
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
@SuppressWarnings("rawtypes")
public synchronized Enumeration enumerateBytes(int pos) {
// Is the data within bounds?
if (!between(pos, 0, (size - 1))) {
throw new IndexOutOfBoundsException();
}
final int cep = seek(pos);
final BlobNode cen = curr;
return new Enumeration() {
int currEnumerationPos = cep;
BlobNode currEnumerationNode = cen;
public synchronized boolean hasMoreElements() {
return (currEnumerationNode != null);
}
public synchronized Object nextElement() {
if (currEnumerationNode == null) {
throw new NoSuchElementException("Past end of current Enumeration");
}
byte ret = currEnumerationNode.data[currEnumerationPos++];
// check to see if we're at the end of the current node
if (currEnumerationPos == currEnumerationNode.size) {
// At end, go to next node
currEnumerationNode = currEnumerationNode.next;
currEnumerationPos = 0;
}
// All done
return Byte.valueOf(ret);
} // end nextElement()
}; // end Enumeration
} // end enumerateBytes
/**
* Sets a byte at a particular position.
* @param pos The position to set the byte at.
* @param b The value to set that postion to.
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
public synchronized void setByteAt(int pos, byte b) {
// Is the data within bounds?
if (!between(pos, 0, (size - 1))) {
throw new IndexOutOfBoundsException();
}
// Find the correct node and index into that node
int currIndex = seek(pos);
// Set the byte
curr.data[currIndex] = b;
}
/**
* Gets the number of bytes in the Blob.
* @return The number of bytes in the Blob.
*
*/
public synchronized int length() {
return size;
}
/**
* Returns true if the blob is equal to the given object.
* True only if supplied object is a Blob and the two contain
* exactly the same data.
*
*/
public synchronized boolean equals(Object o) {
if ((o == null) || !(o instanceof Blob)) {
return false;
}
Blob b = (Blob)o;
// Are they the same length?
if (size != b.size) {
return false;
}
// Compare the data
beginEnumeration(0);
b.beginEnumeration(0);
while (hasMoreBytes()) {
if (nextByte() != b.nextByte()) {
return false;
}
}
// If we got here, they must be equal
return true;
} // end equals()
/**
* Objects that are equal must have the same hashcode
*/
public synchronized int hashCode() {
return toString().hashCode();
}
/**
* Searches for a byte and returns an index to the first one found.
* @param b The byte to search for.
* @return The index of the first match or -1 if not found.
*
*/
public synchronized int indexOf(byte b) {
return indexOf(b, 0);
}
/**
* Searches for a byte and returns an index to the first one found.
* Search starts at given index and includes that index.
* @param b The byte to search for.
* @param pos The position to begin searching at (0 -> beginning).
* @return The index of the first match or -1 if not found.
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
public synchronized int indexOf(byte b, int pos) {
// Is the data within bounds?
if (!between(pos, 0, (size - 1))) {
throw new IndexOutOfBoundsException();
}
beginEnumeration(pos);
int currentPos = pos;
while (hasMoreBytes()) {
if (nextByte() == b) {
return currentPos;
}
currentPos++;
} // endwhile
return -1;
}
//
// IO Methods
//
/**
* Reads bytes from an InputStream into the Blob.
* @param len The number of bytes to attempt to read.
* @param in The InputStream to read from.
* @return The number of bytes read and appended to the blob
* or -1 if the end of the stream was reached.
* @exception java.io.IOException If there is a problem reading.
*
*/
public synchronized int read(int len, InputStream in)
throws IOException {
byte b[] = new byte[len];
int bytesRead;
try {
bytesRead = in.read(b);
} catch (EOFException e) {
// We'll just return a -1 just as if
// we had gotten a -1 in response from the read
return -1;
} catch (IOException e) {
// Throw this one to the caller
throw (IOException)e.fillInStackTrace();
}
append(b, 0, bytesRead);
return bytesRead;
} // end read();
/**
* Reads all bytes from an InputStream into the Blob.
* This will read an InputStream byte-by-byte until EOF and
* append the data to the end of the Blob.
* @param in The InputStream to read from.
* @return The number of bytes read and appended to the blob
* or -1 if the end of the stream was reached immediately.
* @exception java.io.IOException If there is a problem reading.
*
*/
public synchronized int read(InputStream in) throws IOException {
byte b[] = new byte[nodeSize];
int bytesRead, totalBytesRead = -1;
while (true) {
try {
bytesRead = in.read(b);
} catch (EOFException e) {
// We'll just break out of the loop just as if
// we had gotten a -1 in response from the read
break;
} catch (IOException e) {
// Throw this one to the caller
throw (IOException)e.fillInStackTrace();
}
// if we're at EOF, get out of the loop.
if (bytesRead == -1) {
break;
}
// We have totalBytesRead set to -1 initially
// so that we'll return -1 if the stream is at EOF
// right off the bat.
// Now, we have to set it to 0 so that our byte sum
// will be correct.
if (totalBytesRead == -1) {
totalBytesRead = 0;
}
append(b, 0, bytesRead);
totalBytesRead += bytesRead;
}
return totalBytesRead;
} // end read();
/**
* Reads most bytes from an InputStream into the Blob -
* not to exceed <max> bytes by much (it may read up to nodeSize more).
* This will read an InputStream byte-by-byte until EOF and
* append the data to the end of the Blob.
* @param in The InputStream to read from.
* @param max the max (sort of) bytes to read
* @return The number of bytes read and appended to the blob
* or -1 if the end of the stream was reached immediately.
* or -2 if we cut off due to max before end
* @exception java.io.IOException If there is a problem reading.
*
*/
public synchronized int readLimiting(InputStream in, long max)
throws IOException
{
byte b[] = new byte[nodeSize];
int bytesRead, totalBytesRead = -1;
while (true) {
try {
bytesRead = in.read(b);
} catch (EOFException e) {
// We'll just break out of the loop just as if
// we had gotten a -1 in response from the read
break;
} catch (IOException e) {
// Throw this one to the caller
throw (IOException)e.fillInStackTrace();
}
// if we're at EOF, get out of the loop.
if (bytesRead == -1) {
break;
}
// We have totalBytesRead set to -1 initially
// so that we'll return -1 if the stream is at EOF
// right off the bat.
// Now, we have to set it to 0 so that our byte sum
// will be correct.
if (totalBytesRead == -1) {
totalBytesRead = 0;
}
// if we're already over the limit, get out!
if ((totalBytesRead > max) && (bytesRead > 0))
{
totalBytesRead = -2;
break;
}
append(b, 0, bytesRead);
totalBytesRead += bytesRead;
}
return totalBytesRead;
} // readLimiting
/**
* provide an output stream that writes to the END of the blob (appends)
*/
public OutputStream outputStream()
{
return new OutputStream ()
{
public void write(int b)
{
append((byte)b);
}
public void write(byte[] b)
{
append(b);
}
public void write(byte[] b, int off, int len)
{
append(b, off, len);
}
};
} // outputStream
/**
* provide an input stream that reads the blob contents
*/
public InputStream inputStream()
{
return new InputStream ()
{
/** next byte to return */
private int m_pos = 0;
/*public int read(byte b[], int off, int len)
throws IOException
{
return super.read(b, off, len);
}*/
public int read()
throws IOException
{
int rv = -1;
try
{
rv = byteAt(m_pos);
// input streams must return values 0..255, but our bytes are signed
if (rv < 0) rv = 256 + rv;
m_pos++;
}
catch (IndexOutOfBoundsException e) {}
return rv;
}
public int available()
{
return size-m_pos;
}
};
} // inputStream
/**
* Writes the entire contents of the Blob to an OutputStream.
* @param out The OutputStream to write to.
* @exception java.io.IOException If there is a problem writing.
*
*/
public synchronized void write(OutputStream out) throws IOException {
write(0, size, out);
}
/**
* Writes the contents of a part of the Blob to an OutputStream.
* @param pos The position to begin writing from.
* @param len The number of bytes to write.
* @param out The OutputStream to write to.
* @exception java.lang.IndexOutOfBoundsException If pos and len are
* outside range of Blob.
* @exception java.io.IOException If there is a problem writing.
*
*/
public synchronized void write(int pos, int len,
OutputStream out) throws IOException {
// Is the data within bounds?
if (!(between(pos, 0, (size - 1)) &&
between((pos + len), 0, size))) {
throw new IndexOutOfBoundsException();
}
// Go to the beginning of the data
int bytesWritten = 0;
int writeFromThis = 0;
int currIndex = seek(pos);
while (bytesWritten < len) {
writeFromThis = Math.min((curr.size - currIndex),
(len - bytesWritten));
// Write out the stuff,
out.write(curr.data, currIndex, writeFromThis);
bytesWritten += writeFromThis;
curr = curr.next;
currIndex = 0;
} // endwhile
} // end write();
/**
* Generates and returns an 8-byte checksum.
*
*/
public synchronized long checksum() {
long ret = 0, temp = 0, hold = 0;
int index = 0;
if (size == 0) {
return ret;
}
beginEnumeration(0);
while (hasMoreBytes()) {
// put the byte into the low end byte of a long
temp = ((long)nextByte() << (7 * 8)) >>> (7 * 8);
// put the byte into a holder long
hold |= (temp << (8 * (7 - index)));
if (++index == 8) {
// XOR the last eight bytes with the checksum value
ret ^= hold;
// reset the holder long
hold = 0;
index = 0;
}
} // endwhile
// Get any remaining bytes in hold
if (index != 0) {
ret ^=hold;
}
return ret;
}
/**
* Returns a string representation of the Blob.
* Includes length and checksum.
* @see #printContents
*
*/
public synchronized String toString() {
return "Blob[length=" + size +
";checksum=" + toHex(checksum()) + "]";
}
/**
* Prints the contents of this Blob.
* Formats the data neatly and prints it to System.out.
* @see #toString
*
*/
public synchronized void printContents() {
int x, bytesPast;
String holdStr;
char rep[] = new char[16];
byte b;
long cksum = checksum();
// where to go?
PrintStream o = System.out;
// current output width
int currWidth = 86;
holdStr = ("Blob: length = " + size +
" -- Checksum = " + toHex(cksum) + " ");
o.print(holdStr);
o.println(strstr((currWidth - holdStr.length()), '-'));
// If no data, just print the last line and get outta' here
if (size == 0) {
o.println(strstr(currWidth, '-'));
return;
}
o.print(" 0 | ");
bytesPast = 0;
beginEnumeration(0);
while (hasMoreBytes()) {
// print next byte
b = nextByte();
o.print(toHex(b));
// get the representation
if (between(b, 32, 126)) {
rep[bytesPast % 16] = (char)b;
} else {
rep[bytesPast % 16] = '.';
}
// incriment pointer
bytesPast++;
if ((bytesPast % 16) == 0) {
// print out representation
o.print(" <");
for (x = 0 ; x < 16 ; x++) {
o.print(rep[x]);
}
o.println(">");
// Start of new line
if (hasMoreBytes()) {
holdStr = Integer.toString(bytesPast);
o.print(spaces(6 - holdStr.length()));
o.print(holdStr);
o.print(" | ");
}
} else if ((bytesPast % 4) == 0) {
// end of 4-byte chunk
o.print(" ");
} else {
o.print(' ');
} // endif
} // endwhile
if ((bytesPast % 16) != 0) {
// write out some filler spaces
for (x = (bytesPast % 16) ; x < 16 ; x++) {
o.print(" ");
rep[x] = ' ';
if ((x % 4) == 0) {
// end of (what would have been a) 4-byte chunk
o.print(" ");
} else {
o.print(' ');
} // endif
}
// print out representation
o.print(" <");
for (x = 0 ; x < 16 ; x++) {
o.print(rep[x]);
}
o.println(">");
}
// Write last dashed line
holdStr = ("---------------" +
strstr(String.valueOf(size).length(), '-') +
"--- Checksum = " + toHex(cksum) + " ");
o.print(holdStr);
o.println(strstr((currWidth - holdStr.length()), '-'));
}
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////
//
// INTERNAL METHODS
//
/**
* Appends a new node to the end of the internal list.
* @param newNodeSize Size of the new node to create.
*
*/
protected void appendNode(int newNodeSize) {
// Create a new node -- put it on the end of the list
tail.next = new BlobNode(newNodeSize);
// Set the new tail
tail = tail.next;
}
/**
* Seeks a position within the internal list.
* Takes an offset from the beginning and sets the 'curr' pointer
* to the reqested node and returns the offset within that node
* to the correct position.
* @param pos The position within the Blob to seek.
* @return The offest into the current node where the requested
* byte can be found.
* @exception java.lang.IndexOutOfBoundsException If pos is
* outside range of Blob.
*
*/
protected int seek(int pos) {
if (!between(pos, 0, (size - 1))) {
throw new IndexOutOfBoundsException("Seek past end:" +
"pos=" + pos + " " +
"size=" + size);
}
int bytesPast = 0;
curr = this.head;
while ((curr.size + bytesPast) <= pos) {
// Go to next node...
bytesPast += curr.size;
curr = curr.next;
}
// Return the offset into the current node.
return (pos - bytesPast);
}
/**
* Finds the node before the given node.
* This is the one whose next pointer is the given node.
* @return the node before the supplied node or
* null if the given node is the head node.
* @exception java.util.NoSuchElementException if the node is
* not found in the list.
*
*/
protected BlobNode findBefore(BlobNode target) {
BlobNode bn;
if (target == head) {
return null;
}
bn = head;
while (bn.next != target) {
if (bn.next == null) {
throw new NoSuchElementException("Couldn't find BlobNode");
}
bn = bn.next;
}
return bn;
} // end findBefore()
//
// Internal Enumeration Methods
//
/**
* Sets up an enumeration of the bytes of the Blob
* starting from a particular point.
* @param pos The location from which to begin enumerating bytes.
* @exception java.lang.IndexOutOfBoundsException If pos and len are
* outside range of Blob.
*
*/
protected synchronized void beginEnumeration(int pos) {
// Set the current position null
enumerationNode = null;
// Is the data within bounds?
if (!between(pos, 0, (size - 1))) {
throw new IndexOutOfBoundsException();
}
// Find the position
enumerationPos = seek(pos);
enumerationNode = curr;
// We're ready to begin enumerating the bytes
} // end beginInternalEnumeration()
/**
* Returns 'true' if the Blob has more bytes, 'false' if empty.
*
*/
protected synchronized boolean hasMoreBytes() {
return (enumerationNode != null);
}
/**
* Returns the next byte in the Blob.
* @exception java.util.NoSuchElementException If there are no more bytes
* in the current enumeration
*
*/
protected synchronized byte nextByte() {
if (enumerationNode == null) {
throw new NoSuchElementException("Past end of current Enumeration");
}
byte ret = enumerationNode.data[enumerationPos++];
// check to see if we're at the end of the current node
if (enumerationPos == enumerationNode.size) {
// At end, go to next node
enumerationNode = enumerationNode.next;
enumerationPos = 0;
}
// All done
return ret;
} // end nextByte()
//
// Serialization Methods
//
/**
* A specialized object write routine.
* This is because java gets a stack overflow error when
* trying to write the linked list. Damn!
*
*/
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
byte a[] = getBytes();
out.writeObject(a);
}
/**
* A specialized object read routine.
*
*/
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
in.defaultReadObject();
// Create a new node to contain the one massive chunk of data.
byte a[] = (byte [])in.readObject();
head = new BlobNode(nodeSize, a, 0, a.length);
tail = head;
size = a.length;
}
/*
// to test blob as an input stream ...
public static void main(String args[])
throws Exception
{
Blob b1 = new Blob();
Blob b2 = new Blob();
Blob b3 = new Blob();
int size = 2048;
try
{
size = Integer.parseInt(args[0]);
}
catch (Exception ignore) {}
System.out.println("size: " + size);
// load b1
for (int i = 0; i < size; i++)
{
int c = i;
b1.append((byte)c);
// System.out.println(i + ": appending: " + c + " " + (int)(b1.byteAt(i)));
}
// load b2 from b1 via the input stream
b2.read(b1.inputStream());
// load b3 from b1 (not using stream)
b3.append(b1);
System.out.println("b1 size: " + b1.length());
System.out.println("b2 size: " + b2.length());
System.out.println("b3 size: " + b3.length());
// compare
if (b2.equals(b1))
{
System.out.println(" b2.equals(b1) passed");
}
else
{
System.out.println(" b2.equals(b1) failed");
}
// compare
if (b3.equals(b1))
{
System.out.println(" b3.equals(b1) passed");
}
else
{
System.out.println(" b3.equals(b1) failed");
}
try
{
System.out.println("comparing b1 and b2 byte by byte");
for (int i = 0; i < b1.length(); i++)
{
if (b1.byteAt(i) != b2.byteAt(i))
{
System.out.println("mismatch at: " + i + " : b1 = " + b1.byteAt(i) + " : b2 = " + b2.byteAt(i));
}
}
System.out.println(b1.length() + " bytes compared");
}
catch (Exception e) { System.out.println(e); }
try
{
System.out.println("comparing b1 and b3 byte by byte");
for (int i = 0; i < b1.length(); i++)
{
if (b1.byteAt(i) != b3.byteAt(i))
{
System.out.println("mismatch at: " + i + " : b1 = " + b1.byteAt(i) + " : b3 = " + b3.byteAt(i));
}
}
System.out.println(b1.length() + " bytes compared");
}
catch (Exception e) { System.out.println(e); }
InputStream in = b1.inputStream();
for (int i = 0; i < b1.length(); i++)
{
int available = in.available();
int b = in.read();
System.out.println(i + " : avail= " + available + " : read = " + b);
}
System.out.println("final avail= " + in.available());
in.close();
} // main
*/
} // end Blob
/**
* A node in the underlying data-storage structure of the Blob class.
* @see Blob
* @author T. Gee
*
*/
class BlobNode implements Cloneable, Serializable {
/**
*
*/
private static final long serialVersionUID = 3833749897282336560L;
/**
* The next BlobNode in the list, null if last node.
*/
BlobNode next = null;
/**
* The number of bytes currently in this node
*/
int size;
/**
* The actual data held by this node
*/
byte data[];
/**
* Constructs a new, empty node with the requested capacity.
* @param capacity The size of this node's internal storage.
*
*/
public BlobNode(int capacity) {
data = new byte[capacity];
size = 0;
}
/**
* Constructs a new node initialized with the supplied data.
* @param capacity The size of the new node -- if smaller than
* 'data', will be increased to accomidate all of data.
* @param data The data to use to initialize this BlobNode.
*
*/
public BlobNode(int capacity, byte arr[], int startPos, int len) {
data = new byte[Math.max(capacity, len)];
System.arraycopy(arr, startPos, data, 0, len);
size = len;
}
/**
* Gets the number of bytes of free storage within this node.
* @return the number of free bytes.
*
*/
public int freespace() {
return data.length - size;
}
/**
* Clones the node.
*
*/
public Object clone() {
BlobNode b = new BlobNode(data.length);
System.arraycopy(this.data, 0, b.data, 0, size);
b.size = this.size;
return b;
}
} // end BlobNode