0001: /**********************************************************************************
0002: * $URL: https://source.sakaiproject.org/svn/util/tags/sakai_2-4-1/util-util/util/src/java/org/sakaiproject/util/Blob.java $
0003: * $Id: Blob.java 6832 2006-03-21 20:43:34Z ggolden@umich.edu $
0004: ***********************************************************************************
0005: *
0006: * Copyright (c) 2003, 2004, 2005, 2006 The Sakai Foundation.
0007: *
0008: * Licensed under the Educational Community License, Version 1.0 (the "License");
0009: * you may not use this file except in compliance with the License.
0010: * You may obtain a copy of the License at
0011: *
0012: * http://www.opensource.org/licenses/ecl1.php
0013: *
0014: * Unless required by applicable law or agreed to in writing, software
0015: * distributed under the License is distributed on an "AS IS" BASIS,
0016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0017: * See the License for the specific language governing permissions and
0018: * limitations under the License.
0019: *
0020: **********************************************************************************/package org.sakaiproject.util;
0021:
0022: import java.io.EOFException;
0023: import java.io.IOException;
0024: import java.io.InputStream;
0025: import java.io.ObjectInputStream;
0026: import java.io.ObjectOutputStream;
0027: import java.io.OutputStream;
0028: import java.io.PrintStream;
0029: import java.io.Serializable;
0030: import java.util.Enumeration;
0031: import java.util.NoSuchElementException;
0032:
0033: /**
0034: * A blob is a Binary Large OBject.
0035: * The Blob uses a linked list structure internally so it is
0036: * not susceptible to running out of memory when growing. This can
0037: * be a problem with very large Vectors -- when they grow, a new, larger
0038: * internal array is created and elements copied over. Thus, for that
0039: * short amount of time, there are two copies of the array. That, plus
0040: * the added overhead of storing bytes in object form, make the Blob the
0041: * preferrable method for storing raw data.
0042: * <P>Since it uses ints as indicies, a single Blob
0043: * can hold up to Integer.MAX_VALUE (2,147,483,647) bytes.
0044: * Could be modified to use longs and then could hold up to
0045: * Long.MAX_VALUE (9.2233e18) bytes if your computer had that much storage. :^)
0046: * <P>This is my 'pet' class.
0047: * @author T. Gee
0048: *
0049: */
0050: public class Blob implements Cloneable, Serializable {
0051:
0052: /**
0053: *
0054: */
0055: private static final long serialVersionUID = 3832623997476484914L;
0056:
0057: /**
0058: * The default internal data storage node size.
0059: */
0060: public static final int NODE_SIZE = 512;
0061:
0062: // THE FOLLOWING THREE FIELDS ARE TRANSIENT BECAUSE WE DO THE
0063: // SERIALIZATION OURSELVES. WE DO THIS BECAUSE OF JAVA'S
0064: // LIMITED STACK SIZE FOR SERIALIZATION.
0065:
0066: // The head of the linked list of data nodes.
0067: protected transient BlobNode head;
0068:
0069: // The current tail of the linked list of data nodes.
0070: protected transient BlobNode tail;
0071:
0072: // The current size of the Blob.
0073: // It is possible to compute this, but it is stored
0074: // for convience.
0075: protected transient int size;
0076:
0077: // The actual internal data storage node size.
0078: protected int nodeSize;
0079:
0080: // Current Node -- set when seek() is called
0081: protected transient BlobNode curr;
0082:
0083: // For the internal enumeration methods
0084: protected transient BlobNode enumerationNode = null;
0085: protected transient int enumerationPos = 0;
0086:
0087: /**
0088: * An inclusive between function (for chars).
0089: * @param test The char to test.
0090: * @param low The lower bound.
0091: * @param high The upper bound.
0092: * @return true if test is between low and high (inclusive),
0093: * false otherwise.
0094: *
0095: */
0096: protected static final boolean between(char test, char low,
0097: char high) {
0098: return ((test >= low) && (test <= high));
0099: }
0100:
0101: /**
0102: * An inclusive between function (for ints).
0103: * @param test The number to test.
0104: * @param low The lower bound.
0105: * @param high The upper bound.
0106: * @return true if test is between low and high (inclusive),
0107: * false otherwise.
0108: *
0109: */
0110: public static final boolean between(int test, int low, int high) {
0111: return ((test >= low) && (test <= high));
0112: }
0113:
0114: /**
0115: * Returns a hex representation of a byte.
0116: * @param b The byte to convert to hex.
0117: * @return The 2-digit hex value of the supplied byte.
0118: *
0119: */
0120: public static final String toHex(byte b) {
0121:
0122: char ret[] = new char[2];
0123:
0124: ret[0] = hexDigit((b >>> 4) & (byte) 0x0F);
0125: ret[1] = hexDigit((b >>> 0) & (byte) 0x0F);
0126:
0127: return new String(ret);
0128: }
0129:
0130: /**
0131: * Returns the hex representation of a short.
0132: * @param s The short to convert to hex.
0133: * @return The 4-digit hex value of the supplied short.
0134: *
0135: */
0136: public static final String toHex(short s) {
0137:
0138: StringBuffer sb = new StringBuffer(5);
0139:
0140: sb.append(toHex((byte) (s >>> 8)));
0141: sb.append(' ');
0142: sb.append(toHex((byte) (s >>> 0)));
0143:
0144: return sb.toString();
0145: }
0146:
0147: /**
0148: * Returns the hex representation of an int.
0149: * @param i The int to convert to hex.
0150: * @return The 8-digit hex value of the supplied int.
0151: *
0152: */
0153: public static final String toHex(int i) {
0154:
0155: StringBuffer sb = new StringBuffer(11);
0156:
0157: sb.append(toHex((byte) (i >>> 24)));
0158: sb.append(' ');
0159: sb.append(toHex((byte) (i >>> 16)));
0160: sb.append(' ');
0161: sb.append(toHex((byte) (i >>> 8)));
0162: sb.append(' ');
0163: sb.append(toHex((byte) (i >>> 0)));
0164:
0165: return sb.toString();
0166: }
0167:
0168: /**
0169: * Returns the hex representation of an long.
0170: * @param l The long to convert to hex.
0171: * @return The 16-digit hex value of the supplied long.
0172: *
0173: */
0174: public static final String toHex(long l) {
0175:
0176: StringBuffer sb = new StringBuffer(11);
0177:
0178: sb.append(toHex((byte) (l >>> 56)));
0179: sb.append(' ');
0180: sb.append(toHex((byte) (l >>> 48)));
0181: sb.append(' ');
0182: sb.append(toHex((byte) (l >>> 40)));
0183: sb.append(' ');
0184: sb.append(toHex((byte) (l >>> 32)));
0185: sb.append(' ');
0186: sb.append(toHex((byte) (l >>> 24)));
0187: sb.append(' ');
0188: sb.append(toHex((byte) (l >>> 16)));
0189: sb.append(' ');
0190: sb.append(toHex((byte) (l >>> 8)));
0191: sb.append(' ');
0192: sb.append(toHex((byte) (l >>> 0)));
0193:
0194: return sb.toString();
0195: }
0196:
0197: /**
0198: * Returns the hex representation of the characters of a String.
0199: * @param s The String to convert to hex.
0200: * @return A String where each character of the original string
0201: * is given as a space seperated sequence of hex values.
0202: *
0203: */
0204: public static final String toHex(String s) {
0205:
0206: StringBuffer sb = new StringBuffer();
0207:
0208: char chars[] = s.toCharArray();
0209: for (int x = 0; x < chars.length; x++) {
0210: sb.append(toHex((byte) chars[x]));
0211: if (x != (chars.length - 1)) {
0212: sb.append(' ');
0213: }
0214: }
0215:
0216: return sb.toString();
0217:
0218: }
0219:
0220: /**
0221: * Returns the hex digit cooresponding to a number between 0 and 15.
0222: * @param i The number to get the hex digit for.
0223: * @return The hex digit cooresponding to that number.
0224: * @exception java.lang.IllegalArgumentException If supplied digit
0225: * is not between 0 and 15 inclusive.
0226: *
0227: */
0228: public static final char hexDigit(int i) {
0229:
0230: switch (i) {
0231: case 0:
0232: return '0';
0233: case 1:
0234: return '1';
0235: case 2:
0236: return '2';
0237: case 3:
0238: return '3';
0239: case 4:
0240: return '4';
0241: case 5:
0242: return '5';
0243: case 6:
0244: return '6';
0245: case 7:
0246: return '7';
0247: case 8:
0248: return '8';
0249: case 9:
0250: return '9';
0251: case 10:
0252: return 'A';
0253: case 11:
0254: return 'B';
0255: case 12:
0256: return 'C';
0257: case 13:
0258: return 'D';
0259: case 14:
0260: return 'E';
0261: case 15:
0262: return 'F';
0263: }
0264:
0265: throw new IllegalArgumentException("Invalid digit:" + i);
0266: }
0267:
0268: /**
0269: * Returns a string of a specified number of a specified character.
0270: * @param n The number of characters to create in the return String.
0271: * @param c The character to create.
0272: * @return A String of the requested number of the requested character.
0273: *
0274: */
0275: public static final String strstr(int n, char c) {
0276:
0277: StringBuffer ret = new StringBuffer(n);
0278:
0279: for (int x = 0; x < n; x++) {
0280: ret.append(c);
0281: }
0282:
0283: return ret.toString();
0284: }
0285:
0286: /**
0287: * Returns a string of a specified number of spaces.
0288: * @param n The number of spaces to create.
0289: * @return A String of the requested number of spaces.
0290: * @see #strstr
0291: *
0292: */
0293: public static final String spaces(int n) {
0294: return strstr(n, ' ');
0295: }
0296:
0297: ////////////////////////////////////////////////////////////
0298: ////////////////////////////////////////////////////////////
0299: ////////////////////////////////////////////////////////////
0300: //
0301: // CONSTRUCTORS
0302: //
0303:
0304: /**
0305: * Creates a new, empty Blob.
0306: * Uses default internal node size.
0307: *
0308: */
0309: public Blob() {
0310: this (NODE_SIZE);
0311: }
0312:
0313: /**
0314: * Creates a new, empty Blob and specifies the default internal
0315: * node size.
0316: * @param growSize The number of bytes to allocate for a new node
0317: * in the internal data storage structure.
0318: *
0319: */
0320: public Blob(int nodeSize) {
0321: this .nodeSize = nodeSize;
0322:
0323: // Create a new, empty head node
0324: head = new BlobNode(nodeSize);
0325: tail = head;
0326:
0327: size = 0;
0328: }
0329:
0330: /**
0331: * Creates a new blob that is a copy of an existing blob.
0332: * @param b The Blob to copy.
0333: *
0334: */
0335: public Blob(Blob b) {
0336: this ();
0337:
0338: append(b);
0339: }
0340:
0341: /**
0342: * Creates a new blob and initialized it with a byte array.
0343: * @param arr A byte array to copy entirely into the new Blob.
0344: *
0345: */
0346: public Blob(byte arr[]) {
0347: this (arr, 0, arr.length);
0348: }
0349:
0350: /**
0351: * Creates a new blob and initialized it with a byte array.
0352: * @param arr A byte array to copy into the new Blob.
0353: * @param startPos The location to start extracting bytes from.
0354: * @param len The length of the array to copy in.
0355: *
0356: */
0357: public Blob(byte arr[], int startPos, int len) {
0358: this ();
0359:
0360: append(arr, startPos, len);
0361: }
0362:
0363: ////////////////////////////////////////////////////////////
0364: ////////////////////////////////////////////////////////////
0365: ////////////////////////////////////////////////////////////
0366: //
0367: // EXTERNAL METHODS
0368: //
0369:
0370: //
0371: // Append Methods
0372: //
0373:
0374: /**
0375: * Appends a byte to the end of the Blob.
0376: * @param b The byte to add.
0377: *
0378: */
0379: public synchronized void append(byte b) {
0380:
0381: // Ensure the capacity of the current tail node
0382: if (tail.freespace() == 0) {
0383: appendNode(nodeSize);
0384: }
0385:
0386: // Add the byte to the end node
0387: tail.data[tail.size] = b;
0388: tail.size++;
0389:
0390: // incriment our total.
0391: size++;
0392: }
0393:
0394: /**
0395: * Appends one byte (in the form of a char) to the end of the Blob.
0396: * Strips the high byte off.
0397: * @param c The character to add.
0398: *
0399: */
0400: public synchronized void append(char c) {
0401: append(false, c);
0402: }
0403:
0404: /**
0405: * Appends one or two bytes (in the form of a char) to the end
0406: * of the Blob.
0407: * Can optionally add the high byte or not.
0408: * @param addHighByte 'true' to add the character's high byte to the
0409: * Blob; 'false' to strip it.
0410: * @param c The character to add.
0411: *
0412: */
0413: public synchronized void append(boolean addHighByte, char c) {
0414:
0415: if (addHighByte) {
0416: // Byte 1
0417: append((byte) (c >> 8));
0418: }
0419:
0420: // Byte 2
0421: append((byte) (c >> 0));
0422: }
0423:
0424: /**
0425: * Appends two bytes (in the form of a short) to the end of the Blob.
0426: * @param s The short integer to add.
0427: *
0428: */
0429: public synchronized void append(short s) {
0430:
0431: // Byte 1
0432: append((byte) (s >> 8));
0433:
0434: // Byte 2
0435: append((byte) (s >> 0));
0436: }
0437:
0438: /**
0439: * Appends four bytes (in the form of an int) to the end of the Blob.
0440: * @param i The integer to add.
0441: *
0442: */
0443: public synchronized void append(int i) {
0444:
0445: // Byte 1
0446: append((byte) (i >> 24));
0447:
0448: // Byte 2
0449: append((byte) (i >> 16));
0450:
0451: // Byte 3
0452: append((byte) (i >> 8));
0453:
0454: // Byte 4
0455: append((byte) (i >> 0));
0456: }
0457:
0458: /**
0459: * Appends eight bytes (in the form of a long) to the end of the Blob.
0460: * @param l The long integer to add.
0461: *
0462: */
0463: public synchronized void append(long l) {
0464:
0465: // Byte 1
0466: append((byte) (l >> 56));
0467:
0468: // Byte 2
0469: append((byte) (l >> 48));
0470:
0471: // Byte 3
0472: append((byte) (l >> 40));
0473:
0474: // Byte 4
0475: append((byte) (l >> 32));
0476:
0477: // Byte 5
0478: append((byte) (l >> 24));
0479:
0480: // Byte 6
0481: append((byte) (l >> 16));
0482:
0483: // Byte 7
0484: append((byte) (l >> 8));
0485:
0486: // Byte 8
0487: append((byte) (l >> 0));
0488: }
0489:
0490: /**
0491: * Appends four bytes (in the form of a float) to the end of the Blob.
0492: * @param f The float to add.
0493: *
0494: */
0495: public synchronized void append(float f) {
0496:
0497: // Append the integer created from the bytes of this float
0498: append(Float.floatToIntBits(f));
0499: }
0500:
0501: /**
0502: * Appends eight bytes (in the form of a double) to the end of the Blob.
0503: * @param d The double to add.
0504: *
0505: */
0506: public synchronized void append(double d) {
0507:
0508: // Append the long created from the bytes of this double
0509: append(Double.doubleToLongBits(d));
0510: }
0511:
0512: /**
0513: * Appends bytes from a String to the end of the Blob.
0514: * Strips the high bytes from the characters.
0515: * @param s The String to add.
0516: *
0517: */
0518: public synchronized void append(String s) {
0519: append(false, s);
0520: }
0521:
0522: /**
0523: * Appends bytes from a String to the end of the Blob.
0524: * Can optionally add the high bytes from the characters or not.
0525: * @param addHighByte 'true' to add the characters' high byte to the
0526: * Blob; 'false' to strip it.
0527: * @param s The String to add.
0528: *
0529: */
0530: public synchronized void append(boolean addHighByte, String s) {
0531:
0532: for (int x = 0; x < s.length(); x++) {
0533: append(addHighByte, s.charAt(x));
0534: }
0535: }
0536:
0537: /**
0538: * Appends an entire byte array to the end of the Blob.
0539: * @param arr The array to add bytes from.
0540: *
0541: */
0542: public synchronized void append(byte arr[]) {
0543: append(arr, 0, arr.length);
0544: }
0545:
0546: /**
0547: * Appends a byte array to the end of the Blob.
0548: * @param arr The array to add bytes from.
0549: * @param startPos The position to start the byte extraction
0550: * from the array at.
0551: * @param len The number of bytes to read from the array.
0552: *
0553: */
0554: public synchronized void append(byte arr[], int startPos, int len) {
0555:
0556: // If the current tail node has enuff storage for this
0557: // new addition, use it, otherwise, append a new node.
0558: if (tail.freespace() < len) {
0559: BlobNode oldTail = tail;
0560: appendNode(Math.max(len, nodeSize));
0561:
0562: // If old tail node was empty, we'll eliminate it
0563: if (oldTail.size == 0) {
0564: BlobNode bn = findBefore(oldTail);
0565: if (bn == null) {
0566: // oldTail == head
0567: head = tail;
0568: } else {
0569: bn.next = tail;
0570: }
0571: } // endif
0572:
0573: } // endif
0574:
0575: System.arraycopy(arr, startPos, tail.data, tail.size, len);
0576: tail.size += len;
0577:
0578: size += len;
0579:
0580: } // end append()
0581:
0582: /**
0583: * Appends the bytes from a Blob to the end of the Blob.
0584: * @param b The Blob to draw bytes from.
0585: *
0586: */
0587: public synchronized void append(Blob b) {
0588:
0589: BlobNode bn;
0590: boolean setHead = false;
0591:
0592: // We're going to clone the target Blob's nodes and
0593: // tack them on the end.
0594:
0595: // if our current node is empty, we'll back up one before appending
0596: if (tail.size == 0) {
0597: bn = findBefore(tail);
0598: if (bn == null) {
0599: // tail == head
0600: setHead = true;
0601: } else {
0602: tail = bn;
0603: tail.next = null;
0604: }
0605: }
0606:
0607: // Now append all of the target's nodes
0608: bn = b.head;
0609:
0610: if (setHead) {
0611: // set the head node
0612: // (the head node now is empty)
0613: head = (BlobNode) bn.clone();
0614: tail = head;
0615: size = head.size;
0616:
0617: // we'll start with the next node down the line
0618: // (since we just used the first one)
0619: bn = bn.next;
0620: }
0621:
0622: while (bn != null) {
0623: tail.next = (BlobNode) bn.clone();
0624: tail = tail.next;
0625: size += tail.size;
0626:
0627: bn = bn.next;
0628: }
0629:
0630: } // end append()
0631:
0632: //
0633: // Data insert Methods
0634: //
0635:
0636: /**
0637: * Inserts a byte into the blob at the position pos.
0638: * Everything else is moved back.
0639: * @param pos The postition to add the byte at (0 -> beginning).
0640: * @param b The byte to add.
0641: * @see Blob#insertBytes
0642: * @exception java.lang.IndexOutOfBoundsException If pos is
0643: * outside range of Blob.
0644: *
0645: */
0646: public synchronized void insertByte(int pos, byte b) {
0647:
0648: byte arr[] = new byte[1];
0649: arr[0] = b;
0650: insertBytes(pos, arr, 0, 1);
0651:
0652: } // end insertByte()
0653:
0654: /**
0655: * Inserts a byte into the blob at the position pos.
0656: * Everything else is moved back.
0657: * @param pos The postition to begin adding the bytes at (0 -> beginning).
0658: * @param arr The array of bytes to add from.
0659: * @exception java.lang.IndexOutOfBoundsException If pos is
0660: * outside range of Blob.
0661: *
0662: */
0663: public synchronized void insertBytes(int pos, byte arr[]) {
0664: this .insertBytes(pos, arr, 0, arr.length);
0665: }
0666:
0667: /**
0668: * Inserts a byte into the blob at the position pos.
0669: * Everything else is moved back.
0670: * @param pos The postition to begin adding the bytes at (0 -> beginning).
0671: * @param arr The array of bytes to add from.
0672: * @param startPos The position to begin byte copy from.
0673: * @param len The number of bytes to add from the array.
0674: * @exception java.lang.IndexOutOfBoundsException If pos is
0675: * outside range of Blob.
0676: *
0677: */
0678: public synchronized void insertBytes(int pos, byte arr[],
0679: int startPos, int len) {
0680:
0681: // are we just appending?
0682: if (pos == size) {
0683: append(arr, startPos, len);
0684: return;
0685: }
0686:
0687: // Is the position valid?
0688: if (!between(pos, 0, (size - 1))) {
0689: throw new IndexOutOfBoundsException();
0690: } // endif
0691:
0692: // Find the correct node and index into that node
0693: int currIndex = seek(pos);
0694:
0695: // Do we have enough space in the current node for the
0696: // new information
0697: if (curr.freespace() >= len) {
0698: // Insert the info into the current node
0699:
0700: // Move the old stuff back
0701: System.arraycopy(curr.data, currIndex, curr.data,
0702: (currIndex + len), (curr.size - currIndex));
0703:
0704: // Copy in the new stuff
0705: System.arraycopy(arr, startPos, curr.data, currIndex, len);
0706:
0707: // Set the new size
0708: this .curr.size += len;
0709:
0710: } else {
0711: // not enough room in the inn.
0712: // erect a barn, errr, a new node for it.
0713: BlobNode newNode = new BlobNode(nodeSize, arr, startPos,
0714: len);
0715: BlobNode before = findBefore(curr);
0716:
0717: // Where do we stick it?
0718: if (currIndex == 0) {
0719: // put it before the current node
0720: if (before == null) {
0721: // inserting before head
0722: newNode.next = head;
0723: head = newNode;
0724: } else {
0725: // inserting in middle
0726: newNode.next = before.next;
0727: before.next = newNode;
0728: }
0729: } else {
0730: // We'll have to split the current node
0731: BlobNode a = new BlobNode(Math.max(nodeSize, currIndex));
0732: BlobNode b = new BlobNode(Math.max(nodeSize,
0733: (curr.size - currIndex)));
0734:
0735: // Copy the data
0736: a.size = currIndex;
0737: System.arraycopy(curr.data, 0, a.data, 0, a.size);
0738: b.size = curr.size - currIndex;
0739: System.arraycopy(curr.data, currIndex, b.data, 0,
0740: b.size);
0741:
0742: // Set up the links.
0743: b.next = (before == null) ? head : before.next;
0744: newNode.next = b;
0745: a.next = newNode;
0746: if (before == null) {
0747: head = a;
0748: } else {
0749: before.next = a;
0750: }
0751: } // endif
0752:
0753: } // endif
0754:
0755: // adjust size
0756: size += len;
0757:
0758: } // end insertBytes()
0759:
0760: //
0761: // Data remove methods
0762: //
0763:
0764: /**
0765: * Truncates the Blob to the specified position.
0766: * The Blob will have the given number of bytes left.
0767: * @param len The size to truncate the Blob to.
0768: * @exception java.lang.IndexOutOfBoundsException If len is
0769: * outside range of Blob.
0770: *
0771: */
0772: public synchronized void truncate(int len) {
0773:
0774: // Anything to do?
0775: if (len == size) {
0776: return;
0777: }
0778:
0779: // Is the data within bounds?
0780: if (!between(len, 0, size)) {
0781: throw new IndexOutOfBoundsException();
0782: }
0783:
0784: // find the correct node and truncate that node
0785: // to the required number of bytes to make the
0786: // whole thing work.
0787: // We have to do the seek() and truncate() on different
0788: // lines as seek() sets 'curr'..
0789: int currIndex = seek(len);
0790: curr.size = currIndex;
0791:
0792: // Set the new size;
0793: size = len;
0794:
0795: // set the next pointer of this node to null
0796: curr.next = null;
0797: tail = curr;
0798:
0799: } // end truncate()
0800:
0801: /**
0802: * Removes a byte from the Blob at the requested position.
0803: * Everything else is moved up.
0804: * @param pos The position to remove a byte from (0 -> beginning).
0805: * @see Blob#removeBytes
0806: * @exception java.lang.IndexOutOfBoundsException If pos is
0807: * outside range of Blob.
0808: *
0809: */
0810: public synchronized void removeByte(int pos) {
0811: removeBytes(pos, 1);
0812: } // end removeByte()
0813:
0814: /**
0815: * Removes a number of bytes from the Blob at the requested position.
0816: * Everything else is moved up.
0817: * @param pos The position to remove the bytes from (0 -> beginning).
0818: * @param len The number of bytes to remove.
0819: * @exception java.lang.IndexOutOfBoundsException If pos and len are
0820: * outside range of Blob.
0821: *
0822: */
0823: public synchronized void removeBytes(int pos, int len) {
0824:
0825: // Is the data within bounds?
0826: if (!(between(pos, 0, (size - 1)) && between((pos + len), 0,
0827: size))) {
0828:
0829: throw new IndexOutOfBoundsException();
0830: }
0831:
0832: BlobNode startNode, endNode;
0833: int startPos, endPos;
0834:
0835: // Get the starting and ending locations
0836: startPos = seek(pos);
0837: startNode = curr;
0838:
0839: endPos = seek(pos + len);
0840: endNode = curr;
0841:
0842: if (startNode == endNode) {
0843: // Just removing stuff from one node
0844:
0845: // move the stuff up and adjust length
0846: System.arraycopy(curr.data, endPos, curr.data, startPos,
0847: (curr.size - endPos));
0848: curr.size -= len;
0849:
0850: } else {
0851: // Removing stuff across several nodes
0852: // Create a new node to hold the info
0853: BlobNode newNode = new BlobNode(Math.max(nodeSize,
0854: (startPos + (endNode.size - endPos))));
0855:
0856: // Move stuff into this new node
0857: System.arraycopy(startNode.data, 0, newNode.data, 0,
0858: startPos);
0859:
0860: System.arraycopy(endNode.data, endPos, newNode.data,
0861: startPos, (endNode.size - endPos));
0862:
0863: newNode.size = (startPos + 1) + (endNode.size - endPos);
0864:
0865: // update the pointers
0866: newNode.next = endNode.next;
0867:
0868: // Find the node PREVIOUS to the startNode
0869: BlobNode before = findBefore(startNode);
0870: if (before == null) {
0871: head = newNode;
0872: } else {
0873: before.next = newNode;
0874: }
0875: } // endif
0876:
0877: // Set the new size
0878: size -= len;
0879:
0880: } // end removeBytes()
0881:
0882: //
0883: // Data Retrieval methods
0884: //
0885:
0886: /**
0887: * Gets the bytes of the blob as a byte array.
0888: * @return An array of the bytes that compose the Blob.
0889: *
0890: */
0891: public byte[] getBytes() {
0892: return getBytes(0, size);
0893: }
0894:
0895: /**
0896: * Returns a subset of the bytes of the blob as a byte array.
0897: * @param start start index to begin draw (included in get).
0898: * @param len number of bytes to extract.
0899: * @return An array of the bytes that compose the Blob.
0900: * @exception java.lang.IndexOutOfBoundsException If start and len are
0901: * outside range of Blob.
0902: *
0903: */
0904: public synchronized byte[] getBytes(int start, int len) {
0905:
0906: // Special case
0907: if ((start == 0) && (len == 0)) {
0908: return new byte[0];
0909: }
0910:
0911: // Is the data within bounds?
0912: if (!(between(start, 0, (size - 1)) && between((start + len),
0913: 0, size))) {
0914: throw new IndexOutOfBoundsException();
0915: }
0916:
0917: byte ret[] = new byte[len];
0918: int bytesCopied = 0;
0919: int copyFromThis = 0;
0920:
0921: int currIndex = seek(start);
0922: while (bytesCopied < len) {
0923: copyFromThis = Math.min((curr.size - currIndex),
0924: (len - bytesCopied));
0925:
0926: System.arraycopy(curr.data, currIndex, ret, bytesCopied,
0927: copyFromThis);
0928: bytesCopied += copyFromThis;
0929:
0930: curr = curr.next;
0931: currIndex = 0;
0932: } // endwhile
0933:
0934: return ret;
0935:
0936: } // end getBytes()
0937:
0938: /**
0939: * Returns the byte at a specific location.
0940: * @param pos The position to from which to return the byte
0941: * (0 -> beginning).
0942: * @return The byte at requested position.
0943: * @exception java.lang.IndexOutOfBoundsException If pos is
0944: * outside range of Blob.
0945: *
0946: */
0947: public synchronized byte byteAt(int pos) {
0948:
0949: // Is the data within bounds?
0950: if (!between(pos, 0, (size - 1))) {
0951: throw new IndexOutOfBoundsException();
0952: }
0953:
0954: // Find the correct node and index into that node
0955: int currIndex = seek(pos);
0956:
0957: // Get the byte
0958: return curr.data[currIndex];
0959: }
0960:
0961: /**
0962: * Returns a character reconstructed from one byte at a specific location.
0963: * Sets the high byte in the character to 0x0.
0964: * @param pos The position to get the byte to reconstuct the character
0965: * (0 -> beginning).
0966: * @return The reconstructed character.
0967: * @exception java.lang.IndexOutOfBoundsException If the requested
0968: * char is outside the Blob.
0969: *
0970: */
0971: public synchronized char charAt(int pos) {
0972: return charAt(false, pos);
0973: } // end charAt()
0974:
0975: /**
0976: * Returns a character reconstructed from one or two bytes at a
0977: * specific location.
0978: * @param useHighByte 'true' to use two bytes (one for the high byte)
0979: * to reconstruct a character; 'false' to use one byte and set the
0980: * high byte to 0x0.
0981: * @param pos The position to get the byte(s) to reconstuct the character
0982: * (0 -> beginning).
0983: * @return The reconstructed character.
0984: * @exception java.lang.IndexOutOfBoundsException If the requested
0985: * bytes are outside the Blob.
0986: *
0987: */
0988: public synchronized char charAt(boolean useHighByte, int pos) {
0989:
0990: // Is the data within bounds?
0991: if (!between(pos, 0, (size - (useHighByte ? 2 : 1)))) {
0992: throw new IndexOutOfBoundsException();
0993: }
0994:
0995: // Return character
0996: char ret;
0997:
0998: if (useHighByte) {
0999: // get a short and cast it as a char.
1000: ret = (char) shortAt(pos);
1001:
1002: } else {
1003: // get a byte and cast it as a char.
1004: ret = (char) byteAt(pos);
1005:
1006: } // endif
1007:
1008: return ret;
1009: } // end charAt()
1010:
1011: /**
1012: * Returns a short int reconstructed from two bytes at a specific
1013: * location.
1014: * @param pos The position to get the bytes to reconstuct the short
1015: * (0 -> beginning).
1016: * @return The reconstructed short.
1017: * @exception java.lang.IndexOutOfBoundsException If the requested
1018: * bytes are outside the Blob.
1019: *
1020: */
1021: public synchronized short shortAt(int pos) {
1022:
1023: // Is the data within bounds?
1024: if (!between(pos, 0, (size - 2))) {
1025: throw new IndexOutOfBoundsException();
1026: }
1027:
1028: // Return value
1029: short ret = 0x0, temp;
1030:
1031: // Find proper location
1032: beginEnumeration(pos);
1033:
1034: for (int x = 0; x < 2; x++) {
1035:
1036: // get next byte
1037: // We shift the input byte up all the way to the high
1038: // byte and then back down to the req'd position because
1039: // java won't let us cast an a byte as a larger integer
1040: // type without propigating the sign bit thruout the
1041: // higher bytes. This ain't what we need so we jump
1042: // thru some hoops to avoid it.
1043: temp = (short) ((nextByte() << (1 * 8)) >>> (1 * 8));
1044: ret |= (temp << (8 * (1 - x)));
1045:
1046: } // endfor
1047:
1048: return ret;
1049: } // end shortAt()
1050:
1051: /**
1052: * Returns an int reconstructed from four bytes at a specific
1053: * location.
1054: * @param pos The position to get the bytes to reconstuct the int
1055: * (0 -> beginning).
1056: * @return The reconstructed int.
1057: * @exception java.lang.IndexOutOfBoundsException If the requested
1058: * bytes are outside the Blob.
1059: *
1060: */
1061: public synchronized int intAt(int pos) {
1062:
1063: // Is the data within bounds?
1064: if (!between(pos, 0, (size - 4))) {
1065: throw new IndexOutOfBoundsException();
1066: }
1067:
1068: // Return value
1069: int ret = 0x0, temp;
1070:
1071: // Find proper location
1072: beginEnumeration(pos);
1073:
1074: for (int x = 0; x < 4; x++) {
1075:
1076: // get next byte
1077: // We shift the input byte up all the way to the high
1078: // byte and then back down to the req'd position because
1079: // java won't let us cast an a byte as a larger integer
1080: // type without propigating the sign bit thruout the
1081: // higher bytes. This ain't what we need so we jump
1082: // thru some hoops to avoid it.
1083: temp = ((int) nextByte() << (3 * 8)) >>> (3 * 8);
1084: ret |= (temp << (8 * (3 - x)));
1085:
1086: } // endfor
1087:
1088: return ret;
1089: } // end intAt()
1090:
1091: /**
1092: * Returns a long int reconstructed from eight bytes at a specific
1093: * location.
1094: * @param pos The position to get the bytes to reconstuct the long
1095: * (0 -> beginning).
1096: * @return The reconstructed long.
1097: * @exception java.lang.IndexOutOfBoundsException If the requested
1098: * bytes are outside the Blob.
1099: *
1100: */
1101: public synchronized long longAt(int pos) {
1102:
1103: // Is the data within bounds?
1104: if (!between(pos, 0, (size - 8))) {
1105: throw new IndexOutOfBoundsException();
1106: }
1107:
1108: // Return value
1109: long ret = 0x0, temp;
1110:
1111: // Find proper location
1112: beginEnumeration(pos);
1113:
1114: for (int x = 0; x < 8; x++) {
1115:
1116: // get next byte
1117: // We shift the input byte up all the way to the high
1118: // byte and then back down to the req'd position because
1119: // java won't let us cast an a byte as a larger integer
1120: // type without propigating the sign bit thruout the
1121: // higher bytes. This ain't what we need so we jump
1122: // thru some hoops to avoid it.
1123: temp = ((long) nextByte() << (7 * 8)) >>> (7 * 8);
1124: ret |= (temp << (8 * (7 - x)));
1125:
1126: } // endfor
1127:
1128: return ret;
1129: } // end longAt()
1130:
1131: /**
1132: * Returns a floating point number reconstructed from four bytes
1133: * at a specific
1134: * location.
1135: * @param pos The position to get the bytes to reconstuct the float
1136: * (0 -> beginning).
1137: * @return The reconstructed float.
1138: * @exception java.lang.IndexOutOfBoundsException If the requested
1139: * bytes are outside the Blob.
1140: *
1141: */
1142: protected synchronized float floatAt(int pos) {
1143:
1144: return Float.intBitsToFloat(intAt(pos));
1145:
1146: } // end floatAt()
1147:
1148: /**
1149: * Returns a double precision floating point number reconstructed
1150: * from eight bytes at a specific
1151: * @param pos The position to get the bytes to reconstuct the double
1152: * (0 -> beginning).
1153: * @return The reconstructed double.
1154: * @exception java.lang.IndexOutOfBoundsException If the requested
1155: * bytes are outside the Blob.
1156: *
1157: */
1158: public synchronized double doubleAt(int pos) {
1159:
1160: return Double.longBitsToDouble(longAt(pos));
1161:
1162: } // end floatAt()
1163:
1164: /**
1165: * Returns a String reconstructed from the bytes of the Blob.
1166: * Sets the high byte in each character to 0x0.
1167: * @return The reconstructed String.
1168: *
1169: */
1170: public synchronized String getString() {
1171:
1172: return getString(false, 0, size);
1173: } // end getString()
1174:
1175: /**
1176: * Returns a String reconstructed from bytes at a specific location.
1177: * Sets the high byte in each character to 0x0.
1178: * @param pos The position to start retrieving the bytes to reconstuct
1179: * the String (0 -> beginning).
1180: * @param len The length of the to-be-returned String.
1181: * @return The reconstructed String.
1182: * @exception java.lang.IndexOutOfBoundsException If the requested
1183: * bytes are outside the Blob.
1184: *
1185: */
1186: public synchronized String getString(int pos, int len) {
1187: return getString(false, pos, len);
1188: } // end getString()
1189:
1190: /**
1191: * Returns a String reconstructed from bytes at a specific location.
1192: * @param useHighByte 'true' to use two bytes (one for the high byte)
1193: * to reconstruct each character; 'false' to use one byte and set the
1194: * high byte to 0x0.
1195: * @param pos The position to start retrieving the bytes to reconstuct
1196: * the String (0 -> beginning).
1197: * @param len The length of the to-be-returned String.
1198: * @return The reconstructed String.
1199: * @exception java.lang.IndexOutOfBoundsException If the requested
1200: * bytes are outside the Blob.
1201: *
1202: */
1203: public synchronized String getString(boolean useHighByte, int pos,
1204: int len) {
1205:
1206: // Special case
1207: if ((pos == 0) && (len == 0)) {
1208: return new String();
1209: }
1210:
1211: // Is the data within bounds?
1212: if (!(between(pos, 0, (size - 1)) && between(
1213: (pos + (len * (useHighByte ? 2 : 1))), 0, size))) {
1214: throw new IndexOutOfBoundsException();
1215: }
1216:
1217: // Return String
1218: StringBuffer ret = new StringBuffer(len);
1219:
1220: // hold character
1221: char c;
1222:
1223: // Find proper location
1224: beginEnumeration(pos);
1225:
1226: for (int x = 0; x < len; x++) {
1227:
1228: if (useHighByte) {
1229: // Use both bytes
1230:
1231: // high byte
1232: c = (char) (nextByte() << 8);
1233:
1234: // low byte
1235: c |= nextByte();
1236:
1237: } else {
1238: // just get one (low) byte
1239: c = (char) nextByte();
1240:
1241: } // endif
1242:
1243: // Add the new character
1244: ret.append(c);
1245:
1246: } // endfor
1247:
1248: return ret.toString();
1249: } // end getString()
1250:
1251: /**
1252: * Clones this Blob.
1253: * The resulting Blob will have the same data as the first.
1254: * (altho possibly not the same internal configuration).
1255: *
1256: */
1257: public synchronized Object clone() {
1258: // We can just call our getBlob method to do this.
1259: return getBlob(0, size);
1260: }
1261:
1262: /**
1263: * Returns a new Blob drawn from bytes at a specific location.
1264: * @param pos The position to start retrieving the bytes to build
1265: * the new Blob (0 -> beginning).
1266: * @param len The number of bytes to put into the new Blob.
1267: * @return The newly constructed Blob.
1268: * @exception java.lang.IndexOutOfBoundsException If the requested
1269: * bytes are outside the Blob.
1270: *
1271: */
1272: public synchronized Blob getBlob(int pos, int len) {
1273: // cop out and use our other methods. Speed be damned -- this
1274: // isn't that important anyway.
1275: // We *should* just go thru and clone the appropiate
1276: // BlobNodes and just modifiy the ones on the end....
1277: return new Blob(getBytes(pos, len));
1278:
1279: } // end getBlob()
1280:
1281: //
1282: // Misc Methods
1283: //
1284:
1285: /**
1286: * Returns an enumeration of the bytes in this Blob.
1287: * The objects returned from the calls to nextElement are
1288: * of type Byte.
1289: * @param pos The location from which to begin enumerating bytes.
1290: * @exception java.lang.IndexOutOfBoundsException If pos is
1291: * outside range of Blob.
1292: *
1293: */
1294: public synchronized Enumeration enumerateBytes(int pos) {
1295:
1296: // Is the data within bounds?
1297: if (!between(pos, 0, (size - 1))) {
1298: throw new IndexOutOfBoundsException();
1299: }
1300:
1301: final int cep = seek(pos);
1302: final BlobNode cen = curr;
1303:
1304: return new Enumeration() {
1305:
1306: int currEnumerationPos = cep;
1307: BlobNode currEnumerationNode = cen;
1308:
1309: public synchronized boolean hasMoreElements() {
1310: return (currEnumerationNode != null);
1311: }
1312:
1313: public synchronized Object nextElement() {
1314: if (currEnumerationNode == null) {
1315: throw new NoSuchElementException(
1316: "Past end of current Enumeration");
1317: }
1318:
1319: byte ret = currEnumerationNode.data[currEnumerationPos++];
1320:
1321: // check to see if we're at the end of the current node
1322: if (currEnumerationPos == currEnumerationNode.size) {
1323: // At end, go to next node
1324: currEnumerationNode = currEnumerationNode.next;
1325: currEnumerationPos = 0;
1326: }
1327:
1328: // All done
1329: return new Byte(ret);
1330: } // end nextElement()
1331:
1332: }; // end Enumeration
1333: } // end enumerateBytes
1334:
1335: /**
1336: * Sets a byte at a particular position.
1337: * @param pos The position to set the byte at.
1338: * @param b The value to set that postion to.
1339: * @exception java.lang.IndexOutOfBoundsException If pos is
1340: * outside range of Blob.
1341: *
1342: */
1343: public synchronized void setByteAt(int pos, byte b) {
1344: // Is the data within bounds?
1345: if (!between(pos, 0, (size - 1))) {
1346: throw new IndexOutOfBoundsException();
1347: }
1348:
1349: // Find the correct node and index into that node
1350: int currIndex = seek(pos);
1351:
1352: // Set the byte
1353: curr.data[currIndex] = b;
1354: }
1355:
1356: /**
1357: * Gets the number of bytes in the Blob.
1358: * @return The number of bytes in the Blob.
1359: *
1360: */
1361: public synchronized int length() {
1362: return size;
1363: }
1364:
1365: /**
1366: * Returns true if the blob is equal to the given object.
1367: * True only if supplied object is a Blob and the two contain
1368: * exactly the same data.
1369: *
1370: */
1371: public synchronized boolean equals(Object o) {
1372: if ((o == null) || !(o instanceof Blob)) {
1373: return false;
1374: }
1375:
1376: Blob b = (Blob) o;
1377:
1378: // Are they the same length?
1379: if (size != b.size) {
1380: return false;
1381: }
1382:
1383: // Compare the data
1384: beginEnumeration(0);
1385: b.beginEnumeration(0);
1386: while (hasMoreBytes()) {
1387: if (nextByte() != b.nextByte()) {
1388: return false;
1389: }
1390: }
1391:
1392: // If we got here, they must be equal
1393: return true;
1394:
1395: } // end equals()
1396:
1397: /**
1398: * Searches for a byte and returns an index to the first one found.
1399: * @param b The byte to search for.
1400: * @return The index of the first match or -1 if not found.
1401: *
1402: */
1403: public synchronized int indexOf(byte b) {
1404: return indexOf(b, 0);
1405: }
1406:
1407: /**
1408: * Searches for a byte and returns an index to the first one found.
1409: * Search starts at given index and includes that index.
1410: * @param b The byte to search for.
1411: * @param pos The position to begin searching at (0 -> beginning).
1412: * @return The index of the first match or -1 if not found.
1413: * @exception java.lang.IndexOutOfBoundsException If pos is
1414: * outside range of Blob.
1415: *
1416: */
1417: public synchronized int indexOf(byte b, int pos) {
1418:
1419: // Is the data within bounds?
1420: if (!between(pos, 0, (size - 1))) {
1421: throw new IndexOutOfBoundsException();
1422: }
1423:
1424: beginEnumeration(pos);
1425: int currentPos = pos;
1426:
1427: while (hasMoreBytes()) {
1428:
1429: if (nextByte() == b) {
1430: return currentPos;
1431: }
1432:
1433: currentPos++;
1434: } // endwhile
1435:
1436: return -1;
1437: }
1438:
1439: //
1440: // IO Methods
1441: //
1442:
1443: /**
1444: * Reads bytes from an InputStream into the Blob.
1445: * @param len The number of bytes to attempt to read.
1446: * @param in The InputStream to read from.
1447: * @return The number of bytes read and appended to the blob
1448: * or -1 if the end of the stream was reached.
1449: * @exception java.io.IOException If there is a problem reading.
1450: *
1451: */
1452: public synchronized int read(int len, InputStream in)
1453: throws IOException {
1454:
1455: byte b[] = new byte[len];
1456: int bytesRead;
1457: try {
1458: bytesRead = in.read(b);
1459: } catch (EOFException e) {
1460: // We'll just return a -1 just as if
1461: // we had gotten a -1 in response from the read
1462: return -1;
1463:
1464: } catch (IOException e) {
1465: // Throw this one to the caller
1466: throw (IOException) e.fillInStackTrace();
1467: }
1468: append(b, 0, bytesRead);
1469: return bytesRead;
1470:
1471: } // end read();
1472:
1473: /**
1474: * Reads all bytes from an InputStream into the Blob.
1475: * This will read an InputStream byte-by-byte until EOF and
1476: * append the data to the end of the Blob.
1477: * @param in The InputStream to read from.
1478: * @return The number of bytes read and appended to the blob
1479: * or -1 if the end of the stream was reached immediately.
1480: * @exception java.io.IOException If there is a problem reading.
1481: *
1482: */
1483: public synchronized int read(InputStream in) throws IOException {
1484:
1485: byte b[] = new byte[nodeSize];
1486: int bytesRead, totalBytesRead = -1;
1487: while (true) {
1488: try {
1489: bytesRead = in.read(b);
1490: } catch (EOFException e) {
1491: // We'll just break out of the loop just as if
1492: // we had gotten a -1 in response from the read
1493: break;
1494:
1495: } catch (IOException e) {
1496: // Throw this one to the caller
1497: throw (IOException) e.fillInStackTrace();
1498: }
1499:
1500: // if we're at EOF, get out of the loop.
1501: if (bytesRead == -1) {
1502: break;
1503: }
1504:
1505: // We have totalBytesRead set to -1 initially
1506: // so that we'll return -1 if the stream is at EOF
1507: // right off the bat.
1508: // Now, we have to set it to 0 so that our byte sum
1509: // will be correct.
1510: if (totalBytesRead == -1) {
1511: totalBytesRead = 0;
1512: }
1513:
1514: append(b, 0, bytesRead);
1515: totalBytesRead += bytesRead;
1516: }
1517: return totalBytesRead;
1518:
1519: } // end read();
1520:
1521: /**
1522: * Reads most bytes from an InputStream into the Blob -
1523: * not to exceed <max> bytes by much (it may read up to nodeSize more).
1524: * This will read an InputStream byte-by-byte until EOF and
1525: * append the data to the end of the Blob.
1526: * @param in The InputStream to read from.
1527: * @param max the max (sort of) bytes to read
1528: * @return The number of bytes read and appended to the blob
1529: * or -1 if the end of the stream was reached immediately.
1530: * or -2 if we cut off due to max before end
1531: * @exception java.io.IOException If there is a problem reading.
1532: *
1533: */
1534: public synchronized int readLimiting(InputStream in, long max)
1535: throws IOException {
1536: byte b[] = new byte[nodeSize];
1537: int bytesRead, totalBytesRead = -1;
1538: while (true) {
1539: try {
1540: bytesRead = in.read(b);
1541: } catch (EOFException e) {
1542: // We'll just break out of the loop just as if
1543: // we had gotten a -1 in response from the read
1544: break;
1545:
1546: } catch (IOException e) {
1547: // Throw this one to the caller
1548: throw (IOException) e.fillInStackTrace();
1549: }
1550:
1551: // if we're at EOF, get out of the loop.
1552: if (bytesRead == -1) {
1553: break;
1554: }
1555:
1556: // We have totalBytesRead set to -1 initially
1557: // so that we'll return -1 if the stream is at EOF
1558: // right off the bat.
1559: // Now, we have to set it to 0 so that our byte sum
1560: // will be correct.
1561: if (totalBytesRead == -1) {
1562: totalBytesRead = 0;
1563: }
1564:
1565: // if we're already over the limit, get out!
1566: if ((totalBytesRead > max) && (bytesRead > 0)) {
1567: totalBytesRead = -2;
1568: break;
1569: }
1570:
1571: append(b, 0, bytesRead);
1572: totalBytesRead += bytesRead;
1573: }
1574: return totalBytesRead;
1575:
1576: } // readLimiting
1577:
1578: /**
1579: * provide an output stream that writes to the END of the blob (appends)
1580: */
1581: public OutputStream outputStream() {
1582: return new OutputStream() {
1583: public void write(int b) {
1584: append((byte) b);
1585: }
1586:
1587: public void write(byte[] b) {
1588: append(b);
1589: }
1590:
1591: public void write(byte[] b, int off, int len) {
1592: append(b, off, len);
1593: }
1594: };
1595:
1596: } // outputStream
1597:
1598: /**
1599: * provide an input stream that reads the blob contents
1600: */
1601: public InputStream inputStream() {
1602: return new InputStream() {
1603: /** next byte to return */
1604: private int m_pos = 0;
1605:
1606: /*public int read(byte b[], int off, int len)
1607: throws IOException
1608: {
1609: return super.read(b, off, len);
1610: }*/
1611:
1612: public int read() throws IOException {
1613: int rv = -1;
1614: try {
1615: rv = byteAt(m_pos);
1616:
1617: // input streams must return values 0..255, but our bytes are signed
1618: if (rv < 0)
1619: rv = 256 + rv;
1620:
1621: m_pos++;
1622: } catch (IndexOutOfBoundsException e) {
1623: }
1624:
1625: return rv;
1626: }
1627:
1628: public int available() {
1629: return size - m_pos;
1630: }
1631: };
1632:
1633: } // inputStream
1634:
1635: /**
1636: * Writes the entire contents of the Blob to an OutputStream.
1637: * @param out The OutputStream to write to.
1638: * @exception java.io.IOException If there is a problem writing.
1639: *
1640: */
1641: public synchronized void write(OutputStream out) throws IOException {
1642: write(0, size, out);
1643: }
1644:
1645: /**
1646: * Writes the contents of a part of the Blob to an OutputStream.
1647: * @param pos The position to begin writing from.
1648: * @param len The number of bytes to write.
1649: * @param out The OutputStream to write to.
1650: * @exception java.lang.IndexOutOfBoundsException If pos and len are
1651: * outside range of Blob.
1652: * @exception java.io.IOException If there is a problem writing.
1653: *
1654: */
1655: public synchronized void write(int pos, int len, OutputStream out)
1656: throws IOException {
1657:
1658: // Is the data within bounds?
1659: if (!(between(pos, 0, (size - 1)) && between((pos + len), 0,
1660: size))) {
1661: throw new IndexOutOfBoundsException();
1662: }
1663:
1664: // Go to the beginning of the data
1665: int bytesWritten = 0;
1666: int writeFromThis = 0;
1667:
1668: int currIndex = seek(pos);
1669: while (bytesWritten < len) {
1670: writeFromThis = Math.min((curr.size - currIndex),
1671: (len - bytesWritten));
1672:
1673: // Write out the stuff,
1674: out.write(curr.data, currIndex, writeFromThis);
1675: bytesWritten += writeFromThis;
1676:
1677: curr = curr.next;
1678: currIndex = 0;
1679: } // endwhile
1680:
1681: } // end write();
1682:
1683: /**
1684: * Generates and returns an 8-byte checksum.
1685: *
1686: */
1687: public synchronized long checksum() {
1688:
1689: long ret = 0, temp = 0, hold = 0;
1690: int index = 0;
1691:
1692: if (size == 0) {
1693: return ret;
1694: }
1695:
1696: beginEnumeration(0);
1697: while (hasMoreBytes()) {
1698:
1699: // put the byte into the low end byte of a long
1700: temp = ((long) nextByte() << (7 * 8)) >>> (7 * 8);
1701:
1702: // put the byte into a holder long
1703: hold |= (temp << (8 * (7 - index)));
1704:
1705: if (++index == 8) {
1706: // XOR the last eight bytes with the checksum value
1707: ret ^= hold;
1708:
1709: // reset the holder long
1710: hold = 0;
1711: index = 0;
1712: }
1713: } // endwhile
1714:
1715: // Get any remaining bytes in hold
1716: if (index != 0) {
1717: ret ^= hold;
1718: }
1719:
1720: return ret;
1721: }
1722:
1723: /**
1724: * Returns a string representation of the Blob.
1725: * Includes length and checksum.
1726: * @see #printContents
1727: *
1728: */
1729: public synchronized String toString() {
1730: return new String("Blob[length=" + size + ";checksum="
1731: + toHex(checksum()) + "]");
1732: }
1733:
1734: /**
1735: * Prints the contents of this Blob.
1736: * Formats the data neatly and prints it to System.out.
1737: * @see #toString
1738: *
1739: */
1740: public synchronized void printContents() {
1741:
1742: int x, bytesPast;
1743: String holdStr;
1744: char rep[] = new char[16];
1745: byte b;
1746: long cksum = checksum();
1747:
1748: // where to go?
1749: PrintStream o = System.out;
1750:
1751: // current output width
1752: int currWidth = 86;
1753:
1754: holdStr = ("Blob: length = " + size + " -- Checksum = "
1755: + toHex(cksum) + " ");
1756: o.print(holdStr);
1757: o.println(strstr((currWidth - holdStr.length()), '-'));
1758:
1759: // If no data, just print the last line and get outta' here
1760: if (size == 0) {
1761: o.println(strstr(currWidth, '-'));
1762: return;
1763: }
1764:
1765: o.print(" 0 | ");
1766:
1767: bytesPast = 0;
1768: beginEnumeration(0);
1769: while (hasMoreBytes()) {
1770:
1771: // print next byte
1772: b = nextByte();
1773: o.print(toHex(b));
1774:
1775: // get the representation
1776: if (between(b, 32, 126)) {
1777: rep[bytesPast % 16] = (char) b;
1778: } else {
1779: rep[bytesPast % 16] = '.';
1780: }
1781:
1782: // incriment pointer
1783: bytesPast++;
1784:
1785: if ((bytesPast % 16) == 0) {
1786: // print out representation
1787: o.print(" <");
1788: for (x = 0; x < 16; x++) {
1789: o.print(rep[x]);
1790: }
1791: o.println(">");
1792:
1793: // Start of new line
1794: if (hasMoreBytes()) {
1795: holdStr = Integer.toString(bytesPast);
1796: o.print(spaces(6 - holdStr.length()));
1797: o.print(holdStr);
1798: o.print(" | ");
1799: }
1800:
1801: } else if ((bytesPast % 4) == 0) {
1802: // end of 4-byte chunk
1803: o.print(" ");
1804:
1805: } else {
1806: o.print(' ');
1807: } // endif
1808:
1809: } // endwhile
1810:
1811: if ((bytesPast % 16) != 0) {
1812: // write out some filler spaces
1813: for (x = (bytesPast % 16); x < 16; x++) {
1814: o.print(" ");
1815: rep[x] = ' ';
1816:
1817: if ((x % 4) == 0) {
1818: // end of (what would have been a) 4-byte chunk
1819: o.print(" ");
1820: } else {
1821: o.print(' ');
1822: } // endif
1823: }
1824:
1825: // print out representation
1826: o.print(" <");
1827: for (x = 0; x < 16; x++) {
1828: o.print(rep[x]);
1829: }
1830: o.println(">");
1831: }
1832:
1833: // Write last dashed line
1834: holdStr = ("---------------"
1835: + strstr(String.valueOf(size).length(), '-')
1836: + "--- Checksum = " + toHex(cksum) + " ");
1837: o.print(holdStr);
1838: o.println(strstr((currWidth - holdStr.length()), '-'));
1839: }
1840:
1841: ////////////////////////////////////////////////////////////
1842: ////////////////////////////////////////////////////////////
1843: ////////////////////////////////////////////////////////////
1844: //
1845: // INTERNAL METHODS
1846: //
1847:
1848: /**
1849: * Appends a new node to the end of the internal list.
1850: * @param newNodeSize Size of the new node to create.
1851: *
1852: */
1853: protected void appendNode(int newNodeSize) {
1854:
1855: // Create a new node -- put it on the end of the list
1856: tail.next = new BlobNode(newNodeSize);
1857:
1858: // Set the new tail
1859: tail = tail.next;
1860: }
1861:
1862: /**
1863: * Seeks a position within the internal list.
1864: * Takes an offset from the beginning and sets the 'curr' pointer
1865: * to the reqested node and returns the offset within that node
1866: * to the correct position.
1867: * @param pos The position within the Blob to seek.
1868: * @return The offest into the current node where the requested
1869: * byte can be found.
1870: * @exception java.lang.IndexOutOfBoundsException If pos is
1871: * outside range of Blob.
1872: *
1873: */
1874: protected int seek(int pos) {
1875: if (!between(pos, 0, (size - 1))) {
1876: throw new IndexOutOfBoundsException("Seek past end:"
1877: + "pos=" + pos + " " + "size=" + size);
1878:
1879: }
1880:
1881: int bytesPast = 0;
1882: curr = this .head;
1883:
1884: while ((curr.size + bytesPast) <= pos) {
1885:
1886: // Go to next node...
1887: bytesPast += curr.size;
1888: curr = curr.next;
1889: }
1890:
1891: // Return the offset into the current node.
1892: return (pos - bytesPast);
1893: }
1894:
1895: /**
1896: * Finds the node before the given node.
1897: * This is the one whose next pointer is the given node.
1898: * @return the node before the supplied node or
1899: * null if the given node is the head node.
1900: * @exception java.util.NoSuchElementException if the node is
1901: * not found in the list.
1902: *
1903: */
1904: protected BlobNode findBefore(BlobNode target) {
1905:
1906: BlobNode bn;
1907:
1908: if (target == head) {
1909: return null;
1910: }
1911:
1912: bn = head;
1913: while (bn.next != target) {
1914: if (bn.next == null) {
1915: throw new NoSuchElementException(
1916: "Couldn't find BlobNode");
1917: }
1918: bn = bn.next;
1919: }
1920: return bn;
1921:
1922: } // end findBefore()
1923:
1924: //
1925: // Internal Enumeration Methods
1926: //
1927:
1928: /**
1929: * Sets up an enumeration of the bytes of the Blob
1930: * starting from a particular point.
1931: * @param pos The location from which to begin enumerating bytes.
1932: * @exception java.lang.IndexOutOfBoundsException If pos and len are
1933: * outside range of Blob.
1934: *
1935: */
1936: protected synchronized void beginEnumeration(int pos) {
1937:
1938: // Set the current position null
1939: enumerationNode = null;
1940:
1941: // Is the data within bounds?
1942: if (!between(pos, 0, (size - 1))) {
1943: throw new IndexOutOfBoundsException();
1944: }
1945:
1946: // Find the position
1947: enumerationPos = seek(pos);
1948: enumerationNode = curr;
1949:
1950: // We're ready to begin enumerating the bytes
1951: } // end beginInternalEnumeration()
1952:
1953: /**
1954: * Returns 'true' if the Blob has more bytes, 'false' if empty.
1955: *
1956: */
1957: protected synchronized boolean hasMoreBytes() {
1958: return (enumerationNode != null);
1959: }
1960:
1961: /**
1962: * Returns the next byte in the Blob.
1963: * @exception java.util.NoSuchElementException If there are no more bytes
1964: * in the current enumeration
1965: *
1966: */
1967: protected synchronized byte nextByte() {
1968:
1969: if (enumerationNode == null) {
1970: throw new NoSuchElementException(
1971: "Past end of current Enumeration");
1972: }
1973:
1974: byte ret = enumerationNode.data[enumerationPos++];
1975:
1976: // check to see if we're at the end of the current node
1977: if (enumerationPos == enumerationNode.size) {
1978:
1979: // At end, go to next node
1980: enumerationNode = enumerationNode.next;
1981: enumerationPos = 0;
1982: }
1983:
1984: // All done
1985: return ret;
1986: } // end nextByte()
1987:
1988: //
1989: // Serialization Methods
1990: //
1991:
1992: /**
1993: * A specialized object write routine.
1994: * This is because java gets a stack overflow error when
1995: * trying to write the linked list. Damn!
1996: *
1997: */
1998: protected void writeObject(ObjectOutputStream out)
1999: throws IOException {
2000: out.defaultWriteObject();
2001:
2002: byte a[] = getBytes();
2003: out.writeObject(a);
2004: }
2005:
2006: /**
2007: * A specialized object read routine.
2008: *
2009: */
2010: protected void readObject(ObjectInputStream in) throws IOException,
2011: ClassNotFoundException {
2012: in.defaultReadObject();
2013:
2014: // Create a new node to contain the one massive chunk of data.
2015: byte a[] = (byte[]) in.readObject();
2016: head = new BlobNode(nodeSize, a, 0, a.length);
2017:
2018: tail = head;
2019: size = a.length;
2020: }
2021:
2022: /*
2023: // to test blob as an input stream ...
2024: public static void main(String args[])
2025: throws Exception
2026: {
2027: Blob b1 = new Blob();
2028: Blob b2 = new Blob();
2029: Blob b3 = new Blob();
2030:
2031: int size = 2048;
2032: try
2033: {
2034: size = Integer.parseInt(args[0]);
2035: }
2036: catch (Exception ignore) {}
2037:
2038: System.out.println("size: " + size);
2039:
2040: // load b1
2041: for (int i = 0; i < size; i++)
2042: {
2043: int c = i;
2044: b1.append((byte)c);
2045: // System.out.println(i + ": appending: " + c + " " + (int)(b1.byteAt(i)));
2046: }
2047:
2048: // load b2 from b1 via the input stream
2049: b2.read(b1.inputStream());
2050:
2051: // load b3 from b1 (not using stream)
2052: b3.append(b1);
2053:
2054: System.out.println("b1 size: " + b1.length());
2055: System.out.println("b2 size: " + b2.length());
2056: System.out.println("b3 size: " + b3.length());
2057:
2058: // compare
2059: if (b2.equals(b1))
2060: {
2061: System.out.println(" b2.equals(b1) passed");
2062: }
2063: else
2064: {
2065: System.out.println(" b2.equals(b1) failed");
2066: }
2067:
2068: // compare
2069: if (b3.equals(b1))
2070: {
2071: System.out.println(" b3.equals(b1) passed");
2072: }
2073: else
2074: {
2075: System.out.println(" b3.equals(b1) failed");
2076: }
2077:
2078: try
2079: {
2080: System.out.println("comparing b1 and b2 byte by byte");
2081: for (int i = 0; i < b1.length(); i++)
2082: {
2083: if (b1.byteAt(i) != b2.byteAt(i))
2084: {
2085: System.out.println("mismatch at: " + i + " : b1 = " + b1.byteAt(i) + " : b2 = " + b2.byteAt(i));
2086: }
2087: }
2088: System.out.println(b1.length() + " bytes compared");
2089: }
2090: catch (Exception e) { System.out.println(e); }
2091:
2092: try
2093: {
2094: System.out.println("comparing b1 and b3 byte by byte");
2095: for (int i = 0; i < b1.length(); i++)
2096: {
2097: if (b1.byteAt(i) != b3.byteAt(i))
2098: {
2099: System.out.println("mismatch at: " + i + " : b1 = " + b1.byteAt(i) + " : b3 = " + b3.byteAt(i));
2100: }
2101: }
2102: System.out.println(b1.length() + " bytes compared");
2103: }
2104: catch (Exception e) { System.out.println(e); }
2105:
2106: InputStream in = b1.inputStream();
2107: for (int i = 0; i < b1.length(); i++)
2108: {
2109: int available = in.available();
2110: int b = in.read();
2111: System.out.println(i + " : avail= " + available + " : read = " + b);
2112: }
2113: System.out.println("final avail= " + in.available());
2114: in.close();
2115:
2116: } // main
2117: */
2118:
2119: } // end Blob
2120:
2121: /**
2122: * A node in the underlying data-storage structure of the Blob class.
2123: * @see Blob
2124: * @author T. Gee
2125: *
2126: */
2127: class BlobNode implements Cloneable, Serializable {
2128:
2129: /**
2130: *
2131: */
2132: private static final long serialVersionUID = 3833749897282336560L;
2133:
2134: /**
2135: * The next BlobNode in the list, null if last node.
2136: */
2137: BlobNode next = null;
2138:
2139: /**
2140: * The number of bytes currently in this node
2141: */
2142: int size;
2143:
2144: /**
2145: * The actual data held by this node
2146: */
2147: byte data[];
2148:
2149: /**
2150: * Constructs a new, empty node with the requested capacity.
2151: * @param capacity The size of this node's internal storage.
2152: *
2153: */
2154: public BlobNode(int capacity) {
2155: data = new byte[capacity];
2156: size = 0;
2157: }
2158:
2159: /**
2160: * Constructs a new node initialized with the supplied data.
2161: * @param capacity The size of the new node -- if smaller than
2162: * 'data', will be increased to accomidate all of data.
2163: * @param data The data to use to initialize this BlobNode.
2164: *
2165: */
2166: public BlobNode(int capacity, byte arr[], int startPos, int len) {
2167: data = new byte[Math.max(capacity, len)];
2168: System.arraycopy(arr, startPos, data, 0, len);
2169: size = len;
2170: }
2171:
2172: /**
2173: * Gets the number of bytes of free storage within this node.
2174: * @return the number of free bytes.
2175: *
2176: */
2177: public int freespace() {
2178: return data.length - size;
2179: }
2180:
2181: /**
2182: * Clones the node.
2183: *
2184: */
2185: public Object clone() {
2186: BlobNode b = new BlobNode(data.length);
2187:
2188: System.arraycopy(this .data, 0, b.data, 0, size);
2189: b.size = this .size;
2190:
2191: return b;
2192: }
2193:
2194: } // end BlobNode
|