Skip to main content
aboutsummaryrefslogtreecommitdiffstats
blob: e26d2a181f9f49c6b8e41ee35ec78b55201bfdbf (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*******************************************************************************
 * Copyright (c) 2013 Pivotal Software, Inc.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Pivotal Software, Inc. - initial API and implementation
 *******************************************************************************/
package org.eclipse.text.quicksearch.internal.util;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;

/**
 * Provides a helper to efficiently split a file into
 * lines. Similar to BufferedReader.readline, but it also keeps
 * track of character position while reading. This is needed to
 * ease translation from line-relative offsets into stream-relative
 * offsets.
 *
 * @author Kris De Volder
 */
public class LineReader {

	private static final int EXPECTED_LINE_LENGTH = 160;
	public static final int DEFAULT_MAX_LINE_LENGTH = 1000;

	private BufferedReader input;

	//This simple implementation just wraps a BufferedReader and StringBuilder
	//to do the buffering and String building.
	//It may be more efficient to implement our own buffering like BufferedReader
	//does.

	public LineReader(Reader reader) {
		this(reader, DEFAULT_MAX_LINE_LENGTH);
	}

	public LineReader(Reader reader, int maxLineLength) {
		input = buffered(reader);
		MAX_LINE_LENGTH = maxLineLength;
	}


	private StringBuilder line = new StringBuilder(EXPECTED_LINE_LENGTH);

	private final int MAX_LINE_LENGTH;
	private int lineOffset = -1; //Start pos of last line read.
	private int offset = 0; //position of next char in input.
	private int mark = 0; //mark offset in underlying stream

	private BufferedReader buffered(Reader reader) {
		//If already buffered don't wrap it again.
		if (reader instanceof BufferedReader) {
			return (BufferedReader) reader;
		} else {
			return new BufferedReader(reader);
		}
	}

	/**
	 * Close the underlying stream. Does nothing if already closed.
	 */
	public void close() {
		BufferedReader toClose = null;
		synchronized (input) {
			if (input==null) {
				return;
			}
			toClose = input;
			input = null;
		}
		try {
			toClose.close();
		} catch (IOException e) {
			//Ignore.
		}
	}

	public String readLine() throws IOException {
		lineOffset = offset; //remember start of line
		int maxOffset = offset + MAX_LINE_LENGTH;
		//Read text until we see either a CR, CR LF or LF.
		int c = read();
		if (c==-1) {
			return null;
		}
		//read until newline
		while (c!='\r' && c!='\n' && c!=-1) {
			line.append((char)c);
			c = read();
			if (offset>maxOffset) {
				throw new IOException("Very long lines of text. Minified file?"); //$NON-NLS-1$
			}
		}
		//Last char read was some kind of line terminator. But only read first char of it.
		if (c=='\r') {
			mark(); //next char may be part of next line. May need to 'unread' it.
			int next = read();
			if (next == '\n') {
				//skip
			} else {
				unread();
			}
		}
		try {
			return line.toString();
		} finally {
			line.setLength(0);
		}
	}

	private void unread() throws IOException {
		offset = mark;
		input.reset();
	}

	private void mark() throws IOException {
		mark = offset;
		input.mark(1);
	}

	private int read() throws IOException {
		try {
			offset++;
			return input.read();
		} catch (IOException e) {
			//pretend errors are like EOF.
			return -1;
		}
	}

	/**
	 * @return The offset of the start of the last line read relative to beginning of the stream; or -1 if
	 * no line has been read yet.
	 */
	public int getLastLineOffset() {
		return lineOffset;
	}

}

Back to the top