WorkingTreeIterator: directly filter input stream

This way we can avoid to access the byte buffers backing array.
Implement a ByteBufferInputStream to wrap a byte buffer which we can use
to expose the filter result as an input stream.

Change-Id: I461c82090de2562ea9b649b3f953aad4571e3d25
This commit is contained in:
Matthias Sohn 2023-09-24 01:41:13 +02:00
parent 84ced89dc3
commit a2bce029aa
5 changed files with 296 additions and 18 deletions

View File

@ -0,0 +1,152 @@
/*
* Copyright (C) 2023, SAP SE 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.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import org.eclipse.jgit.internal.JGitText;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class ByteBufferInputStreamTest {
private static final byte data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05,
0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
private ByteBuffer buf;
private ByteBufferInputStream is;
@Before
public void setup() {
buf = ByteBuffer.wrap(data);
is = new ByteBufferInputStream(buf);
}
@After
public void tearDown() {
is.close();
}
@Test
public void testRead() throws IOException {
assertEquals(0x00, is.read());
assertEquals(0x01, is.read());
assertEquals(0x02, is.read());
assertEquals(0x03, is.read());
assertEquals(0x04, is.read());
assertEquals(0x05, is.read());
assertEquals(0x06, is.read());
assertEquals(0x07, is.read());
assertEquals(0x08, is.read());
assertEquals(0x09, is.read());
assertEquals(0x0A, is.read());
assertEquals(0x0B, is.read());
assertEquals(0x0C, is.read());
assertEquals(0x0D, is.read());
assertEquals(0x0E, is.read());
assertEquals(0x0F, is.read());
assertEquals(-1, is.read());
}
@Test
public void testReadMultiple() throws IOException {
byte[] x = new byte[5];
int n = is.read(x);
assertEquals(5, n);
assertArrayEquals(new byte[] { 0x00, 0x01, 0x02, 0x03, 0x04 }, x);
}
@Test
public void testReadMultipleOffset() throws IOException {
byte[] x = new byte[7];
int n = is.read(x, 4, 3);
assertEquals(3, n);
assertArrayEquals(
new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02 },
x);
}
@Test
public void testReadAll() throws IOException {
byte[] x = is.readAllBytes();
assertEquals(16, x.length);
assertArrayEquals(data, x);
}
@Test
public void testMarkReset() throws IOException {
byte[] x = new byte[5];
int n = is.read(x);
assertEquals(11, is.available());
assertTrue(is.markSupported());
is.mark(is.available());
is.reset();
byte[] y = new byte[5];
int m = is.read(y);
assertEquals(n, m);
assertArrayEquals(new byte[] { 0x05, 0x06, 0x07, 0x08, 0x09 }, y);
}
@Test
public void testClosed() {
is.close();
Exception e = assertThrows(IOException.class, () -> is.read());
assertEquals(JGitText.get().inputStreamClosed, e.getMessage());
}
@Test
public void testReadNBytes() throws IOException {
byte[] x = is.readNBytes(4);
assertArrayEquals(new byte[] { 0x00, 0x01, 0x02, 0x03 }, x);
}
@Test
public void testReadNBytesOffset() throws IOException {
byte[] x = new byte[10];
Arrays.fill(x, (byte) 0x0F);
is.readNBytes(x, 3, 4);
assertArrayEquals(new byte[] { 0x0F, 0x0F, 0x0F, 0x00, 0x01, 0x02, 0x03,
0x0F, 0x0F, 0x0F }, x);
}
@Test
public void testRead0() throws IOException {
byte[] x = new byte[7];
int n = is.read(x, 4, 0);
assertEquals(0, n);
is.readAllBytes();
n = is.read(x, 4, 3);
assertEquals(-1, n);
}
@Test
public void testSkip() throws IOException {
assertEquals(15, is.skip(15));
assertEquals(0x0F, is.read());
assertEquals(-1, is.read());
}
@Test
public void testSkip0() throws IOException {
assertEquals(0, is.skip(0));
assertEquals(0x00, is.read());
}
}

View File

@ -389,6 +389,7 @@ initFailedGitDirIsNoDirectory=Cannot set git-dir to ''{0}'' which is not a direc
initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {0} and separate git-dir {1} specified both folders should not point to the same location
inMemoryBufferLimitExceeded=In-memory buffer limit exceeded
inputDidntMatchLength=Input did not match supplied length. {0} bytes are missing.
inputStreamClosed=InputStream was closed
inputStreamMustSupportMark=InputStream must support mark()
integerValueNotInRange=Integer value {0}.{1} = {2} not in range {3}..{4}
integerValueNotInRangeSubSection=Integer value {0}.{1}.{2} = {3} not in range {4}..{5}

View File

@ -419,6 +419,7 @@ public static JGitText get() {
/***/ public String initFailedNonBareRepoSameDirs;
/***/ public String inMemoryBufferLimitExceeded;
/***/ public String inputDidntMatchLength;
/***/ public String inputStreamClosed;
/***/ public String inputStreamMustSupportMark;
/***/ public String integerValueNotInRange;
/***/ public String integerValueNotInRangeSubSection;

