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