/********************************************************************************* * TotalCross Software Development Kit * * Copyright (C) 2000-2012 SuperWaba Ltda. * * All Rights Reserved * * * * This library and virtual machine is distributed in the hope that it will * * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * * * * This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 * * A copy of this license is located in file license.txt at the root of this * * SDK or can be downloaded here: * * http://www.gnu.org/licenses/lgpl-3.0.txt * * * *********************************************************************************/ package totalcross.io; import totalcross.sys.Convert; import totalcross.sys.Vm; /** * BufferedStream offers a faster way to read and write data from streams in a * buffered manner. This is especially useful when reading or writing large * amounts of data. It works like the CompressedByteArrayStream, however it does not * compresses the data like that one. * * Here's a sample code: * <pre> * public void writeLargeFile(String path, byte[] largeData) throws IOException * { * File f = new File(path, File.CREATE_EMPTY); * BufferedStream bs = new BufferedStream(f, BufferedStream.WRITE, 4096); * bs.writeBytes(largeData, 0, largeData.length); * bs.close(); // important! * f.close(); * } * </pre> * * @since TotalCross 1.0 */ public class BufferedStream extends Stream { private Stream stream; private int mode; private byte[] buffer; private int size; private int pos; private byte[] rlbuf; /** Used for opening this buffered stream for reading. */ public static final int READ = 0; /** Used for opening this buffered stream for writing. */ public static final int WRITE = 1; /** * Creates a new buffered stream given the underlying stream and the mode to use. * This constructor will use the default buffer size: 2048 bytes. * @param stream The underlying stream. * @param mode The mode to use - READ or WRITE. * @throws IllegalArgumentIOException if the mode is invalid. */ public BufferedStream(Stream stream, int mode) throws IllegalArgumentIOException { this(stream, mode, 2048); } /** * Creates a new buffered stream given the underlying stream, the mode to use and * the buffer size. * @param stream The underlying stream. * @param mode The mode to use - READ or WRITE. * @param bufferSize The buffer size. * @throws IllegalArgumentIOException if the mode is invalid or the bufferSize is not * a positive number. * @throws NullPointerException if stream is null. */ public BufferedStream(Stream stream, int mode, int bufferSize) throws IllegalArgumentIOException, NullPointerException { if (mode != READ && mode != WRITE) throw new IllegalArgumentIOException("mode", Convert.toString(mode)); if (bufferSize < 0) throw new IllegalArgumentIOException("bufferSize", Convert.toString(bufferSize)); if (stream == null) throw new NullPointerException("Argument 'stream' cannot be null"); this.stream = stream; this.mode = mode; buffer = new byte[bufferSize]; pos = 0; size = mode == READ ? 0 : bufferSize; } public final int readBytes(byte[] buf, int start, int count) throws IllegalArgumentIOException, IOException, NullPointerException { if (mode != READ) throw new IOException("Operation can only be used in READ mode"); if (start < 0) throw new IllegalArgumentIOException("start", Convert.toString(start)); if (count < 0) throw new IllegalArgumentIOException("count", Convert.toString(count)); if (buf == null) throw new NullPointerException("Argument 'buf' cannot be null"); int r = 0, step, max; if (pos == size) // read next block, if needed { pos = 0; size = stream.readBytes(buffer, 0, buffer.length); if (size < 0) { size = 0; if (r == 0) r = -1; } } // Get the maximum to read on this iteration step = count - r; max = size - pos; if (step > max) step = max; // Read bytes Vm.arrayCopy(buffer, pos, buf, start, step); // Update positions r += step; start += step; pos += step; return r; } public final int writeBytes(byte[] buf, int start, int count) throws IllegalArgumentIOException, IOException, NullPointerException { if (mode != WRITE) throw new IOException("Operation can only be used in WRITE mode"); if (start < 0) throw new IllegalArgumentIOException("start", Convert.toString(start)); if (count < 0) throw new IllegalArgumentIOException("count", Convert.toString(count)); if (buf == null) throw new NullPointerException("Argument 'buf' cannot be null"); int w = 0, step, max; while (w != count) { if (pos == size) // write block, if needed { stream.writeBytes(buffer, 0, size); pos = 0; } // Get the maximum to read on this iteration step = count - w; max = size - pos; if (step > max) step = max; // Read bytes Vm.arrayCopy(buf, start, buffer, pos, step); // Update positions w += step; start += step; pos += step; } return w; } /** This method closes this stream, flushing any pending WRITE data. * It does NOT close the underlying stream. */ public final void close() throws IOException { if (mode == WRITE && pos > 0) stream.writeBytes(buffer, 0, pos); } /** Changes the initial Stream to the attached one. * Reusing a BufferedStream throught this method can preserve memory. * @since TotalCross 1.23 */ public void setStream(Stream f) throws IOException, NullPointerException // guich@tc123_34 { if (f == null) throw new NullPointerException("Argument 'f' cannot be null"); this.stream = f; pos = 0; size = mode == READ ? 0 : buffer.length; } /** Returns the Stream attached to this BufferedStream. * @since TotalCross 1.23 */ public Stream getStream() { return stream; } /** * Reads a line of text from this BufferedStream. Any char lower than space * is considered a new line separator. This method correctly handles newlines * with \\n or \\r\\n. * <br><br>Note: this method is VERY slow since it reads a single character per time. Consider using * LineReader instead. * * @return the read line or <code>null</code> if nothing was read. * @throws totalcross.io.IOException * @since SuperWaba 5.61 * @see totalcross.io.LineReader */ public String readLine() throws totalcross.io.IOException { if (rlbuf == null) // guich@tc123_31: no longer static and initialized on first use rlbuf = new byte[256]; byte[] buf = rlbuf; int pos = 0; int r; while ((r = readBytes(buf, pos, 1)) == 1) { if (buf[pos] == '\n') // guich@tc123_47 { if (pos > 0 && buf[pos-1] == '\r') // cut last \r pos--; // note that pos must be same of length, otherwise the String will be constructed with one less character break; } if (++pos == buf.length) // reached buffer size? { byte[] temp = new byte[buf.length+256]; Vm.arrayCopy(buf, 0, temp, 0, pos); rlbuf = buf = temp; } } return (pos > 0 || r == 1) ? new String(Convert.charConverter.bytes2chars(buf, 0, pos)) : null; // brunosoares@582_11 } }