|
|
@@ -0,0 +1,370 @@
|
|
|
+/*
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ * @copyright Ranides Atterwim
|
|
|
+ * @license WTFPL
|
|
|
+ * @url http://ranides.net/projects/assira
|
|
|
+ */
|
|
|
+package net.ranides.assira.io;
|
|
|
+
|
|
|
+import java.io.DataInput;
|
|
|
+import java.io.DataOutput;
|
|
|
+import java.io.EOFException;
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.UTFDataFormatException;
|
|
|
+import java.nio.ByteBuffer;
|
|
|
+import java.nio.CharBuffer;
|
|
|
+import java.nio.charset.Charset;
|
|
|
+import java.nio.charset.CharsetDecoder;
|
|
|
+import java.nio.charset.CoderResult;
|
|
|
+import net.ranides.assira.collection.arrays.ArrayAllocator;
|
|
|
+import net.ranides.assira.collection.arrays.NativeArray;
|
|
|
+import net.ranides.assira.collection.arrays.NativeArrayAllocator;
|
|
|
+import net.ranides.assira.text.Charsets;
|
|
|
+
|
|
|
+/**
|
|
|
+ *
|
|
|
+ * @author Ranides Atterwim <ranides@gmail.com>
|
|
|
+ */
|
|
|
+public class DataBuffer implements DataInput, DataOutput {
|
|
|
+
|
|
|
+ // @todo (assira #3) io DataBuffer implements DataInput, DataOutput
|
|
|
+
|
|
|
+ NativeArray array;
|
|
|
+ ByteBuffer buffer;
|
|
|
+
|
|
|
+ public DataBuffer() {
|
|
|
+ byte[] data = new byte[16];
|
|
|
+ this.array = NativeArray.wrap(data);
|
|
|
+ this.buffer = ByteBuffer.wrap(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ public DataBuffer seek(int position) throws IOException {
|
|
|
+ if(position != buffer.position(position).position()) {
|
|
|
+ throw new IOException("EOF ?");
|
|
|
+ }
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ public int seek() {
|
|
|
+ return buffer.position();
|
|
|
+ }
|
|
|
+
|
|
|
+ private void unget(byte v) {
|
|
|
+ int n =buffer.position();
|
|
|
+ buffer.position(n-1);
|
|
|
+ buffer.put(n-1, v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void readFully(byte[] b) throws IOException {
|
|
|
+ readFully(b, 0, b.length);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void readFully(byte[] b, int off, int len) throws IOException {
|
|
|
+ checkEOF(len);
|
|
|
+ buffer.get(b, off, len);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int skipBytes(int n) throws IOException {
|
|
|
+ reserve(n);
|
|
|
+ buffer.position(buffer.position()+n);
|
|
|
+ return n;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public boolean readBoolean() throws IOException {
|
|
|
+ checkEOF(1);
|
|
|
+ return buffer.get() != 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public byte readByte() throws IOException {
|
|
|
+ checkEOF(1);
|
|
|
+ return buffer.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int readUnsignedByte() throws IOException {
|
|
|
+ checkEOF(1);
|
|
|
+ return buffer.get() & 0xFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public short readShort() throws IOException {
|
|
|
+ checkEOF(2);
|
|
|
+ return buffer.getShort();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int readUnsignedShort() throws IOException {
|
|
|
+ checkEOF(2);
|
|
|
+ return buffer.getShort() & 0xFFFF;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public char readChar() throws IOException {
|
|
|
+ checkEOF(2);
|
|
|
+ return buffer.getChar();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public int readInt() throws IOException {
|
|
|
+ checkEOF(4);
|
|
|
+ return buffer.getInt();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public long readLong() throws IOException {
|
|
|
+ checkEOF(8);
|
|
|
+ return buffer.getLong();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public float readFloat() throws IOException {
|
|
|
+ checkEOF(4);
|
|
|
+ return buffer.getFloat();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public double readDouble() throws IOException {
|
|
|
+ checkEOF(8);
|
|
|
+ return buffer.getDouble();
|
|
|
+ }
|
|
|
+
|
|
|
+ public String readLine(Charset cs) throws IOException {
|
|
|
+ if(buffer.remaining() <=0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ CharsetDecoder cd = cs.newDecoder();
|
|
|
+ SBuffer out = new SBuffer();
|
|
|
+
|
|
|
+ while(true) {
|
|
|
+ out.reserve();
|
|
|
+ CharBuffer cb = out.cbuf();
|
|
|
+ int p = buffer.position(); // NOPMD
|
|
|
+ CoderResult cr = cd.decode(buffer, cb, true);
|
|
|
+ if(cr.isError()) {
|
|
|
+ throw new IOException("Encoding error:" + cr.toString());
|
|
|
+ }
|
|
|
+ int end = out.endl();
|
|
|
+ if(end >= 0) {
|
|
|
+ buffer.position(p);
|
|
|
+ cd.decode(buffer, out.cbuf(end), true);
|
|
|
+ return out.toString();
|
|
|
+ }
|
|
|
+ if(cr.isUnderflow()) {
|
|
|
+ out.end = out.position + cb.position();
|
|
|
+ return out.toString();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Reads the next line of text from the input stream.
|
|
|
+ * <b>Uses UTF-8 charset</b>.
|
|
|
+ * <p>
|
|
|
+ * If end of file is encountered
|
|
|
+ * before even one byte can be read, then {@code null}
|
|
|
+ * is returned. Otherwise, bytes are converted to type {@code char}
|
|
|
+ * using UTF-8 charset. If the character {@code '\n'}
|
|
|
+ * is encountered, it is discarded and reading
|
|
|
+ * ceases. If the character {@code '\r'}
|
|
|
+ * is encountered, it is discarded and, if
|
|
|
+ * the following byte converts  to the
|
|
|
+ * character {@code '\n'}, then that is
|
|
|
+ * discarded also; reading then ceases. If
|
|
|
+ * end of file is encountered before either
|
|
|
+ * of the characters {@code '\n'} and
|
|
|
+ * {@code '\r'} is encountered, reading
|
|
|
+ * ceases. Once reading has ceased, a {@code String}
|
|
|
+ * is returned that contains all the characters
|
|
|
+ * read and not discarded, taken in order.
|
|
|
+ *
|
|
|
+ * @return the next line of text from the input stream,
|
|
|
+ * or {@code null} if the end of file is
|
|
|
+ * encountered before a byte can be read.
|
|
|
+ * @exception IOException if an I/O error occurs.
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public String readLine() throws IOException {
|
|
|
+ return readLine(Charsets.UTF8);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String readUTF() throws IOException {
|
|
|
+ checkEOF(2);
|
|
|
+ int size = readUnsignedShort();
|
|
|
+ byte[] bytes = new byte[size];
|
|
|
+ readFully(bytes);
|
|
|
+ return new String(bytes, Charsets.UTF8);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(int b) throws IOException {
|
|
|
+ reserve(1);
|
|
|
+ buffer.put((byte)b);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(byte[] b, int off, int len) throws IOException {
|
|
|
+ reserve(len);
|
|
|
+ buffer.put(b, off, len);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void write(byte[] b) throws IOException {
|
|
|
+ reserve(b.length);
|
|
|
+ buffer.put(b);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeBoolean(boolean v) throws IOException {
|
|
|
+ reserve(1);
|
|
|
+ buffer.put(v ? (byte)1 : (byte)0);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeByte(int v) throws IOException {
|
|
|
+ reserve(1);
|
|
|
+ buffer.put((byte)v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeShort(int v) throws IOException {
|
|
|
+ reserve(2);
|
|
|
+ buffer.putShort((short)v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeChar(int v) throws IOException {
|
|
|
+ reserve(2);
|
|
|
+ buffer.putChar((char)v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeInt(int v) throws IOException {
|
|
|
+ reserve(4);
|
|
|
+ buffer.putInt(v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeLong(long v) throws IOException {
|
|
|
+ reserve(8);
|
|
|
+ buffer.putLong(v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeFloat(float v) throws IOException {
|
|
|
+ reserve(4);
|
|
|
+ buffer.putFloat(v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeDouble(double v) throws IOException {
|
|
|
+ reserve(8);
|
|
|
+ buffer.putDouble(v);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeBytes(String s) throws IOException {
|
|
|
+ int len = s.length();
|
|
|
+ reserve(len);
|
|
|
+ for (int i = 0 ; i < len ; i++) {
|
|
|
+ write((byte)s.charAt(i));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeChars(String s) throws IOException {
|
|
|
+ int len = s.length();
|
|
|
+ reserve(2*len);
|
|
|
+ for (int i = 0 ; i < len ; i++) {
|
|
|
+ buffer.putChar(s.charAt(i));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public void writeUTF(String str) throws IOException {
|
|
|
+ byte[] data = str.getBytes(Charsets.UTF8);
|
|
|
+ int size = data.length;
|
|
|
+ if (size > 65535) {
|
|
|
+ throw new UTFDataFormatException("encoded string too long: " + size + " bytes");
|
|
|
+ }
|
|
|
+
|
|
|
+ reserve(2 + size);
|
|
|
+ buffer.putShort((short)size);
|
|
|
+ buffer.put(data);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void reserve(int size) {
|
|
|
+ if( buffer.remaining() >= size) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int rem = buffer.capacity() - buffer.position();
|
|
|
+ if( rem >= size) {
|
|
|
+ buffer.limit(buffer.position() + size);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ array = NativeArrayAllocator.grow(array, array.size()+size);
|
|
|
+ ByteBuffer newbuf = ByteBuffer.wrap(array.$array());
|
|
|
+ newbuf.position(buffer.position());
|
|
|
+ newbuf.limit(buffer.position()+size);
|
|
|
+ buffer = newbuf;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void checkEOF(int size) throws EOFException {
|
|
|
+ if(buffer.remaining() < size) {
|
|
|
+ buffer.position(buffer.limit());
|
|
|
+ throw new EOFException();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class SBuffer {
|
|
|
+
|
|
|
+ char[] chars = new char[80];
|
|
|
+ int position = 0;
|
|
|
+ int end = -1;
|
|
|
+
|
|
|
+ CharBuffer cbuf() {
|
|
|
+ return CharBuffer.wrap(chars, position, chars.length-position);
|
|
|
+ }
|
|
|
+
|
|
|
+ CharBuffer cbuf(int size) {
|
|
|
+ return CharBuffer.wrap(chars, position, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ void reserve() {
|
|
|
+ chars = ArrayAllocator.ensureCapacity(chars, position+80);
|
|
|
+ }
|
|
|
+
|
|
|
+ boolean peek(int index, char c) {
|
|
|
+ return (index < (chars.length)) && (chars[index] == c);
|
|
|
+ }
|
|
|
+
|
|
|
+ int endl() {
|
|
|
+ for(int i=position; i<chars.length; i++) {
|
|
|
+ char c = chars[i];
|
|
|
+ if(c=='\n') {
|
|
|
+ this.end = i;
|
|
|
+ return i+1;
|
|
|
+ }
|
|
|
+ if(c=='\r') {
|
|
|
+ this.end = i;
|
|
|
+ return peek(i+1, '\n') ? i+2 : i+1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public String toString() {
|
|
|
+ return new String(chars, 0, end);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+}
|