1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 package net.sf.hermesftp.streams;
26
27 import java.io.IOException;
28 import java.io.InputStream;
29 import java.util.ArrayList;
30 import java.util.List;
31
32 /***
33 * In a record structured file EOR and EOF will each be indicated by a two-byte control code. The
34 * first byte of the control code will be all ones, the escape character. The second byte will have
35 * the low order bit on and zeros elsewhere for EOR and the second low order bit on for EOF; that
36 * is, the byte will have value 1 for EOR and value 2 for EOF. EOR and EOF may be indicated together
37 * on the last byte transmitted by turning both low order bits on (i.e., the value 3). If a byte of
38 * all ones was intended to be sent as data, it should be repeated in the second byte of the control
39 * code.
40 *
41 * @author Lars Behnke
42 */
43 public class RecordInputStream extends InputStream implements RecordReadSupport {
44
45 private static final int ESCAPE_CODE = 0xFF;
46
47 private InputStream is;
48
49 private byte[] eorMarker;
50
51 private int eorMarkerIdx;
52
53 private boolean completed;
54
55 /***
56 * Constructor.
57 *
58 * @param is The input stream.
59 */
60 public RecordInputStream(InputStream is) {
61 this(is, null);
62 }
63
64 /***
65 * Constructor.
66 *
67 * @param is The input stream.
68 * @param eorMarker The byte sequence the EOR marker is translated to (e.g. line break).
69 */
70 public RecordInputStream(InputStream is, byte[] eorMarker) {
71 super();
72 this.is = is;
73 if (eorMarker == null) {
74 String lineSep = System.getProperty("line.separator");
75 eorMarker = lineSep.getBytes();
76 }
77 this.eorMarker = eorMarker;
78 this.eorMarkerIdx = eorMarker.length;
79 }
80
81 /***
82 * {@inheritDoc}
83 */
84 public int read() throws IOException {
85 int b = -1;
86 if (eorMarkerIdx < eorMarker.length) {
87 b = eorMarker[eorMarkerIdx++];
88 } else if (completed) {
89 b = -1;
90 } else {
91 b = is.read();
92 if (b == ESCAPE_CODE) {
93 b = processControlCode();
94 }
95 }
96 return b;
97 }
98
99 /***
100 * Reads a complete record, excluding the end marker.
101 *
102 * @return The record without EOR/EOF marker.
103 * @throws IOException If something goes wrong.
104 */
105 public byte[] readRecord() throws IOException {
106 if (completed) {
107 return null;
108 }
109 List<Byte> byteList = new ArrayList<Byte>();
110 boolean done = false;
111 while (!done) {
112 int b = is.read();
113 if (b == -1) {
114 throw new IOException("Unexpected end of file. No EOF marker found.");
115 } else if (b == ESCAPE_CODE) {
116 b = is.read();
117
118 if (b != ESCAPE_CODE) {
119 boolean eor = (b & 1) > 0;
120 boolean eof = (b & 2) > 0;
121 if (eof) {
122 completed = true;
123 done = true;
124 }
125 if (eor) {
126 done = true;
127 }
128 continue;
129 }
130 }
131 byteList.add(new Byte((byte) b));
132 }
133 return createByteArrayByList(byteList);
134 }
135
136 /***
137 * {@inheritDoc}
138 */
139 public void close() throws IOException {
140 is.close();
141 if (!completed) {
142 throw new IOException("No EOF marker found.");
143 }
144 }
145
146 private byte[] createByteArrayByList(List<Byte> byteList) {
147 int idx = 0;
148 byte[] result = new byte[byteList.size()];
149 for (Byte bObj : byteList) {
150 result[idx++] = bObj.byteValue();
151 }
152 return result;
153 }
154
155 /***
156 * Processes the control code and returns the next data byte.
157 *
158 * @return The next data byte.
159 * @throws IOException
160 */
161 private int processControlCode() throws IOException {
162 int b;
163 b = is.read();
164 if (b != ESCAPE_CODE) {
165 boolean eor = (b & 1) > 0;
166 boolean eof = (b & 2) > 0;
167 if (eof) {
168 completed = true;
169 }
170 if (eor) {
171 eorMarkerIdx = 0;
172 b = read();
173 }
174 }
175 return b;
176 }
177
178 }