From: Niki Roo Date: Sat, 27 Apr 2019 16:57:26 +0000 (+0200) Subject: Merge branch 'master' into streamify X-Git-Url: http://git.nikiroo.be/?p=nikiroo-utils.git;a=commitdiff_plain;h=be6f67896f1412f87ebae15660713e918d37e0a0;hp=cfb8e5161044032426ed8627fd2061851b9dd6e6 Merge branch 'master' into streamify --- diff --git a/changelog.md b/changelog.md index 5bdc946..4792f9b 100644 --- a/changelog.md +++ b/changelog.md @@ -4,8 +4,10 @@ - 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 diff --git a/src/be/nikiroo/utils/Cache.java b/src/be/nikiroo/utils/Cache.java index ce33592..cf8a780 100644 --- a/src/be/nikiroo/utils/Cache.java +++ b/src/be/nikiroo/utils/Cache.java @@ -8,6 +8,8 @@ import java.io.InputStream; 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. *

diff --git a/src/be/nikiroo/utils/IOUtils.java b/src/be/nikiroo/utils/IOUtils.java index 9cdaba8..5a7e179 100644 --- a/src/be/nikiroo/utils/IOUtils.java +++ b/src/be/nikiroo/utils/IOUtils.java @@ -13,6 +13,8 @@ import java.util.zip.ZipEntry; 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. * diff --git a/src/be/nikiroo/utils/StringUtils.java b/src/be/nikiroo/utils/StringUtils.java index 1ee9ac4..954d341 100644 --- a/src/be/nikiroo/utils/StringUtils.java +++ b/src/be/nikiroo/utils/StringUtils.java @@ -844,15 +844,15 @@ public class StringUtils { 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 = ""; diff --git a/src/be/nikiroo/utils/NextableInputStream.java b/src/be/nikiroo/utils/streams/BufferedInputStream.java similarity index 52% rename from src/be/nikiroo/utils/NextableInputStream.java rename to src/be/nikiroo/utils/streams/BufferedInputStream.java index b5374a1..be4b24d 100644 --- a/src/be/nikiroo/utils/NextableInputStream.java +++ b/src/be/nikiroo/utils/streams/BufferedInputStream.java @@ -1,30 +1,31 @@ -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. *

- * 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; @@ -35,48 +36,38 @@ public class NextableInputStream extends InputStream { 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 @@ -88,8 +79,7 @@ public class NextableInputStream extends InputStream { * @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) { @@ -97,20 +87,17 @@ public class NextableInputStream extends InputStream { } 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. *

* You are now responsible for it — you must close it. @@ -130,49 +117,41 @@ public class NextableInputStream extends InputStream { } /** - * Unblock the processing of the next sub-stream. - *

- * 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. *

- * We consider that when the under-laying {@link InputStream} is also spent, - * we cannot have a next sub-stream (it will thus return FALSE). - *

- * {@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 must 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. *

- * 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 must 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( @@ -188,7 +167,7 @@ public class NextableInputStream extends InputStream { 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) { @@ -204,9 +183,7 @@ public class NextableInputStream extends InputStream { len2 += pos2; } - if (available() + (len2 - pos2) >= search.length) { - return startsWith(search, buffer2, pos2); - } + return StreamUtils.startsWith(search, buffer2, pos2, len2); } return false; @@ -228,7 +205,7 @@ public class NextableInputStream extends InputStream { * @return TRUE if it is */ public boolean eof() { - return closed || (len < 0 && !hasMoreData()); + return closed || (stop < 0 && !hasMoreData()); } @Override @@ -240,7 +217,7 @@ public class NextableInputStream extends InputStream { return -1; } - return buffer[pos++]; + return buffer[start++]; } @Override @@ -264,10 +241,11 @@ public class NextableInputStream extends InputStream { 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; } } } @@ -286,7 +264,7 @@ public class NextableInputStream extends InputStream { preRead(); long inBuffer = Math.min(n, available()); - pos += inBuffer; + start += inBuffer; n -= inBuffer; skipped += inBuffer; } @@ -300,7 +278,7 @@ public class NextableInputStream extends InputStream { return 0; } - return Math.max(0, len - pos); + return Math.max(0, stop - start); } /** @@ -309,11 +287,11 @@ public class NextableInputStream extends InputStream { *

* Including the under-laying {@link InputStream}. *

- * Note: if you called the {@link NextableInputStream#open()} method + * Note: 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 @@ -333,11 +311,14 @@ public class NextableInputStream extends InputStream { * You can call this method multiple times, it will not cause an * {@link IOException} for subsequent calls. *

- * Note: if you called the {@link NextableInputStream#open()} method + * Note: 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 @@ -365,31 +346,31 @@ public class NextableInputStream extends InputStream { * @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; } @@ -397,86 +378,29 @@ public class NextableInputStream extends InputStream { } /** - * 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. - *

- * 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); } /** @@ -486,24 +410,10 @@ public class NextableInputStream extends InputStream { * @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; - } } diff --git a/src/be/nikiroo/utils/streams/BufferedOutputStream.java b/src/be/nikiroo/utils/streams/BufferedOutputStream.java new file mode 100644 index 0000000..8b74ae1 --- /dev/null +++ b/src/be/nikiroo/utils/streams/BufferedOutputStream.java @@ -0,0 +1,255 @@ +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. + *

+ * 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). + *

+ * 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. + *

+ * You are now responsible for it — you must close it. + *

+ * 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. + *

+ * 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. + *

+ * Including the under-laying {@link InputStream}. + *

+ * Note: 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. + *

+ * Including the under-laying {@link InputStream} if + * incudingSubStream is true. + *

+ * You can call this method multiple times, it will not cause an + * {@link IOException} for subsequent calls. + *

+ * Note: 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(); + } + } + } + } +} diff --git a/src/be/nikiroo/utils/MarkableFileInputStream.java b/src/be/nikiroo/utils/streams/MarkableFileInputStream.java similarity index 96% rename from src/be/nikiroo/utils/MarkableFileInputStream.java rename to src/be/nikiroo/utils/streams/MarkableFileInputStream.java index f4d95d5..dab4cdc 100644 --- a/src/be/nikiroo/utils/MarkableFileInputStream.java +++ b/src/be/nikiroo/utils/streams/MarkableFileInputStream.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils; +package be.nikiroo.utils.streams; import java.io.FileInputStream; import java.io.FilterInputStream; diff --git a/src/be/nikiroo/utils/streams/NextableInputStream.java b/src/be/nikiroo/utils/streams/NextableInputStream.java new file mode 100644 index 0000000..4a6e0ab --- /dev/null +++ b/src/be/nikiroo/utils/streams/NextableInputStream.java @@ -0,0 +1,235 @@ +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). + *

+ * 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. + *

+ * It can only be called when the "current" stream is spent (i.e., you must + * first process the stream until it is spent). + *

+ * We consider that when the under-laying {@link InputStream} is also spent, + * we cannot have a next sub-stream (it will thus return FALSE). + *

+ * {@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. + *

+ * 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). + *

+ * 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. + *

+ * 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; + } +} diff --git a/src/be/nikiroo/utils/NextableInputStreamStep.java b/src/be/nikiroo/utils/streams/NextableInputStreamStep.java similarity index 98% rename from src/be/nikiroo/utils/NextableInputStreamStep.java rename to src/be/nikiroo/utils/streams/NextableInputStreamStep.java index a2ee039..818abf5 100755 --- a/src/be/nikiroo/utils/NextableInputStreamStep.java +++ b/src/be/nikiroo/utils/streams/NextableInputStreamStep.java @@ -1,4 +1,4 @@ -package be.nikiroo.utils; +package be.nikiroo.utils.streams; import java.io.InputStream; diff --git a/src/be/nikiroo/utils/streams/ReplaceInputStream.java b/src/be/nikiroo/utils/streams/ReplaceInputStream.java new file mode 100644 index 0000000..f5138ee --- /dev/null +++ b/src/be/nikiroo/utils/streams/ReplaceInputStream.java @@ -0,0 +1,83 @@ +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 from with + * to. + * + * @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 from with + * to. + * + * @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; + } +} diff --git a/src/be/nikiroo/utils/streams/ReplaceOutputStream.java b/src/be/nikiroo/utils/streams/ReplaceOutputStream.java new file mode 100644 index 0000000..e889b76 --- /dev/null +++ b/src/be/nikiroo/utils/streams/ReplaceOutputStream.java @@ -0,0 +1,72 @@ +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 from with + * to. + * + * @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 from with + * to. + * + * @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(); + } + } +} diff --git a/src/be/nikiroo/utils/streams/StreamUtils.java b/src/be/nikiroo/utils/streams/StreamUtils.java new file mode 100644 index 0000000..6b8251a --- /dev/null +++ b/src/be/nikiroo/utils/streams/StreamUtils.java @@ -0,0 +1,70 @@ +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). + *

+ * Note: the parameter stop is the index of the last + * position, not the length. + *

+ * Note: the search term size must 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 not a + * length, but an index) + * + * @return TRUE if the search content is present at the given location and + * does not exceed the len 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; + } + } +} diff --git a/src/be/nikiroo/utils/test_code/BufferedInputStreamTest.java b/src/be/nikiroo/utils/test_code/BufferedInputStreamTest.java new file mode 100644 index 0000000..3ba7be8 --- /dev/null +++ b/src/be/nikiroo/utils/test_code/BufferedInputStreamTest.java @@ -0,0 +1,46 @@ +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]); + } + } +} diff --git a/src/be/nikiroo/utils/test_code/BufferedOutputStreamTest.java b/src/be/nikiroo/utils/test_code/BufferedOutputStreamTest.java new file mode 100644 index 0000000..cf6eb2a --- /dev/null +++ b/src/be/nikiroo/utils/test_code/BufferedOutputStreamTest.java @@ -0,0 +1,80 @@ +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]); + } + } +} diff --git a/src/be/nikiroo/utils/test_code/NextableInputStreamTest.java b/src/be/nikiroo/utils/test_code/NextableInputStreamTest.java index 4664cbf..70123b9 100644 --- a/src/be/nikiroo/utils/test_code/NextableInputStreamTest.java +++ b/src/be/nikiroo/utils/test_code/NextableInputStreamTest.java @@ -4,8 +4,8 @@ import java.io.ByteArrayInputStream; 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; diff --git a/src/be/nikiroo/utils/test_code/ReplaceInputStreamTest.java b/src/be/nikiroo/utils/test_code/ReplaceInputStreamTest.java new file mode 100644 index 0000000..e6e2112 --- /dev/null +++ b/src/be/nikiroo/utils/test_code/ReplaceInputStreamTest.java @@ -0,0 +1,106 @@ +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]); + } + } +} diff --git a/src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java b/src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java new file mode 100644 index 0000000..1db3397 --- /dev/null +++ b/src/be/nikiroo/utils/test_code/ReplaceOutputStreamTest.java @@ -0,0 +1,168 @@ +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]); + } + } +} diff --git a/src/be/nikiroo/utils/test_code/StringUtilsTest.java b/src/be/nikiroo/utils/test_code/StringUtilsTest.java index f9c5272..de28359 100644 --- a/src/be/nikiroo/utils/test_code/StringUtilsTest.java +++ b/src/be/nikiroo/utils/test_code/StringUtilsTest.java @@ -238,7 +238,7 @@ class StringUtilsTest extends TestLauncher { 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)); } }); @@ -246,31 +246,31 @@ class StringUtilsTest extends TestLauncher { 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)); } }); } diff --git a/src/be/nikiroo/utils/test_code/Test.java b/src/be/nikiroo/utils/test_code/Test.java index 01766ac..f994480 100644 --- a/src/be/nikiroo/utils/test_code/Test.java +++ b/src/be/nikiroo/utils/test_code/Test.java @@ -33,7 +33,11 @@ public class Test extends TestLauncher { 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;