View File

@ -15,7 +15,6 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@ -72,6 +71,7 @@
import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.TemporaryBuffer;
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
import org.eclipse.jgit.util.io.ByteBufferInputStream;
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
import org.eclipse.jgit.util.sha1.SHA1;
@ -407,9 +407,9 @@ private long possiblyFilteredLength(Entry e, long len) throws IOException {
if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
InputStream is = e.openInputStream();
try {
ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
rawbuf = filterClean(rawbuf.array(), rawbuf.limit());
return rawbuf.limit();
ByteBuffer filteredData = IO.readWholeStream(filterClean(is),
(int) len);
return filteredData.remaining();
} finally {
safeClose(is);
}
@ -438,10 +438,9 @@ && getEolStreamType(
}
if (len <= MAXIMUM_FILE_SIZE_TO_READ_FULLY) {
ByteBuffer rawbuf = IO.readWholeStream(is, (int) len);
rawbuf = filterClean(rawbuf.array(), rawbuf.limit());
canonLen = rawbuf.limit();
return new ByteArrayInputStream(rawbuf.array(), 0, (int) canonLen);
ByteBuffer filteredData = IO.readWholeStream(filterClean(is), (int) len);
canonLen = filteredData.remaining();
return new ByteBufferInputStream(filteredData);
}
if (getCleanFilterCommand() == null && isBinary(e)) {
@ -477,16 +476,6 @@ private static boolean isBinary(Entry entry) throws IOException {
}
}
private ByteBuffer filterClean(byte[] src, int n)
throws IOException {
InputStream in = new ByteArrayInputStream(src);
try {
return IO.readWholeStream(filterClean(in), n);
} finally {
safeClose(in);
}
}
private InputStream filterClean(InputStream in)
throws IOException {
in = EolStreamTypeUtil.wrapInputStream(in,

View File

@ -0,0 +1,135 @@
/*
* Copyright (C) 2023, SAP SE 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.InputStream;
import java.nio.ByteBuffer;
import java.nio.InvalidMarkException;
import java.util.Objects;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.JGitText;
/**
* An {@link InputStream} backed by a {@link ByteBuffer}.
*/
public class ByteBufferInputStream extends InputStream {
private ByteBuffer buf;
/**
* Creates a {@link ByteBufferInputStream}
*
* @param buf
* the ByteBuffer backing the stream
*/
public ByteBufferInputStream(@NonNull ByteBuffer buf) {
this.buf = buf;
}
@Override
public int read() throws IOException {
nullCheck();
if (buf.hasRemaining()) {
return buf.get() & 0xFF;
}
return -1;
}
@Override
public int read(byte[] b) throws IOException {
nullCheck();
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
nullCheck();
Objects.checkFromIndexSize(off, len, b.length);
if (len == 0) {
return 0;
}
int length = Math.min(buf.remaining(), len);
if (length == 0) {
return -1;
}
buf.get(b, off, length);
return length;
}
@Override
public byte[] readAllBytes() throws IOException {
return readNBytes(buf.remaining());
}
@Override
public byte[] readNBytes(int len) throws IOException {
int l = Math.min(len, buf.remaining());
byte[] b = new byte[l];
read(b);
return b;
}
@Override
public int readNBytes(byte[] b, int off, int len) throws IOException {
return read(b, off, len);
}
@Override
public long skip(long n) throws IOException {
nullCheck();
if (n <= 0) {
return 0;
}
// ByteBuffer index has type int
int delta = n > Integer.MAX_VALUE ? buf.remaining()
: Math.min((int) n, buf.remaining());
buf.position(buf.position() + delta);
return delta;
}
@Override
public int available() throws IOException {
nullCheck();
return buf.remaining();
}
@Override
public void close() {
buf = null;
}
@Override
public synchronized void mark(int readlimit) {
buf.mark();
}
@Override
public synchronized void reset() throws IOException {
try {
buf.reset();
} catch (InvalidMarkException e) {
throw new IOException(e);
}
}
@Override
public boolean markSupported() {
return true;
}
private void nullCheck() throws IOException {
if (buf == null) {
throw new IOException(JGitText.get().inputStreamClosed);
}
}
}