Bläddra i källkod

Add ReverseFileReader from logging plugin

Change-Id: I8fcf1d9e686834dfad9d93c177b97b0ea19fd742
Reviewed-on: http://gerrit.dmdirc.com/2325
Reviewed-by: Shane Mc Cormack <shane@dmdirc.com>
Automatic-Compile: DMDirc Build Manager
tags/0.7rc1
Chris Smith 12 år sedan
förälder
incheckning
7c2788db0e

+ 261
- 0
src/com/dmdirc/util/io/ReverseFileReader.java Visa fil

@@ -0,0 +1,261 @@
1
+/*
2
+ * Copyright (c) 2006-2012 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.util.io;
24
+
25
+import java.io.EOFException;
26
+import java.io.File;
27
+import java.io.FileNotFoundException;
28
+import java.io.IOException;
29
+import java.io.RandomAccessFile;
30
+import java.nio.charset.Charset;
31
+import java.util.ArrayList;
32
+import java.util.Stack;
33
+
34
+/**
35
+ * Reads a file in reverse.
36
+ */
37
+public class ReverseFileReader {
38
+
39
+    /** File to manipulate. */
40
+    private RandomAccessFile file;
41
+
42
+    /** Number of bytes to skip backwards at a time. */
43
+    private byte seekLength = 50;
44
+
45
+    /**
46
+     * Create a new ReverseFileReader.
47
+     *
48
+     * @param filename File to open.
49
+     * @throws FileNotFoundException If the file is not a regular file.
50
+     * @throws SecurityException If a security manager exists and its checkRead method denies read access to the file.
51
+     * @throws IOException If there is an error seeking to the end of the file.
52
+     */
53
+    public ReverseFileReader(final String filename) throws FileNotFoundException,
54
+            SecurityException, IOException {
55
+        file = new RandomAccessFile(filename, "r");
56
+        reset();
57
+    }
58
+
59
+    /**
60
+     * Create a new ReverseFileReader.
61
+     *
62
+     * @param myFile Existing file to use.
63
+     * @throws FileNotFoundException If the file is not a regular file.
64
+     * @throws SecurityException If a security manager exists and its checkRead method denies read access to the file.
65
+     * @throws IOException If there is an error seeking to the end of the file.
66
+     */
67
+    public ReverseFileReader(final File myFile) throws FileNotFoundException,
68
+            SecurityException, IOException {
69
+        file = new RandomAccessFile(myFile, "r");
70
+        reset();
71
+    }
72
+
73
+    /**
74
+     * Reset the file pointer to the end of the file.
75
+     *
76
+     * @throws IOException If there is an error seeking, or the file is closed.
77
+     */
78
+    public void reset() throws IOException {
79
+        if (file == null) {
80
+            throw new IOException("File has been closed.");
81
+        }
82
+        file.seek(file.length());
83
+    }
84
+
85
+    /**
86
+     * Get the current seekLength.
87
+     *
88
+     * @return current seekLength
89
+     */
90
+    public byte getSeekLength() {
91
+        return seekLength;
92
+    }
93
+
94
+    /**
95
+     * Set the seekLength.
96
+     *
97
+     * @param newValue New value for seekLength
98
+     */
99
+    public void setSeekLength(final byte newValue) {
100
+        seekLength = newValue;
101
+    }
102
+
103
+    /**
104
+     * Close the file pointer.
105
+     * This should be called before removing the reference to this object
106
+     *
107
+     * @throws IOException If there is an error closing the file, or if it has been closed already.
108
+     */
109
+    public void close() throws IOException {
110
+        if (file == null) {
111
+            throw new IOException("File has been closed.");
112
+        }
113
+        file.close();
114
+        file = null;
115
+    }
116
+
117
+    /**
118
+     * Get the next full line.
119
+     *
120
+     * @return The next full line.
121
+     * @throws EOFException If there is no more lines.
122
+     * @throws IOException If an error reading or seeking occured, or if the fiel is closed.
123
+     */
124
+    public String getNextLine() throws EOFException, IOException {
125
+        if (file == null) {
126
+            throw new IOException("File has been closed.");
127
+        }
128
+        // Used to store result to output.
129
+
130
+        final ArrayList<Byte> line = new ArrayList<Byte>(seekLength);
131
+        // Used to store position in file pre-read
132
+        long fp = 0;
133
+        // Used to store position in file when this is called
134
+        long startfp = 0;
135
+        // Used to store read bytes
136
+        byte[] bytes;
137
+        // Distance seeked
138
+        int seekDistance = 0;
139
+
140
+        // Check current position, if 0 we are at the start of the file
141
+        // and should throw an exception.
142
+        startfp = file.getFilePointer();
143
+        if (startfp == 0) {
144
+            throw new EOFException("Reached Start of file");
145
+        }
146
+
147
+        // Keep looping untill we get a full line, or the end of the file
148
+        boolean keepLooping = true;
149
+        boolean gotNewLine;
150
+        while (keepLooping) {
151
+            gotNewLine = false;
152
+            // Get Current Position
153
+            fp = file.getFilePointer();
154
+
155
+            // Check how far to seek backwards (seekLength or to the start of the file)
156
+            if (fp < seekLength) {
157
+                // Seek to the start of the file;
158
+                seekDistance = (int) fp;
159
+                fp = 0;
160
+            } else {
161
+                // Seek to position current-seekLength
162
+                seekDistance = seekLength;
163
+                fp = fp - seekDistance;
164
+            }
165
+            // Seek!
166
+            file.seek(fp);
167
+
168
+            bytes = new byte[seekDistance];
169
+            // Read into the bytes array
170
+            file.read(bytes);
171
+
172
+            // And loop looking for data
173
+            // This uses seekDistance so that only wanted data is checked.
174
+            for (int i = seekDistance - 1; i >= 0; --i) {
175
+                // Check for New line Character, or a non carraige-return char
176
+                if (bytes[i] == '\n') {
177
+                    // Seek to the location of this character and exit this loop.
178
+                    file.seek(fp + i);
179
+                    gotNewLine = true;
180
+                    break;
181
+                } else if (bytes[i] != '\r') {
182
+                    // Add to the result, the loop will continue going.
183
+                    line.add(0, bytes[i]);
184
+                }
185
+            }
186
+
187
+            // We have now processed the data we read (Either added it all to the
188
+            // buffer, or found a newline character.)
189
+
190
+            if (fp == 0 && !gotNewLine) {
191
+                // This part of the loop started from the start of the file, but didn't
192
+                // find a new line anywhere. no more loops are posssible, so Treat
193
+                // this as "got new line"
194
+                gotNewLine = true;
195
+                file.seek(0);
196
+            }
197
+
198
+            // Do we need to continue?
199
+            if (gotNewLine) {
200
+                // We have found a new line somewhere, thus we don't need
201
+                // to read any more bytes, so exit the while loop!
202
+                keepLooping = false;
203
+            } else {
204
+                // We have not found a new line anywhere,
205
+                // Seek to the pre-read position, and repeat.
206
+                file.seek(fp);
207
+            }
208
+
209
+        }
210
+
211
+        // Return the data obtained.
212
+        byte[] result = new byte[line.size()];
213
+        for (int i = 0; i < line.size(); ++i) {
214
+            result[i] = line.get(i);
215
+        }
216
+        return new String(result, Charset.forName("UTF-8"));
217
+    }
218
+
219
+    /**
220
+     * Try and get x number of lines.
221
+     * If the file is closed, an empty stack will be returned.
222
+     *
223
+     * @param numLines Number of lines to try and get.
224
+     * @return The requested lines
225
+     */
226
+    public Stack<String> getLines(final int numLines) {
227
+        final Stack<String> result = new Stack<String>();
228
+        for (int i = 0; i < numLines; ++i) {
229
+            try {
230
+                result.push(getNextLine());
231
+            } catch (IOException e) {
232
+                break;
233
+            }
234
+        }
235
+        return result;
236
+    }
237
+
238
+    /**
239
+     * Try and get x number of lines and return a \n delimited String.
240
+     * If the file is closed, an empty string will be returned.
241
+     *
242
+     * @param numLines Number of lines to try and get.
243
+     * @return The requested lines
244
+     */
245
+    public String getLinesAsString(final int numLines) {
246
+        final StringBuilder result = new StringBuilder();
247
+        for (int i = 0; i < numLines; ++i) {
248
+            try {
249
+                result.insert(0, "\n");
250
+                result.insert(0, getNextLine());
251
+            } catch (IOException e) {
252
+                break;
253
+            }
254
+        }
255
+        if (result.charAt(0) == '\n') {
256
+            result.deleteCharAt(0);
257
+        }
258
+        return result.toString();
259
+    }
260
+
261
+}

