View Javadoc

1   /*
2    * ------------------------------------------------------------------------------
3    * Hermes FTP Server
4    * Copyright (c) 2005-2007 Lars Behnke
5    * ------------------------------------------------------------------------------
6    * 
7    * This file is part of Hermes FTP Server.
8    * 
9    * Hermes FTP Server is free software; you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as published by
11   * the Free Software Foundation; either version 2 of the License, or
12   * (at your option) any later version.
13   * 
14   * Hermes FTP Server is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public License
20   * along with Hermes FTP Server; if not, write to the Free Software
21   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
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 }