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  
21  
22  package org.apache.james.util.stream;
23  
24  import java.io.IOException;
25  import java.io.InputStream;
26  
27  /**
28   * An InputStream class that terminates the stream when it encounters a
29   * particular byte sequence.
30   *
31   * @version 1.0.0, 24/04/1999
32   */
33  public class CharTerminatedInputStream
34      extends InputStream {
35  
36      /**
37       * The wrapped input stream
38       */
39      private InputStream in;
40  
41      /**
42       * The terminating character array
43       */
44      private int match[];
45  
46      /**
47       * An array containing the last N characters read from the stream, where
48       * N is the length of the terminating character array
49       */
50      private int buffer[];
51  
52      /**
53       * The number of bytes that have been read that have not been placed
54       * in the internal buffer.
55       */
56      private int pos = 0;
57  
58      /**
59       * Whether the terminating sequence has been read from the stream
60       */
61      private boolean endFound = false;
62  
63      /**
64       * A constructor for this object that takes a stream to be wrapped
65       * and a terminating character sequence.
66       *
67       * @param in the <code>InputStream</code> to be wrapped
68       * @param terminator the array of characters that will terminate the stream.
69       *
70       * @throws IllegalArgumentException if the terminator array is null or empty
71       */
72      public CharTerminatedInputStream(InputStream in, char[] terminator) {
73          if (terminator == null) {
74              throw new IllegalArgumentException("The terminating character array cannot be null.");
75          }
76          if (terminator.length == 0) {
77              throw new IllegalArgumentException("The terminating character array cannot be of zero length.");
78          }
79          match = new int[terminator.length];
80          buffer = new int[terminator.length];
81          for (int i = 0; i < terminator.length; i++) {
82              match[i] = (int)terminator[i];
83              buffer[i] = (int)terminator[i];
84          }
85          this.in = in;
86      }
87  
88      /**
89       * Read a byte off this stream.
90       *
91       * @return the byte read off the stream
92       * @throws IOException if an IOException is encountered while reading off the stream
93       * throws ProtocolException if the underlying stream returns -1 before the terminator is seen.
94       */
95      public int read() throws IOException {
96          if (endFound) {
97              //We've found the match to the terminator
98              return -1;
99          }
100         if (pos == 0) {
101             //We have no data... read in a record
102             int b = in.read();
103             if (b == -1) {
104                 //End of stream reached without seeing the terminator
105                 throw new java.net.ProtocolException("pre-mature end of data");
106             }
107             if (b != match[0]) {
108                 //this char is not the first char of the match
109                 return b;
110             }
111             //this is a match...put this in the first byte of the buffer,
112             // and fall through to matching logic
113             buffer[0] = b;
114             pos++;
115         } else {
116             if (buffer[0] != match[0]) {
117                 //Maybe from a previous scan, there is existing data,
118                 // and the first available char does not match the
119                 // beginning of the terminating string.
120                 return topChar();
121             }
122             //we have a match... fall through to matching logic.
123         }
124         //MATCHING LOGIC
125 
126         //The first character is a match... scan for complete match,
127         // reading extra chars as needed, until complete match is found
128         for (int i = 0; i < match.length; i++) {
129             if (i >= pos) {
130                 int b = in.read();
131                 if (b == -1) {
132                     //end of stream found, so match cannot be fulfilled.
133                     // note we don't set endFound, because otherwise
134                     // remaining part of buffer won't be returned.
135                     return topChar();
136                 }
137                 //put the read char in the buffer
138                 buffer[pos] = b;
139                 pos++;
140             }
141             if (buffer[i] != match[i]) {
142                 //we did not find a match... return the top char
143                 return topChar();
144             }
145         }
146         //A complete match was made...
147         endFound = true;
148         return -1;
149     }
150 
151     /**
152      * Private helper method to update the internal buffer of last read characters
153      *
154      * @return the byte that was previously at the front of the internal buffer
155      */
156     private int topChar() {
157         int b = buffer[0];
158         if (pos > 1) {
159             //copy down the buffer to keep the fresh data at top
160             System.arraycopy(buffer, 1, buffer, 0, pos - 1);
161         }
162         pos--;
163         return b;
164     }
165 }
166