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
20package org.apache.james.mime4j;
21
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.PushbackInputStream;
25
26/**
27 * InputStream which converts <code>\r</code>
28 * bytes not followed by <code>\n</code> and <code>\n</code> not
29 * preceded by <code>\r</code> to <code>\r\n</code>.
30 *
31 *
32 * @version $Id: EOLConvertingInputStream.java,v 1.4 2004/11/29 13:15:42 ntherning Exp $
33 */
34public class EOLConvertingInputStream extends InputStream {
35    /** Converts single '\r' to '\r\n' */
36    public static final int CONVERT_CR   = 1;
37    /** Converts single '\n' to '\r\n' */
38    public static final int CONVERT_LF   = 2;
39    /** Converts single '\r' and '\n' to '\r\n' */
40    public static final int CONVERT_BOTH = 3;
41
42    private PushbackInputStream in = null;
43    private int previous = 0;
44    private int flags = CONVERT_BOTH;
45    private int size = 0;
46    private int pos = 0;
47    private int nextTenPctPos;
48    private int tenPctSize;
49    private Callback callback;
50
51    public interface Callback {
52        public void report(int bytesRead);
53    }
54
55    /**
56     * Creates a new <code>EOLConvertingInputStream</code>
57     * instance converting bytes in the given <code>InputStream</code>.
58     * The flag <code>CONVERT_BOTH</code> is the default.
59     *
60     * @param in the <code>InputStream</code> to read from.
61     */
62    public EOLConvertingInputStream(InputStream _in) {
63        super();
64        in = new PushbackInputStream(_in, 2);
65    }
66
67    /**
68     * Creates a new <code>EOLConvertingInputStream</code>
69     * instance converting bytes in the given <code>InputStream</code>.
70     *
71     * @param _in the <code>InputStream</code> to read from.
72     * @param _size the size of the input stream (need not be exact)
73     * @param _callback a callback reporting when each 10% of stream's size is reached
74     */
75    public EOLConvertingInputStream(InputStream _in, int _size, Callback _callback) {
76        this(_in);
77        size = _size;
78        tenPctSize = size / 10;
79        nextTenPctPos = tenPctSize;
80        callback = _callback;
81    }
82
83    /**
84     * Closes the underlying stream.
85     *
86     * @throws IOException on I/O errors.
87     */
88    public void close() throws IOException {
89        in.close();
90    }
91
92    private int readByte() throws IOException {
93        int b = in.read();
94        if (b != -1) {
95            if (callback != null && pos++ == nextTenPctPos) {
96                nextTenPctPos += tenPctSize;
97                if (callback != null) {
98                    callback.report(pos);
99                }
100            }
101        }
102        return b;
103    }
104
105    private void unreadByte(int c) throws IOException {
106        in.unread(c);
107        pos--;
108    }
109
110    /**
111     * @see java.io.InputStream#read()
112     */
113    public int read() throws IOException {
114        int b = readByte();
115
116        if (b == -1) {
117            pos = size;
118            return -1;
119        }
120
121        if ((flags & CONVERT_CR) != 0 && b == '\r') {
122            int c = readByte();
123            if (c != -1) {
124                unreadByte(c);
125            }
126            if (c != '\n') {
127                unreadByte('\n');
128            }
129        } else if ((flags & CONVERT_LF) != 0 && b == '\n' && previous != '\r') {
130            b = '\r';
131            unreadByte('\n');
132        }
133
134        previous = b;
135
136        return b;
137    }
138
139}
140