Repository: commons-compress Updated Branches: refs/heads/master ffb618d50 -> e15c80ae3
COMPRESS-443 provide a custom InputStream for special skip problems Project: http://git-wip-us.apache.org/repos/asf/commons-compress/repo Commit: http://git-wip-us.apache.org/repos/asf/commons-compress/commit/e15c80ae Tree: http://git-wip-us.apache.org/repos/asf/commons-compress/tree/e15c80ae Diff: http://git-wip-us.apache.org/repos/asf/commons-compress/diff/e15c80ae Branch: refs/heads/master Commit: e15c80ae3fafe8903f220a6232c37a77b919c60e Parents: ffb618d Author: Stefan Bodewig <bode...@apache.org> Authored: Wed May 2 06:16:14 2018 +0200 Committer: Stefan Bodewig <bode...@apache.org> Committed: Wed May 2 06:16:14 2018 +0200 ---------------------------------------------------------------------- src/changes/changes.xml | 4 + .../utils/SkipShieldingInputStream.java | 51 +++++++++++ src/site/xdoc/limitations.xml | 15 ++++ .../utils/SkipShieldingInputStreamTest.java | 91 ++++++++++++++++++++ 4 files changed, 161 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/commons-compress/blob/e15c80ae/src/changes/changes.xml ---------------------------------------------------------------------- diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 11f0193..44a69ab 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -74,6 +74,10 @@ The <action> type attribute can be add,update,fix,remove. Added a unit test that is supposed to fail if we break the OSGi manifest entries again. </action> + <action issue="COMPRESS-443" type="add" date="2018-05-02"> + Add a new SkipShieldingInputStream class that can be used wit + streams that throw an IOException whne skip is invoked. + </action> </release> <release version="1.16.1" date="2018-02-10" description="Release 1.16.1"> http://git-wip-us.apache.org/repos/asf/commons-compress/blob/e15c80ae/src/main/java/org/apache/commons/compress/utils/SkipShieldingInputStream.java ---------------------------------------------------------------------- diff --git a/src/main/java/org/apache/commons/compress/utils/SkipShieldingInputStream.java b/src/main/java/org/apache/commons/compress/utils/SkipShieldingInputStream.java new file mode 100644 index 0000000..09b5ce2 --- /dev/null +++ b/src/main/java/org/apache/commons/compress/utils/SkipShieldingInputStream.java @@ -0,0 +1,51 @@ +/* + * 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.commons.compress.utils; + +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * A wrapper that overwrites {@link #skip} and delegates to {@link #read} instead. + * + * <p>Some implementations of {@link InputStream} implement {@link + * InputStream#skip} in a way that throws an expecption if the stream + * is not seekable - {@link System#in System.in} is known to behave + * that way. For such a stream it is impossible to invoke skip at all + * and you have to read from the stream (and discard the data read) + * instead. Skipping is potentially much faster than reading so we do + * want to invoke {@code skip} when possible. We provide this class so + * you can wrap your own {@link InputStream} in it if you encounter + * problems with {@code skip} throwing an excpetion.</p> + * + * @since 1.17 + */ +public class SkipShieldingInputStream extends FilterInputStream { + private static final int SKIP_BUFFER_SIZE = 8192; + // we can use a shared buffer as the content is discarded anyway + private static final byte[] SKIP_BUFFER = new byte[SKIP_BUFFER_SIZE]; + public SkipShieldingInputStream(InputStream in) { + super(in); + } + + @Override + public long skip(long n) throws IOException { + return n < 0 ? 0 : read(SKIP_BUFFER, 0, (int) Math.min(n, SKIP_BUFFER_SIZE)); + } +} http://git-wip-us.apache.org/repos/asf/commons-compress/blob/e15c80ae/src/site/xdoc/limitations.xml ---------------------------------------------------------------------- diff --git a/src/site/xdoc/limitations.xml b/src/site/xdoc/limitations.xml index 9fc45f6..c78adcc 100644 --- a/src/site/xdoc/limitations.xml +++ b/src/site/xdoc/limitations.xml @@ -25,6 +25,21 @@ Commons Compress™ grouped by the archiving/compression format they apply to.</p> + <section name="General"> + <ul> + <li>Several implementations of decompressors and unarchivers will + invoke <a + href="https://docs.oracle.com/javase/10/docs/api/java/io/InputStream.html#skip(long)"><code>skip</code></a> + on the underlying <code>InputStream</code> which may throw an + <code>IOException</code> in some stream implementations. One + known case where this happens is when using + <code>System.in</code> as input. If you encounter an + exception with a message like "Illegal seek" we recommend you + wrap your stream in a <code>SkipShieldingInputStream</code> + from our utils package before passing it to Compress.</li> + </ul> + </section> + <section name="7Z"> <ul> <li>the format requires the otherwise optional <a http://git-wip-us.apache.org/repos/asf/commons-compress/blob/e15c80ae/src/test/java/org/apache/commons/compress/utils/SkipShieldingInputStreamTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/org/apache/commons/compress/utils/SkipShieldingInputStreamTest.java b/src/test/java/org/apache/commons/compress/utils/SkipShieldingInputStreamTest.java new file mode 100644 index 0000000..5ae69cf --- /dev/null +++ b/src/test/java/org/apache/commons/compress/utils/SkipShieldingInputStreamTest.java @@ -0,0 +1,91 @@ +/* + * 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.commons.compress.utils; + +import java.io.IOException; +import java.io.InputStream; +import org.junit.Assert; +import org.junit.Test; + +public class SkipShieldingInputStreamTest { + + @Test + public void skipDelegatesToRead() throws IOException { + try (InputStream i = new SkipShieldingInputStream(new InputStream() { + @Override + public long skip(long n) { + Assert.fail("skip invoked"); + return -1; + } + @Override + public int read() { + return -1; + } + @Override + public int read(byte[] b, int off, int len) { + return len; + } + })) { + Assert.assertEquals(100, i.skip(100)); + } + } + + @Test + public void skipHasAnUpperBoundOnRead() throws IOException { + try (InputStream i = new SkipShieldingInputStream(new InputStream() { + @Override + public long skip(long n) { + Assert.fail("skip invoked"); + return -1; + } + @Override + public int read() { + return -1; + } + @Override + public int read(byte[] b, int off, int len) { + return len; + } + })) { + Assert.assertTrue(Integer.MAX_VALUE > i.skip(Long.MAX_VALUE)); + } + } + + @Test + public void skipSwallowsNegativeArguments() throws IOException { + try (InputStream i = new SkipShieldingInputStream(new InputStream() { + @Override + public long skip(long n) { + Assert.fail("skip invoked"); + return -1; + } + @Override + public int read() { + return -1; + } + @Override + public int read(byte[] b, int off, int len) { + Assert.fail("read invoked"); + return len; + } + })) { + Assert.assertEquals(0, i.skip(Long.MIN_VALUE)); + } + } + +}