View Javadoc

1   /*****************************************************************
2    * Licensed to the Apache Software Foundation (ASF) under one   *
3    * or more contributor license agreements.  See the NOTICE file *
4    * distributed with this work for additional information        *
5    * regarding copyright ownership.  The ASF licenses this file   *
6    * to you under the Apache License, Version 2.0 (the            *
7    * "License"); you may not use this file except in compliance   *
8    * with the License.  You may obtain a copy of the License at   *
9    *                                                              *
10   *   http://www.apache.org/licenses/LICENSE-2.0                 *
11   *                                                              *
12   * Unless required by applicable law or agreed to in writing,   *
13   * software distributed under the License is distributed on an  *
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
15   * KIND, either express or implied.  See the License for the    *
16   * specific language governing permissions and limitations      *
17   * under the License.                                           *
18   ****************************************************************/
19  
20  package org.apache.james.util;
21  
22  import java.io.IOException;
23  import java.io.InputStream;
24  
25  /***
26   * An InputStream class that terminates the stream when it encounters a
27   * particular byte sequence.
28   *
29   * @version 1.0.0, 24/04/1999
30   */
31  public class CharTerminatedInputStream
32      extends InputStream {
33  
34      /***
35       * The wrapped input stream
36       */
37      private InputStream in;
38  
39      /***
40       * The terminating character array
41       */
42      private int match[];
43  
44      /***
45       * An array containing the last N characters read from the stream, where
46       * N is the length of the terminating character array
47       */
48      private int buffer[];
49  
50      /***
51       * The number of bytes that have been read that have not been placed
52       * in the internal buffer.
53       */
54      private int pos = 0;
55  
56      /***
57       * Whether the terminating sequence has been read from the stream
58       */
59      private boolean endFound = false;
60  
61      /***
62       * A constructor for this object that takes a stream to be wrapped
63       * and a terminating character sequence.
64       *
65       * @param in the <code>InputStream</code> to be wrapped
66       * @param terminator the array of characters that will terminate the stream.
67       *
68       * @throws IllegalArgumentException if the terminator array is null or empty
69       */
70      public CharTerminatedInputStream(InputStream in, char[] terminator) {
71          if (terminator == null) {
72              throw new IllegalArgumentException("The terminating character array cannot be null.");
73          }
74          if (terminator.length == 0) {
75              throw new IllegalArgumentException("The terminating character array cannot be of zero length.");
76          }
77          match = new int[terminator.length];
78          buffer = new int[terminator.length];
79          for (int i = 0; i < terminator.length; i++) {
80              match[i] = (int)terminator[i];
81              buffer[i] = (int)terminator[i];
82          }
83          this.in = in;
84      }
85  
86      /***
87       * Read a byte off this stream.
88       *
89       * @return the byte read off the stream
90       * @throws IOException if an IOException is encountered while reading off the stream
91       * @throws ProtocolException if the underlying stream returns -1 before the terminator is seen.
92       */
93      public int read() throws IOException {
94          if (endFound) {
95              //We've found the match to the terminator
96              return -1;
97          }
98          if (pos == 0) {
99              //We have no data... read in a record
100             int b = in.read();
101             if (b == -1) {
102                 //End of stream reached without seeing the terminator
103                 throw new java.net.ProtocolException("pre-mature end of data");
104             }
105             if (b != match[0]) {
106                 //this char is not the first char of the match
107                 return b;
108             }
109             //this is a match...put this in the first byte of the buffer,
110             // and fall through to matching logic
111             buffer[0] = b;
112             pos++;
113         } else {
114             if (buffer[0] != match[0]) {
115                 //Maybe from a previous scan, there is existing data,
116                 // and the first available char does not match the
117                 // beginning of the terminating string.
118                 return topChar();
119             }
120             //we have a match... fall through to matching logic.
121         }
122         //MATCHING LOGIC
123 
124         //The first character is a match... scan for complete match,
125         // reading extra chars as needed, until complete match is found
126         for (int i = 0; i < match.length; i++) {
127             if (i >= pos) {
128                 int b = in.read();
129                 if (b == -1) {
130                     //end of stream found, so match cannot be fulfilled.
131                     // note we don't set endFound, because otherwise
132                     // remaining part of buffer won't be returned.
133                     return topChar();
134                 }
135                 //put the read char in the buffer
136                 buffer[pos] = b;
137                 pos++;
138             }
139             if (buffer[i] != match[i]) {
140                 //we did not find a match... return the top char
141                 return topChar();
142             }
143         }
144         //A complete match was made...
145         endFound = true;
146         return -1;
147     }
148 
149     /***
150      * Private helper method to update the internal buffer of last read characters
151      *
152      * @return the byte that was previously at the front of the internal buffer
153      */
154     private int topChar() {
155         int b = buffer[0];
156         if (pos > 1) {
157             //copy down the buffer to keep the fresh data at top
158             System.arraycopy(buffer, 1, buffer, 0, pos - 1);
159         }
160         pos--;
161         return b;
162     }
163 }
164