/* * Copyright (C) 2014 James Lawrence. * * This file is part of LibLab. * * LibLab is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.sqrt.liblab.io; import com.sqrt.liblab.LabFile; import com.sqrt.liblab.threed.Angle; import com.sqrt.liblab.threed.Vector2f; import com.sqrt.liblab.threed.Vector3f; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; /** * Provides an abstract way to read data from an unspecified source */ public abstract class DataSource { /** * The LabFile that we are providing data from/for */ public final LabFile container; private String name; private long mark; private DataSourceInputStream in; private DataSourceOutputStream out; /** * The only constructor. Enforces implementations to provide a container and a name * @param container the container of the data * @param name the name of the entry */ protected DataSource(LabFile container, String name) { this.container = container; this.name = name; } /** * Returns the name of the entry we provide data for * @return the name of the entry we provider data for */ public String getName() { return name; } /** * Sets the name of the entry we provide data for * @param name the name */ public void setName(String name) { this.name = name; } /** * Returns the position we are currently at in the data stream * @return the position * @throws IOException */ public abstract long position() throws IOException; /** * Seeks to the specified position in the data stream * @param pos the position from the beginning of this source * @throws IOException */ public abstract void position(long pos) throws IOException; /** * Skips the specified number of bytes in the DataSource * @param count the number of bytes to advance over * @throws IOException */ public void skip(long count) throws IOException { position(position() + count); } /** * Returns the total length of the data stream * @return the total length of the data stream */ public abstract long length(); /** * Returns the number of bytes remaining in the data stream to be consumed * @return the number of bytes remaining in the data stream to be consumed * @throws IOException */ public long remaining() throws IOException { return length() - position(); } /** * Reads a null-padded string from the stream of the specified length * @param maxLen the number of bytes to read from the stream * @return the string with all nulls removed * @throws IOException */ public String getString(int maxLen) throws IOException { byte[] data = new byte[maxLen]; get(data); String s = new String(data); int idx = s.indexOf(0); if (idx != -1) s = s.substring(0, idx); return s; } /** * Writes a null-padded string to this DataSource * @param s the String * @throws IOException */ public void putString(String s, int len) throws IOException { byte[] data = s.getBytes(); int e = Math.min(data.length, len); put(data, 0, e); } /** * Returns a string containing all encountered bytes up to the next \n character * @return the string * @throws IOException */ public String getLine() throws IOException { StringBuilder sb = new StringBuilder(); while(remaining() > 0) { char c = (char) get(); if(c == '\n') break; sb.append(c); } return sb.toString(); } /** * Reads len bytes from the stream, or dies trying * @param b the destination for the data we read * @param off the offset into the destination to start placing the data * @param len the number of bytes to read into the destination * @throws IOException */ public abstract void get(byte[] b, int off, int len) throws IOException; /** * Writes len bytes to the stream * @param b the data to write * @param off the offset into the supplied data that we start copying from * @param len the number of bytes to write * @throws IOException */ public abstract void put(byte[] b, int off, int len) throws IOException; /** * Reads dest.length bytes into dest from index 0 * @param dest the destination of the read data * @throws IOException */ public void get(byte[] dest) throws IOException { get(dest, 0, dest.length); } /** * Writes data.length bytes to this stream * @param data the bytes to write * @throws IOException */ public void put(byte[] data) throws IOException { put(data, 0, data.length); } /** * Reads a byte from the stream * @return a byte * @throws IOException */ public abstract byte get() throws IOException; /** * Writes a byte to the stream * @param b the byte to write * @throws IOException */ public abstract void put(byte b) throws IOException; /** * Reads an unsigned byte from the stream * @return and unsigned byte * @throws IOException */ public int getUByte() throws IOException { return get() & 0xff; } /** * Reads an integer from the stream * @return an integer * @throws IOException */ public int getInt() throws IOException { int c1 = getUByte(), c2 = getUByte(), c3 = getUByte(), c4 = getUByte(); return (c1 << 24) | (c2 << 16) | (c3 << 8) | c4; } /** * Writes an integer to the stream * @param i the integer to write * @throws IOException */ public void putInt(int i) throws IOException { put((byte) ((i >> 24) & 0xff)); put((byte) ((i >> 16) & 0xff)); put((byte) ((i >> 8) & 0xff)); put((byte) (i & 0xff)); } /** * Reads a boolean from the stream (getIntLE() != 0). Not efficient, but how grimE formats seem to work. * @return the boolean value * @throws IOException */ public boolean getBoolean() throws IOException { return getIntLE() != 0; // As grimE does it... } /** * Writes a boolean to the data stream in grimE format * @param b the boolean to write */ public void putBoolean(boolean b) throws IOException { putIntLE(b? 1: 0); } /** * Reads an integer from the stream with little endian ordering * @return an integer * @throws IOException */ public int getIntLE() throws IOException { return Integer.reverseBytes(getInt()); } /** * Writes an integer to the stream with little endian ordering * @param i the integer to write * @throws IOException */ public void putIntLE(int i) throws IOException{ putInt(Integer.reverseBytes(i)); } /** * Reads an unsigned integer from the stream with little endian byte ordering * @return an integer * @throws IOException */ public long getUIntLE() throws IOException { return (long) getIntLE() & 0xffffffffL; } /** * Reads a short from the stream * @return a short * @throws IOException */ public short getShort() throws IOException { int c1 = getUByte(), c2 = getUByte(); if ((c1 | c2) < 0) throw new EOFException(); return (short) ((c1 << 8) | c2); } /** * Writes a short to the stream * @param s the short to write * @throws IOException */ public void putShort(short s) throws IOException { put((byte) ((s >> 8) & 0xff)); put((byte) (s & 0xff)); } /** * Reads an unsigned short from the stream * @return an unsigned short * @throws IOException */ public int getUShort() throws IOException { return getShort() & 0xffff; } /** * Reads a short from the stream in little endian byte order * @return a short * @throws IOException */ public short getShortLE() throws IOException { return Short.reverseBytes(getShort()); } /** * Writes a short to the stream in little endian byte order * @param s the short to write * @throws IOException */ public void putShortLE(short s) throws IOException { putShort(Short.reverseBytes(s)); } /** * Returns an unsigned short from the stream in little endian byte ordering * @return an unsigned short * @throws IOException */ public int getUShortLE() throws IOException { return getShortLE() & 0xffff; } /** * Reads a float from the stream in big endian format * @return a float * @throws IOException */ public float getFloat() throws IOException { return Float.intBitsToFloat(getInt()); } /** * Writes a float to the stream in big endian byte order * @param f */ public void putFloat(float f) throws IOException { putInt(Float.floatToIntBits(f)); } /** * Reads a float from the stream in little endian format * @return a float * @throws IOException */ public float getFloatLE() throws IOException { return Float.intBitsToFloat(getIntLE()); } /** * Writes a float to the stream in little endian byte order * @param f */ public void putFloatLE(float f) throws IOException { putIntLE(Float.floatToIntBits(f)); } /** * Reads an angle from the stream (a float encapsulated in an Angle object) * @return the angle * @throws IOException */ public Angle getAngle() throws IOException { return new Angle(getFloatLE()); } /** * Writes an angle to the data stream * @param a the angle to write */ public void putAngle(Angle a) throws IOException { putFloatLE(a.degrees); } /** * Reads a vector2 from the stream (2 floats encapsulated in a Vector2f) * @return the vector * @throws IOException */ public Vector2f getVector2f() throws IOException { return new Vector2f(getFloatLE(), getFloatLE()); } /** * Writes a vector2f to the stream * @param v the vector to write * @throws IOException */ public void putVector2f(Vector2f v) throws IOException { putFloatLE(v.x); putFloatLE(v.y); } /** * Reads a 3D vector from the stream (3 floats) * @return the vector * @throws IOException */ public Vector3f getVector3f() throws IOException { return new Vector3f(getFloatLE(), getFloatLE(), getFloatLE()); } /** * Writes a 3d vector to the stream * @param v the vector to write */ public void putVector3f(Vector3f v) throws IOException { putFloatLE(v.x); putFloatLE(v.y); putFloatLE(v.z); } public synchronized void mark(int readlimit) throws IOException { mark = position(); } public synchronized void reset() throws IOException { position(mark); } public boolean markSupported() { return true; } public InputStream asInputStream() { if(in == null) in = new DataSourceInputStream(); return in; } public OutputStream asOutputStream() { if(out == null) out = new DataSourceOutputStream(); return out; } public abstract int hashCode(); public String toString() { return name; } private class DataSourceInputStream extends InputStream { public int read() throws IOException { return remaining() < 0? -1: getUByte(); } public int read(byte[] b, int off, int len) throws IOException { int l = (int) Math.min(remaining(), len); if(l == 0) return -1; get(b, off, l); return l; } public long skip(long n) throws IOException { DataSource.this.skip(n); return n; } public int available() throws IOException { return (int) DataSource.this.remaining(); } public synchronized void mark(int readlimit) { try { DataSource.this.mark(readlimit); } catch (IOException e) { throw new RuntimeException(e); } } public synchronized void reset() throws IOException { DataSource.this.reset(); } public boolean markSupported() { return DataSource.this.markSupported(); } } private class DataSourceOutputStream extends OutputStream { public void write(int b) throws IOException { put((byte) b); } public void write(byte[] b, int off, int len) throws IOException { put(b, off, len); } } }