- new: server: count the bytes we rec/send
- new: CryptUtils
+- new: streams classes
- fix: IOUtils.readSmallStream and \n at the end
- change: serial: SSL -> CryptUtils
+- change: MarkableFileInputStream moved to nikiroo.utils.streams
## Version 4.7.2
import java.net.URL;
import java.util.Date;
+import be.nikiroo.utils.streams.MarkableFileInputStream;
+
/**
* A generic cache system, with special support for {@link URL}s.
* <p>
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;
+import be.nikiroo.utils.streams.MarkableFileInputStream;
+
/**
* This class offer some utilities based around Streams and Files.
*
if (value >= 1000000000l) {
mult = 1000000000l;
userValue = value / 1000000000l;
- suffix = "G";
+ suffix = " G";
} else if (value >= 1000000l) {
mult = 1000000l;
userValue = value / 1000000l;
- suffix = "M";
+ suffix = " M";
} else if (value >= 1000l) {
mult = 1000l;
userValue = value / 1000l;
- suffix = "k";
+ suffix = " k";
}
String deci = "";
-package be.nikiroo.utils;
+package be.nikiroo.utils.streams;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
/**
- * This {@link InputStream} can be separated into sub-streams (you can process
- * it as a normal {@link InputStream} but, when it is spent, you can call
- * {@link NextableInputStream#next()} on it to unlock new data).
+ * A simple {@link InputStream} that is buffered with a bytes array.
* <p>
- * The separation in sub-streams is done via {@link NextableInputStreamStep}.
+ * It is mostly intended to be used as a base class to create new
+ * {@link InputStream}s with special operation modes, and to give some default
+ * methods.
*
* @author niki
*/
-public class NextableInputStream extends InputStream {
- private NextableInputStreamStep step;
- private boolean started;
- private boolean stopped;
- private boolean closed;
+public class BufferedInputStream extends InputStream {
+ /** The current position in the buffer. */
+ protected int start;
+ /** The index of the last usable position of the buffer. */
+ protected int stop;
+ /** The buffer itself. */
+ protected byte[] buffer;
+ /** An End-Of-File (or buffer, here) marker. */
+ protected boolean eof;
+ private boolean closed;
private InputStream in;
private int openCounter;
- private boolean eof;
- private int pos;
- private int len;
- private byte[] buffer;
// special use, prefetched next buffer
private byte[] buffer2;
private long bytesRead;
/**
- * Create a new {@link NextableInputStream} that wraps the given
+ * Create a new {@link BufferedInputStream} that wraps the given
* {@link InputStream}.
*
* @param in
* the {@link InputStream} to wrap
- * @param step
- * how to separate it into sub-streams (can be NULL, but in that
- * case it will behave as a normal {@link InputStream})
*/
- public NextableInputStream(InputStream in, NextableInputStreamStep step) {
+ public BufferedInputStream(InputStream in) {
this.in = in;
- this.step = step;
this.buffer = new byte[4096];
this.originalBuffer = this.buffer;
- this.pos = 0;
- this.len = 0;
+ this.start = 0;
+ this.stop = 0;
}
/**
- * Create a new {@link NextableInputStream} that wraps the given bytes array
+ * Create a new {@link BufferedInputStream} that wraps the given bytes array
* as a data source.
*
* @param in
* the array to wrap, cannot be NULL
- * @param step
- * how to separate it into sub-streams (can be NULL, but in that
- * case it will behave as a normal {@link InputStream})
*/
- public NextableInputStream(byte[] in, NextableInputStreamStep step) {
- this(in, step, 0, in.length);
+ public BufferedInputStream(byte[] in) {
+ this(in, 0, in.length);
}
/**
- * Create a new {@link NextableInputStream} that wraps the given bytes array
+ * Create a new {@link BufferedInputStream} that wraps the given bytes array
* as a data source.
*
* @param in
* the array to wrap, cannot be NULL
- * @param step
- * how to separate it into sub-streams (can be NULL, but in that
- * case it will behave as a normal {@link InputStream})
* @param offset
* the offset to start the reading at
* @param length
* @throws IndexOutOfBoundsException
* if the offset and length do not correspond to the given array
*/
- public NextableInputStream(byte[] in, NextableInputStreamStep step,
- int offset, int length) {
+ public BufferedInputStream(byte[] in, int offset, int length) {
if (in == null) {
throw new NullPointerException();
} else if (offset < 0 || length < 0 || length > in.length - offset) {
}
this.in = null;
- this.step = step;
this.buffer = in;
this.originalBuffer = this.buffer;
- this.pos = offset;
- this.len = length;
-
- checkBuffer(true);
+ this.start = offset;
+ this.stop = length;
}
/**
- * Return this very same {@link NextableInputStream}, but keep a counter of
+ * Return this very same {@link BufferedInputStream}, but keep a counter of
* how many streams were open this way. When calling
- * {@link NextableInputStream#close()}, decrease this counter if it is not
+ * {@link BufferedInputStream#close()}, decrease this counter if it is not
* already zero instead of actually closing the stream.
* <p>
* You are now responsible for it — you <b>must</b> close it.
}
/**
- * Unblock the processing of the next sub-stream.
- * <p>
- * It can only be called when the "current" stream is spent (i.e., you must
- * first process the stream until it is spent).
+ * Check if the current content (what will be read next) starts with the
+ * given search term.
* <p>
- * We consider that when the under-laying {@link InputStream} is also spent,
- * we cannot have a next sub-stream (it will thus return FALSE).
- * <p>
- * {@link IOException}s can happen when we have no data available in the
- * buffer; in that case, we fetch more data to know if we can have a next
- * sub-stream or not.
+ * Note: the search term size <b>must</b> be smaller or equal the internal
+ * buffer size.
+ *
+ * @param search
+ * the term to search for
*
- * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ * @return TRUE if the content that will be read starts with it
*
* @throws IOException
- * in case of I/O error or if the stream is closed
+ * in case of I/O error or if the size of the search term is
+ * greater than the internal buffer
*/
- public boolean next() throws IOException {
- return next(false);
+ public boolean startsWiths(String search) throws IOException {
+ return startsWith(search.getBytes("UTF-8"));
}
/**
- * Unblock the next sub-stream as would have done
- * {@link NextableInputStream#next()}, but disable the sub-stream systems.
+ * Check if the current content (what will be read next) starts with the
+ * given search term.
* <p>
- * That is, the next stream, if any, will be the last one and will not be
- * subject to the {@link NextableInputStreamStep}.
+ * Note: the search term size <b>must</b> be smaller or equal the internal
+ * buffer size.
+ *
+ * @param search
+ * the term to search for
*
- * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ * @return TRUE if the content that will be read starts with it
*
* @throws IOException
- * in case of I/O error or if the stream is closed
+ * in case of I/O error or if the size of the search term is
+ * greater than the internal buffer
*/
- public boolean nextAll() throws IOException {
- return next(true);
- }
-
- // max is buffer.size !
- public boolean startsWiths(String search) throws IOException {
- return startsWith(search.getBytes("UTF-8"));
- }
-
- // max is buffer.size !
public boolean startsWith(byte[] search) throws IOException {
if (search.length > originalBuffer.length) {
throw new IOException(
if (available() >= search.length) {
// Easy path
- return startsWith(search, buffer, pos);
+ return StreamUtils.startsWith(search, buffer, start, stop);
} else if (!eof) {
// Harder path
if (buffer2 == null && buffer.length == originalBuffer.length) {
len2 += pos2;
}
- if (available() + (len2 - pos2) >= search.length) {
- return startsWith(search, buffer2, pos2);
- }
+ return StreamUtils.startsWith(search, buffer2, pos2, len2);
}
return false;
* @return TRUE if it is
*/
public boolean eof() {
- return closed || (len < 0 && !hasMoreData());
+ return closed || (stop < 0 && !hasMoreData());
}
@Override
return -1;
}
- return buffer[pos++];
+ return buffer[start++];
}
@Override
while (hasMoreData() && done < blen) {
preRead();
if (hasMoreData()) {
- for (int i = pos; i < blen && i < len; i++) {
- b[boff + done] = buffer[i];
- pos++;
- done++;
+ int now = Math.min(blen, stop) - start;
+ if (now > 0) {
+ System.arraycopy(buffer, start, b, boff + done, now);
+ start += now;
+ done += now;
}
}
}
preRead();
long inBuffer = Math.min(n, available());
- pos += inBuffer;
+ start += inBuffer;
n -= inBuffer;
skipped += inBuffer;
}
return 0;
}
- return Math.max(0, len - pos);
+ return Math.max(0, stop - start);
}
/**
* <p>
* Including the under-laying {@link InputStream}.
* <p>
- * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
+ * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
* prior to this one, it will just decrease the internal count of how many
* open streams it held and do nothing else. The stream will actually be
- * closed when you have called {@link NextableInputStream#close()} once more
- * than {@link NextableInputStream#open()}.
+ * closed when you have called {@link BufferedInputStream#close()} once more
+ * than {@link BufferedInputStream#open()}.
*
* @exception IOException
* in case of I/O error
* You can call this method multiple times, it will not cause an
* {@link IOException} for subsequent calls.
* <p>
- * <b>Note:</b> if you called the {@link NextableInputStream#open()} method
+ * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
* prior to this one, it will just decrease the internal count of how many
* open streams it held and do nothing else. The stream will actually be
- * closed when you have called {@link NextableInputStream#close()} once more
- * than {@link NextableInputStream#open()}.
+ * closed when you have called {@link BufferedInputStream#close()} once more
+ * than {@link BufferedInputStream#open()}.
+ *
+ * @param includingSubStream
+ * also close the under-laying stream
*
* @exception IOException
* in case of I/O error
* @throws IOException
* in case of I/O error
*/
- private boolean preRead() throws IOException {
+ protected boolean preRead() throws IOException {
boolean hasRead = false;
- if (!eof && in != null && pos >= len && !stopped) {
- pos = 0;
+ if (!eof && in != null && start >= stop) {
+ start = 0;
if (buffer2 != null) {
buffer = buffer2;
- pos = pos2;
- len = len2;
+ start = pos2;
+ stop = len2;
buffer2 = null;
pos2 = 0;
len2 = 0;
} else {
buffer = originalBuffer;
- len = in.read(buffer);
- if (len > 0) {
- bytesRead += len;
+
+ stop = read(in, buffer);
+ if (stop > 0) {
+ bytesRead += stop;
}
}
- checkBuffer(true);
hasRead = true;
}
- if (pos >= len) {
+ if (start >= stop) {
eof = true;
}
}
/**
- * We have more data available in the buffer or we can fetch more.
+ * Read the under-laying stream into the local buffer.
*
- * @return TRUE if it is the case, FALSE if not
- */
- private boolean hasMoreData() {
- return !closed && started && !(eof && pos >= len);
- }
-
- /**
- * Check that the buffer didn't overshot to the next item, and fix
- * {@link NextableInputStream#len} if needed.
- * <p>
- * If {@link NextableInputStream#len} is fixed,
- * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
- * are set to TRUE.
+ * @param in
+ * the under-laying {@link InputStream}
+ * @param buffer
+ * the buffer we use in this {@link BufferedInputStream}
*
- * @param newBuffer
- * we changed the buffer, we need to clear some information in
- * the {@link NextableInputStreamStep}
+ * @return the number of bytes read
+ *
+ * @throws IOException
+ * in case of I/O error
*/
- private void checkBuffer(boolean newBuffer) {
- if (step != null && len > 0) {
- if (newBuffer) {
- step.clearBuffer();
- }
-
- int stopAt = step.stop(buffer, pos, len);
- if (stopAt >= 0) {
- len = stopAt;
- eof = true;
- stopped = true;
- }
- }
+ protected int read(InputStream in, byte[] buffer) throws IOException {
+ return in.read(buffer);
}
/**
- * The implementation of {@link NextableInputStream#next()} and
- * {@link NextableInputStream#nextAll()}.
- *
- * @param all
- * TRUE for {@link NextableInputStream#nextAll()}, FALSE for
- * {@link NextableInputStream#next()}
- *
- * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ * We have more data available in the buffer or we can fetch more.
*
- * @throws IOException
- * in case of I/O error or if the stream is closed
+ * @return TRUE if it is the case, FALSE if not
*/
- private boolean next(boolean all) throws IOException {
- checkClose();
-
- if (!started) {
- // First call before being allowed to read
- started = true;
-
- if (all) {
- step = null;
- }
-
- return true;
- }
-
- if (step != null && !hasMoreData() && stopped) {
- len = step.getResumeLen();
- pos += step.getResumeSkip();
- eof = false;
-
- if (all) {
- step = null;
- }
-
- if (!preRead()) {
- checkBuffer(false);
- }
-
- // consider that if EOF, there is no next
- return hasMoreData();
- }
-
- return false;
+ protected boolean hasMoreData() {
+ return !closed && !(eof && start >= stop);
}
/**
* @throws IOException
* if it was closed
*/
- private void checkClose() throws IOException {
+ protected void checkClose() throws IOException {
if (closed) {
throw new IOException(
- "This NextableInputStream was closed, you cannot use it anymore.");
+ "This BufferedInputStream was closed, you cannot use it anymore.");
}
}
-
- // buffer must be > search
- static private boolean startsWith(byte[] search, byte[] buffer,
- int offset) {
- boolean same = true;
- for (int i = 0; i < search.length; i++) {
- if (search[i] != buffer[offset + i]) {
- same = false;
- break;
- }
- }
-
- return same;
- }
}
--- /dev/null
+package be.nikiroo.utils.streams;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * A simple {@link OutputStream} that is buffered with a bytes array.
+ * <p>
+ * It is mostly intended to be used as a base class to create new
+ * {@link OutputStream}s with special operation modes, and to give some default
+ * methods.
+ *
+ * @author niki
+ */
+public class BufferedOutputStream extends OutputStream {
+ /** The current position in the buffer. */
+ protected int start;
+ /** The index of the last usable position of the buffer. */
+ protected int stop;
+ /** The buffer itself. */
+ protected byte[] buffer;
+ /** An End-Of-File (or buffer, here) marker. */
+ protected boolean eof;
+ /** The actual under-laying stream. */
+ protected OutputStream out;
+ /** The number of bytes written to the under-laying stream. */
+ protected long bytesWritten;
+ /**
+ * Can bypass the flush process for big writes (will directly write to the
+ * under-laying buffer if the array to write is > the internal buffer
+ * size).
+ * <p>
+ * By default, this is true.
+ */
+ protected boolean bypassFlush = true;
+
+ private boolean closed;
+ private int openCounter;
+
+ /**
+ * Create a new {@link BufferedInputStream} that wraps the given
+ * {@link InputStream}.
+ *
+ * @param out
+ * the {@link OutputStream} to wrap
+ */
+ public BufferedOutputStream(OutputStream out) {
+ this.out = out;
+
+ this.buffer = new byte[4096];
+ this.start = 0;
+ this.stop = 0;
+ }
+
+ @Override
+ public void write(int b) throws IOException {
+ checkClose();
+
+ if (available() <= 0) {
+ flush(false);
+ }
+
+ buffer[start++] = (byte) b;
+ }
+
+ @Override
+ public void write(byte[] b) throws IOException {
+ write(b, 0, b.length);
+ }
+
+ @Override
+ public void write(byte[] source, int sourceOffset, int sourceLength)
+ throws IOException {
+
+ checkClose();
+
+ if (source == null) {
+ throw new NullPointerException();
+ } else if ((sourceOffset < 0) || (sourceOffset > source.length)
+ || (sourceLength < 0)
+ || ((sourceOffset + sourceLength) > source.length)
+ || ((sourceOffset + sourceLength) < 0)) {
+ throw new IndexOutOfBoundsException();
+ } else if (sourceLength == 0) {
+ return;
+ }
+
+ if (sourceLength >= buffer.length) {
+ /*
+ * If the request length exceeds the size of the output buffer,
+ * flush the output buffer and then write the data directly. In this
+ * way buffered streams will cascade harmlessly.
+ */
+ flush(false);
+ out.write(source, sourceOffset, sourceLength);
+ return;
+ }
+
+ int done = 0;
+ while (done < sourceLength) {
+ if (available() <= 0) {
+ flush(false);
+ }
+
+ int now = Math.min(sourceLength, available());
+ System.arraycopy(source, sourceOffset + done, buffer, stop, now);
+ stop += now;
+ done += now;
+ }
+ }
+
+ /**
+ * The available space in the buffer.
+ *
+ * @return the space in bytes
+ */
+ private int available() {
+ if (closed) {
+ return 0;
+ }
+
+ return Math.max(0, buffer.length - stop - 1);
+ }
+
+ /**
+ * The number of bytes written to the under-laying {@link OutputStream}.
+ *
+ * @return the number of bytes
+ */
+ public long getBytesWritten() {
+ return bytesWritten;
+ }
+
+ /**
+ * Return this very same {@link BufferedInputStream}, but keep a counter of
+ * how many streams were open this way. When calling
+ * {@link BufferedInputStream#close()}, decrease this counter if it is not
+ * already zero instead of actually closing the stream.
+ * <p>
+ * You are now responsible for it — you <b>must</b> close it.
+ * <p>
+ * This method allows you to use a wrapping stream around this one and still
+ * close the wrapping stream.
+ *
+ * @return the same stream, but you are now responsible for closing it
+ *
+ * @throws IOException
+ * in case of I/O error or if the stream is closed
+ */
+ public synchronized OutputStream open() throws IOException {
+ checkClose();
+ openCounter++;
+ return this;
+ }
+
+ /**
+ * Check that the stream was not closed, and throw an {@link IOException} if
+ * it was.
+ *
+ * @throws IOException
+ * if it was closed
+ */
+ protected void checkClose() throws IOException {
+ if (closed) {
+ throw new IOException(
+ "This BufferedInputStream was closed, you cannot use it anymore.");
+ }
+ }
+
+ @Override
+ public void flush() throws IOException {
+ flush(true);
+ }
+
+ /**
+ * Flush the {@link BufferedOutputStream}, write the current buffered data
+ * to (and optionally also flush) the under-laying stream.
+ * <p>
+ * If {@link BufferedOutputStream#bypassFlush} is false, all writes to the
+ * under-laying stream are done in this method.
+ *
+ * @param includingSubStream
+ * also flush the under-laying stream
+ * @throws IOException
+ * in case of I/O error
+ */
+ protected void flush(boolean includingSubStream) throws IOException {
+ out.write(buffer, start, stop - start);
+ bytesWritten += (stop - start);
+ start = 0;
+ stop = 0;
+
+ if (includingSubStream) {
+ out.flush();
+ }
+ }
+
+ /**
+ * Closes this stream and releases any system resources associated with the
+ * stream.
+ * <p>
+ * Including the under-laying {@link InputStream}.
+ * <p>
+ * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
+ * prior to this one, it will just decrease the internal count of how many
+ * open streams it held and do nothing else. The stream will actually be
+ * closed when you have called {@link BufferedInputStream#close()} once more
+ * than {@link BufferedInputStream#open()}.
+ *
+ * @exception IOException
+ * in case of I/O error
+ */
+ @Override
+ public synchronized void close() throws IOException {
+ close(true);
+ }
+
+ /**
+ * Closes this stream and releases any system resources associated with the
+ * stream.
+ * <p>
+ * Including the under-laying {@link InputStream} if
+ * <tt>incudingSubStream</tt> is true.
+ * <p>
+ * You can call this method multiple times, it will not cause an
+ * {@link IOException} for subsequent calls.
+ * <p>
+ * <b>Note:</b> if you called the {@link BufferedInputStream#open()} method
+ * prior to this one, it will just decrease the internal count of how many
+ * open streams it held and do nothing else. The stream will actually be
+ * closed when you have called {@link BufferedInputStream#close()} once more
+ * than {@link BufferedInputStream#open()}.
+ *
+ * @param includingSubStream
+ * also close the under-laying stream
+ *
+ * @exception IOException
+ * in case of I/O error
+ */
+ public synchronized void close(boolean includingSubStream)
+ throws IOException {
+ if (!closed) {
+ if (openCounter > 0) {
+ openCounter--;
+ } else {
+ closed = true;
+ flush(true);
+ if (includingSubStream && out != null) {
+ out.close();
+ }
+ }
+ }
+ }
+}
-package be.nikiroo.utils;
+package be.nikiroo.utils.streams;
import java.io.FileInputStream;
import java.io.FilterInputStream;
--- /dev/null
+package be.nikiroo.utils.streams;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This {@link InputStream} can be separated into sub-streams (you can process
+ * it as a normal {@link InputStream} but, when it is spent, you can call
+ * {@link NextableInputStream#next()} on it to unlock new data).
+ * <p>
+ * The separation in sub-streams is done via {@link NextableInputStreamStep}.
+ *
+ * @author niki
+ */
+public class NextableInputStream extends BufferedInputStream {
+ private NextableInputStreamStep step;
+ private boolean started;
+ private boolean stopped;
+
+ /**
+ * Create a new {@link NextableInputStream} that wraps the given
+ * {@link InputStream}.
+ *
+ * @param in
+ * the {@link InputStream} to wrap
+ * @param step
+ * how to separate it into sub-streams (can be NULL, but in that
+ * case it will behave as a normal {@link InputStream})
+ */
+ public NextableInputStream(InputStream in, NextableInputStreamStep step) {
+ super(in);
+ this.step = step;
+ }
+
+ /**
+ * Create a new {@link NextableInputStream} that wraps the given bytes array
+ * as a data source.
+ *
+ * @param in
+ * the array to wrap, cannot be NULL
+ * @param step
+ * how to separate it into sub-streams (can be NULL, but in that
+ * case it will behave as a normal {@link InputStream})
+ */
+ public NextableInputStream(byte[] in, NextableInputStreamStep step) {
+ this(in, step, 0, in.length);
+ }
+
+ /**
+ * Create a new {@link NextableInputStream} that wraps the given bytes array
+ * as a data source.
+ *
+ * @param in
+ * the array to wrap, cannot be NULL
+ * @param step
+ * how to separate it into sub-streams (can be NULL, but in that
+ * case it will behave as a normal {@link InputStream})
+ * @param offset
+ * the offset to start the reading at
+ * @param length
+ * the number of bytes to take into account in the array,
+ * starting from the offset
+ *
+ * @throws NullPointerException
+ * if the array is NULL
+ * @throws IndexOutOfBoundsException
+ * if the offset and length do not correspond to the given array
+ */
+ public NextableInputStream(byte[] in, NextableInputStreamStep step,
+ int offset, int length) {
+ super(in, offset, length);
+ this.step = step;
+ checkBuffer(true);
+ }
+
+ /**
+ * Unblock the processing of the next sub-stream.
+ * <p>
+ * It can only be called when the "current" stream is spent (i.e., you must
+ * first process the stream until it is spent).
+ * <p>
+ * We consider that when the under-laying {@link InputStream} is also spent,
+ * we cannot have a next sub-stream (it will thus return FALSE).
+ * <p>
+ * {@link IOException}s can happen when we have no data available in the
+ * buffer; in that case, we fetch more data to know if we can have a next
+ * sub-stream or not.
+ *
+ * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ *
+ * @throws IOException
+ * in case of I/O error or if the stream is closed
+ */
+ public boolean next() throws IOException {
+ return next(false);
+ }
+
+ /**
+ * Unblock the next sub-stream as would have done
+ * {@link NextableInputStream#next()}, but disable the sub-stream systems.
+ * <p>
+ * That is, the next stream, if any, will be the last one and will not be
+ * subject to the {@link NextableInputStreamStep}.
+ *
+ * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ *
+ * @throws IOException
+ * in case of I/O error or if the stream is closed
+ */
+ public boolean nextAll() throws IOException {
+ return next(true);
+ }
+
+ /**
+ * Check if this stream is totally spent (no more data to read or to
+ * process).
+ * <p>
+ * Note: an empty stream that is still not started will return FALSE, as we
+ * don't know yet if it is empty.
+ *
+ * @return TRUE if it is
+ */
+ @Override
+ public boolean eof() {
+ return super.eof();
+ }
+
+ /**
+ * Check if we still have some data in the buffer and, if not, fetch some.
+ *
+ * @return TRUE if we fetched some data, FALSE if there are still some in
+ * the buffer
+ *
+ * @throws IOException
+ * in case of I/O error
+ */
+ @Override
+ protected boolean preRead() throws IOException {
+ if (!stopped) {
+ boolean bufferChanged = super.preRead();
+ checkBuffer(true);
+ return bufferChanged;
+ }
+
+ if (start >= stop) {
+ eof = true;
+ }
+
+ return false;
+ }
+
+ /**
+ * We have more data available in the buffer or we can fetch more.
+ *
+ * @return TRUE if it is the case, FALSE if not
+ */
+ @Override
+ protected boolean hasMoreData() {
+ return started && super.hasMoreData();
+ }
+
+ /**
+ * Check that the buffer didn't overshot to the next item, and fix
+ * {@link NextableInputStream#stop} if needed.
+ * <p>
+ * If {@link NextableInputStream#stop} is fixed,
+ * {@link NextableInputStream#eof} and {@link NextableInputStream#stopped}
+ * are set to TRUE.
+ *
+ * @param newBuffer
+ * we changed the buffer, we need to clear some information in
+ * the {@link NextableInputStreamStep}
+ */
+ private void checkBuffer(boolean newBuffer) {
+ if (step != null && stop > 0) {
+ if (newBuffer) {
+ step.clearBuffer();
+ }
+
+ int stopAt = step.stop(buffer, start, stop);
+ if (stopAt >= 0) {
+ stop = stopAt;
+ eof = true;
+ stopped = true;
+ }
+ }
+ }
+
+ /**
+ * The implementation of {@link NextableInputStream#next()} and
+ * {@link NextableInputStream#nextAll()}.
+ *
+ * @param all
+ * TRUE for {@link NextableInputStream#nextAll()}, FALSE for
+ * {@link NextableInputStream#next()}
+ *
+ * @return TRUE if we unblocked the next sub-stream, FALSE if not
+ *
+ * @throws IOException
+ * in case of I/O error or if the stream is closed
+ */
+ private boolean next(boolean all) throws IOException {
+ checkClose();
+
+ if (!started) {
+ // First call before being allowed to read
+ started = true;
+
+ if (all) {
+ step = null;
+ }
+
+ return true;
+ }
+
+ if (step != null && !hasMoreData() && stopped) {
+ stop = step.getResumeLen();
+ start += step.getResumeSkip();
+ eof = false;
+
+ if (all) {
+ step = null;
+ }
+
+ if (!preRead()) {
+ checkBuffer(false);
+ }
+
+ // consider that if EOF, there is no next
+ return hasMoreData();
+ }
+
+ return false;
+ }
+}
-package be.nikiroo.utils;
+package be.nikiroo.utils.streams;
import java.io.InputStream;
--- /dev/null
+package be.nikiroo.utils.streams;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This {@link InputStream} will change some of its content by replacing it with
+ * something else.
+ *
+ * @author niki
+ */
+public class ReplaceInputStream extends BufferedInputStream {
+ private byte[] from;
+ private byte[] to;
+
+ private byte[] source;
+ private int spos;
+ private int slen;
+
+ /**
+ * Create a {@link ReplaceInputStream} that will replace <tt>from</tt> with
+ * <tt>to</tt>.
+ *
+ * @param in
+ * the under-laying {@link InputStream}
+ * @param from
+ * the {@link String} to replace
+ * @param to
+ * the {@link String} to replace with
+ */
+ public ReplaceInputStream(InputStream in, String from, String to) {
+ this(in, StreamUtils.bytes(from), StreamUtils.bytes(to));
+ }
+
+ /**
+ * Create a {@link ReplaceInputStream} that will replace <tt>from</tt> with
+ * <tt>to</tt>.
+ *
+ * @param in
+ * the under-laying {@link InputStream}
+ * @param from
+ * the value to replace
+ * @param to
+ * the value to replace with
+ */
+ public ReplaceInputStream(InputStream in, byte[] from, byte[] to) {
+ super(in);
+ this.from = from;
+ this.to = to;
+
+ source = new byte[4096];
+ spos = 0;
+ slen = 0;
+ }
+
+ @Override
+ protected int read(InputStream in, byte[] buffer) throws IOException {
+ if (buffer.length < to.length || source.length < to.length * 2) {
+ throw new IOException(
+ "An underlaying buffer is too small for this replace value");
+ }
+
+ if (spos >= slen) {
+ spos = 0;
+ slen = in.read(source);
+ }
+
+ // Note: very simple, not efficient implementation, sorry.
+ int count = 0;
+ while (spos < slen && count < buffer.length - to.length) {
+ if (from.length > 0
+ && StreamUtils.startsWith(from, source, spos, slen)) {
+ System.arraycopy(to, 0, buffer, spos, to.length);
+ count += to.length;
+ spos += from.length;
+ } else {
+ buffer[count++] = source[spos++];
+ }
+ }
+
+ return count;
+ }
+}
--- /dev/null
+package be.nikiroo.utils.streams;
+
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * This {@link OutputStream} will change some of its content by replacing it
+ * with something else.
+ *
+ * @author niki
+ */
+public class ReplaceOutputStream extends BufferedOutputStream {
+ private byte[] from;
+ private byte[] to;
+
+ /**
+ * Create a {@link ReplaceOutputStream} that will replace <tt>from</tt> with
+ * <tt>to</tt>.
+ *
+ * @param out
+ * the under-laying {@link OutputStream}
+ * @param from
+ * the {@link String} to replace
+ * @param to
+ * the {@link String} to replace with
+ */
+ public ReplaceOutputStream(OutputStream out, String from, String to) {
+ this(out, StreamUtils.bytes(from), StreamUtils.bytes(to));
+ }
+
+ /**
+ * Create a {@link ReplaceOutputStream} that will replace <tt>from</tt> with
+ * <tt>to</tt>.
+ *
+ * @param out
+ * the under-laying {@link OutputStream}
+ * @param from
+ * the value to replace
+ * @param to
+ * the value to replace with
+ */
+ public ReplaceOutputStream(OutputStream out, byte[] from, byte[] to) {
+ super(out);
+ bypassFlush = false;
+
+ this.from = from;
+ this.to = to;
+ }
+
+ @Override
+ protected void flush(boolean includingSubStream) throws IOException {
+ // Note: very simple, not efficient implementation, sorry.
+ while (start < stop) {
+ if (from.length > 0
+ && StreamUtils.startsWith(from, buffer, start, stop)) {
+ out.write(to);
+ bytesWritten += to.length;
+ start += from.length;
+ } else {
+ out.write(buffer[start++]);
+ bytesWritten++;
+ }
+ }
+
+ start = 0;
+ stop = 0;
+
+ if (includingSubStream) {
+ out.flush();
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.utils.streams;
+
+import java.io.UnsupportedEncodingException;
+
+/**
+ * Some non-public utilities used in the stream classes.
+ *
+ * @author niki
+ */
+class StreamUtils {
+ /**
+ * Check if the buffer starts with the given search term (given as an array,
+ * a start position and an end position).
+ * <p>
+ * Note: the parameter <tt>stop</tt> is the <b>index</b> of the last
+ * position, <b>not</b> the length.
+ * <p>
+ * Note: the search term size <b>must</b> be smaller or equal the internal
+ * buffer size.
+ *
+ * @param search
+ * the term to search for
+ * @param buffer
+ * the buffer to look into
+ * @param start
+ * the offset at which to start the search
+ * @param stop
+ * the maximum index of the data to check (this is <b>not</b> a
+ * length, but an index)
+ *
+ * @return TRUE if the search content is present at the given location and
+ * does not exceed the <tt>len</tt> index
+ */
+ static public boolean startsWith(byte[] search, byte[] buffer, int start,
+ int stop) {
+
+ // Check if there even is enough space for it
+ if (search.length > (stop - start)) {
+ return false;
+ }
+
+ boolean same = true;
+ for (int i = 0; i < search.length; i++) {
+ if (search[i] != buffer[start + i]) {
+ same = false;
+ break;
+ }
+ }
+
+ return same;
+ }
+
+ /**
+ * Return the bytes array representation of the given {@link String} in
+ * UTF-8.
+ *
+ * @param str
+ * the string to transform into bytes
+ * @return the content in bytes
+ */
+ static public byte[] bytes(String str) {
+ try {
+ return str.getBytes("UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ // All conforming JVM must support UTF-8
+ e.printStackTrace();
+ return null;
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.utils.test_code;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.streams.BufferedInputStream;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class BufferedInputStreamTest extends TestLauncher {
+ public BufferedInputStreamTest(String[] args) {
+ super("BufferedInputStream test", args);
+
+ addTest(new TestCase("Simple InputStream reading") {
+ @Override
+ public void test() throws Exception {
+ byte[] expected = new byte[] { 42, 12, 0, 127 };
+ BufferedInputStream in = new BufferedInputStream(
+ new ByteArrayInputStream(expected));
+ checkArrays(this, "FIRST", in, expected);
+ }
+ });
+
+ addTest(new TestCase("Simple byte array reading") {
+ @Override
+ public void test() throws Exception {
+ byte[] expected = new byte[] { 42, 12, 0, 127 };
+ BufferedInputStream in = new BufferedInputStream(expected);
+ checkArrays(this, "FIRST", in, expected);
+ }
+ });
+ }
+
+ static void checkArrays(TestCase test, String prefix, InputStream in,
+ byte[] expected) throws Exception {
+ byte[] actual = IOUtils.toByteArray(in);
+ test.assertEquals("The " + prefix
+ + " resulting array has not the correct number of items",
+ expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ test.assertEquals(prefix + ": item " + i
+ + " (0-based) is not the same", expected[i], actual[i]);
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.utils.test_code;
+
+import java.io.ByteArrayOutputStream;
+
+import be.nikiroo.utils.streams.BufferedOutputStream;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class BufferedOutputStreamTest extends TestLauncher {
+ public BufferedOutputStreamTest(String[] args) {
+ super("BufferedOutputStream test", args);
+
+ addTest(new TestCase("Single write") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ BufferedOutputStream out = new BufferedOutputStream(bout);
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, data);
+ }
+ });
+
+ addTest(new TestCase("Multiple writes") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ BufferedOutputStream out = new BufferedOutputStream(bout);
+
+ byte[] data1 = new byte[] { 42, 12, 0, 127 };
+ byte[] data2 = new byte[] { 15, 55 };
+ byte[] data3 = new byte[] {};
+
+ byte[] dataAll = new byte[] { 42, 12, 0, 127, 15, 55 };
+
+ out.write(data1);
+ out.write(data2);
+ out.write(data3);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, dataAll);
+ }
+ });
+ }
+
+ static void checkArrays(TestCase test, String prefix,
+ ByteArrayOutputStream bout, byte[] expected) throws Exception {
+ byte[] actual = bout.toByteArray();
+
+ if (false) {
+ System.out.print("\nExpected data: [ ");
+ for (int i = 0; i < expected.length; i++) {
+ if (i > 0)
+ System.out.print(", ");
+ System.out.print(expected[i]);
+ }
+ System.out.println(" ]");
+
+ System.out.print("Actual data : [ ");
+ for (int i = 0; i < actual.length; i++) {
+ if (i > 0)
+ System.out.print(", ");
+ System.out.print(actual[i]);
+ }
+ System.out.println(" ]");
+ }
+
+ test.assertEquals("The " + prefix
+ + " resulting array has not the correct number of items",
+ expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ test.assertEquals(prefix + ": item " + i
+ + " (0-based) is not the same", expected[i], actual[i]);
+ }
+ }
+}
import java.io.IOException;
import be.nikiroo.utils.IOUtils;
-import be.nikiroo.utils.NextableInputStream;
-import be.nikiroo.utils.NextableInputStreamStep;
+import be.nikiroo.utils.streams.NextableInputStream;
+import be.nikiroo.utils.streams.NextableInputStreamStep;
import be.nikiroo.utils.test.TestCase;
import be.nikiroo.utils.test.TestLauncher;
--- /dev/null
+package be.nikiroo.utils.test_code;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+import be.nikiroo.utils.IOUtils;
+import be.nikiroo.utils.streams.ReplaceInputStream;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class ReplaceInputStreamTest extends TestLauncher {
+ public ReplaceInputStreamTest(String[] args) {
+ super("ReplaceInputStream test", args);
+
+ addTest(new TestCase("Empty replace") {
+ @Override
+ public void test() throws Exception {
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+ ReplaceInputStream in = new ReplaceInputStream(
+ new ByteArrayInputStream(data), new byte[0],
+ new byte[0]);
+
+ checkArrays(this, "FIRST", in, data);
+ }
+ });
+
+ addTest(new TestCase("Simple replace") {
+ @Override
+ public void test() throws Exception {
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+ ReplaceInputStream in = new ReplaceInputStream(
+ new ByteArrayInputStream(data), new byte[] { 0 },
+ new byte[] { 10 });
+
+ checkArrays(this, "FIRST", in, new byte[] { 42, 12, 10, 127 });
+ }
+ });
+
+ addTest(new TestCase("3/4 replace") {
+ @Override
+ public void test() throws Exception {
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+ ReplaceInputStream in = new ReplaceInputStream(
+ new ByteArrayInputStream(data),
+ new byte[] { 12, 0, 127 }, new byte[] { 10, 10, 10 });
+
+ checkArrays(this, "FIRST", in, new byte[] { 42, 10, 10, 10 });
+ }
+ });
+
+ addTest(new TestCase("Lnger replace") {
+ @Override
+ public void test() throws Exception {
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+ ReplaceInputStream in = new ReplaceInputStream(
+ new ByteArrayInputStream(data), new byte[] { 0 },
+ new byte[] { 10, 10, 10 });
+
+ checkArrays(this, "FIRST", in, new byte[] { 42, 12, 10, 10, 10,
+ 127 });
+ }
+ });
+
+ addTest(new TestCase("Shorter replace") {
+ @Override
+ public void test() throws Exception {
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+ ReplaceInputStream in = new ReplaceInputStream(
+ new ByteArrayInputStream(data),
+ new byte[] { 42, 12, 0 }, new byte[] { 10 });
+
+ checkArrays(this, "FIRST", in, new byte[] { 10, 127 });
+ }
+ });
+
+ addTest(new TestCase("String replace") {
+ @Override
+ public void test() throws Exception {
+ byte[] data = "I like red".getBytes("UTF-8");
+ ReplaceInputStream in = new ReplaceInputStream(
+ new ByteArrayInputStream(data),
+ "red".getBytes("UTF-8"), "blue".getBytes("UTF-8"));
+
+ checkArrays(this, "FIRST", in, "I like blue".getBytes("UTF-8"));
+
+ data = "I like blue".getBytes("UTF-8");
+ in = new ReplaceInputStream(new ByteArrayInputStream(data),
+ "blue".getBytes("UTF-8"), "red".getBytes("UTF-8"));
+
+ checkArrays(this, "FIRST", in, "I like red".getBytes("UTF-8"));
+ }
+ });
+ }
+
+ static void checkArrays(TestCase test, String prefix, InputStream in,
+ byte[] expected) throws Exception {
+ byte[] actual = IOUtils.toByteArray(in);
+ test.assertEquals("The " + prefix
+ + " resulting array has not the correct number of items",
+ expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ test.assertEquals("Item " + i + " (0-based) is not the same",
+ expected[i], actual[i]);
+ }
+ }
+}
--- /dev/null
+package be.nikiroo.utils.test_code;
+
+import java.io.ByteArrayOutputStream;
+
+import be.nikiroo.utils.streams.ReplaceOutputStream;
+import be.nikiroo.utils.test.TestCase;
+import be.nikiroo.utils.test.TestLauncher;
+
+class ReplaceOutputStreamTest extends TestLauncher {
+ public ReplaceOutputStreamTest(String[] args) {
+ super("ReplaceOutputStream test", args);
+
+ addTest(new TestCase("Single write, empty bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[0], new byte[0]);
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, data);
+ }
+ });
+
+ addTest(new TestCase("Multiple writes, empty Strings replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout, "", "");
+
+ byte[] data1 = new byte[] { 42, 12, 0, 127 };
+ byte[] data2 = new byte[] { 15, 55 };
+ byte[] data3 = new byte[] {};
+
+ byte[] dataAll = new byte[] { 42, 12, 0, 127, 15, 55 };
+
+ out.write(data1);
+ out.write(data2);
+ out.write(data3);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, dataAll);
+ }
+ });
+
+ addTest(new TestCase("Single write, bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12 }, new byte[] { 55 });
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 55, 0, 127 });
+ }
+ });
+
+ addTest(new TestCase("Multiple writes, Strings replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout, "(-)",
+ "(.)");
+
+ byte[] data1 = "un mot ".getBytes("UTF-8");
+ byte[] data2 = "(-) of twee ".getBytes("UTF-8");
+ byte[] data3 = "(-) makes the difference".getBytes("UTF-8");
+
+ out.write(data1);
+ out.write(data2);
+ out.write(data3);
+ out.close();
+
+ checkArrays(this, "FIRST", bout,
+ "un mot (.) of twee (.) makes the difference"
+ .getBytes("UTF-8"));
+ }
+ });
+
+ addTest(new TestCase("Single write, longer bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12 }, new byte[] { 55, 55, 66 });
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 55, 55, 66,
+ 0, 127 });
+ }
+ });
+
+ addTest(new TestCase("Single write, shorter bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12, 0 }, new byte[] { 55 });
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 55, 127 });
+ }
+ });
+
+ addTest(new TestCase("Single write, remove bytes replaces") {
+ @Override
+ public void test() throws Exception {
+ ByteArrayOutputStream bout = new ByteArrayOutputStream();
+ ReplaceOutputStream out = new ReplaceOutputStream(bout,
+ new byte[] { 12 }, new byte[] {});
+
+ byte[] data = new byte[] { 42, 12, 0, 127 };
+
+ out.write(data);
+ out.close();
+
+ checkArrays(this, "FIRST", bout, new byte[] { 42, 0, 127 });
+ }
+ });
+ }
+
+ static void checkArrays(TestCase test, String prefix,
+ ByteArrayOutputStream bout, byte[] expected) throws Exception {
+ byte[] actual = bout.toByteArray();
+
+ if (false) {
+ System.out.print("\nExpected data: [ ");
+ for (int i = 0; i < expected.length; i++) {
+ if (i > 0)
+ System.out.print(", ");
+ System.out.print(expected[i]);
+ }
+ System.out.println(" ]");
+
+ System.out.print("Actual data : [ ");
+ for (int i = 0; i < actual.length; i++) {
+ if (i > 0)
+ System.out.print(", ");
+ System.out.print(actual[i]);
+ }
+ System.out.println(" ]");
+ }
+
+ test.assertEquals("The " + prefix
+ + " resulting array has not the correct number of items",
+ expected.length, actual.length);
+ for (int i = 0; i < actual.length; i++) {
+ test.assertEquals(prefix + ": item " + i
+ + " (0-based) is not the same", expected[i], actual[i]);
+ }
+ }
+}
assertEquals(21200l, StringUtils.toNumber("21200"));
assertEquals(0l, StringUtils.toNumber("0"));
assertEquals("263", StringUtils.formatNumber(263l));
- assertEquals("21k", StringUtils.formatNumber(21000l));
+ assertEquals("21 k", StringUtils.formatNumber(21000l));
assertEquals("0", StringUtils.formatNumber(0l));
}
});
addTest(new TestCase("format/toNumber not 000") {
@Override
public void test() throws Exception {
- assertEquals(263200l, StringUtils.toNumber("263.2k"));
- assertEquals(42000l, StringUtils.toNumber("42.0k"));
- assertEquals(12000000l, StringUtils.toNumber("12M"));
- assertEquals(2000000000l, StringUtils.toNumber("2G"));
- assertEquals("263k", StringUtils.formatNumber(263012l));
- assertEquals("42k", StringUtils.formatNumber(42012l));
- assertEquals("12M", StringUtils.formatNumber(12012121l));
- assertEquals("7G", StringUtils.formatNumber(7364635928l));
+ assertEquals(263200l, StringUtils.toNumber("263.2 k"));
+ assertEquals(42000l, StringUtils.toNumber("42.0 k"));
+ assertEquals(12000000l, StringUtils.toNumber("12 M"));
+ assertEquals(2000000000l, StringUtils.toNumber("2 G"));
+ assertEquals("263 k", StringUtils.formatNumber(263012l));
+ assertEquals("42 k", StringUtils.formatNumber(42012l));
+ assertEquals("12 M", StringUtils.formatNumber(12012121l));
+ assertEquals("7 G", StringUtils.formatNumber(7364635928l));
}
});
addTest(new TestCase("format/toNumber decimals") {
@Override
public void test() throws Exception {
- assertEquals(263200l, StringUtils.toNumber("263.2k"));
- assertEquals(1200l, StringUtils.toNumber("1.2k"));
- assertEquals(42700000l, StringUtils.toNumber("42.7M"));
- assertEquals(1220l, StringUtils.toNumber("1.22k"));
- assertEquals(1432l, StringUtils.toNumber("1.432k"));
- assertEquals(6938l, StringUtils.toNumber("6.938k"));
- assertEquals("1.3k", StringUtils.formatNumber(1300l, 1));
- assertEquals("263.2020k", StringUtils.formatNumber(263202l, 4));
- assertEquals("1.26k", StringUtils.formatNumber(1267l, 2));
- assertEquals("42.7M", StringUtils.formatNumber(42712121l, 1));
- assertEquals("5.09G", StringUtils.formatNumber(5094837485l, 2));
+ assertEquals(263200l, StringUtils.toNumber("263.2 k"));
+ assertEquals(1200l, StringUtils.toNumber("1.2 k"));
+ assertEquals(42700000l, StringUtils.toNumber("42.7 M"));
+ assertEquals(1220l, StringUtils.toNumber("1.22 k"));
+ assertEquals(1432l, StringUtils.toNumber("1.432 k"));
+ assertEquals(6938l, StringUtils.toNumber("6.938 k"));
+ assertEquals("1.3 k", StringUtils.formatNumber(1300l, 1));
+ assertEquals("263.2020 k", StringUtils.formatNumber(263202l, 4));
+ assertEquals("1.26 k", StringUtils.formatNumber(1267l, 2));
+ assertEquals("42.7 M", StringUtils.formatNumber(42712121l, 1));
+ assertEquals("5.09 G", StringUtils.formatNumber(5094837485l, 2));
}
});
}
addSeries(new StringUtilsTest(args));
addSeries(new TempFilesTest(args));
addSeries(new CryptUtilsTest(args));
+ addSeries(new BufferedInputStreamTest(args));
addSeries(new NextableInputStreamTest(args));
+ addSeries(new ReplaceInputStreamTest(args));
+ addSeries(new BufferedOutputStreamTest(args));
+ addSeries(new ReplaceOutputStreamTest(args));
// TODO: test cache and downloader
Cache cache = null;