/*
* ------------------------------------------------------------------------------
* Hermes FTP Server
* Copyright (c) 2005-2014 Lars Behnke
* ------------------------------------------------------------------------------
*
* This file is part of Hermes FTP Server.
*
* Hermes FTP Server 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 2 of the License, or
* (at your option) any later version.
*
* Hermes FTP Server 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 Hermes FTP Server; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
* ------------------------------------------------------------------------------
*/
package com.apporiented.hermesftp.streams;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* In a record structured file EOR and EOF will each be indicated by a two-byte control code. The
* first byte of the control code will be all ones, the escape character. The second byte will have
* the low order bit on and zeros elsewhere for EOR and the second low order bit on for EOF; that
* is, the byte will have value 1 for EOR and value 2 for EOF. EOR and EOF may be indicated together
* on the last byte transmitted by turning both low order bits on (i.e., the value 3). If a byte of
* all ones was intended to be sent as data, it should be repeated in the second byte of the control
* code.
*
* @author Lars Behnke
*/
public class RecordInputStream extends InputStream implements RecordReadSupport {
private static final int ESCAPE_CODE = 0xFF;
private InputStream is;
private byte[] eorMarker;
private int eorMarkerIdx;
private boolean completed;
/**
* Constructor.
*
* @param is The input stream.
*/
public RecordInputStream(InputStream is) {
this(is, null);
}
/**
* Constructor.
*
* @param is The input stream.
* @param eorMarker The byte sequence the EOR marker is translated to (e.g. line break).
*/
public RecordInputStream(InputStream is, byte[] eorMarker) {
super();
this.is = is;
if (eorMarker == null) {
String lineSep = System.getProperty("line.separator");
eorMarker = lineSep.getBytes();
}
this.eorMarker = eorMarker;
this.eorMarkerIdx = eorMarker.length;
}
/**
* {@inheritDoc}
*/
public int read() throws IOException {
int b;
if (eorMarkerIdx < eorMarker.length) {
b = eorMarker[eorMarkerIdx++];
} else if (completed) {
b = -1;
} else {
b = is.read();
if (b == ESCAPE_CODE) {
b = processControlCode();
}
}
return b;
}
/**
* Reads a complete record, excluding the end marker.
*
* @return The record without EOR/EOF marker.
* @throws IOException If something goes wrong.
*/
public byte[] readRecord() throws IOException {
if (completed) {
return null;
}
List<Byte> byteList = new ArrayList<Byte>();
boolean done = false;
while (!done) {
int b = is.read();
if (b == -1) {
throw new IOException("Unexpected end of file. No EOF marker found.");
} else if (b == ESCAPE_CODE) {
b = is.read();
if (b != ESCAPE_CODE) {
boolean eor = (b & 1) > 0;
boolean eof = (b & 2) > 0;
if (eof) {
completed = true;
done = true;
}
if (eor) {
done = true;
}
continue;
}
}
byteList.add((byte) b);
}
return createByteArrayByList(byteList);
}
/**
* {@inheritDoc}
*/
public void close() throws IOException {
is.close();
if (!completed) {
throw new IOException("No EOF marker found.");
}
}
private byte[] createByteArrayByList(List<Byte> byteList) {
int idx = 0;
byte[] result = new byte[byteList.size()];
for (Byte bObj : byteList) {
result[idx++] = bObj;
}
return result;
}
/**
* Processes the control code and returns the next data byte.
*
* @return The next data byte.
* @throws IOException
*/
private int processControlCode() throws IOException {
int b;
b = is.read();
if (b != ESCAPE_CODE) {
boolean eor = (b & 1) > 0;
boolean eof = (b & 2) > 0;
if (eof) {
completed = true;
}
if (eor) {
eorMarkerIdx = 0;
b = read();
}
}
return b;
}
}