1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package com.puppycrawl.tools.checkstyle.api;
20
21 import java.io.BufferedReader;
22 import java.io.File;
23 import java.io.FileInputStream;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.io.Reader;
27 import java.io.StringReader;
28 import java.io.UnsupportedEncodingException;
29 import java.nio.ByteBuffer;
30 import java.nio.charset.Charset;
31 import java.nio.charset.CharsetDecoder;
32 import java.nio.charset.CodingErrorAction;
33 import java.nio.charset.UnsupportedCharsetException;
34 import java.util.AbstractList;
35 import java.util.ArrayList;
36 import java.util.Arrays;
37 import java.util.ConcurrentModificationException;
38 import java.util.List;
39 import java.util.regex.Matcher;
40 import java.util.regex.Pattern;
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public final class FileText extends AbstractList<String>
55 {
56
57
58
59
60 private static final int READ_BUFFER_SIZE = 1024;
61
62
63
64
65 private static final Pattern LINE_TERMINATOR =
66 Utils.getPattern("\\n|\\r\\n?");
67
68
69
70
71
72
73
74
75
76
77 private final File file;
78
79
80
81
82
83 private final Charset charset;
84
85
86
87
88 private final String fullText;
89
90
91
92
93 private final String[] lines;
94
95
96
97
98 private int[] lineBreaks;
99
100
101
102
103
104
105
106
107
108
109
110
111
112 public FileText(File file, String charsetName) throws IOException
113 {
114 this.file = file;
115
116
117
118 final CharsetDecoder decoder;
119 try {
120 charset = Charset.forName(charsetName);
121 decoder = charset.newDecoder();
122 decoder.onMalformedInput(CodingErrorAction.REPLACE);
123 decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
124 }
125 catch (final UnsupportedCharsetException ex) {
126 final String message = "Unsupported charset: " + charsetName;
127 final UnsupportedEncodingException ex2;
128 ex2 = new UnsupportedEncodingException(message);
129 ex2.initCause(ex);
130 throw ex2;
131 }
132
133 final char[] chars = new char[READ_BUFFER_SIZE];
134 final StringBuilder buf = new StringBuilder();
135 final FileInputStream stream = new FileInputStream(file);
136 final Reader reader = new InputStreamReader(stream, decoder);
137 try {
138 while (true) {
139 final int len = reader.read(chars);
140 if (len < 0) {
141 break;
142 }
143 buf.append(chars, 0, len);
144 }
145 }
146 finally {
147 Utils.closeQuietly(reader);
148 }
149
150 fullText = buf.toString();
151
152
153
154
155 final ArrayList<String> lines = new ArrayList<>();
156 final BufferedReader br =
157 new BufferedReader(new StringReader(fullText));
158 for (;;) {
159 final String l = br.readLine();
160 if (null == l) {
161 break;
162 }
163 lines.add(l);
164 }
165 this.lines = lines.toArray(new String[lines.size()]);
166 }
167
168
169
170
171
172
173
174
175
176
177
178
179 private FileText(File file, List<String> lines)
180 {
181 final StringBuilder buf = new StringBuilder();
182 for (final String line : lines) {
183 buf.append(line).append('\n');
184 }
185 buf.trimToSize();
186
187 this.file = file;
188 charset = null;
189 fullText = buf.toString();
190 this.lines = lines.toArray(new String[lines.size()]);
191 }
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 public static FileText fromLines(File file, List<String> lines)
207 {
208 return lines instanceof FileText
209 ? (FileText) lines
210 : new FileText(file, lines);
211 }
212
213
214
215
216
217 public File getFile()
218 {
219 return file;
220 }
221
222
223
224
225
226
227 public Charset getCharset()
228 {
229 return charset;
230 }
231
232
233
234
235
236
237
238 public ByteBuffer getBytes() throws IOException
239 {
240
241 if (file == null) {
242 return null;
243 }
244 if (file.length() > Integer.MAX_VALUE) {
245 throw new IOException("File too large.");
246 }
247 byte[] bytes = new byte[(int) file.length() + 1];
248 final FileInputStream stream = new FileInputStream(file);
249 try {
250 int fill = 0;
251 while (true) {
252 if (fill >= bytes.length) {
253
254 final byte[] newBytes = new byte[bytes.length * 2 + 1];
255 System.arraycopy(bytes, 0, newBytes, 0, fill);
256 bytes = newBytes;
257 }
258 final int len = stream.read(bytes, fill,
259 bytes.length - fill);
260 if (len == -1) {
261 break;
262 }
263 fill += len;
264 }
265 return ByteBuffer.wrap(bytes, 0, fill).asReadOnlyBuffer();
266 }
267 finally {
268 Utils.closeQuietly(stream);
269 }
270 }
271
272
273
274
275
276 public CharSequence getFullText()
277 {
278 return fullText;
279 }
280
281
282
283
284
285
286
287 public String[] toLinesArray()
288 {
289 return lines.clone();
290 }
291
292
293
294
295
296 private int[] lineBreaks()
297 {
298 if (lineBreaks == null) {
299 final int[] lineBreaks = new int[size() + 1];
300 lineBreaks[0] = 0;
301 int lineNo = 1;
302 final Matcher matcher = LINE_TERMINATOR.matcher(fullText);
303 while (matcher.find()) {
304 lineBreaks[lineNo++] = matcher.end();
305 }
306 if (lineNo < lineBreaks.length) {
307 lineBreaks[lineNo++] = fullText.length();
308 }
309 if (lineNo != lineBreaks.length) {
310 throw new ConcurrentModificationException("Text changed.");
311 }
312 this.lineBreaks = lineBreaks;
313 }
314 return lineBreaks;
315 }
316
317
318
319
320
321
322 public LineColumn lineColumn(int pos)
323 {
324 final int[] lineBreaks = lineBreaks();
325 int lineNo = Arrays.binarySearch(lineBreaks, pos);
326 if (lineNo < 0) {
327
328
329 lineNo = -lineNo - 2;
330 }
331 final int startOfLine = lineBreaks[lineNo];
332 final int columnNo = pos - startOfLine;
333
334 return new LineColumn(lineNo + 1, columnNo);
335 }
336
337
338
339
340
341
342
343 @Override
344 public String get(final int lineNo)
345 {
346 return lines[lineNo];
347 }
348
349
350
351
352
353 @Override
354 public int size()
355 {
356 return lines.length;
357 }
358
359 }