+ 144
- 0
test/com/dmdirc/util/io/ReverseFileReaderTest.java Visa fil

@@ -0,0 +1,144 @@
1
+/*
2
+ * Copyright (c) 2006-2012 DMDirc Developers
3
+ *
4
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ * of this software and associated documentation files (the "Software"), to deal
6
+ * in the Software without restriction, including without limitation the rights
7
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ * copies of the Software, and to permit persons to whom the Software is
9
+ * furnished to do so, subject to the following conditions:
10
+ *
11
+ * The above copyright notice and this permission notice shall be included in
12
+ * all copies or substantial portions of the Software.
13
+ *
14
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20
+ * SOFTWARE.
21
+ */
22
+
23
+package com.dmdirc.util.io;
24
+
25
+import java.io.File;
26
+import java.io.IOException;
27
+import java.net.URISyntaxException;
28
+import java.util.Stack;
29
+
30
+import org.junit.Test;
31
+
32
+import static org.junit.Assert.*;
33
+
34
+public class ReverseFileReaderTest {
35
+
36
+    @Test
37
+    public void testIndividual() throws IOException, URISyntaxException {
38
+        final ReverseFileReader reader = new ReverseFileReader(
39
+                new File(getClass().getResource("test1.txt").toURI()));
40
+        assertEquals("Line 7", reader.getNextLine());
41
+        assertEquals("Line 6", reader.getNextLine());
42
+        assertEquals("Line 5", reader.getNextLine());
43
+        assertEquals("Line 4", reader.getNextLine());
44
+        assertEquals("Line 3", reader.getNextLine());
45
+        assertEquals("Line 2", reader.getNextLine());
46
+        assertEquals("Line 1", reader.getNextLine());
47
+        reader.close();
48
+    }
49
+
50
+    @Test
51
+    public void testCarriageReturn() throws IOException, URISyntaxException {
52
+        final ReverseFileReader reader = new ReverseFileReader(
53
+                new File(getClass().getResource("test4.txt").toURI()));
54
+        reader.getNextLine();
55
+        assertEquals("Normal line", reader.getNextLine());
56
+        reader.close();
57
+    }
58
+
59
+    @Test
60
+    public void testLongLine() throws IOException, URISyntaxException {
61
+        final ReverseFileReader reader = new ReverseFileReader(
62
+                new File(getClass().getResource("test4.txt").toURI()));
63
+        assertEquals("This is a line that is longer than 50 characters, so " +
64
+                "should cause the reader to have to scan back multiple times.",
65
+                reader.getNextLine());
66
+        reader.close();
67
+    }
68
+
69
+    @Test
70
+    public void testStack() throws IOException, URISyntaxException {
71
+        final ReverseFileReader reader = new ReverseFileReader(
72
+                new File(getClass().getResource("test1.txt").toURI()));
73
+        final Stack<String> lines = reader.getLines(10);
74
+
75
+        assertEquals(7, lines.size());
76
+        assertEquals("Line 1", lines.pop());
77
+        assertEquals("Line 2", lines.pop());
78
+        assertEquals("Line 3", lines.pop());
79
+        assertEquals("Line 4", lines.pop());
80
+        assertEquals("Line 5", lines.pop());
81
+        assertEquals("Line 6", lines.pop());
82
+        assertEquals("Line 7", lines.pop());
83
+        reader.close();
84
+    }
85
+
86
+    @Test
87
+    public void testSmallStack() throws IOException, URISyntaxException {
88
+        final ReverseFileReader reader = new ReverseFileReader(
89
+                new File(getClass().getResource("test1.txt").toURI()));
90
+        final Stack<String> lines = reader.getLines(3);
91
+
92
+        assertEquals(3, lines.size());
93
+        assertEquals("Line 5", lines.pop());
94
+        assertEquals("Line 6", lines.pop());
95
+        assertEquals("Line 7", lines.pop());
96
+        reader.close();
97
+    }
98
+
99
+    @Test
100
+    public void testReset() throws IOException, URISyntaxException {
101
+        final ReverseFileReader reader = new ReverseFileReader(
102
+                new File(getClass().getResource("test1.txt").toURI()));
103
+        assertEquals("Line 7", reader.getNextLine());
104
+        assertEquals("Line 6", reader.getNextLine());
105
+        reader.reset();
106
+        assertEquals("Line 7", reader.getNextLine());
107
+        assertEquals("Line 6", reader.getNextLine());
108
+        assertEquals("Line 5", reader.getNextLine());
109
+        reader.close();
110
+    }
111
+
112
+    @Test(expected=IOException.class)
113
+    public void testIllegalClose() throws URISyntaxException, IOException {
114
+        final ReverseFileReader reader = new ReverseFileReader(
115
+                new File(getClass().getResource("test1.txt").toURI()));
116
+        reader.close();
117
+        reader.close();
118
+    }
119
+
120
+    @Test(expected=IOException.class)
121
+    public void testIllegalReset() throws URISyntaxException, IOException {
122
+        final ReverseFileReader reader = new ReverseFileReader(
123
+                new File(getClass().getResource("test1.txt").toURI()));
124
+        reader.close();
125
+        reader.reset();
126
+    }
127
+
128
+    @Test(expected=IOException.class)
129
+    public void testIllegalGetNextLine() throws URISyntaxException, IOException {
130
+        final ReverseFileReader reader = new ReverseFileReader(
131
+                new File(getClass().getResource("test1.txt").toURI()));
132
+        reader.close();
133
+        reader.getNextLine();
134
+    }
135
+
136
+    @Test
137
+    public void testSeekLength() throws IOException, URISyntaxException {
138
+        final ReverseFileReader reader = new ReverseFileReader(
139
+                new File(getClass().getResource("test1.txt").toURI()));
140
+        reader.setSeekLength((byte) 100);
141
+        assertEquals((byte) 100, reader.getSeekLength());
142
+    }
143
+
144
+}

+ 2
- 0
test/com/dmdirc/util/io/test4.txt Visa fil

@@ -0,0 +1,2 @@
1
+Normal line
2
+This is a line that is longer than 50 characters, so should cause the reader to have to scan back multiple times.

Laddar…
Avbryt
Spara