/*******************************************************************************
* Copyright (c) 2013, 2014 Lectorius, Inc.
* Authors:
* Vijay Pandurangan (vijayp@mitro.co)
* Evan Jones (ej@mitro.co)
* Adam Hilss (ahilss@mitro.co)
*
*
* This program 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/>.
*
* You can contact the authors at inbound@mitro.co.
*******************************************************************************/
package co.mitro.recordio;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import co.mitro.recordio.RecordWriter.RecordType.TYPE;
public class RecordWriter implements AutoCloseable {
// protocol specification:
// big endian
// record type: int
// record length: int
// - value: byte buffer
public static class Header {
private int version = 0;
/**
* @return the version
*/
public int getVersion() {
return version;
}
public void write(DataOutputStream os) throws IOException {
os.writeInt(version);
}
public static Header MakeFromStream(DataInputStream is) throws IOException {
Header h = new Header();
h.version = is.readInt();
return h;
}
}
public static class Pointers {
private static final int LOG_EVERY_BYTES = 1<<17; // one pointer every 128k bytes
long numRecords = 0;
long lastOffset = 0;
DataOutputStream footerStream = null;
public Pointers(OutputStream footerOutput) {
footerStream = footerOutput != null ? new DataOutputStream(footerOutput) : null;
}
void addRecord(long offset) throws IOException {
++numRecords;
if (numRecords == 1 || (offset - lastOffset) > LOG_EVERY_BYTES) {
lastOffset = offset;
if (null != footerStream) {
writePointerRecord(footerStream, numRecords, lastOffset);
}
}
}
private void writePointerRecord(DataOutputStream os, long key, long lastOffset)
throws IOException {
os.writeLong(key);
os.writeLong(lastOffset);
footerStream.flush();
}
public void close() throws IOException {
if (footerStream != null) {
footerStream.close();
}
}
}
public static class RecordType {
public enum TYPE {
FILE_FOOTER,
REVERSE_POINTER,
DATA_UNCOMPRESSED,
DATA_COMPRESSED
}
public static void write(DataOutputStream os, TYPE type) throws IOException {
os.writeInt(type.ordinal());
}
public static TYPE read(DataInputStream is) throws IOException {
int ord = is.readInt();
return TYPE.values()[ord];
}
}
protected DataOutputStream os;
private boolean initialized = false;
private Pointers footer;
private long offset = 0;
public long bytesWritten() {
return offset;
}
public RecordWriter(OutputStream os, OutputStream pointerOutput) {
this.os = new DataOutputStream(os);
footer = new Pointers(pointerOutput);
}
public synchronized void write(byte[] data) throws IOException {
write(data, RecordType.TYPE.DATA_UNCOMPRESSED);
}
public synchronized void write(byte[] data, TYPE recordType) throws IOException {
initialize();
footer.addRecord(offset);
int recLen = data.length;
offset += recLen + 4;
RecordType.write(os, recordType);
os.writeInt(recLen);
os.write(data);
os.flush();
}
protected void initialize() throws IOException {
if (!initialized) {
(new Header()).write(os);
offset += 4;
initialized = true;
}
}
@Override
public synchronized void close() throws IOException {
initialize();
RecordType.write(os, RecordType.TYPE.FILE_FOOTER);
this.os.close();
footer.close();
}
}