| /* |
| * Copyright 1999,2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache 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.apache.org/licenses/LICENSE-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.apache.catalina.cluster.io; |
| |
| /** |
| * The XByteBuffer provides a dual functionality. |
| * One, it stores message bytes and automatically extends the byte buffer if needed.<BR> |
| * Two, it can encode and decode packages so that they can be defined and identified |
| * as they come in on a socket. |
| * |
| * @author Filip Hanik |
| * @version $Revision$, $Date$ |
| */ |
| |
| |
| public class XByteBuffer |
| { |
| |
| public static org.apache.commons.logging.Log log = |
| org.apache.commons.logging.LogFactory.getLog( XByteBuffer.class ); |
| |
| /** |
| * This is a package header, 7 bytes |
| */ |
| public static final byte[] START_DATA = {70,76,84,50,48,48,50}; |
| /** |
| * This is the package footer, 7 bytes |
| */ |
| public static final byte[] END_DATA = {84,76,70,50,48,48,51}; |
| //A package looks like, always. |
| /** |
| * START_DATA - 7 bytes |
| * SIZE - 4 bytes - size of the data package |
| * DATA - should be as many bytes as the prev SIZE |
| * END_DATA - 7 bytes |
| */ |
| |
| /** |
| * Default size on the initial byte buffer |
| */ |
| static final int DEF_SIZE = 1024; |
| /** |
| * Default size to extend the buffer with |
| */ |
| static final int DEF_EXT = 1024; |
| /** |
| * Variable to hold the data |
| */ |
| protected byte[] buf = null; |
| /** |
| * Current length of data in the buffer |
| */ |
| protected int bufSize = 0; |
| |
| /** |
| * Constructs a new XByteBuffer |
| * @param size - the initial size of the byte buffer |
| */ |
| public XByteBuffer(int size) { |
| buf = new byte[size]; |
| }//XByteBuffer |
| |
| /** |
| * Constructs a new XByteBuffer with an initial size of 1024 bytes |
| */ |
| public XByteBuffer() { |
| this(DEF_SIZE); |
| }//XByteBuffer |
| |
| /** |
| * Returns the bytes in the buffer, in its exact length |
| */ |
| public byte[] getBytes() { |
| byte[] b = new byte[bufSize]; |
| System.arraycopy(buf,0,b,0,bufSize); |
| return b; |
| }//getBytes |
| |
| /** |
| * Resets the buffer |
| */ |
| public void clear() { |
| bufSize = 0; |
| } |
| |
| /** |
| * Appends the data to the buffer. If the data is incorrectly formatted, ie, the data should always start with the |
| * header, false will be returned and the data will be discarded. |
| * @param b - bytes to be appended |
| * @param off - the offset to extract data from |
| * @param len - the number of bytes to append. |
| * @return true if the data was appended correctly. Returns false if the package is incorrect, ie missing header or something, or the length of data is 0 |
| */ |
| public boolean append(byte[] b, int off, int len) { |
| if ((off < 0) || (off > b.length) || (len < 0) || |
| ((off + len) > b.length) || ((off + len) < 0)) { |
| throw new IndexOutOfBoundsException(); |
| } else if (len == 0) { |
| return false; |
| }//end if |
| |
| int newcount = bufSize + len; |
| if (newcount > buf.length) { |
| byte newbuf[] = new byte[Math.max(buf.length << 1, newcount)]; |
| System.arraycopy(buf, 0, newbuf, 0, bufSize); |
| buf = newbuf; |
| } |
| System.arraycopy(b, off, buf, bufSize, len); |
| bufSize = newcount; |
| |
| if (bufSize > START_DATA.length && (firstIndexOf(buf,0,START_DATA)==-1)){ |
| bufSize = 0; |
| log.error("Discarded the package, invalid header"); |
| return false; |
| } |
| return true; |
| }//append |
| |
| |
| /** |
| * Internal mechanism to make a check if a complete package exists |
| * within the buffer |
| * @return - true if a complete package (header,size,data,footer) exists within the buffer |
| */ |
| public int countPackages() |
| { |
| int cnt = 0; |
| int pos = START_DATA.length; |
| int start = 0; |
| |
| while ( start < bufSize ) { |
| //first check start header |
| int index = XByteBuffer.firstIndexOf(buf,start,START_DATA); |
| //if the header (START_DATA) isn't the first thing or |
| //the buffer isn't even 10 bytes |
| if ( index != start || ((bufSize-start)<10) ) break; |
| //then get the size 4 bytes |
| int size = toInt(buf, pos); |
| //now the total buffer has to be long enough to hold |
| //START_DATA.length+4+size+END_DATA.length |
| pos = start + START_DATA.length + 4 + size; |
| if ( (pos + END_DATA.length) > bufSize) break; |
| //and finally check the footer of the package END_DATA |
| int newpos = firstIndexOf(buf, pos, END_DATA); |
| //mismatch, there is no package |
| if (newpos != pos) break; |
| //increase the packet count |
| cnt++; |
| //reset the values |
| start = pos + END_DATA.length; |
| pos = start + START_DATA.length; |
| }//while |
| return cnt; |
| }//getSize |
| |
| /** |
| * Method to check if a package exists in this byte buffer. |
| * @return - true if a complete package (header,size,data,footer) exists within the buffer |
| */ |
| public boolean doesPackageExist() { |
| return (countPackages()>0); |
| }//doesPackageExist |
| |
| /** |
| * Extracts the message bytes from a package. |
| * If no package exists, a IllegalStateException will be thrown. |
| * @param clearFromBuffer - if true, the package will be removed from the byte buffer |
| * @return - returns the actual message bytes (header, size and footer not included). |
| */ |
| public byte[] extractPackage(boolean clearFromBuffer) throws java.io.IOException { |
| int psize = countPackages(); |
| if ( psize == 0 ) throw new java.lang.IllegalStateException("No package exists in XByteBuffer"); |
| int size = toInt(buf, START_DATA.length); |
| byte[] data = new byte[size]; |
| System.arraycopy(buf,START_DATA.length+4,data,0,size); |
| if ( clearFromBuffer ) { |
| int totalsize = START_DATA.length + 4 + size + END_DATA.length; |
| bufSize = bufSize - totalsize; |
| System.arraycopy(buf, totalsize, buf, 0, bufSize); |
| } |
| java.io.ByteArrayInputStream bin = new java.io.ByteArrayInputStream(data); |
| java.util.zip.GZIPInputStream gin = new java.util.zip.GZIPInputStream(bin); |
| byte[] tmp = new byte[1024]; |
| byte[] result = new byte[0]; |
| int length = gin.read(tmp); |
| while ( length > 0 ) { |
| byte[] tmpdata = result; |
| result = new byte[result.length+length]; |
| System.arraycopy(tmpdata,0,result,0,tmpdata.length); |
| System.arraycopy(tmp,0,result,tmpdata.length,length); |
| length = gin.read(tmp); |
| } |
| gin.close(); |
| return result; |
| }//extractPackage |
| |
| /** |
| * Convert four bytes to an int |
| * @param b - the byte array containing the four bytes |
| * @param off - the offset |
| * @return the integer value constructed from the four bytes |
| * @exception java.lang.ArrayIndexOutOfBoundsException |
| */ |
| public static int toInt(byte[] b,int off){ |
| return ( ( (int) b[off+3]) & 0xFF) + |
| ( ( ( (int) b[off+2]) & 0xFF) << 8) + |
| ( ( ( (int) b[off+1]) & 0xFF) << 16) + |
| ( ( ( (int) b[off+0]) & 0xFF) << 24); |
| }//toInt |
| |
| /** |
| * Convert eight bytes to a long |
| * @param b - the byte array containing the four bytes |
| * @param off - the offset |
| * @return the long value constructed from the eight bytes |
| * @exception java.lang.ArrayIndexOutOfBoundsException |
| */ |
| public static long toLong(byte[] b,int off){ |
| return ( ( (long) b[off+7]) & 0xFF) + |
| ( ( ( (long) b[off+6]) & 0xFF) << 8) + |
| ( ( ( (long) b[off+5]) & 0xFF) << 16) + |
| ( ( ( (long) b[off+4]) & 0xFF) << 24) + |
| ( ( ( (long) b[off+3]) & 0xFF) << 32) + |
| ( ( ( (long) b[off+2]) & 0xFF) << 40) + |
| ( ( ( (long) b[off+1]) & 0xFF) << 48) + |
| ( ( ( (long) b[off+0]) & 0xFF) << 56); |
| }//toInt |
| |
| |
| /** |
| * Converts an integer to four bytes |
| * @param n - the integer |
| * @return - four bytes in an array |
| */ |
| public static byte[] toBytes(int n) { |
| byte[] b = new byte[4]; |
| b[3] = (byte) (n); |
| n >>>= 8; |
| b[2] = (byte) (n); |
| n >>>= 8; |
| b[1] = (byte) (n); |
| n >>>= 8; |
| b[0] = (byte) (n); |
| return b; |
| } //toBytes |
| |
| |
| /** |
| * Converts an long to eight bytes |
| * @param n - the long |
| * @return - eight bytes in an array |
| */ |
| public static byte[] toBytes(long n) { |
| byte[] b = new byte[8]; |
| b[7] = (byte) (n); |
| n >>>= 8; |
| b[6] = (byte) (n); |
| n >>>= 8; |
| b[5] = (byte) (n); |
| n >>>= 8; |
| b[4] = (byte) (n); |
| n >>>= 8; |
| b[3] = (byte) (n); |
| n >>>= 8; |
| b[2] = (byte) (n); |
| n >>>= 8; |
| b[1] = (byte) (n); |
| n >>>= 8; |
| b[0] = (byte) (n); |
| return b; |
| } //toBytes |
| |
| |
| /** |
| * Similar to a String.IndexOf, but uses pure bytes |
| * @param src - the source bytes to be searched |
| * @param srcOff - offset on the source buffer |
| * @param find - the string to be found within src |
| * @return - the index of the first matching byte. -1 if the find array is not found |
| */ |
| public static int firstIndexOf(byte[] src, int srcOff, byte[] find){ |
| int result = -1; |
| if (find.length > src.length) return result; |
| if (find.length == 0 || src.length == 0) return result; |
| if (srcOff >= src.length ) throw new java.lang.ArrayIndexOutOfBoundsException(); |
| boolean found = false; |
| int srclen = src.length; |
| int findlen = find.length; |
| byte first = find[0]; |
| int pos = srcOff; |
| while (!found) { |
| //find the first byte |
| while (pos < srclen){ |
| if (first == src[pos]) |
| break; |
| pos++; |
| } //while |
| if (pos >= srclen) |
| return -1; |
| |
| //we found the first character |
| //match the rest of the bytes - they have to match |
| if ( (srclen - pos) < findlen) |
| return -1; |
| //assume it does exist |
| found = true; |
| for (int i = 1; ( (i < findlen) && found); i++) |
| found = found && (find[i] == src[pos + i]); |
| if (found) |
| result = pos; |
| else if ( (srclen - pos) < findlen) |
| return -1; //no more matches possible |
| else |
| pos++; |
| } //while |
| return result; |
| } //firstIndexOf |
| |
| /** |
| * Creates a complete data package |
| * @param indata - the message data to be contained within the package |
| * @return - a full package (header,size,data,footer) |
| */ |
| public static byte[] createDataPackage(byte[] indata) throws java.io.IOException { |
| java.io.ByteArrayOutputStream bout = new java.io.ByteArrayOutputStream(indata.length/2); |
| java.util.zip.GZIPOutputStream gout = new java.util.zip.GZIPOutputStream(bout); |
| gout.write(indata); |
| gout.flush(); |
| gout.close(); |
| byte[] data = bout.toByteArray(); |
| byte[] result = new byte[START_DATA.length+4+data.length+END_DATA.length]; |
| System.arraycopy(START_DATA,0,result,0,START_DATA.length); |
| System.arraycopy(toBytes(data.length),0,result,START_DATA.length,4); |
| System.arraycopy(data,0,result,START_DATA.length+4,data.length); |
| System.arraycopy(END_DATA,0,result,START_DATA.length+4+data.length,END_DATA.length); |
| return result; |
| }//createDataPackage |
| |
| public static void main(String[] args) throws Exception { |
| log.info("Before="+Integer.MAX_VALUE); |
| byte[] d = toBytes(Integer.MAX_VALUE); |
| log.info("After="+toInt(d,0)); |
| |
| |
| log.info("Before="+Long.MAX_VALUE); |
| d = toBytes(Long.MAX_VALUE); |
| log.info("After="+toLong(d,0)); |
| |
| log.info("Before=" + 4564564); |
| d = toBytes((long)4564564); |
| log.info("After=" + toLong(d, 0)); |
| |
| byte[] d1 = createDataPackage(new byte[] {1}); |
| byte[] d2 = createDataPackage(new byte[] {2}); |
| byte[] d3 = createDataPackage(new byte[] {3}); |
| byte[] test = new byte[d1.length+d2.length+d3.length+5]; |
| System.arraycopy(d1,0,test,0,d1.length); |
| System.arraycopy(d2,0,test,d1.length,d2.length); |
| System.arraycopy(d3,0,test,d2.length+d1.length,d3.length); |
| printBuf(d1); |
| printBuf(d2); |
| printBuf(d3); |
| printBuf(test); |
| XByteBuffer b = new XByteBuffer(); |
| b.append(test,0,test.length); |
| int s = b.countPackages(); |
| log.info("Nr of packages="+s); |
| while ( s > 0 ) { |
| d = b.extractPackage(true); |
| System.out.print("Package d1="); |
| printBuf(d); |
| s--; |
| }//while |
| |
| } |
| |
| public static void printBuf(byte[] b) { |
| StringBuffer buf = new StringBuffer(); |
| for ( int i=0; i<b.length; i++ ) { |
| buf.append(b[i] + " "); |
| } |
| log.info(buf); |
| } |
| |
| }//class |