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              
96              return -1;
97          }
98          if (pos == 0) {
99              
100             int b = in.read();
101             if (b == -1) {
102                 
103                 throw new java.net.ProtocolException("pre-mature end of data");
104             }
105             if (b != match[0]) {
106                 
107                 return b;
108             }
109             
110             
111             buffer[0] = b;
112             pos++;
113         } else {
114             if (buffer[0] != match[0]) {
115                 
116                 
117                 
118                 return topChar();
119             }
120             
121         }
122         
123 
124         
125         
126         for (int i = 0; i < match.length; i++) {
127             if (i >= pos) {
128                 int b = in.read();
129                 if (b == -1) {
130                     
131                     
132                     
133                     return topChar();
134                 }
135                 
136                 buffer[pos] = b;
137                 pos++;
138             }
139             if (buffer[i] != match[i]) {
140                 
141                 return topChar();
142             }
143         }
144         
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             
158             System.arraycopy(buffer, 1, buffer, 0, pos - 1);
159         }
160         pos--;
161         return b;
162     }
163 }
164