--- old/src/java.base/share/classes/java/nio/file/Files.java 2018-10-15 14:57:28.240327861 -0700 +++ new/src/java.base/share/classes/java/nio/file/Files.java 2018-10-15 14:57:27.513250050 -0700 @@ -91,6 +91,9 @@ */ public final class Files { + // buffer size used for reading and writing + private static final int BUFFER_SIZE = 8192; + private Files() { } /** @@ -1531,6 +1534,90 @@ } /** + * Finds and returns the position of the first mismatched byte in the content + * of two files, or {@code -1L} if there is no mismatch. The position will be + * in the inclusive range of {@code 0L} up to the size (in bytes) of the + * smaller file. + * + *

Two files are considered to match if they satisfy one of the following + * conditions: + *

+ * + *

Otherwise there is a mismatch between the two files and the value + * returned by this method is: + *

+ * + *

This method may not be atomic with respect to other file system + * operations. This method is always reflexive (for {@code Path f}, + * {@code mismatch(f,f)} returns {@code -1L}). If the file system and files + * remain static, then this method is symmetric (for two {@code Paths f} + * and {@code g}, {@code mismatch(f,g)} will return the same value as + * {@code mismatch(g,f)}). + * + * @param path + * the path to the first file + * @param path2 + * the path to the second file + * + * @return the position of the first mismatch or {@code -1L} if no mismatch + * + * @throws IOException + * if an I/O error occurs + * @throws SecurityException + * In the case of the default provider, and a security manager is + * installed, the {@link SecurityManager#checkRead(String) checkRead} + * method is invoked to check read access to both files. + * + * @since 12 + */ + public static long mismatch(Path path, Path path2) throws IOException { + if (isSameFile(path, path2)) { + return -1; + } + byte[] buffer1 = new byte[Files.BUFFER_SIZE]; + byte[] buffer2 = new byte[Files.BUFFER_SIZE]; + try (InputStream in1 = Files.newInputStream(path); + InputStream in2 = Files.newInputStream(path2);) { + long totalRead = 0; + while (true) { + int nRead1 = in1.readNBytes(buffer1, 0, Files.BUFFER_SIZE); + int nRead2 = in2.readNBytes(buffer2, 0, Files.BUFFER_SIZE); + if (nRead1 == 0 && nRead2 == 0) { + // both files reached EOF + return -1; + } else if (nRead1 == 0 || nRead2 == 0) { + // one but not both files reached EOF + return totalRead; + } else if (nRead1 != nRead2) { + // there's always a mismatch when nRead1 != nRead2 + return totalRead + Arrays.mismatch(buffer1, 0, nRead1, buffer2, 0, nRead2); + } else { + int i = Arrays.mismatch(buffer1, 0, nRead1, buffer2, 0, nRead1); + if (i > -1) { + return totalRead + i; + } + if (nRead1 < Files.BUFFER_SIZE) { + // we've reached the end of the files, but found no mismatch + return -1; + } + totalRead += nRead1; + } + } + } + } + + /** * Tells whether or not a file is considered hidden. The exact * definition of hidden is platform or provider dependent. On UNIX for * example a file is considered to be hidden if its name begins with a @@ -2802,8 +2889,6 @@ // -- Utility methods for simple usages -- - // buffer size used for reading and writing - private static final int BUFFER_SIZE = 8192; /** * Opens a file for reading, returning a {@code BufferedReader} that may be --- /dev/null 2018-05-07 23:51:01.519000000 -0700 +++ new/test/jdk/java/nio/file/Files/Mismatch.java 2018-10-15 14:57:29.362447949 -0700 @@ -0,0 +1,397 @@ +/* + * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.testng.Assert; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.spi.FileSystemProvider; +import java.util.Map; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +/* @test + * @bug 8202285 + * @build Mismatch + * @run testng Mismatch + * @summary Unit test for the mismatch method + */ + +@Test(groups = "mismatch") +public class Mismatch { + // the standard buffer size + final static int BUFFER_SIZE = 8192; + + private static final int MISMATCH_NO = -1; + + // Map to be used for creating a ZIP archive + private static final Map ZIPFS_MAP = Map.of("create", "true"); + + // temporary test directory where all test files will be created + Path testDir; + + Path test0a; // empty file, size = 0 + Path test0b; // empty file, size = 0 + + /** + * The following files are used to test cases where the files are smaller, + * equal and greater than the buffer size of 8192. + */ + Path test1024a; // a test file with size 1024 + Path test1024b; // a test file with size 1024, identical to test1024a + Path test1024m1014a; // a test file with size 1024, with an altered byte at 1014 + Path test1024m1014b; // a test file with size 1024, with an altered byte at 1014 + Path test1024m60; // a test file with size 1024, with an altered byte at 60 + + Path test8192a; // a test file with size 8192 + Path test8192b; // a test file with size 8192, identical to test8192a + Path test8192m8182a; // a test file with size 8192, with an altered byte at 8182 + Path test8192m8182b; // a test file with size 8192, with an altered byte at 8182 + Path test8192m60; // a test file with size 8192, with an altered byte at 60 + + /** + * Files with a size >= BUFFER_SIZE to verify the situation where they will be + * read a few times before a mismatch is found in the last full or partial buffer + */ + Path test65536a; // a test file with size 65536 + Path test65536b; // a test file with size 65536, identical to test65536a + Path test65536m65526a; // a test file with size 65536, with an altered byte at 65526 + Path test65536m65526b; // a test file with size 65536, with an altered byte at 65526 + Path test65536m60; // a test file with size 65536, with an altered byte at 60 + Path test69632a; // a test file with size 69632 + Path test69632b; // a test file with size 69632, identical to test69632a + Path test69632m69622a; // a test file with size 69632, with an altered byte at 69622 + Path test69632m69622b; // a test file with size 69632, with an altered byte at 69622 + Path test69632m60; // a test file with size 69632, with an altered byte at 60 + + /** + * Larger files (>=1048576) to exercise the process + */ + Path test1048576a; // a test file with size 1048576 + Path test1048576b; // a test file with size 1048576, identical to test1048576a + Path test1048576m1048566a; // a test file with size 1048576, with an altered byte at 1048566 + Path test1048576m1048566b; // a test file with size 1048576, with an altered byte at 1048566 + Path test1048576m60; // a test file with size 1048576, with an altered byte at 60 + Path test1052672m1052662a; // a test file with size 1052672, with an altered byte at 1052662 + Path test1052672m1052662b; // a test file with size 1052672, with an altered byte at 1052662 + Path test1052672m60; // a test file with size 1052672, with an altered byte at 60 + + + @BeforeClass + void setup() throws IOException { + /** + * Create files with alphanumeric contents in a temporary directory + */ + testDir = Files.createTempDirectory("testMismatch"); + + // create empty files + int size = 0; + test0a = createANFile(testDir, "test0a", 0, -1, (byte)' '); + test0b = createANFile(testDir, "test0b", 0, -1, (byte)' '); + + // The Impl uses a standard buffer of 8192 + // Test cases with files <= and > 8192 + size = 1024; + test1024a = createANFile(testDir, "test1024a", size, -1, (byte)' '); + test1024b = createANFile(testDir, "test1024b", size, -1, (byte)' '); + test1024m1014a = createANFile(testDir, "test1024m1014a", size, size - 10, (byte)'@'); + test1024m1014b = createANFile(testDir, "test1024m1014b", size, size - 10, (byte)'#'); + test1024m60 = createANFile(testDir, "test1024m60", size, 60, (byte)'$'); + + size = BUFFER_SIZE; + test8192a = createANFile(testDir, "test8192a", size, -1, (byte)' '); + test8192b = createANFile(testDir, "test8192b", size, -1, (byte)' '); + test8192m8182a = createANFile(testDir, "test8192m8182a", size, size - 10, (byte)'@'); + test8192m8182b = createANFile(testDir, "test8192m8182b", size, size - 10, (byte)'#'); + test8192m60 = createANFile(testDir, "test8192m60", size, 60, (byte)'$'); + + // create files with size several times > BUFFER_SIZE to be used for tests that verify + // the situations where they are read into full buffers a few times + size = BUFFER_SIZE << 3; + test65536a = createANFile(testDir, "test65536a", size, -1, (byte)' '); + test65536b = createANFile(testDir, "test65536b", size, -1, (byte)' '); + test65536m65526a = createANFile(testDir, "test65536m65526a", size, size - 10, (byte)'@'); + test65536m65526b = createANFile(testDir, "test65536m65526b", size, size - 10, (byte)'#'); + test65536m60 = createANFile(testDir, "test65536m60", size, 60, (byte)'$'); + // create files with sizes that will be iterated several times with full buffers, and + // then a partial one at the last + size = 69632; + test69632a = createANFile(testDir, "test69632a", size, -1, (byte)' '); + test69632b = createANFile(testDir, "test69632b", size, -1, (byte)' '); + test69632m69622a = createANFile(testDir, "test69632m69622a", size, size - 10, (byte)'@'); + test69632m69622b = createANFile(testDir, "test69632m69622b", size, size - 10, (byte)'#'); + test69632m60 = createANFile(testDir, "test69632m60", size, 60, (byte)'$'); + + // create larger files with >= 1048576. The mismatching will be similar. These are just + // tests to exercise the process with larger files + size = 1048576; + test1048576a = createANFile(testDir, "test1048576a", size, -1, (byte)' '); + test1048576b = createANFile(testDir, "test1048576b", size, -1, (byte)' '); + test1048576m1048566a = createANFile(testDir, "test1048576m1048566a", size, size - 10, (byte)'@'); + test1048576m1048566b = createANFile(testDir, "test1048576m1048566b", size, size - 10, (byte)'#'); + test1048576m60 = createANFile(testDir, "test1048576m60", size, 60, (byte)'$'); + size = 1052672; + test1052672m1052662a = createANFile(testDir, "test1052672m1052662a", size, size - 10, (byte)'@'); + test1052672m1052662b = createANFile(testDir, "test1052672m1052662b", size, size - 10, (byte)'#'); + test1052672m60 = createANFile(testDir, "test1052672m60", size, 60, (byte)'$'); + } + + @AfterClass + void cleanup() throws IOException { + // clean up files created under the test directory + Files.walk(testDir).map(Path::toFile).forEach(File::delete); + Files.deleteIfExists(testDir); + } + + /* + * DataProvider for mismatch test. Provides the following fields: + * path1 -- the path to a file + * path2 -- the path to another file + * expected -- expected result of the mismatch method + * note -- a note about the test + */ + @DataProvider(name = "testMismatch") + public Object[][] getDataForMismatch() throws IOException { + Path foo = Paths.get("foo"); + + return new Object[][]{ + // Spec Case 1: the two paths locate the same file , even if one does not exist + {foo, foo, MISMATCH_NO, "Same file, no mismatch"}, + {test0a, test0a, MISMATCH_NO, "Same file, no mismatch"}, + {test1024a, test1024a, MISMATCH_NO, "Same file, no mismatch"}, + + // Spec Case 2: The two files are the same size, and every byte in the first file + // is identical to the corresponding byte in the second file. + {test0a, test0b, MISMATCH_NO, "Sizes == 0, no mismatch"}, + {test1024a, test1024b, MISMATCH_NO, "size = 1024 < buffer = 8192, no mismatch"}, + {test8192a, test8192b, MISMATCH_NO, "size = 8192 = buffer = 8192, no mismatch"}, + {test65536a, test65536b, MISMATCH_NO, "read 8 * full buffer, no mismatch"}, + {test69632a, test69632b, MISMATCH_NO, "read 8 * full buffer plus a partial buffer, no mismatch"}, + + // Spec Case 3: the value returned is the position of the first mismatched byte + // Impl: the impl uses a buffer 8192. The testcases below covers a range of files + // with sizes <= and > the buffer size. The last buffer is either full or partially full. + {test1024m1014a, test1024m1014b, 1014, "read one partial buffer, mismatch = 1014"}, + {test1024m1014a, test1024m60, 60, "read one partial buffer, mismatch = 60"}, + {test1024m1014a, test8192m8182a, 1014, "one partial vs full buffer, mismatch = 1014"}, + {test8192m8182a, test8192m8182b, 8182, "read one full buffer, mismatch = 8182"}, + {test65536m65526a, test65536m65526b, 65526, + "read several times, mismatch in the last full buffer, mismatch = 65526"}, + {test65536m60, test65536m65526a, 60, "mismatch in the first buffer, mismatch = 60"}, + {test69632m69622a, test69632m69622b, 69622, + "mismatch in the last partial buffer, mismatch = 69622"}, + {test69632m69622a, test69632m60, 60, "mismatch in the first buffer, mismatch = 60"}, + {test1048576m1048566a, test1048576m1048566b, 1048566, + "mismatch in the last full buffer, mismatch = 1048566"}, + {test1048576m60, test1048576m1048566b, 60, "mismatch in the first buffer, mismatch = 60"}, + {test1052672m1052662a, test1052672m1052662b, 1052662, + "mismatch in the last partial buffer, mismatch = 1052662"}, + {test1052672m1052662a, test1052672m60, 60, "mismatch in the first buffer, mismatch = 60"}, + + // Spec Case 4: returns the size of the smaller file (in bytes) when the files are + // different sizes and every byte of the smaller file is identical to the corresponding + // byte of the larger file. + // Impl: similar to case 3, covers a range of file sizes + {test0a, test1024a, 0, "Size of one of files = 0, mismatch at 0"}, + {test1024a, test8192a, 1024, "mismatch is the length of the smaller file: 1024"}, + {test1024a, test65536a, 1024, "mismatch is the length of the smaller file: 1024"}, + {test8192a, test65536a, 8192, "mismatch is the length of the smaller file: 8192"}, + {test69632a, test65536a, 65536, "mismatch is the length of the smaller file: 65536"}, + {test1048576a, test69632a, 69632, "mismatch is the length of the smaller file: 69632"}, + }; + } + + /* + * DataProvider for mismatch tests involving ZipFS using a few test cases selected + * from those of the original mismatch tests. + */ + @DataProvider(name = "testMismatchZipfs") + public Object[][] getDataForMismatchZipfs() throws IOException { + + return new Object[][]{ + {test1024a, test1024a, MISMATCH_NO, "Compares the file and its copy in zip, no mismatch"}, + {test8192m8182a, test8192m8182b, 8182, + "Compares a copy of test8192m8182a in zip and test8192m8182b, shall return 8182"}, + {test1048576a, test65536a, 65536, "mismatch is the length of the smaller file: 65536"}, + }; + } + + /* + * DataProvider for verifying null handling. + */ + @DataProvider(name = "testFileNull") + public Object[][] getDataForNull() throws IOException { + return new Object[][]{ + {(Path)null, (Path)null}, + {(Path)null, test1024a}, + {test1024a, (Path)null}, + }; + } + + /* + * DataProvider for verifying how the mismatch method handles the situation + * when one or both files do not exist. + */ + @DataProvider(name = "testFileNotExist") + public Object[][] getDataForFileNotExist() throws IOException { + return new Object[][]{ + {Paths.get("foo"), Paths.get("bar")}, + {Paths.get("foo"), test1024a}, + {test1024a, Paths.get("bar")}, + }; + } + + /** + * Tests the mismatch method. Refer to the dataProvider testMismatch for more + * details about the cases. + * @param path the path to a file + * @param path2 the path to another file + * @param expected the expected result + * @param msg the message about the test + * @throws IOException if the test fails + */ + @Test(dataProvider = "testMismatch", priority = 0) + public void testMismatch(Path path, Path path2, long expected, String msg) + throws IOException { + long result = Files.mismatch(path, path2); + Assert.assertTrue(result == expected, msg); + } + + /** + * Tests the mismatch method by comparing files with those in a ZIP file. + * @param path the path to a file + * @param path2 the path to another file to be added into a ZIP file + * @param expected the expected result + * @param msg the message about the test + * @throws IOException if the test fails + */ + @Test(dataProvider = "testMismatchZipfs", priority = 1) + public void testMismatchZipfs(Path path, Path path2, long expected, String msg) + throws IOException { + Path zipPath = Paths.get(testDir.toString(), "TestWithFSZip.zip"); + try (FileSystem fs = getZipFSProvider().newFileSystem(zipPath, ZIPFS_MAP)) { + Path copy = fs.getPath(path.getFileName().toString()); + Files.copy(path, copy, REPLACE_EXISTING); + + if (path2 == null) { + Assert.assertTrue(Files.mismatch(copy, path) == expected, msg); + } else { + Assert.assertTrue(Files.mismatch(copy, path2) == expected, msg); + } + } + } + + /** + * Verifies that NullPointerException is thrown when one or both files are null. + * @param path the path to a file + * @param path2 the path to another file + * @throws NullPointerException as expected + */ + @Test(dataProvider = "testFileNull", priority = 2, expectedExceptions = NullPointerException.class) + public void testMismatchNull(Path path, Path path2) throws Exception { + long result = Files.mismatch(path, path2); + } + + /** + * Verifies that IOException is thrown when one or both files do not exist. + * @param path the path to a file + * @param path2 the path to another file + * @throws IOException as expected + */ + @Test(dataProvider = "testFileNotExist", priority = 2, expectedExceptions = IOException.class) + public void testMismatchNotExist(Path path, Path path2) throws IOException { + long result = Files.mismatch(path, path2); + } + + /** + * Creates a file with alphanumeric content with one character altered + * at the specified position. + * + * @param dir the directory in which the file is to be created + * @param purpose the purpose of the file + * @param size the size of the file + * @param pos the position where the alternative char is to be added. If it + * is smaller than zero, no alternation shall be made. + * @param c the character + * @return path of the created file + * @throws IOException + */ + private static Path createANFile(Path dir, String purpose, int size, int pos, + byte c) throws IOException { + Path path = Files.createFile(Paths.get(dir.toString(), purpose + ".txt")); + if (size > 0) { + writeAlphanumericFile(path, size, pos, c); + } + return path; + } + + private static void writeAlphanumericFile(Path path, int size, int pos, byte c) + throws IOException { + byte[] a = createAlphanumericArray(size); + if (pos > 0) a[pos] = c; + Files.write(path, a); + } + + private static byte[] createAlphanumericArray(int length) { + byte[] bytes = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz 0123456789 \n".getBytes(); + byte[] a = new byte[length]; + fillArray(a, bytes); + return a; + } + + private static FileSystemProvider getZipFSProvider() { + for (FileSystemProvider provider : FileSystemProvider.installedProviders()) { + if ("jar".equals(provider.getScheme())) { + return provider; + } + } + return null; + } + + /** + * Fills the byte array by repeating the bytes from the sample array sequentially + * until it is completely filled. + * + * @param samples the sample array + * @return + */ + private static void fillArray(byte[] a, byte[] samples) { + int i1 = 0; + for (int i=0; i