/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil -*- * * The contents of this file are subject to the Mozilla Public License * Version 1.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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Grendel mail/news client. * * The Initial Developer of the Original Code is Netscape Communications * Corporation. Portions created by Netscape are Copyright (C) 1997 * Netscape Communications Corporation. All Rights Reserved. * * Contributors: Edwin Woudt */ package calypso.util; import java.util.*; import java.io.ByteArrayInputStream; import java.io.FileInputStream; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.io.PrintStream; import java.io.RandomAccessFile; /** * This class is similar to java/lang/StringBuffer with the following changes: * */ public final class ByteBuf { private byte value[]; private int count; // static ByteBufRecycler gRecycler; /** * Constructs an empty String buffer, reusing one from the * recycler. */ static synchronized public ByteBuf Alloc() { return new ByteBuf(); // if (gRecycler == null) { // gRecycler = new ByteBufRecycler(); // } // return gRecycler.alloc(); } /** * Release a ByteBuf to the recycler. It is up to the caller * to ensure that buffer is no longer being used otherwise * unpredicatable program behavior will result. */ static synchronized public void Recycle(ByteBuf aBuf) { // if (gRecycler == null) { // gRecycler = new ByteBufRecycler(); // } // gRecycler.recycle(aBuf); } /** * Empty the recycler discarding any cached ByteBuf objects */ static synchronized public void EmptyRecycler() { // if (null != gRecycler) { // gRecycler = null; // } } static void classFinalize() throws Throwable { // Poof! Now we are unloadable even though we have a static // variable. } /** * Constructs an empty String buffer. */ public ByteBuf() { this(16); } /** * Constructs an empty byte buffer with the specified initial length. * @param length the initial length */ public ByteBuf(int length) { value = new byte[length]; } /** * Constructs a byte buffer with the specified initial value. * @param str the initial value of the buffer */ public ByteBuf(String str) { this(str.length() + 16); append(str); } public ByteBuf(byte bytes[], int offset, int length) { value = new byte[length]; System.arraycopy(bytes, offset, value, 0, length); count = length; } /** * Returns the length (character count) of the buffer. */ public int length() { return count; } /** * Returns the current capacity of the String buffer. The capacity * is the amount of storage available for newly inserted * characters; beyond which an allocation will occur. */ public int capacity() { return value.length; } /** * Ensures that the capacity of the buffer is at least equal to the * specified minimum. * @param minimumCapacity the minimum desired capacity */ public void ensureCapacity(int minimumCapacity) { int maxCapacity = value.length; if (minimumCapacity > maxCapacity) { int newCapacity = (maxCapacity + 1) * 2; if (minimumCapacity > newCapacity) { newCapacity = minimumCapacity; } byte newValue[] = new byte[newCapacity]; System.arraycopy(value, 0, newValue, 0, count); value = newValue; } } /** * Sets the length of the String. If the length is reduced, characters * are lost. If the length is extended, the values of the new characters * are set to 0. * @param newLength the new length of the buffer * @exception StringIndexOutOfBoundsException If the length is invalid. */ public void setLength(int newLength) { if (newLength < 0) { throw new StringIndexOutOfBoundsException(newLength); } if (count < newLength) { ensureCapacity(newLength); for (; count < newLength; count++) { value[count] = (byte)'\0'; } } count = newLength; } /** * Returns the byte at the specified index. An index ranges * from 0..length()-1. * @param index the index of the desired character * @exception StringIndexOutOfBoundsException If the index is invalid. */ public byte byteAt(int index) { if ((index < 0) || (index >= count)) { throw new StringIndexOutOfBoundsException(index); } return value[index]; } /** * Copies the characters of the specified substring (determined by * srcBegin and srcEnd) into the character array, starting at the * array's dstBegin location. Both srcBegin and srcEnd must be legal * indexes into the buffer. * @param srcBegin begin copy at this offset in the String * @param srcEnd stop copying at this offset in the String * @param dst the array to copy the data into * @param dstBegin offset into dst * @exception StringIndexOutOfBoundsException If there is an invalid index into the buffer. */ public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) { if ((srcBegin < 0) || (srcBegin >= count)) { throw new StringIndexOutOfBoundsException(srcBegin); } if ((srcEnd < 0) || (srcEnd > count)) { throw new StringIndexOutOfBoundsException(srcEnd); } if (srcBegin < srcEnd) { System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin); } } /** * Changes the byte at the specified index to be ch. * @param index the index of the character * @param ch the new character * @exception StringIndexOutOfBoundsException If the index is invalid. */ public void setByteAt(int index, byte b) { if ((index < 0) || (index >= count)) { throw new StringIndexOutOfBoundsException(index); } value[index] = b; } /** * Appends an object to the end of this buffer. * @param obj the object to be appended * @return the ByteBuf itself, NOT a new one. */ public ByteBuf append(Object obj) { return append(String.valueOf(obj)); } /** * Appends a String to the end of this buffer. This just appends one byte * per char; it strips off the upper 8 bits. If you want the localized * method of converting chars to bytes, use append(String.getBytes()). * @param str the String to be appended * @return the ByteBuf itself, NOT a new one. */ public ByteBuf append(String str) { if (str == null) { str = String.valueOf(str); } int len = str.length(); ensureCapacity(count + len); for (int i=0 ; i count)) { throw new StringIndexOutOfBoundsException(); } int len = str.length(); ensureCapacity(count + len); System.arraycopy(value, offset, value, offset + len, count - offset); for (int i=0 ; i count)) { throw new StringIndexOutOfBoundsException(); } int len = str.length; ensureCapacity(count + len); System.arraycopy(value, offset, value, offset + len, count - offset); System.arraycopy(str, 0, value, offset, len); count += len; return this; } /** * Inserts a boolean into the String buffer. * @param offset the offset at which to insert * @param b the boolean to insert * @return the ByteBuf itself, NOT a new one. * @exception StringIndexOutOfBoundsException If the offset is invalid. */ public ByteBuf insert(int offset, boolean b) { return insert(offset, String.valueOf(b)); } /** * Inserts a byte into the String buffer. * @param offset the offset at which to insert * @param ch the character to insert * @return the ByteBuf itself, NOT a new one. * @exception StringIndexOutOfBoundsException If the offset invalid. */ public ByteBuf insert(int offset, byte b) { ensureCapacity(count + 1); System.arraycopy(value, offset, value, offset + 1, count - offset); value[offset] = b; count += 1; return this; } /** * Inserts an integer into the String buffer. * @param offset the offset at which to insert * @param i the integer to insert * @return the ByteBuf itself, NOT a new one. * @exception StringIndexOutOfBoundsException If the offset is invalid. */ public ByteBuf insert(int offset, int i) { return insert(offset, String.valueOf(i)); } /** * Inserts a long into the String buffer. * @param offset the offset at which to insert * @param l the long to insert * @return the ByteBuf itself, NOT a new one. * @exception StringIndexOutOfBoundsException If the offset is invalid. */ public ByteBuf insert(int offset, long l) { return insert(offset, String.valueOf(l)); } /** * Inserts a float into the String buffer. * @param offset the offset at which to insert * @param f the float to insert * @return the ByteBuf itself, NOT a new one. * @exception StringIndexOutOfBoundsException If the offset is invalid. */ public ByteBuf insert(int offset, float f) { return insert(offset, String.valueOf(f)); } /** * Inserts a double into the String buffer. * @param offset the offset at which to insert * @param d the double to insert * @return the ByteBuf itself, NOT a new one. * @exception StringIndexOutOfBoundsException If the offset is invalid. */ public ByteBuf insert(int offset, double d) { return insert(offset, String.valueOf(d)); } /** * Reverse the order of the characters in the String buffer. */ public ByteBuf reverse() { int n = count - 1; for (int j = (n-1) >> 1; j >= 0; --j) { byte temp = value[j]; value[j] = value[n - j]; value[n - j] = temp; } return this; } /** * Converts to a String representing the data in the buffer. */ public String toString() { return new String(value, 0, count); } public byte[] toBytes() { return value; } /** * Compares this ByteBuf to another ByteBuf. Returns true if the * other ByteBuf is equal to this ByteBuf; that is, has the same length * and the same characters in the same sequence. Upper case * characters are folded to lower case before they are compared. * * @param anotherString the String to compare this String against * @return true if the Strings are equal, ignoring case; false otherwise. */ public boolean equalsIgnoreCase(ByteBuf anotherString) { if ((anotherString != null) && (anotherString.count == count)) { for (int i = 0; i < count; i++) { byte c1 = value[i]; byte c2 = anotherString.value[i]; if (c1 != c2) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. // If the results match, then the comparison scan should // continue. char u1 = Character.toUpperCase((char) c1); char u2 = Character.toUpperCase((char) c2); if (u1 == u2) continue; // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) continue; return false; } } return true; } return false; } public boolean equals(Object aObject) { if (aObject != null) { if (aObject instanceof ByteBuf) { return equals((ByteBuf) aObject); } else if (aObject instanceof String) { return equals((String) aObject); } } return false; } /** * Compares this ByteBuf to another ByteBuf. Returns true if the * other ByteBuf is equal to this ByteBuf; that is, has the same length * and the same characters in the same sequence. * * @param anotherString the String to compare this String against * @return true if the Strings are equal, ignoring case; false otherwise. */ public boolean equals(ByteBuf anotherString) { if ((anotherString != null) && (anotherString.count == count)) { for (int i = 0; i < count; i++) { byte c1 = value[i]; byte c2 = anotherString.value[i]; if (c1 != c2) { return false; } } return true; } return false; } /** * Compares this ByteBuf to another String. Returns true if the * other String is equal to this ByteBuf; that is, has the same length * and the same characters in the same sequence. (No localization is done; * if the string doesn't contain 8-bit chars, it won't be equal to this * ByteBuf.) * * @param anotherString the String to compare this String against * @return true if the Strings are equal, ignoring case; false otherwise. */ public boolean equals(String anotherString) { if ((anotherString != null) && (anotherString.length() == count)) { for (int i = 0; i < count; i++) { byte c1 = value[i]; byte c2 = (byte) anotherString.charAt(i); if (c1 != c2) { return false; } } return true; } return false; } /** * Tests if two byte regions are equal. *

* If toffset or ooffset is negative, or * if toffset+length is greater than the * length of this ByteBuf, or if * ooffset+length is greater than the * length of the argument, then this method returns * false. * * @param toffset the starting offset of the subregion in this ByteBuf. * @param other the other bytes. * @param ooffset the starting offset of the subregion in the argument. * @param len the number of bytes to compare. * @return true if the specified subregion of this ByteBuf * exactly matches the specified subregion of the argument; * false otherwise. */ public boolean regionMatches(int toffset, byte other[], int ooffset, int len) { return regionMatches(false, toffset, other, ooffset, len); } /** * Tests if two byte regions are equal. *

* If toffset or ooffset is negative, or * if toffset+length is greater than the * length of this ByteBuf, or if * ooffset+length is greater than the * length of the argument, then this method returns * false. * * @param ignoreCase if true, ignore case when comparing * bytes (treating them as characters). * @param toffset the starting offset of the subregion in this * ByteBuf. * @param other the other bytes. * @param ooffset the starting offset of the subregion in the * argument. * @param len the number of bytes to compare. * @return true if the specified subregion of this ByteBuf * matches the specified subregion of the argument; * false otherwise. Whether the matching is exact * or case insensitive depends on the ignoreCase * argument. */ public boolean regionMatches(boolean ignoreCase, int toffset, byte other[], int ooffset, int len) { /* Lifted (and changed from char to byte) from java.lang.String. */ byte ta[] = value; int to = toffset; int tlim = count; byte pa[] = other; int po = ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > count - len) || (ooffset > other.length - len)) { return false; } while (len-- > 0) { byte c1 = ta[to++]; byte c2 = pa[po++]; if (c1 == c2) continue; if (ignoreCase) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. // If the results match, then the comparison scan should // continue. char u1 = Character.toUpperCase((char) c1); char u2 = Character.toUpperCase((char) c2); if (u1 == u2) continue; // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) continue; } return false; } return true; } public boolean regionMatches(int toffset, ByteBuf other, int ooffset, int len) { return regionMatches(false, toffset, other.value, ooffset, len); } public boolean regionMatches(boolean ignoreCase, int toffset, ByteBuf other, int ooffset, int len) { return regionMatches(ignoreCase, toffset, other.value, ooffset, len); } /** * Tests if two byte regions are equal. *

* If toffset or ooffset is negative, or * if toffset+length is greater than the * length of this ByteBuf, or if * ooffset+length is greater than the * length of the argument, then this method returns * false. * * @param toffset the starting offset of the subregion in this ByteBuf. * @param other the other String. * @param ooffset the starting offset of the subregion in the argument. * @param len the number of bytes/characters to compare. * @return true if the specified subregion of this ByteBuf * exactly matches the specified subregion of the String argument; * false otherwise. */ public boolean regionMatches(int toffset, String other, int ooffset, int len) { return regionMatches(false, toffset, other, ooffset, len); } /** * Tests if two byte regions are equal. *

* If toffset or ooffset is negative, or * if toffset+length is greater than the * length of this ByteBuf, or if * ooffset+length is greater than the * length of the argument, then this method returns * false. * * @param ignoreCase if true, ignore case when comparing * bytes (treating them as characters). * @param toffset the starting offset of the subregion in this * ByteBuf. * @param other the other String. * @param ooffset the starting offset of the subregion in the * String argument. * @param len the number of bytes to compare. * @return true if the specified subregion of this ByteBuf * matches the specified subregion of the String argument; * false otherwise. Whether the matching is exact * or case insensitive depends on the ignoreCase * argument. */ public boolean regionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len) { /* Lifted (and changed from char to byte) from java.lang.String. */ byte ta[] = value; int to = toffset; int tlim = count; int po = ooffset; // Note: toffset, ooffset, or len might be near -1>>>1. if ((ooffset < 0) || (toffset < 0) || (toffset > count - len) || (ooffset > other.length() - len)) { return false; } while (len-- > 0) { byte c1 = ta[to++]; byte c2 = (byte) other.charAt(po++); if (c1 == c2) continue; if (ignoreCase) { // If characters don't match but case may be ignored, // try converting both characters to uppercase. // If the results match, then the comparison scan should // continue. char u1 = Character.toUpperCase((char) c1); char u2 = Character.toUpperCase((char) c2); if (u1 == u2) continue; // Unfortunately, conversion to uppercase does not work properly // for the Georgian alphabet, which has strange rules about case // conversion. So we need to make one last check before // exiting. if (Character.toLowerCase(u1) == Character.toLowerCase(u2)) continue; } return false; } return true; } public int indexOf(int ch) { return indexOf(ch, 0); } public int indexOf(int ch, int fromIndex) { int max = count; byte v[] = value; if (fromIndex < 0) { fromIndex = 0; } else if (fromIndex >= count) { // Note: fromIndex might be near -1>>>1. return -1; } for (int i = fromIndex ; i < max ; i++) { if (v[i] == ch) { return i; } } return -1; } public void remove(int fromIndex) { remove(fromIndex, count); } /** * Remove characters from the ByteBuf starting at fromIndex and up * to but not including toIndex. If toIndex is beyond the length of * the ByteBuf then it is automatically clamped to the end of the * ByteBuf. If fromIndex is out of range a StringIndexOutOfBoundsException * is thrown. */ public void remove(int fromIndex, int toIndex) { if ((fromIndex >= toIndex) || (fromIndex >= count)) { throw new StringIndexOutOfBoundsException(fromIndex); } if (toIndex > count) toIndex = count; if (toIndex == count) { count = fromIndex; return; } System.arraycopy(value, toIndex, value, fromIndex, count - toIndex); count -= toIndex - fromIndex; } public int toInteger() throws NumberFormatException { int result = 0; int sign = 1; int i = 0; if (count == 0) { throw new NumberFormatException(); } while (i < count - 1 && (value[i] <= ' ')) { i++; } if (value[i] == '-' && i < count - 1) { sign = -1; i++; } do { byte b = value[i]; if (b >= '0' || b <= '9') { result = (result * 10) + (b - '0'); } else { throw new NumberFormatException(); } i++; } while (i < count && value[i] > ' '); while (i < count && value[i] < ' ') { i++; } if (i < count) { throw new NumberFormatException(); } return result * sign; } public ByteBuf trim() { int i=0; while (i < count && value[i] <= ' ') i++; if (i > 0) { count -= i; System.arraycopy(value, i, value, 0, count); } while (count > 0 && value[count-1] <= ' ') count--; return this; } /** Write to the given output stream a detailed description of each byte in this buffer. */ public void fullDump(PrintStream out) { fullDump(out, 0, count); } /** Write to the given output stream a detailed description of the given bytes in this buffer. */ public void fullDump(PrintStream out, int start, int end) { for (int i=start ; i 0) count += i; return i; } /** Invokes RandomAccessFile.read(), appending the bytes to this Bytebuf. (A RandomAccessFile is not an InputStream, because Java is a crock.) @return the number of bytes read, or -1 if eof. */ public int read(RandomAccessFile file, int max_bytes) throws IOException { ensureCapacity(count + max_bytes); int i = file.read(value, count, max_bytes); if (i > 0) count += i; return i; } /** Writes the contents to the given output stream. */ public void write(OutputStream out) throws IOException { out.write(value, 0, count); } /** Writes the contents to the given RandomAccessFile. */ public void write(RandomAccessFile out) throws IOException { out.write(value, 0, count); } /** Creates a new InputStream whose content is this ByteBuf. Note that changing the ByteBuf can affect the stream; the data is not copied. */ public InputStream makeInputStream() { return new ByteArrayInputStream(value, 0, count); } }