/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework 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 Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.common.record;
import static java.lang.String.format;
import static java.lang.System.arraycopy;
import org.openbel.framework.common.InvalidArgument;
/**
* Models a record for some {@code <T>} contained by a {@link RecordFile}.
*
* @param <T> The modeled item
* @author Nick Bargnesi
*/
public abstract class Record<T> {
protected final Column<?>[] columns;
protected final int recordSize;
/**
* Creates a record associated with fixed-size columns.
*
* @param columns {@link Column Columns} composing each record; may not be
* null or have zero length
* @throws InvalidArgument Thrown if {@code columns} is null or invalid
*/
public Record(Column<?>... columns) {
if (columns == null) {
throw new InvalidArgument("columns", columns);
} else if (columns.length == 0) {
throw new InvalidArgument("invalid number of columns");
}
this.columns = columns;
int size = 0;
for (final Column<?> c : columns) {
if (c.size == 0) {
throw new InvalidArgument("column size may not be 0");
}
size += c.size;
}
recordSize = size;
}
/**
* Creates a record associated with some number of columns with the last
* columns having an unknown size.
*
* @param recordSize The record size used to derive the size of the last
* column
* @param columns {@link Column Columns} composing each record; may not be
* zero or have zero length.
* @throws InvalidArgument Thrown if {@code columns} is null, zero-length,
* or invalid
*/
public Record(final int recordSize, Column<?>... columns) {
if (columns == null) {
throw new InvalidArgument("columns", columns);
} else if (columns.length == 0) {
throw new InvalidArgument("invalid number of columns");
}
this.columns = columns;
int size = 0, i = 0;
for (; i < columns.length; i++) {
Column<?> c = columns[i];
final boolean lastColumn = (i == (columns.length - 1));
if (lastColumn && c.size != 0) {
// last column's size must be zero
final String msg = "the last column size must be unknown";
throw new InvalidArgument(msg);
} else if (!lastColumn && c.size == 0) {
// column size may not be zero here
final String msg = "only the last column size may be unknown";
throw new InvalidArgument(msg);
}
size += c.size;
}
columns[i - 1].setSize(recordSize - size);
this.recordSize = recordSize;
}
/**
* Returns the {@code column's} byte array data contained in {@code buffer}.
*
* @param buffer {@code byte[]}; must be non-null and of the same length as
* {@link #getRecordSize()}
* @param column Column to read
* @return {@code byte[]}
* @throws InvalidArgument Thrown if {@code buffer} is null or invalid
*/
public final byte[] readColumn(byte[] buffer, int column) {
if (buffer == null) {
throw new InvalidArgument("buffer", buffer);
} else if (buffer.length != recordSize) {
final String fmt = "invalid buffer (%d bytes, expected %d)";
final String msg = format(fmt, buffer.length, recordSize);
throw new InvalidArgument(msg);
}
int i = 0, offset = 0;
for (; i < column; i++) {
offset += columns[i].size;
}
Column<?> c = columns[i];
byte[] ret = new byte[c.size];
arraycopy(buffer, offset, ret, 0, c.size);
return ret;
}
/**
* Copies the {@code column's} byte array data contained in
* {@code recBuffer} into the {@code colBuffer}.
*
* @param recBuffer {@code byte[]}; must be non-null and of the same length
* as {@link #getRecordSize()}
* @param colBuffer {@code byte[]}; must be non-null and of the same length
* as the {@code column} size
* @param column Column to read
* @throws InvalidArgument Thrown either buffer or column is null or invalid
*/
public final void readColumn(byte[] recBuffer,
byte[] colBuffer,
int column) {
if (recBuffer == null) {
throw new InvalidArgument("recBuffer", recBuffer);
} else if (colBuffer == null) {
throw new InvalidArgument("colBuffer", colBuffer);
} else if (recBuffer.length != recordSize) {
final String fmt = "invalid record buffer (%d bytes, expected %d)";
final String msg = format(fmt, recBuffer.length, recordSize);
throw new InvalidArgument(msg);
}
int i = 0, offset = 0;
for (; i < column; i++) {
offset += columns[i].size;
}
Column<?> c = columns[i];
if (colBuffer.length != c.size) {
final String fmt = "invalid column buffer (%d bytes, expected %d)";
final String msg = format(fmt, colBuffer.length, c.size);
throw new InvalidArgument(msg);
}
arraycopy(recBuffer, offset, colBuffer, 0, c.size);
}
/**
* Writes the column's buffer byte array data into the {@code column}
* contained by the record buffer.
*
* @param colBuffer Column buffer data
* @param column Column to write
* @param recBuffer Record buffer
* @throws InvalidArgument Thrown if either buffer or column is invalid, or
* either buffer is null
*/
public final void writeColumn(byte[] colBuffer,
int column,
byte[] recBuffer) {
if (colBuffer == null) {
throw new InvalidArgument("colBuffer may not be null");
} else if (recBuffer == null) {
throw new InvalidArgument("recBuffer may not be null");
} else if (column < 0 || column >= columns.length) {
final String fmt = "invalid column: %d";
final String msg = format(fmt, column);
throw new InvalidArgument(msg);
} else if (recBuffer.length != recordSize) {
final String fmt = "invalid record buffer (%d bytes, expected %d)";
final String msg = format(fmt, recBuffer.length, recordSize);
throw new InvalidArgument(msg);
}
int i = 0, offset = 0;
for (; i < column; i++) {
offset += columns[i].size;
}
Column<?> c = columns[i];
if (colBuffer.length != c.size) {
final String fmt = "invalid column buffer (%d bytes, expected %d)";
final String msg = format(fmt, colBuffer.length, c.size);
throw new InvalidArgument(msg);
}
arraycopy(colBuffer, 0, recBuffer, offset, colBuffer.length);
return;
}
/**
* Returns the record size (the summation of all column sizes).
*
* @return {@code int}
*/
public final int getRecordSize() {
return recordSize;
}
/**
* Reads {@code <T>} from a byte buffer.
*
* @param buffer {@code byte[]}; of size {@link #getRecordSize()}
* @return {@code <T>}
* @throws InvalidArgument Thrown if {@code buffer} is null or invalid
*/
public final T readBuffer(byte[] buffer) {
if (buffer == null) {
throw new InvalidArgument("buffer", buffer);
} else if (buffer.length != recordSize) {
final String fmt = "invalid buffer (%d bytes, expected %d)";
final String msg = format(fmt, buffer.length, recordSize);
throw new InvalidArgument(msg);
}
return read(buffer);
}
/**
* Reads {@code <T>} to the provided {@code buffer}.
*
* @param buffer {@code byte[]}; of size {@link #getRecordSize()}
* @param t {@code <T>}
* @throws InvalidArgument Thrown if either argument is null or if
* {@code buffer} is invalid
*/
public final void readToEntry(byte[] buffer, T t) {
if (buffer == null) {
throw new InvalidArgument("buffer", buffer);
} else if (t == null) {
throw new InvalidArgument("cannot read a null entry");
} else if (buffer.length != recordSize) {
final String fmt = "invalid buffer (%d bytes, expected %d)";
final String msg = format(fmt, buffer.length, recordSize);
throw new InvalidArgument(msg);
}
readTo(buffer, t);
}
/**
* Writes {@code <T>} to a byte buffer.
*
* @param t {@code <T>}
* @return {@code byte[]}; of size {@link #getRecordSize()}
* @throws InvalidArgument Thrown if {@code t} is null
*/
public final byte[] writeEntry(T t) {
if (t == null) {
throw new InvalidArgument("cannot write a null");
}
return write(t);
}
/**
* Writes {@code <T>} to the provided {@code buffer}.
*
* @param t {@code <T>}
* @param buffer {@code byte[]}; of size {@link #getRecordSize()}
* @throws InvalidArgument Thrown if either argument is null or if
*/
public final void writeToBuffer(T t, byte[] buffer) {
if (t == null) {
throw new InvalidArgument("cannot write a null");
} else if (buffer == null) {
throw new InvalidArgument("cannot write to a null buffer");
} else if (buffer.length != recordSize) {
final String fmt = "invalid buffer (%d bytes, expected %d)";
final String msg = format(fmt, buffer.length, recordSize);
throw new InvalidArgument(msg);
}
writeTo(t, buffer);
}
/**
* Returns an empty, uninitialized record.
*
* @return {@code byte[]}
* @see java.util.Arrays#fill(byte[], byte)
*/
public byte[] emptyRecord() {
return new byte[recordSize];
}
/**
* Internal {@link #readBuffer(byte[])} implementation deferred to
* {@link Record record} subclasses.
*
* @param buffer {@code byte[]}; guaranteed to be non-null and of the
* correct size (@link {@link #getRecordSize()})
* @return {@code <T>}
*/
protected abstract T read(byte[] buffer);
/**
* Internal {@link Record#readToEntry(byte[], Object)} implementation
* deferred to {@link Record record} subclasses.
*
* @param buffer {@code byte[]}; guaranteed to be non-null and of the
* correct size (@link {@link #getRecordSize()})
* @param t {@code <T>}; guaranteed to be non-null
*/
protected abstract void readTo(byte[] buffer, T t);
/**
* Internal {@link Record#write(Object)} implementation deferred to
* {@link Record record} subclasses.
*
* @param t {@code <T>}; guaranteed to be non-null
* @return {@code byte[]}
*/
protected abstract byte[] write(T t);
/**
* Internal {@link Record#writeToBuffer(Object, byte[])} implementation
* deferred to {@link Record record} subclasses.
*
* @param t {@code <T>}; guaranteed to be non-null
* @param buffer {@code byte[]}; guaranteed to be non-null and of the
* correct size (@link {@link #getRecordSize()})
*/
protected abstract void writeTo(T t, byte[] buffer);
}