diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TeeOutputStreamTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TeeOutputStreamTest.java new file mode 100644 index 000000000..bb1c97445 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/io/TeeOutputStreamTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2020, Michael Dardis. and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ +package org.eclipse.jgit.util.io; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import org.eclipse.jgit.lib.Constants; +import org.junit.Test; + +public class TeeOutputStreamTest { + + @Test + public void test() throws IOException { + byte[] data = Constants.encode("Hello World"); + + TestOutput first = new TestOutput(); + TestOutput second = new TestOutput(); + try (TeeOutputStream tee = new TeeOutputStream(first, second)) { + tee.write(data); + assertArrayEquals("Stream output must match", first.toByteArray(), + second.toByteArray()); + + tee.write(1); + assertArrayEquals("Stream output must match", first.toByteArray(), + second.toByteArray()); + + tee.write(data, 1, 4); // Test partial write methods + assertArrayEquals("Stream output must match", first.toByteArray(), + second.toByteArray()); + } + assertTrue("First stream should be closed", first.closed); + assertTrue("Second stream should be closed", second.closed); + } + + @Test + public void testCloseException() { + TestOutput first = new TestOutput() { + @Override + public void close() throws IOException { + throw new IOException(); + } + + }; + TestOutput second = new TestOutput(); + + @SuppressWarnings("resource") + TeeOutputStream tee = new TeeOutputStream(first, second); + try { + tee.close(); + } catch (IOException ex) { + // Expected from first closed + } + assertFalse("First stream should not be closed", first.closed); + assertTrue("Second stream should still be closed if first close failed", + second.closed); + } + + private static class TestOutput extends ByteArrayOutputStream { + + private boolean closed; + + @Override + public void close() throws IOException { + closed = true; + super.close(); + } + } + +} diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index 1fd25780d..dbf122ef9 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -172,7 +172,6 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", org.bouncycastle.openpgp.operator;version="[1.61.0,2.0.0)", org.bouncycastle.openpgp.operator.jcajce;version="[1.61.0,2.0.0)", org.bouncycastle.util.encoders;version="[1.61.0,2.0.0)", - org.bouncycastle.util.io;version="[1.61.0,2.0.0)", org.slf4j;version="[1.7.0,2.0.0)", org.xml.sax, org.xml.sax.helpers diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java index ed9c38a4d..4059b16b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java @@ -17,11 +17,11 @@ import java.io.UnsupportedEncodingException; import java.util.concurrent.Callable; -import org.bouncycastle.util.io.TeeOutputStream; import org.eclipse.jgit.api.errors.AbortedByHookException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.ProcessResult; +import org.eclipse.jgit.util.io.TeeOutputStream; /** * Git can fire off custom scripts when certain important actions occur. These diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java new file mode 100644 index 000000000..e6fdd709b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/io/TeeOutputStream.java @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2020, Michael Dardis. and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.jgit.util.io; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An output stream that writes all data to two streams. + * + * @since 5.7 + */ +public class TeeOutputStream extends OutputStream { + + private final OutputStream stream1; + private final OutputStream stream2; + + /** + * Initialize a tee output stream. + * + * @param stream1 first output stream + * @param stream2 second output stream + */ + public TeeOutputStream(OutputStream stream1, OutputStream stream2) { + this.stream1 = stream1; + this.stream2 = stream2; + } + + /** {@inheritDoc} */ + @Override + public void write(byte[] buf) throws IOException { + this.stream1.write(buf); + this.stream2.write(buf); + } + + /** {@inheritDoc} */ + @Override + public void write(byte[] buf, int off, int len) throws IOException { + this.stream1.write(buf, off, len); + this.stream2.write(buf, off, len); + } + + /** {@inheritDoc} */ + @Override + public void write(int b) throws IOException { + this.stream1.write(b); + this.stream2.write(b); + } + + /** {@inheritDoc} */ + @Override + public void flush() throws IOException { + this.stream1.flush(); + this.stream2.flush(); + } + + /** {@inheritDoc} */ + @Override + public void close() throws IOException { + try { + this.stream1.close(); + } finally { + this.stream2.close(); + } + } +}