|
@@ -23,10 +23,13 @@
|
23
|
23
|
package com.dmdirc.util.io;
|
24
|
24
|
|
25
|
25
|
import java.io.EOFException;
|
26
|
|
-import java.io.File;
|
27
|
26
|
import java.io.IOException;
|
28
|
|
-import java.io.RandomAccessFile;
|
|
27
|
+import java.nio.ByteBuffer;
|
|
28
|
+import java.nio.channels.SeekableByteChannel;
|
29
|
29
|
import java.nio.charset.Charset;
|
|
30
|
+import java.nio.file.Files;
|
|
31
|
+import java.nio.file.Path;
|
|
32
|
+import java.nio.file.StandardOpenOption;
|
30
|
33
|
import java.util.ArrayList;
|
31
|
34
|
import java.util.Stack;
|
32
|
35
|
|
|
@@ -35,33 +38,25 @@ import java.util.Stack;
|
35
|
38
|
*/
|
36
|
39
|
public class ReverseFileReader {
|
37
|
40
|
|
|
41
|
+ /** Path to the file we're reading. */
|
|
42
|
+ private final Path file;
|
38
|
43
|
/** File to manipulate. */
|
39
|
|
- private RandomAccessFile file;
|
40
|
|
-
|
|
44
|
+ private SeekableByteChannel byteChannel;
|
41
|
45
|
/** Number of bytes to skip backwards at a time. */
|
42
|
46
|
private byte seekLength = 50;
|
43
|
47
|
|
44
|
48
|
/**
|
45
|
49
|
* Create a new ReverseFileReader.
|
46
|
50
|
*
|
47
|
|
- * @param filename File to open.
|
48
|
|
- * @throws SecurityException If a security manager exists and its checkRead method denies read access to the file.
|
49
|
|
- * @throws IOException If there is an error seeking to the end of the file.
|
50
|
|
- */
|
51
|
|
- public ReverseFileReader(final String filename) throws SecurityException, IOException {
|
52
|
|
- file = new RandomAccessFile(filename, "r");
|
53
|
|
- reset();
|
54
|
|
- }
|
55
|
|
-
|
56
|
|
- /**
|
57
|
|
- * Create a new ReverseFileReader.
|
|
51
|
+ * @param file File to read
|
58
|
52
|
*
|
59
|
|
- * @param myFile Existing file to use.
|
60
|
|
- * @throws SecurityException If a security manager exists and its checkRead method denies read access to the file.
|
|
53
|
+ * @throws SecurityException If a security manager exists and its checkRead method denies
|
|
54
|
+ * read access to the file.
|
61
|
55
|
* @throws IOException If there is an error seeking to the end of the file.
|
62
|
56
|
*/
|
63
|
|
- public ReverseFileReader(final File myFile) throws SecurityException, IOException {
|
64
|
|
- file = new RandomAccessFile(myFile, "r");
|
|
57
|
+ public ReverseFileReader(final Path file) throws SecurityException, IOException {
|
|
58
|
+ this.file = file;
|
|
59
|
+ byteChannel = Files.newByteChannel(file, StandardOpenOption.READ);
|
65
|
60
|
reset();
|
66
|
61
|
}
|
67
|
62
|
|
|
@@ -70,11 +65,12 @@ public class ReverseFileReader {
|
70
|
65
|
*
|
71
|
66
|
* @throws IOException If there is an error seeking, or the file is closed.
|
72
|
67
|
*/
|
73
|
|
- public void reset() throws IOException {
|
74
|
|
- if (file == null) {
|
75
|
|
- throw new IOException("File has been closed.");
|
|
68
|
+ public final void reset() throws IOException {
|
|
69
|
+ if (!byteChannel.isOpen()) {
|
|
70
|
+ throw new IOException("Channel has been closed.");
|
76
|
71
|
}
|
77
|
|
- file.seek(file.length());
|
|
72
|
+ byteChannel = Files.newByteChannel(file, StandardOpenOption.READ);
|
|
73
|
+ byteChannel.position(byteChannel.size());
|
78
|
74
|
}
|
79
|
75
|
|
80
|
76
|
/**
|
|
@@ -102,11 +98,10 @@ public class ReverseFileReader {
|
102
|
98
|
* @throws IOException If there is an error closing the file, or if it has been closed already.
|
103
|
99
|
*/
|
104
|
100
|
public void close() throws IOException {
|
105
|
|
- if (file == null) {
|
106
|
|
- throw new IOException("File has been closed.");
|
|
101
|
+ if (!byteChannel.isOpen()) {
|
|
102
|
+ throw new IOException("Channel has been closed.");
|
107
|
103
|
}
|
108
|
|
- file.close();
|
109
|
|
- file = null;
|
|
104
|
+ byteChannel.close();
|
110
|
105
|
}
|
111
|
106
|
|
112
|
107
|
/**
|
|
@@ -116,37 +111,26 @@ public class ReverseFileReader {
|
116
|
111
|
* @throws IOException If an error reading or seeking occured, or if the fiel is closed.
|
117
|
112
|
*/
|
118
|
113
|
public String getNextLine() throws IOException {
|
119
|
|
- if (file == null) {
|
120
|
|
- throw new IOException("File has been closed.");
|
|
114
|
+ if (!byteChannel.isOpen()) {
|
|
115
|
+ throw new IOException("Channel has been closed.");
|
121
|
116
|
}
|
122
|
117
|
// Used to store result to output.
|
123
|
|
-
|
124
|
118
|
final ArrayList<Byte> line = new ArrayList<>(seekLength);
|
125
|
|
- // Used to store position in file pre-read
|
126
|
|
- long fp;
|
127
|
|
- // Used to store position in file when this is called
|
128
|
|
- final long startfp;
|
129
|
|
- // Used to store read bytes
|
130
|
|
- byte[] bytes;
|
131
|
|
- // Distance seeked
|
132
|
|
- int seekDistance;
|
133
|
|
-
|
134
|
119
|
// Check current position, if 0 we are at the start of the file
|
135
|
120
|
// and should throw an exception.
|
136
|
|
- startfp = file.getFilePointer();
|
|
121
|
+ final long startfp = byteChannel.position();
|
137
|
122
|
if (startfp == 0) {
|
138
|
123
|
throw new EOFException("Reached Start of file");
|
139
|
124
|
}
|
140
|
125
|
|
141
|
126
|
// Keep looping until we get a full line, or the end of the file
|
142
|
127
|
boolean keepLooping = true;
|
143
|
|
- boolean gotNewLine;
|
144
|
128
|
while (keepLooping) {
|
145
|
|
- gotNewLine = false;
|
146
|
129
|
// Get Current Position
|
147
|
|
- fp = file.getFilePointer();
|
|
130
|
+ long fp = byteChannel.position();
|
148
|
131
|
|
149
|
132
|
// Check how far to seek backwards (seekLength or to the start of the file)
|
|
133
|
+ final int seekDistance;
|
150
|
134
|
if (fp < seekLength) {
|
151
|
135
|
// Seek to the start of the file;
|
152
|
136
|
seekDistance = (int) fp;
|
|
@@ -154,27 +138,28 @@ public class ReverseFileReader {
|
154
|
138
|
} else {
|
155
|
139
|
// Seek to position current-seekLength
|
156
|
140
|
seekDistance = seekLength;
|
157
|
|
- fp = fp - seekDistance;
|
|
141
|
+ fp -= seekDistance;
|
158
|
142
|
}
|
159
|
143
|
// Seek!
|
160
|
|
- file.seek(fp);
|
|
144
|
+ byteChannel.position(fp);
|
161
|
145
|
|
162
|
|
- bytes = new byte[seekDistance];
|
|
146
|
+ final ByteBuffer bytes = ByteBuffer.allocate(seekDistance);
|
163
|
147
|
// Read into the bytes array
|
164
|
|
- file.read(bytes);
|
|
148
|
+ byteChannel.read(bytes);
|
165
|
149
|
|
166
|
150
|
// And loop looking for data
|
167
|
151
|
// This uses seekDistance so that only wanted data is checked.
|
|
152
|
+ boolean gotNewLine = false;
|
168
|
153
|
for (int i = seekDistance - 1; i >= 0; --i) {
|
169
|
154
|
// Check for New line Character, or a non carriage-return char
|
170
|
|
- if (bytes[i] == '\n') {
|
|
155
|
+ if (bytes.get(i) == '\n') {
|
171
|
156
|
// Seek to the location of this character and exit this loop.
|
172
|
|
- file.seek(fp + i);
|
|
157
|
+ byteChannel.position(fp + i);
|
173
|
158
|
gotNewLine = true;
|
174
|
159
|
break;
|
175
|
|
- } else if (bytes[i] != '\r') {
|
|
160
|
+ } else if (bytes.get(i) != '\r') {
|
176
|
161
|
// Add to the result, the loop will continue going.
|
177
|
|
- line.add(0, bytes[i]);
|
|
162
|
+ line.add(0, bytes.get(i));
|
178
|
163
|
}
|
179
|
164
|
}
|
180
|
165
|
|
|
@@ -186,7 +171,7 @@ public class ReverseFileReader {
|
186
|
171
|
// find a new line anywhere. no more loops are possible, so Treat
|
187
|
172
|
// this as "got new line"
|
188
|
173
|
gotNewLine = true;
|
189
|
|
- file.seek(0);
|
|
174
|
+ byteChannel.position(0);
|
190
|
175
|
}
|
191
|
176
|
|
192
|
177
|
// Do we need to continue?
|
|
@@ -197,7 +182,7 @@ public class ReverseFileReader {
|
197
|
182
|
} else {
|
198
|
183
|
// We have not found a new line anywhere,
|
199
|
184
|
// Seek to the pre-read position, and repeat.
|
200
|
|
- file.seek(fp);
|
|
185
|
+ byteChannel.position(fp);
|
201
|
186
|
}
|
202
|
187
|
|
203
|
188
|
}
|
|
@@ -240,7 +225,7 @@ public class ReverseFileReader {
|
240
|
225
|
final StringBuilder result = new StringBuilder();
|
241
|
226
|
for (int i = 0; i < numLines; ++i) {
|
242
|
227
|
try {
|
243
|
|
- result.insert(0, "\n");
|
|
228
|
+ result.insert(0, '\n');
|
244
|
229
|
result.insert(0, getNextLine());
|
245
|
230
|
} catch (IOException e) {
|
246
|
231
|
break;
|