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: ****************************************************************/package org.apache.james.util;
019:
020: import java.io.IOException;
021: import java.io.InputStream;
022:
023: /**
024: * An InputStream class that terminates the stream when it encounters a
025: * particular byte sequence.
026: *
027: * @version 1.0.0, 24/04/1999
028: */
029: public class CharTerminatedInputStream extends InputStream {
030:
031: /**
032: * The wrapped input stream
033: */
034: private InputStream in;
035:
036: /**
037: * The terminating character array
038: */
039: private int match[];
040:
041: /**
042: * An array containing the last N characters read from the stream, where
043: * N is the length of the terminating character array
044: */
045: private int buffer[];
046:
047: /**
048: * The number of bytes that have been read that have not been placed
049: * in the internal buffer.
050: */
051: private int pos = 0;
052:
053: /**
054: * Whether the terminating sequence has been read from the stream
055: */
056: private boolean endFound = false;
057:
058: /**
059: * A constructor for this object that takes a stream to be wrapped
060: * and a terminating character sequence.
061: *
062: * @param in the <code>InputStream</code> to be wrapped
063: * @param terminator the array of characters that will terminate the stream.
064: *
065: * @throws IllegalArgumentException if the terminator array is null or empty
066: */
067: public CharTerminatedInputStream(InputStream in, char[] terminator) {
068: if (terminator == null) {
069: throw new IllegalArgumentException(
070: "The terminating character array cannot be null.");
071: }
072: if (terminator.length == 0) {
073: throw new IllegalArgumentException(
074: "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(
103: "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: }
|