001    /****************************************************************
002     * Licensed to the Apache Software Foundation (ASF) under one   *
003     * or more contributor license agreements.  See the NOTICE file *
004     * distributed with this work for additional information        *
005     * regarding copyright ownership.  The ASF licenses this file   *
006     * to you under the Apache License, Version 2.0 (the            *
007     * "License"); you may not use this file except in compliance   *
008     * with the License.  You may obtain a copy of the License at   *
009     *                                                              *
010     *   http://www.apache.org/licenses/LICENSE-2.0                 *
011     *                                                              *
012     * Unless required by applicable law or agreed to in writing,   *
013     * software distributed under the License is distributed on an  *
014     * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
015     * KIND, either express or implied.  See the License for the    *
016     * specific language governing permissions and limitations      *
017     * under the License.                                           *
018     ****************************************************************/
019    package com.hs.mail.io;
020    
021    import java.io.IOException;
022    import java.io.InputStream;
023    
024    /**
025     * An InputStream class that terminates the stream when it encounters a
026     * particular byte sequence.
027     *
028     * @version 1.0.0, 24/04/1999
029     */
030    public class CharTerminatedInputStream
031        extends InputStream {
032    
033        /**
034         * The wrapped input stream
035         */
036        private InputStream in;
037    
038        /**
039         * The terminating character array
040         */
041        private int match[];
042    
043        /**
044         * An array containing the last N characters read from the stream, where
045         * N is the length of the terminating character array
046         */
047        private int buffer[];
048    
049        /**
050         * The number of bytes that have been read that have not been placed
051         * in the internal buffer.
052         */
053        private int pos = 0;
054    
055        /**
056         * Whether the terminating sequence has been read from the stream
057         */
058        private boolean endFound = false;
059    
060        /**
061         * A constructor for this object that takes a stream to be wrapped
062         * and a terminating character sequence.
063         *
064         * @param in the <code>InputStream</code> to be wrapped
065         * @param terminator the array of characters that will terminate the stream.
066         *
067         * @throws IllegalArgumentException if the terminator array is null or empty
068         */
069        public CharTerminatedInputStream(InputStream in, char[] terminator) {
070            if (terminator == null) {
071                throw new IllegalArgumentException("The terminating character array cannot be null.");
072            }
073            if (terminator.length == 0) {
074                throw new IllegalArgumentException("The terminating character array cannot be of zero length.");
075            }
076            match = new int[terminator.length];
077            buffer = new int[terminator.length];
078            for (int i = 0; i < terminator.length; i++) {
079                match[i] = (int)terminator[i];
080                buffer[i] = (int)terminator[i];
081            }
082            this.in = in;
083        }
084    
085        /**
086         * Read a byte off this stream.
087         *
088         * @return the byte read off the stream
089         * @throws IOException if an IOException is encountered while reading off the stream
090         * @throws ProtocolException if the underlying stream returns -1 before the terminator is seen.
091         */
092        public int read() throws IOException {
093            if (endFound) {
094                //We've found the match to the terminator
095                return -1;
096            }
097            if (pos == 0) {
098                //We have no data... read in a record
099                int b = in.read();
100                if (b == -1) {
101                    //End of stream reached without seeing the terminator
102                    throw new java.net.ProtocolException("pre-mature end of data");
103                }
104                if (b != match[0]) {
105                    //this char is not the first char of the match
106                    return b;
107                }
108                //this is a match...put this in the first byte of the buffer,
109                // and fall through to matching logic
110                buffer[0] = b;
111                pos++;
112            } else {
113                if (buffer[0] != match[0]) {
114                    //Maybe from a previous scan, there is existing data,
115                    // and the first available char does not match the
116                    // beginning of the terminating string.
117                    return topChar();
118                }
119                //we have a match... fall through to matching logic.
120            }
121            //MATCHING LOGIC
122    
123            //The first character is a match... scan for complete match,
124            // reading extra chars as needed, until complete match is found
125            for (int i = 0; i < match.length; i++) {
126                if (i >= pos) {
127                    int b = in.read();
128                    if (b == -1) {
129                        //end of stream found, so match cannot be fulfilled.
130                        // note we don't set endFound, because otherwise
131                        // remaining part of buffer won't be returned.
132                        return topChar();
133                    }
134                    //put the read char in the buffer
135                    buffer[pos] = b;
136                    pos++;
137                }
138                if (buffer[i] != match[i]) {
139                    //we did not find a match... return the top char
140                    return topChar();
141                }
142            }
143            //A complete match was made...
144            endFound = true;
145            return -1;
146        }
147    
148        /**
149         * Private helper method to update the internal buffer of last read characters
150         *
151         * @return the byte that was previously at the front of the internal buffer
152         */
153        private int topChar() {
154            int b = buffer[0];
155            if (pos > 1) {
156                //copy down the buffer to keep the fresh data at top
157                System.arraycopy(buffer, 1, buffer, 0, pos - 1);
158            }
159            pos--;
160            return b;
161        }
162    }
163