You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by md...@apache.org on 2015/03/11 11:21:59 UTC

svn commit: r1665833 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/segment/file/ test/java/org/apache/jackrabbit/oak/plugins/segment/file/

Author: mduerig
Date: Wed Mar 11 10:21:58 2015
New Revision: 1665833

URL: http://svn.apache.org/r1665833
Log:
OAK-2605: Support for additional encodings needed in ReversedLinesFileReader
Copying ReversedLinesFileReader from commons-io with the fix for IO-471. Credits to Leandro Reis for the patch.

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReader.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamBlockSize.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamFile.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesReaderTestData.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/JournalReader.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/JournalReader.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/JournalReader.java?rev=1665833&r1=1665832&r2=1665833&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/JournalReader.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/JournalReader.java Wed Mar 11 10:21:58 2015
@@ -25,7 +25,6 @@ import java.io.IOException;
 import java.util.Iterator;
 
 import com.google.common.collect.AbstractIterator;
-import org.apache.commons.io.input.ReversedLinesFileReader;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -71,4 +70,5 @@ public final class JournalReader impleme
     public void close() throws IOException {
         journal.close();
     }
+
 }

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReader.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReader.java?rev=1665833&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReader.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReader.java Wed Mar 11 10:21:58 2015
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.jackrabbit.oak.plugins.segment.file;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.UnsupportedCharsetException;
+
+import org.apache.commons.io.Charsets;
+
+/**
+ * Reads lines in a file reversely (similar to a BufferedReader, but starting at
+ * the last line). Useful for e.g. searching in log files.
+ *
+ * FIXME: this is a copy of org.apache.commons.io.input.ReversedLinesFileReader
+ * with a fix for IO-471. Replace again once commons-io has released a fixed version.
+ */
+class ReversedLinesFileReader implements Closeable {
+
+    private final int blockSize;
+    private final Charset encoding;
+
+    private final RandomAccessFile randomAccessFile;
+
+    private final long totalByteLength;
+    private final long totalBlockCount;
+
+    private final byte[][] newLineSequences;
+    private final int avoidNewlineSplitBufferSize;
+    private final int byteDecrement;
+
+    private FilePart currentFilePart;
+
+    private boolean trailingNewlineOfFileSkipped = false;
+
+    /**
+     * Creates a ReversedLinesFileReader with default block size of 4KB and the
+     * platform's default encoding.
+     *
+     * @param file
+     *            the file to be read
+     * @throws IOException  if an I/O error occurs
+     */
+    public ReversedLinesFileReader(final File file) throws IOException {
+        this(file, 4096, Charset.defaultCharset().toString());
+    }
+
+    /**
+     * Creates a ReversedLinesFileReader with the given block size and encoding.
+     *
+     * @param file
+     *            the file to be read
+     * @param blockSize
+     *            size of the internal buffer (for ideal performance this should
+     *            match with the block size of the underlying file system).
+     * @param encoding
+     *            the encoding of the file
+     * @throws IOException  if an I/O error occurs
+     * @since 2.3
+     */
+    public ReversedLinesFileReader(final File file, final int blockSize, final Charset encoding) throws IOException {
+        this.blockSize = blockSize;
+        this.encoding = encoding;
+
+        randomAccessFile = new RandomAccessFile(file, "r");
+        totalByteLength = randomAccessFile.length();
+        int lastBlockLength = (int) (totalByteLength % blockSize);
+        if (lastBlockLength > 0) {
+            totalBlockCount = totalByteLength / blockSize + 1;
+        } else {
+            totalBlockCount = totalByteLength / blockSize;
+            if (totalByteLength > 0) {
+                lastBlockLength = blockSize;
+            }
+        }
+        currentFilePart = new FilePart(totalBlockCount, lastBlockLength, null);
+
+        // --- check & prepare encoding ---
+        Charset charset = Charsets.toCharset(encoding);
+        CharsetEncoder charsetEncoder = charset.newEncoder();
+        float maxBytesPerChar = charsetEncoder.maxBytesPerChar();
+        if(maxBytesPerChar==1f) {
+            // all one byte encodings are no problem
+            byteDecrement = 1;
+        } else if(charset == Charset.forName("UTF-8")) {
+            // UTF-8 works fine out of the box, for multibyte sequences a second UTF-8 byte can never be a newline byte
+            // http://en.wikipedia.org/wiki/UTF-8
+            byteDecrement = 1;
+        } else if(charset == Charset.forName("Shift_JIS") || // Same as for UTF-8 http://www.herongyang.com/Unicode/JIS-Shift-JIS-Encoding.html
+                charset == Charset.forName("windows-31j") || // Windows code page 932 (Japanese)
+                charset == Charset.forName("x-windows-949") || // Windows code page 949 (Korean)
+                charset == Charset.forName("gbk") || // Windows code page 936 (Simplified Chinese)
+                charset == Charset.forName("x-windows-950")) { // Windows code page 950 (Traditional Chinese)
+            byteDecrement = 1;
+        } else if(charset == Charset.forName("UTF-16BE") || charset == Charset.forName("UTF-16LE")) {
+            // UTF-16 new line sequences are not allowed as second tuple of four byte sequences,
+            // however byte order has to be specified
+            byteDecrement = 2;
+        } else if(charset == Charset.forName("UTF-16")) {
+            throw new UnsupportedEncodingException(
+                    "For UTF-16, you need to specify the byte order (use UTF-16BE or UTF-16LE)");
+        } else {
+            throw new UnsupportedEncodingException(
+                    "Encoding "+encoding+" is not supported yet (feel free to submit a patch)");
+        }
+        // NOTE: The new line sequences are matched in the order given, so it is important that \r\n is BEFORE \n
+        newLineSequences = new byte[][] { "\r\n".getBytes(encoding), "\n".getBytes(encoding), "\r".getBytes(encoding) };
+
+        avoidNewlineSplitBufferSize = newLineSequences[0].length;
+    }
+
+    /**
+     * Creates a ReversedLinesFileReader with the given block size and encoding.
+     *
+     * @param file
+     *            the file to be read
+     * @param blockSize
+     *            size of the internal buffer (for ideal performance this should
+     *            match with the block size of the underlying file system).
+     * @param encoding
+     *            the encoding of the file
+     * @throws IOException  if an I/O error occurs
+     * @throws UnsupportedCharsetException
+     *             thrown instead of {@link UnsupportedEncodingException} in version 2.2 if the encoding is not
+     *             supported.
+     */
+    public ReversedLinesFileReader(final File file, final int blockSize, final String encoding) throws IOException {
+        this(file, blockSize, Charsets.toCharset(encoding));
+    }
+
+    /**
+     * Returns the lines of the file from bottom to top.
+     *
+     * @return the next line or null if the start of the file is reached
+     * @throws IOException  if an I/O error occurs
+     */
+    public String readLine() throws IOException {
+
+        String line = currentFilePart.readLine();
+        while (line == null) {
+            currentFilePart = currentFilePart.rollOver();
+            if (currentFilePart != null) {
+                line = currentFilePart.readLine();
+            } else {
+                // no more fileparts: we're done, leave line set to null
+                break;
+            }
+        }
+
+        // aligned behaviour wiht BufferedReader that doesn't return a last, emtpy line
+        if("".equals(line) && !trailingNewlineOfFileSkipped) {
+            trailingNewlineOfFileSkipped = true;
+            line = readLine();
+        }
+
+        return line;
+    }
+
+    /**
+     * Closes underlying resources.
+     *
+     * @throws IOException  if an I/O error occurs
+     */
+    public void close() throws IOException {
+        randomAccessFile.close();
+    }
+
+    private class FilePart {
+        private final long no;
+
+        private final byte[] data;
+
+        private byte[] leftOver;
+
+        private int currentLastBytePos;
+
+        /**
+         * ctor
+         * @param no the part number
+         * @param length its length
+         * @param leftOverOfLastFilePart remainder
+         * @throws IOException if there is a problem reading the file
+         */
+        private FilePart(final long no, final int length, final byte[] leftOverOfLastFilePart) throws IOException {
+            this.no = no;
+            int dataLength = length + (leftOverOfLastFilePart != null ? leftOverOfLastFilePart.length : 0);
+            this.data = new byte[dataLength];
+            final long off = (no - 1) * blockSize;
+
+            // read data
+            if (no > 0 /* file not empty */) {
+                randomAccessFile.seek(off);
+                final int countRead = randomAccessFile.read(data, 0, length);
+                if (countRead != length) {
+                    throw new IllegalStateException("Count of requested bytes and actually read bytes don't match");
+                }
+            }
+            // copy left over part into data arr
+            if (leftOverOfLastFilePart != null) {
+                System.arraycopy(leftOverOfLastFilePart, 0, data, length, leftOverOfLastFilePart.length);
+            }
+            this.currentLastBytePos = data.length - 1;
+            this.leftOver = null;
+        }
+
+        /**
+         * Handles block rollover
+         *
+         * @return the new FilePart or null
+         * @throws IOException if there was a problem reading the file
+         */
+        private FilePart rollOver() throws IOException {
+
+            if (currentLastBytePos > -1) {
+                throw new IllegalStateException("Current currentLastCharPos unexpectedly positive... "
+                        + "last readLine() should have returned something! currentLastCharPos=" + currentLastBytePos);
+            }
+
+            if (no > 1) {
+                return new FilePart(no - 1, blockSize, leftOver);
+            } else {
+                // NO 1 was the last FilePart, we're finished
+                if (leftOver != null) {
+                    throw new IllegalStateException("Unexpected leftover of the last block: leftOverOfThisFilePart="
+                            + new String(leftOver, encoding));
+                }
+                return null;
+            }
+        }
+
+        /**
+         * Reads a line.
+         *
+         * @return the line or null
+         * @throws IOException if there is an error reading from the file
+         */
+        private String readLine() throws IOException {
+
+            String line = null;
+            int newLineMatchByteCount;
+
+            boolean isLastFilePart = no == 1;
+
+            int i = currentLastBytePos;
+            while (i > -1) {
+
+                if (!isLastFilePart && i < avoidNewlineSplitBufferSize) {
+                    // avoidNewlineSplitBuffer: for all except the last file part we
+                    // take a few bytes to the next file part to avoid splitting of newlines
+                    createLeftOver();
+                    break; // skip last few bytes and leave it to the next file part
+                }
+
+                // --- check for newline ---
+                if ((newLineMatchByteCount = getNewLineMatchByteCount(data, i)) > 0 /* found newline */) {
+                    final int lineStart = i + 1;
+                    int lineLengthBytes = currentLastBytePos - lineStart + 1;
+
+                    if (lineLengthBytes < 0) {
+                        throw new IllegalStateException("Unexpected negative line length="+lineLengthBytes);
+                    }
+                    byte[] lineData = new byte[lineLengthBytes];
+                    System.arraycopy(data, lineStart, lineData, 0, lineLengthBytes);
+
+                    line = new String(lineData, encoding);
+
+                    currentLastBytePos = i - newLineMatchByteCount;
+                    break; // found line
+                }
+
+                // --- move cursor ---
+                i -= byteDecrement;
+
+                // --- end of file part handling ---
+                if (i < 0) {
+                    createLeftOver();
+                    break; // end of file part
+                }
+            }
+
+            // --- last file part handling ---
+            if (isLastFilePart && leftOver != null) {
+                // there will be no line break anymore, this is the first line of the file
+                line = new String(leftOver, encoding);
+                leftOver = null;
+            }
+
+            return line;
+        }
+
+        /**
+         * Creates the buffer containing any left over bytes.
+         */
+        private void createLeftOver() {
+            int lineLengthBytes = currentLastBytePos + 1;
+            if (lineLengthBytes > 0) {
+                // create left over for next block
+                leftOver = new byte[lineLengthBytes];
+                System.arraycopy(data, 0, leftOver, 0, lineLengthBytes);
+            } else {
+                leftOver = null;
+            }
+            currentLastBytePos = -1;
+        }
+
+        /**
+         * Finds the new-line sequence and return its length.
+         *
+         * @param data buffer to scan
+         * @param i start offset in buffer
+         * @return length of newline sequence or 0 if none found
+         */
+        private int getNewLineMatchByteCount(byte[] data, int i) {
+            for (byte[] newLineSequence : newLineSequences) {
+                boolean match = true;
+                for (int j = newLineSequence.length - 1; j >= 0; j--) {
+                    int k = i + j - (newLineSequence.length - 1);
+                    match &= k >= 0 && data[k] == newLineSequence[j];
+                }
+                if (match) {
+                    return newLineSequence.length;
+                }
+            }
+            return 0;
+        }
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamBlockSize.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamBlockSize.java?rev=1665833&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamBlockSize.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamBlockSize.java Wed Mar 11 10:21:58 2015
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.segment.file;
+
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.GBK_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.WINDOWS_31J_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.X_WINDOWS_949_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.X_WINDOWS_950_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.createFile;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test checks symmetric behaviour with  BufferedReader
+ * FIXME: this is mostly taken from a copy of org.apache.commons.io.input
+ * with a fix for IO-471. Replace again once commons-io has released a fixed version.
+ */
+@RunWith(Parameterized.class)
+public class ReversedLinesFileReaderTestParamBlockSize {
+
+    private static final String UTF_8 = "UTF-8";
+    private static final String ISO_8859_1 = "ISO-8859-1";
+
+    @SuppressWarnings("boxing")
+    @Parameters // small and uneven block sizes are not used in reality but are good to show that the algorithm is solid
+    public static Collection<Integer[]> blockSizes() {
+            return Arrays.asList(new Integer[][] { {1}, {3}, {8}, {256}, {4096} });
+    }
+
+    private ReversedLinesFileReader reversedLinesFileReader;
+    private final int testParamBlockSize;
+
+    public ReversedLinesFileReaderTestParamBlockSize(Integer testWithBlockSize) {
+        testParamBlockSize = testWithBlockSize;
+    }
+
+    // Strings are escaped in constants to avoid java source encoding issues (source file enc is UTF-8):
+
+    // windows-31j characters
+    private static final String TEST_LINE_WINDOWS_31J_1 = "\u3041\u3042\u3043\u3044\u3045";
+    private static final String TEST_LINE_WINDOWS_31J_2 = "\u660E\u8F38\u5B50\u4EAC";
+    // gbk characters (Simplified Chinese)
+    private static final String TEST_LINE_GBK_1 = "\u660E\u8F38\u5B50\u4EAC";
+    private static final String TEST_LINE_GBK_2 = "\u7B80\u4F53\u4E2D\u6587";
+    // x-windows-949 characters (Korean)
+    private static final String TEST_LINE_X_WINDOWS_949_1 = "\uD55C\uAD6D\uC5B4";
+    private static final String TEST_LINE_X_WINDOWS_949_2 = "\uB300\uD55C\uBBFC\uAD6D";
+    // x-windows-950 characters (Traditional Chinese)
+    private static final String TEST_LINE_X_WINDOWS_950_1 = "\u660E\u8F38\u5B50\u4EAC";
+    private static final String TEST_LINE_X_WINDOWS_950_2 = "\u7E41\u9AD4\u4E2D\u6587";
+
+    @After
+    public void closeReader() {
+        try {
+            reversedLinesFileReader.close();
+        } catch(Exception e) {
+            // ignore
+        }
+    }
+
+    @Test
+    public void testWindows31jFile() throws URISyntaxException, IOException {
+        File testFileWindows31J = createFile(WINDOWS_31J_BIN);
+        reversedLinesFileReader = new ReversedLinesFileReader(testFileWindows31J, testParamBlockSize, "windows-31j");
+        assertEqualsAndNoLineBreaks(TEST_LINE_WINDOWS_31J_2, reversedLinesFileReader.readLine());
+        assertEqualsAndNoLineBreaks(TEST_LINE_WINDOWS_31J_1, reversedLinesFileReader.readLine());
+    }
+
+    @Test
+    public void testGBK() throws URISyntaxException, IOException {
+        File testFileGBK = createFile(GBK_BIN);
+        reversedLinesFileReader = new ReversedLinesFileReader(testFileGBK, testParamBlockSize, "GBK");
+        assertEqualsAndNoLineBreaks(TEST_LINE_GBK_2, reversedLinesFileReader.readLine());
+        assertEqualsAndNoLineBreaks(TEST_LINE_GBK_1, reversedLinesFileReader.readLine());
+    }
+
+    @Test
+    public void testxWindows949File() throws URISyntaxException, IOException {
+        File testFilexWindows949 = createFile(X_WINDOWS_949_BIN);
+        reversedLinesFileReader = new ReversedLinesFileReader(testFilexWindows949, testParamBlockSize, "x-windows-949");
+        assertEqualsAndNoLineBreaks(TEST_LINE_X_WINDOWS_949_2, reversedLinesFileReader.readLine());
+        assertEqualsAndNoLineBreaks(TEST_LINE_X_WINDOWS_949_1, reversedLinesFileReader.readLine());
+    }
+
+    @Test
+    public void testxWindows950File() throws URISyntaxException, IOException {
+        File testFilexWindows950 = createFile(X_WINDOWS_950_BIN);
+        reversedLinesFileReader = new ReversedLinesFileReader(testFilexWindows950, testParamBlockSize, "x-windows-950");
+        assertEqualsAndNoLineBreaks(TEST_LINE_X_WINDOWS_950_2, reversedLinesFileReader.readLine());
+        assertEqualsAndNoLineBreaks(TEST_LINE_X_WINDOWS_950_1, reversedLinesFileReader.readLine());
+    }
+
+    static void assertEqualsAndNoLineBreaks(String msg, String expected, String actual) {
+        if(actual!=null) {
+            assertFalse("Line contains \\n: line="+actual, actual.contains("\n"));
+            assertFalse("Line contains \\r: line="+actual, actual.contains("\r"));
+        }
+        assertEquals(msg, expected, actual);
+    }
+    static void assertEqualsAndNoLineBreaks(String expected, String actual) {
+        assertEqualsAndNoLineBreaks(null, expected, actual);
+    }
+}

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamFile.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamFile.java?rev=1665833&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamFile.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesFileReaderTestParamFile.java Wed Mar 11 10:21:58 2015
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.oak.plugins.segment.file;
+
+
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.GBK_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.WINDOWS_31J_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.X_WINDOWS_949_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.X_WINDOWS_950_BIN;
+import static org.apache.jackrabbit.oak.plugins.segment.file.ReversedLinesReaderTestData.createFile;
+import static org.junit.Assert.assertEquals;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URISyntaxException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Stack;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Test checks symmetric behaviour with  BufferedReader
+ * FIXME: this is mostly taken from a copy of org.apache.commons.io.input
+ * with a fix for IO-471. Replace again once commons-io has released a fixed version.
+ */
+@RunWith(Parameterized.class)
+public class ReversedLinesFileReaderTestParamFile {
+
+    @Parameters
+    public static Collection<Object[]> blockSizes() {
+            return Arrays.asList(new Object[][] {
+                    {WINDOWS_31J_BIN, "windows-31j", null},
+                    {GBK_BIN, "gbk", null},
+                    {X_WINDOWS_949_BIN, "x-windows-949", null},
+                    {X_WINDOWS_950_BIN, "x-windows-950", null},
+            });
+    }
+
+    private ReversedLinesFileReader reversedLinesFileReader;
+    private BufferedReader bufferedReader;
+
+    private final byte[] data;
+    private final String encoding;
+    private final int buffSize;
+
+    public ReversedLinesFileReaderTestParamFile(byte[] data, String encoding, Integer buffSize) {
+        this.data = data;
+        this.encoding = encoding;
+        this.buffSize = buffSize == null ? 4096 : buffSize;
+    }
+
+    @Test
+    public void testDataIntegrityWithBufferedReader() throws URISyntaxException, IOException {
+        File testFileIso = createFile(data);
+        reversedLinesFileReader = new ReversedLinesFileReader(testFileIso, buffSize, encoding);
+
+        Stack<String> lineStack = new Stack<String>();
+
+        bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(testFileIso), encoding));
+        String line;
+
+        // read all lines in normal order
+        while((line = bufferedReader.readLine())!=null) {
+            lineStack.push(line);
+        }
+
+        // read in reverse order and compare with lines from stack
+        while((line = reversedLinesFileReader.readLine())!=null) {
+            String lineFromBufferedReader = lineStack.pop();
+            assertEquals(lineFromBufferedReader, line);
+        }
+
+    }
+
+    @After
+    public void closeReader() {
+        try {
+            bufferedReader.close();
+        } catch(Exception e) {
+            // ignore
+        }
+        try {
+            reversedLinesFileReader.close();
+        } catch(Exception e) {
+            // ignore
+        }
+    }
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesReaderTestData.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesReaderTestData.java?rev=1665833&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesReaderTestData.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/segment/file/ReversedLinesReaderTestData.java Wed Mar 11 10:21:58 2015
@@ -0,0 +1,47 @@
+package org.apache.jackrabbit.oak.plugins.segment.file;
+
+import static java.io.File.createTempFile;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Test checks symmetric behaviour with  BufferedReader
+ * FIXME: this is mostly taken from a copy of org.apache.commons.io.input
+ * with a fix for IO-471. Replace again once commons-io has released a fixed version.
+ */
+public final class ReversedLinesReaderTestData {
+    private ReversedLinesReaderTestData() {}
+
+    public static final byte[] WINDOWS_31J_BIN = new byte[]{
+            -126, -97, -126, -96, -126, -95, -126, -94, -126, -93, 13, 10, -106, -66, -105, 65, -114,
+            113, -117, -98, 13, 10,
+    };
+
+    public static final byte[] GBK_BIN = new byte[]{
+            -61, -9, -35, -108, -41, -45, -66, -87, 13, 10, -68, -14, -52, -27, -42, -48, -50, -60,
+            13, 10,
+    };
+
+    public static final byte[] X_WINDOWS_949_BIN = new byte[]{
+            -57, -47, -79, -71, -66, -18, 13, 10, -76, -21, -57, -47, -71, -50, -79, -71, 13, 10,
+    };
+
+    public static final byte[] X_WINDOWS_950_BIN = new byte[]{
+            -87, -6, -65, -23, -92, 108, -88, -54, 13, 10, -63, 99, -59, -23, -92, -92, -92, -27,
+            13, 10,
+    };
+
+    public static File createFile(byte[] data) throws IOException {
+        File file = createTempFile(ReversedLinesReaderTestData.class.getSimpleName(), null);
+        FileOutputStream os = new FileOutputStream(file);
+        try {
+            os.write(data);
+            return file;
+        } finally {
+            os.close();
+        }
+    }
+
+}