1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package java.io;
19
20import java.util.Arrays;
21import libcore.io.Streams;
22
23/**
24 * Wraps an existing {@link InputStream} and counts the line terminators
25 * encountered while reading the data. Line numbering starts at 0. Recognized
26 * line terminator sequences are {@code '\r'}, {@code '\n'} and {@code "\r\n"}.
27 * When using {@code read}, line terminator sequences are always translated into
28 * {@code '\n'}.
29 *
30 * @deprecated Use {@link LineNumberReader} instead.
31 */
32@Deprecated
33public class LineNumberInputStream extends FilterInputStream {
34
35    private int lineNumber;
36
37    private int markedLineNumber = -1;
38
39    private int lastChar = -1;
40
41    private int markedLastChar;
42
43    /**
44     * Constructs a new {@code LineNumberInputStream} on the {@link InputStream}
45     * {@code in}. Line numbers are counted for all data read from this stream.
46     *
47     * <p><strong>Warning:</strong> passing a null source creates an invalid
48     * {@code LineNumberInputStream}. All operations on such a stream will fail.
49     *
50     * @param in
51     *            The non-null input stream to count line numbers.
52     */
53    public LineNumberInputStream(InputStream in) {
54        super(in);
55    }
56
57    /**
58     * {@inheritDoc}
59     *
60     * <p>Note that the source stream may just be a sequence of {@code "\r\n"} bytes
61     * which are converted into {@code '\n'} by this stream. Therefore,
62     * {@code available} returns only {@code in.available() / 2} bytes as
63     * result.
64     */
65    @Override
66    public int available() throws IOException {
67        return in.available() / 2 + (lastChar == -1 ? 0 : 1);
68    }
69
70    /**
71     * Returns the current line number for this stream. Numbering starts at 0.
72     *
73     * @return the current line number.
74     */
75    public int getLineNumber() {
76        return lineNumber;
77    }
78
79    /**
80     * Sets a mark position in this stream. The parameter {@code readlimit}
81     * indicates how many bytes can be read before the mark is invalidated.
82     * Sending {@code reset()} will reposition this stream back to the marked
83     * position, provided that {@code readlimit} has not been surpassed.
84     * The line number count will also be reset to the last marked
85     * line number count.
86     * <p>
87     * This implementation sets a mark in the filtered stream.
88     *
89     * @param readlimit
90     *            the number of bytes that can be read from this stream before
91     *            the mark is invalidated.
92     * @see #markSupported()
93     * @see #reset()
94     */
95    @Override
96    public void mark(int readlimit) {
97        in.mark(readlimit);
98        markedLineNumber = lineNumber;
99        markedLastChar = lastChar;
100    }
101
102    /**
103     * Reads a single byte from the filtered stream and returns it as an integer
104     * in the range from 0 to 255. Returns -1 if the end of this stream has been
105     * reached.
106     * <p>
107     * The line number count is incremented if a line terminator is encountered.
108     * Recognized line terminator sequences are {@code '\r'}, {@code '\n'} and
109     * {@code "\r\n"}. Line terminator sequences are always translated into
110     * {@code '\n'}.
111     *
112     * @return the byte read or -1 if the end of the filtered stream has been
113     *         reached.
114     * @throws IOException
115     *             if the stream is closed or another IOException occurs.
116     */
117    @SuppressWarnings("fallthrough")
118    @Override
119    public int read() throws IOException {
120        int currentChar = lastChar;
121        if (currentChar == -1) {
122            currentChar = in.read();
123        } else {
124            lastChar = -1;
125        }
126        switch (currentChar) {
127            case '\r':
128                currentChar = '\n';
129                lastChar = in.read();
130                if (lastChar == '\n') {
131                    lastChar = -1;
132                }
133                // fall through
134            case '\n':
135                lineNumber++;
136        }
137        return currentChar;
138    }
139
140    /**
141     * Reads up to {@code byteCount} bytes from the filtered stream and stores
142     * them in the byte array {@code buffer} starting at {@code byteOffset}.
143     * Returns the number of bytes actually read or -1 if no bytes have been
144     * read and the end of this stream has been reached.
145     *
146     * <p>The line number count is incremented if a line terminator is encountered.
147     * Recognized line terminator sequences are {@code '\r'}, {@code '\n'} and
148     * {@code "\r\n"}. Line terminator sequences are always translated into
149     * {@code '\n'}.
150     *
151     * @throws IndexOutOfBoundsException
152     *     if {@code byteOffset < 0 || byteCount < 0 || byteOffset + byteCount > buffer.length}.
153     * @throws IOException
154     *             if this stream is closed or another IOException occurs.
155     * @throws NullPointerException
156     *             if {@code buffer == null}.
157     */
158    @Override
159    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {
160        Arrays.checkOffsetAndCount(buffer.length, byteOffset, byteCount);
161        for (int i = 0; i < byteCount; ++i) {
162            int currentChar;
163            try {
164                currentChar = read();
165            } catch (IOException e) {
166                if (i != 0) {
167                    return i;
168                }
169                throw e;
170            }
171            if (currentChar == -1) {
172                return i == 0 ? -1 : i;
173            }
174            buffer[byteOffset + i] = (byte) currentChar;
175        }
176        return byteCount;
177    }
178
179    /**
180     * Resets this stream to the last marked location. It also resets the line
181     * count to what is was when this stream was marked.
182     *
183     * @throws IOException
184     *             if this stream is already closed, no mark has been set or the
185     *             mark is no longer valid because more than {@code readlimit}
186     *             bytes have been read since setting the mark.
187     * @see #mark(int)
188     * @see #markSupported()
189     */
190    @Override
191    public void reset() throws IOException {
192        in.reset();
193        lineNumber = markedLineNumber;
194        lastChar = markedLastChar;
195    }
196
197    /**
198     * Sets the line number of this stream to the specified
199     * {@code lineNumber}. Note that this may have side effects on the
200     * line number associated with the last marked position.
201     *
202     * @param lineNumber
203     *            the new lineNumber value.
204     * @see #mark(int)
205     * @see #reset()
206     */
207    public void setLineNumber(int lineNumber) {
208        this.lineNumber = lineNumber;
209    }
210
211    /**
212     * Skips {@code count} number of bytes in this stream. Subsequent
213     * calls to {@code read} will not return these bytes unless {@code reset} is
214     * used. This implementation skips {@code byteCount} bytes in the
215     * filtered stream and increments the line number count whenever line
216     * terminator sequences are skipped.
217     *
218     * @param byteCount
219     *            the number of bytes to skip.
220     * @return the number of bytes actually skipped.
221     * @throws IOException
222     *             if this stream is closed or another IOException occurs.
223     * @see #mark(int)
224     * @see #read()
225     * @see #reset()
226     */
227    @Override
228    public long skip(long byteCount) throws IOException {
229        return Streams.skipByReading(this, byteCount);
230    }
231}
232