Bug 573161 - [performance] avoid chunked File write

ByteArrayInputStream:transferTo avoids split into 8192 chunks

Change-Id: Ia0759fd6a1028d58673dcbd191a3d48c858bc84c
Signed-off-by: Joerg Kubitz <jkubitz-eclipse@gmx.de>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.resources/+/179819
Tested-by: Platform Bot <platform-bot@eclipse.org>
Reviewed-by: Andrey Loskutov <loskutov@gmx.de>
diff --git a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java
index 9b3d44b..fbfa0fc 100644
--- a/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java
+++ b/bundles/org.eclipse.core.resources/src/org/eclipse/core/internal/utils/FileUtil.java
@@ -392,30 +392,37 @@
 		return null;
 	}
 
-	public static final void transferStreams(InputStream source, OutputStream destination, String path, IProgressMonitor monitor) throws CoreException {
+	public static final void transferStreams(InputStream source, OutputStream destination, String path,
+			IProgressMonitor monitor) throws CoreException {
 		SubMonitor subMonitor = SubMonitor.convert(monitor);
 		try {
-			byte[] buffer = new byte[8192];
-			while (true) {
-				int bytesRead = -1;
-				try {
-					bytesRead = source.read(buffer);
-				} catch (IOException e) {
-					String msg = NLS.bind(Messages.localstore_failedReadDuringWrite, path);
-					throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, new Path(path), msg, e);
-				}
-				try {
-					if (bytesRead == -1) {
-						// Bug 332543 - ensure we don't ignore failures on close()
-						destination.close();
-						break;
+			try {
+				if (source instanceof ByteArrayInputStream) {
+					// ByteArrayInputStream does overload transferTo avoiding buffering
+					((ByteArrayInputStream) source).transferTo(destination);
+					subMonitor.split(1);
+				} else {
+					byte[] buffer = new byte[8192];
+					while (true) {
+						int bytesRead = -1;
+						try {
+							bytesRead = source.read(buffer);
+						} catch (IOException e) {
+							String msg = NLS.bind(Messages.localstore_failedReadDuringWrite, path);
+							throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, new Path(path), msg, e);
+						}
+						if (bytesRead == -1) {
+							break;
+						}
+						destination.write(buffer, 0, bytesRead);
+						subMonitor.split(1);
 					}
-					destination.write(buffer, 0, bytesRead);
-				} catch (IOException e) {
-					String msg = NLS.bind(Messages.localstore_couldNotWrite, path);
-					throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, new Path(path), msg, e);
 				}
-				subMonitor.split(1);
+				// Bug 332543 - ensure we don't ignore failures on close()
+				destination.close();
+			} catch (IOException e) {
+				String msg = NLS.bind(Messages.localstore_couldNotWrite, path);
+				throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, new Path(path), msg, e);
 			}
 		} finally {
 			safeClose(source);
diff --git a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/Bug_332543.java b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/Bug_332543.java
index cb0eaff..6615c65 100644
--- a/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/Bug_332543.java
+++ b/tests/org.eclipse.core.tests.resources/src/org/eclipse/core/tests/resources/regression/Bug_332543.java
@@ -15,6 +15,7 @@
 
 import java.io.*;
 import java.net.URI;
+import java.util.function.Function;
 import org.eclipse.core.filesystem.IFileStore;
 import org.eclipse.core.filesystem.URIUtil;
 import org.eclipse.core.resources.*;
@@ -24,12 +25,13 @@
 import org.eclipse.core.tests.resources.ResourceTest;
 
 /**
- * This tests that I/O Exception on OuptuStream#close() after IFile#setContents is correctly reported.
+ * This tests that I/O Exception on OuptuStream#close() after IFile#setContents
+ * is correctly reported.
  */
 public class Bug_332543 extends ResourceTest {
 	/**
-	 * Wrapper FS which throws an IOException when someone
-	 * closes an output stream...
+	 * Wrapper FS which throws an IOException when someone closes an output
+	 * stream...
 	 */
 	public static class IOErrOnCloseFileStore extends WrapperFileStore {
 		public IOErrOnCloseFileStore(IFileStore store) {
@@ -42,7 +44,8 @@
 			os = new BufferedOutputStream(os) {
 				@Override
 				public void close() throws java.io.IOException {
-					// We close the output stream (so there aren't issues deleting the project during tear-down)
+					// We close the output stream (so there aren't issues deleting the project
+					// during tear-down)
 					super.close();
 					// But we also throw IOException as if the operation had failed.
 					throw new IOException("Whoops I dunno how to close!");
@@ -58,7 +61,26 @@
 		super.tearDown();
 	}
 
-	public void testBug() throws Exception {
+	public void testBugForByteArrayInputStream() throws Exception {
+		testCancel(s -> s);
+	}
+
+	public void testBugForInputStream() throws Exception {
+		testCancel(delegate -> new InputStream() { // Not ArrayInputStream
+			@Override
+			public int read() throws IOException {
+				return delegate.read();
+			}
+
+			@Override
+			public int read(byte b[], int off, int len) throws IOException {
+				return delegate.read(b, off, len);
+			}
+
+		});
+	}
+
+	private void testCancel(Function<ByteArrayInputStream, InputStream> wrap) throws CoreException {
 		IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
 
 		String proj_name = getUniqueString();
@@ -83,7 +105,7 @@
 
 		// Try #setContents on an existing file
 		try {
-			f.setContents(new ByteArrayInputStream("Random".getBytes()), false, true, getMonitor());
+			f.setContents(wrap.apply(new ByteArrayInputStream("Random".getBytes())), false, true, getMonitor());
 			fail("1.0");
 		} catch (CoreException e) {
 			// This is expected.
@@ -92,10 +114,11 @@
 		// Try create on a non-existent file
 		f = project.getFile("foo1.txt");
 		try {
-			f.create(new ByteArrayInputStream("Random".getBytes()), false, getMonitor());
+			f.create(wrap.apply(new ByteArrayInputStream("Random".getBytes())), false, getMonitor());
 			fail("2.0");
 		} catch (CoreException e) {
 			// This is expected.
 		}
 	}
+
 }