-
Notifications
You must be signed in to change notification settings - Fork 227
Expand file tree
/
Copy pathTextIOWrapper.java
More file actions
262 lines (216 loc) · 8.77 KB
/
TextIOWrapper.java
File metadata and controls
262 lines (216 loc) · 8.77 KB
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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
/* Copyright (c) 2007 Jython Developers */
package org.python.core.io;
import java.nio.ByteBuffer;
import java.util.regex.Pattern;
/**
* A Buffered text stream.
*
* This differs from py3k TextIOWrapper, which currently handles both
* text mode (py3k text mode is incompatible with Python 2.x's text
* mode) as well as universal mode.
*
* @see UniversalIOWrapper
*
* @author Philip Jenvey
*/
public class TextIOWrapper extends BinaryIOWrapper {
/** LF RE pattern, used for writes */
private static final Pattern LF_PATTERN = Pattern.compile("\n");
/** The platform's Newline character */
private String newline;
/** Whether or not newline is \n */
private boolean newlineIsLF;
/**
* Contruct a TextIOWrapper wrapping the given BufferedIOBase.
*
* @param bufferedIO {@inheritDoc}
*/
public TextIOWrapper(BufferedIOBase bufferedIO) {
super(bufferedIO);
newline = System.getProperty("line.separator");
newlineIsLF = newline.equals("\n");
}
@Override
public String read(int size) {
if (newlineIsLF) {
return super.read(size);
}
if (size < 0) {
return readall();
}
// Avoid ByteBuffer (this.readahead) method calls in the inner
// loop by reading directly from the readahead's backing array
byte[] readaheadArray;
int readaheadPos;
char[] builderArray = new char[size];
int builderPos = 0;
do {
readaheadArray = readahead.array();
readaheadPos = readahead.position();
while (readaheadPos < readahead.limit() && builderPos < size) {
char next = (char)(readaheadArray[readaheadPos++] & 0xff);
if (next == '\r') {
// Don't translate CR at EOF
if (readaheadPos == readahead.limit()) {
if (readChunk() == 0) {
// EOF
builderArray[builderPos++] = next;
return new String(builderArray, 0, builderPos);
}
// Not EOF and readChunk replaced the
// readahead; reset the readahead info
readaheadArray = readahead.array();
readaheadPos = readahead.position();
}
if (readaheadArray[readaheadPos] == LF_BYTE) {
next = '\n';
readaheadPos++;
}
}
builderArray[builderPos++] = next;
}
} while (builderPos < size && readChunk(size - builderPos) > 0);
// Finally reposition the readahead to where we ended. The
// position is invalid if the readahead is empty (at EOF;
// readChunk() returned 0)
if (readahead.hasRemaining()) {
readahead.position(readaheadPos);
}
// Shrink the readahead if it grew
packReadahead();
return new String(builderArray, 0, builderPos);
}
@Override
public String readall() {
if (newlineIsLF) {
return super.readall();
}
// Read the remainder of file
ByteBuffer remaining = bufferedIO.readall();
// Detect CRLF spanning the readahead and remaining
int length = 0;
if (readahead.hasRemaining() && readahead.get(readahead.limit() - 1) == CR_BYTE &&
remaining.hasRemaining() && remaining.get(remaining.position()) == LF_BYTE) {
// The trailing value of readahead is CR, the first value
// of remaining is LF. Overwrite CR with that LF (CRLF ->
// LF)
length--;
}
// Create an array that accommodates the readahead and the
// remainder
char[] all = new char[readahead.remaining() + remaining.remaining()];
// Consume the readahead
length += readLoop(readahead.array(), readahead.position(), all, 0, readahead.remaining());
readahead.position(readahead.limit());
// Consume the remainder of the file
length += readLoop(remaining.array(), remaining.position(), all, length,
remaining.remaining());
return new String(all, 0, length);
}
/**
* Read and convert the src byte array into the dest char array.
*
* Converts CRLF to LF. src is assumed to be the end of the file;
* that is, when src ends with CR, no attempt is made to peek into
* the underlying file to detect a succeeding LF.
*
* @param src the source byte array
* @param srcPos starting position in the source array
* @param dest the destination char array
* @param destPos starting position in the destination array
* @param length the number of array elements to be copied
* @return the number of chars written to the destination array
*/
private int readLoop(byte[] src, int srcPos, char[] dest, int destPos, int length) {
int destStartPos = destPos;
int srcEndPos = srcPos + length;
while (srcPos < srcEndPos) {
char next = (char)(src[srcPos++] & 0xff);
if (next == '\r') {
// Don't translate CR at EOF
if (srcPos == srcEndPos) {
dest[destPos++] = next;
continue;
}
if (src[srcPos] == LF_BYTE) {
next = '\n';
srcPos++;
}
}
dest[destPos++] = next;
}
return destPos - destStartPos;
}
@Override
public String readline(int size) {
if (newlineIsLF) {
return super.readline(size);
}
// Avoid ByteBuffer (this.readahead) and StringBuilder
// (this.builder) method calls in the inner loop by reading
// directly from the readahead's backing array and writing to
// an interim char array (this.interimBuilder)
byte[] readaheadArray;
int readaheadPos;
int interimBuilderPos;
do {
readaheadArray = readahead.array();
readaheadPos = readahead.position();
interimBuilderPos = 0;
while (readaheadPos < readahead.limit() &&
(size < 0 || builder.length() + interimBuilderPos < size)) {
char next = (char)(readaheadArray[readaheadPos++] & 0xff);
interimBuilder[interimBuilderPos++] = next;
if (next == '\r') {
boolean flushInterimBuilder = false;
// Don't translate CR at EOF
if (readaheadPos == readahead.limit()) {
if (readChunk() == 0) {
// EOF
builder.append(interimBuilder, 0, interimBuilderPos);
return drainBuilder();
}
// Not EOF and readChunk replaced the
// readahead; reset the readahead info and
// flush the interimBuilder (it's full)
readaheadArray = readahead.array();
readaheadPos = readahead.position();
flushInterimBuilder = true;
}
if (readaheadArray[readaheadPos] == LF_BYTE) {
// A CRLF: overwrite CR with the LF
readaheadPos++;
interimBuilder[interimBuilderPos - 1] = next = '\n';
}
if (flushInterimBuilder) {
// Safe to flush now, incase a CRLF was
// changed
builder.append(interimBuilder, 0, interimBuilderPos);
interimBuilderPos = 0;
}
}
if (next == '\n') {
builder.append(interimBuilder, 0, interimBuilderPos);
// Reposition the readahead to where we ended
readahead.position(readaheadPos);
return drainBuilder();
}
}
builder.append(interimBuilder, 0, interimBuilderPos);
} while ((size < 0 || builder.length() < size) && readChunk() > 0);
// Finally reposition the readahead to where we ended. The
// position is invalid if the readahead is empty (at EOF;
// readChunk() returned 0)
if (readahead.hasRemaining()) {
readahead.position(readaheadPos);
}
return drainBuilder();
}
@Override
public int write(String buf) {
if (!newlineIsLF) {
buf = LF_PATTERN.matcher(buf).replaceAll(newline);
}
return super.write(buf);
}
}