Java tutorial
/* * Copyright 2001-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.axis.attachments; import org.apache.axis.components.logger.LogFactory; import org.apache.axis.utils.Messages; import org.apache.commons.logging.Log; /** * This class takes the input stream and turns it multiple streams. * * @author Rick Rineholt */ public class BoundaryDelimitedStream extends java.io.FilterInputStream { /** The <code>Log</code> that this class should log all events to. */ protected static Log log = LogFactory.getLog(BoundaryDelimitedStream.class.getName()); protected byte[] boundary = null; /** The boundary length. */ int boundaryLen = 0; /** The boundary length plus crlf. */ int boundaryBufLen = 0; /** The source input stream. */ java.io.InputStream is = null; /** The stream has been closed. */ boolean closed = true; /** eof has been detected. */ boolean eos = false; /** There are no more streams left. */ boolean theEnd = false; /** Minimum to read at one time. */ int readbufsz = 0; /** The buffer we are reading. */ byte[] readbuf = null; /** Where we have read so far in the stream. */ int readBufPos = 0; /** The number of bytes in array. */ int readBufEnd = 0; /** Field BOUNDARY_NOT_FOUND. */ protected static final int BOUNDARY_NOT_FOUND = Integer.MAX_VALUE; // Where in the stream a boundary is located. /** Field boundaryPos. */ int boundaryPos = BOUNDARY_NOT_FOUND; /** The number of streams produced. */ static int streamCount = 0; /** * Signal that a new stream has been created. * * @return */ protected static synchronized int newStreamNo() { log.debug(Messages.getMessage("streamNo", "" + (streamCount + 1))); return ++streamCount; } /** Field streamNo. */ protected int streamNo = -1; // Keeps track of stream /** Field isDebugEnabled. */ static boolean isDebugEnabled = false; /** * Gets the next stream. From the previous using the same buffer size to * read. * * @return the boundary delmited stream, null if there are no more streams. * @throws java.io.IOException if there was an error loading the data for * the next stream */ public synchronized BoundaryDelimitedStream getNextStream() throws java.io.IOException { return getNextStream(readbufsz); } /** * Gets the next stream. From the previous using new buffer reading size. * * @param readbufsz * @return the boundary delmited stream, null if there are no more streams. * @throws java.io.IOException if there was an error loading the data for * the next stream */ protected synchronized BoundaryDelimitedStream getNextStream(int readbufsz) throws java.io.IOException { BoundaryDelimitedStream ret = null; if (!theEnd) { // Create an new boundary stream that comes after this one. ret = new BoundaryDelimitedStream(this, readbufsz); } return ret; } /** * Constructor to create the next stream from the previous one. * * @param prev the previous stream * @param readbufsz how many bytes to make the read buffer * @throws java.io.IOException if there was a problem reading data from * <code>prev</code> */ protected BoundaryDelimitedStream(BoundaryDelimitedStream prev, int readbufsz) throws java.io.IOException { super(null); streamNo = newStreamNo(); boundary = prev.boundary; boundaryLen = prev.boundaryLen; boundaryBufLen = prev.boundaryBufLen; skip = prev.skip; is = prev.is; closed = false; // The new one is not closed. eos = false; // Its not at th EOS. readbufsz = prev.readbufsz; readbuf = prev.readbuf; // Move past the old boundary. readBufPos = prev.readBufPos + boundaryBufLen; readBufEnd = prev.readBufEnd; // find the new boundary. boundaryPos = boundaryPosition(readbuf, readBufPos, readBufEnd); prev.theEnd = theEnd; // The stream. } /** * Create a new boundary stream. * * @param is * @param boundary is the boundary that separates the individual streams. * @param readbufsz lets you have some control over the amount of buffering. * by buffering you can some effiency in searching. * * @throws org.apache.axis.AxisFault */ BoundaryDelimitedStream(java.io.InputStream is, byte[] boundary, int readbufsz) throws org.apache.axis.AxisFault { // super (is); super(null); // we handle everything so this is not necessary, don't won't to hang on to a reference. isDebugEnabled = log.isDebugEnabled(); streamNo = newStreamNo(); closed = false; this.is = is; // Copy the boundary array to make certain it is never altered. this.boundary = new byte[boundary.length]; System.arraycopy(boundary, 0, this.boundary, 0, boundary.length); this.boundaryLen = this.boundary.length; this.boundaryBufLen = boundaryLen + 2; // allways leave room for at least a 2x boundary // Most mime boundaries are 40 bytes or so. this.readbufsz = Math.max((boundaryBufLen) * 2, readbufsz); } private final int readFromStream(final byte[] b) throws java.io.IOException { return readFromStream(b, 0, b.length); } private final int readFromStream(final byte[] b, final int start, final int length) throws java.io.IOException { int minRead = Math.max(boundaryBufLen * 2, length); minRead = Math.min(minRead, length - start); int br = 0; int brTotal = 0; do { br = is.read(b, brTotal + start, length - brTotal); if (br > 0) { brTotal += br; } } while ((br > -1) && (brTotal < minRead)); return (brTotal != 0) ? brTotal : br; } /** * Read from the boundary delimited stream. * @param b is the array to read into. * @param off is the offset * @param len * @return the number of bytes read. -1 if endof stream. * * @throws java.io.IOException */ public synchronized int read(byte[] b, final int off, final int len) throws java.io.IOException { if (closed) { throw new java.io.IOException(Messages.getMessage("streamClosed")); } if (eos) { return -1; } if (readbuf == null) { // Allocate the buffer. readbuf = new byte[Math.max(len, readbufsz)]; readBufEnd = readFromStream(readbuf); if (readBufEnd < 0) { readbuf = null; closed = true; finalClose(); throw new java.io.IOException(Messages.getMessage("eosBeforeMarker")); } readBufPos = 0; // Finds the boundary pos. boundaryPos = boundaryPosition(readbuf, 0, readBufEnd); } int bwritten = 0; // Number of bytes written. // read and copy bytes in. do { // Always allow to have a boundary length left in the buffer. int bcopy = Math.min(readBufEnd - readBufPos - boundaryBufLen, len - bwritten); // never go past the boundary. bcopy = Math.min(bcopy, boundaryPos - readBufPos); if (bcopy > 0) { System.arraycopy(readbuf, readBufPos, b, off + bwritten, bcopy); bwritten += bcopy; readBufPos += bcopy; } if (readBufPos == boundaryPos) { eos = true; // hit the boundary so it the end of the stream. log.debug(Messages.getMessage("atEOS", "" + streamNo)); } else if (bwritten < len) { // need to get more data. byte[] dstbuf = readbuf; if (readbuf.length < len) { dstbuf = new byte[len]; } int movecnt = readBufEnd - readBufPos; // copy what was left over. System.arraycopy(readbuf, readBufPos, dstbuf, 0, movecnt); // Read in the new data. int readcnt = readFromStream(dstbuf, movecnt, dstbuf.length - movecnt); if (readcnt < 0) { readbuf = null; closed = true; finalClose(); throw new java.io.IOException(Messages.getMessage("eosBeforeMarker")); } readBufEnd = readcnt + movecnt; readbuf = dstbuf; readBufPos = 0; // start at the begining. // just move the boundary by what we moved if (BOUNDARY_NOT_FOUND != boundaryPos) { boundaryPos -= movecnt; } else { boundaryPos = boundaryPosition(readbuf, readBufPos, readBufEnd); // See if the boundary is now there. } } } // read till we get the amount or the stream is finished. while (!eos && (bwritten < len)); if (log.isDebugEnabled()) { if (bwritten > 0) { byte tb[] = new byte[bwritten]; System.arraycopy(b, off, tb, 0, bwritten); log.debug(Messages.getMessage("readBStream", new String[] { "" + bwritten, "" + streamNo, new String(tb) })); } } if (eos && theEnd) { readbuf = null; // dealloc even in Java. } return bwritten; } /** * Read from the boundary delimited stream. * @param b is the array to read into. Read as much as possible * into the size of this array. * @return the number of bytes read. -1 if endof stream. * * @throws java.io.IOException */ public int read(byte[] b) throws java.io.IOException { return read(b, 0, b.length); } /** * Read from the boundary delimited stream. * @return The byte read, or -1 if endof stream. * * @throws java.io.IOException */ public int read() throws java.io.IOException { byte[] b = new byte[1]; // quick and dirty. //for now int read = read(b); if (read < 0) { return -1; } else { return b[0] & 0xff; } } /** * Closes the stream. * * @throws java.io.IOException */ public synchronized void close() throws java.io.IOException { if (closed) { return; } log.debug(Messages.getMessage("bStreamClosed", "" + streamNo)); closed = true; // mark it closed. if (!eos) { // We need get this off the stream. // Easy way to flush through the stream; byte[] readrest = new byte[1024 * 16]; int bread = 0; do { bread = read(readrest); } while (bread > -1); } } /** * mark the stream. * This is not supported. * * @param readlimit */ public void mark(int readlimit) { // do nothing } /** * reset the stream. * This is not supported. * * @throws java.io.IOException */ public void reset() throws java.io.IOException { throw new java.io.IOException(Messages.getMessage("attach.bounday.mns")); } /** * markSupported * return false; * * @return */ public boolean markSupported() { return false; } public int available() throws java.io.IOException { int bcopy = readBufEnd - readBufPos - boundaryBufLen; // never go past the boundary. bcopy = Math.min(bcopy, boundaryPos - readBufPos); return Math.max(0, bcopy); } /** * Read from the boundary delimited stream. * * @param searchbuf buffer to read from * @param start starting index * @param end ending index * @return The position of the boundary. Detects the end of the source stream. * @throws java.io.IOException if there was an error manipulating the * underlying stream */ protected int boundaryPosition(byte[] searchbuf, int start, int end) throws java.io.IOException { int foundAt = boundarySearch(searchbuf, start, end); // First find the boundary marker if (BOUNDARY_NOT_FOUND != foundAt) { // Something was found. if (foundAt + boundaryLen + 2 > end) { foundAt = BOUNDARY_NOT_FOUND; } else { // If the marker has a "--" at the end then this is the last boundary. if ((searchbuf[foundAt + boundaryLen] == '-') && (searchbuf[foundAt + boundaryLen + 1] == '-')) { finalClose(); } else if ((searchbuf[foundAt + boundaryLen] != 13) || (searchbuf[foundAt + boundaryLen + 1] != 10)) { // If there really was no crlf at then end then this is not a boundary. foundAt = BOUNDARY_NOT_FOUND; } } } return foundAt; } /* The below uses a standard textbook Boyer-Moore pattern search. */ private int[] skip = null; private int boundarySearch(final byte[] text, final int start, final int end) { // log.debug(">>>>" + start + "," + end); int i = 0, j = 0, k = 0; if (boundaryLen > (end - start)) { return BOUNDARY_NOT_FOUND; } if (null == skip) { skip = new int[256]; java.util.Arrays.fill(skip, boundaryLen); for (k = 0; k < boundaryLen - 1; k++) { skip[boundary[k]] = boundaryLen - k - 1; } } for (k = start + boundaryLen - 1; k < end; k += skip[text[k] & (0xff)]) { // log.debug(">>>>" + k); // printarry(text, k-boundaryLen+1, end); try { for (j = boundaryLen - 1, i = k; (j >= 0) && (text[i] == boundary[j]); j--) { i--; } } catch (ArrayIndexOutOfBoundsException e) { StringBuffer sb = new StringBuffer(); sb.append(">>>" + e); // rr temporary till a boundary issue is resolved. sb.append("start=" + start); sb.append("k=" + k); sb.append("text.length=" + text.length); sb.append("i=" + i); sb.append("boundary.length=" + boundary.length); sb.append("j=" + j); sb.append("end=" + end); log.warn(Messages.getMessage("exception01", sb.toString())); throw e; } if (j == (-1)) { return i + 1; } } // log.debug(">>>> not found" ); return BOUNDARY_NOT_FOUND; } /** * Close the underlying stream and remove all references to it. * * @throws java.io.IOException if the stream could not be closed */ protected void finalClose() throws java.io.IOException { if (theEnd) return; theEnd = true; is.close(); is = null; } /** * Method printarry * * @param b * @param start * @param end */ public static void printarry(byte[] b, int start, int end) { if (log.isDebugEnabled()) { byte tb[] = new byte[end - start]; System.arraycopy(b, start, tb, 0, end - start); log.debug("\"" + new String(tb) + "\""); } } }