You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by gg...@apache.org on 2024/11/18 14:01:07 UTC
(commons-compress) branch master updated: (doc) Add gzip's ExtraField public apis for accessing SubField(s). (#608)
This is an automated email from the ASF dual-hosted git repository.
ggregory pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-compress.git
The following commit(s) were added to refs/heads/master by this push:
new 55cb3b235 (doc) Add gzip's ExtraField public apis for accessing SubField(s). (#608)
55cb3b235 is described below
commit 55cb3b235c08c109b253d7af967a06c591670576
Author: ddeschenes-1 <54...@users.noreply.github.com>
AuthorDate: Mon Nov 18 09:01:00 2024 -0500
(doc) Add gzip's ExtraField public apis for accessing SubField(s). (#608)
Improve javadoc, particularly the important immutability part when using
the iterator.
Co-authored-by: Danny Deschenes <dd...@towerlogic.com>
---
.../compress/compressors/gzip/ExtraField.java | 76 +++++++++++++++++++---
.../gzip/GzipCompressorOutputStreamTest.java | 31 ++++++---
2 files changed, 88 insertions(+), 19 deletions(-)
diff --git a/src/main/java/org/apache/commons/compress/compressors/gzip/ExtraField.java b/src/main/java/org/apache/commons/compress/compressors/gzip/ExtraField.java
index 3bf5a8a72..2de74e9fb 100644
--- a/src/main/java/org/apache/commons/compress/compressors/gzip/ExtraField.java
+++ b/src/main/java/org/apache/commons/compress/compressors/gzip/ExtraField.java
@@ -21,6 +21,7 @@ package org.apache.commons.compress.compressors.gzip;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
@@ -28,8 +29,17 @@ import java.util.Objects;
import org.apache.commons.compress.compressors.gzip.ExtraField.SubField;
/**
- * If the {@code FLG.FEXTRA} bit is set, an "extra field" is present in the header, with total length XLEN bytes. It consists of a series of subfields, each of
- * the form:
+ * If the {@code FLG.FEXTRA} bit is set, an "extra field" is present in the
+ * header, with total length XLEN bytes.
+ *
+ * <pre>
+ * +---+---+=================================+
+ * | XLEN |...XLEN bytes of "extra field"...| (more-->)
+ * +---+---+=================================+
+ * </pre>
+ *
+ * This class represents the extra field payload (excluding the XLEN 2 bytes).
+ * The ExtraField payload consists of a series of subfields, each of the form:
*
* <pre>
* +---+---+---+---+==================================+
@@ -37,8 +47,9 @@ import org.apache.commons.compress.compressors.gzip.ExtraField.SubField;
* +---+---+---+---+==================================+
* </pre>
*
- * This class does not expose the internal subfields list to prevent adding subfields without total extra length validation. However a copy of the list is
- * available.
+ * This class does not expose the internal subfields list to prevent adding
+ * subfields without total extra length validation. The class is iterable, but
+ * this iterator is immutable.
*
* @see <a href="https://datatracker.ietf.org/doc/html/rfc1952">RFC 1952 GZIP File Format Specification</a>
* @since 1.28.0
@@ -46,8 +57,8 @@ import org.apache.commons.compress.compressors.gzip.ExtraField.SubField;
public class ExtraField implements Iterable<SubField> {
/**
- * If the {@code FLG.FEXTRA} bit is set, an "extra field" is present in the header, with total length XLEN bytes. It consists of a series of subfields, each
- * of the form:
+ * If the {@code FLG.FEXTRA} bit is set, an "extra field" is present in the header, with total length XLEN bytes.
+ * It consists of a series of subfields, each of the form:
*
* <pre>
* +---+---+---+---+==================================+
@@ -185,13 +196,14 @@ public class ExtraField implements Iterable<SubField> {
}
/**
- * Returns an iterator over the SubField elements in this extra field in proper sequence.
+ * Returns an immutable iterator over the SubField elements in this extra field
+ * in the order they were added.
*
- * @return an iterator over the SubField elements in this extra field in proper sequence.
+ * @return an immutable naturally ordered iterator over the SubField elements.
*/
@Override
public Iterator<SubField> iterator() {
- return subFields.iterator();
+ return Collections.unmodifiableList(subFields).iterator();
}
byte[] toByteArray() {
@@ -211,4 +223,50 @@ public class ExtraField implements Iterable<SubField> {
return ba;
}
+ /**
+ * Test is this extra field has no subfields.
+ *
+ * @return true if there are no subfields, false otherwise.
+ */
+ public boolean isEmpty() {
+ return subFields.isEmpty();
+ }
+
+ /**
+ * Removes all subfields from this instance.
+ */
+ public void clear() {
+ subFields.clear();
+ totalSize = 0;
+ }
+
+ /**
+ * Calculate the size in bytes of the encoded extra field. This does not include
+ * its own 16 bits size when embeded in the gzip header. For N sub fields, the
+ * total is all subfields payloads bytes + 4N.
+ *
+ * @return the bytes count of this extra payload when encoded.
+ */
+ public int getEncodedSize() {
+ return totalSize;
+ }
+
+ /**
+ * Return the count of subfields currently in in this extra field.
+ *
+ * @return the count of subfields contained in this instance.
+ */
+ public int getSize() {
+ return subFields.size();
+ }
+
+ /**
+ * Finds the first subfield that matched the id if found, null otherwise
+ *
+ * @return the 1st SubField that matched or null.
+ */
+ public SubField findFirstSubField(String subfieldId) {
+ return subFields.stream().filter(f -> f.getId().equals(subfieldId)).findFirst().orElse(null);
+ }
+
}
diff --git a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java
index 3f7b930fb..370148b98 100644
--- a/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java
+++ b/src/test/java/org/apache/commons/compress/compressors/gzip/GzipCompressorOutputStreamTest.java
@@ -22,6 +22,8 @@ package org.apache.commons.compress.compressors.gzip;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
@@ -32,8 +34,8 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.ArrayList;
import java.util.Arrays;
-import java.util.concurrent.atomic.AtomicInteger;
import org.apache.commons.compress.compressors.gzip.ExtraField.SubField;
import org.junit.jupiter.api.Test;
@@ -94,11 +96,12 @@ public class GzipCompressorOutputStreamTest {
/**
* Tests the gzip extra header containing subfields.
*
- * @throws IOException When the test fails.
+ * @throws IOException When the test has issues with the underlying file system or unexpected gzip operations.
*/
@ParameterizedTest
// @formatter:off
@CsvSource({
+ "0, 42, false",
"1, , true",
"1, 0, false",
"1, 65531, false",
@@ -108,7 +111,8 @@ public class GzipCompressorOutputStreamTest {
"2, 32763, false"
})
// @formatter:on
- public void testExtraSubfields(final int subFieldCount, final Integer payloadSize, final boolean shouldFail) throws IOException {
+ public void testExtraSubfields(final int subFieldCount, final Integer payloadSize, final boolean shouldFail)
+ throws IOException {
final Path tempSourceFile = Files.createTempFile("test_gzip_extra_", ".txt");
final Path targetFile = Files.createTempFile("test_gzip_extra_", ".txt.gz");
Files.write(tempSourceFile, "Hello World!".getBytes(StandardCharsets.ISO_8859_1));
@@ -128,10 +132,13 @@ public class GzipCompressorOutputStreamTest {
break;
}
}
- assertEquals(shouldFail, failed, "appending subfield " + (shouldFail ? "succes" : "failure") + " was not expected.");
+ assertEquals(shouldFail, failed, "add subfield " + (shouldFail ? "succes" : "failure") + " was not expected.");
if (shouldFail) {
return;
}
+ if (subFieldCount > 0) {
+ assertThrows(UnsupportedOperationException.class, () -> extra.iterator().remove());
+ }
parameters.setExtraField(extra);
try (OutputStream fos = Files.newOutputStream(targetFile);
GzipCompressorOutputStream gos = new GzipCompressorOutputStream(fos, parameters)) {
@@ -139,17 +146,21 @@ public class GzipCompressorOutputStreamTest {
}
try (GzipCompressorInputStream gis = new GzipCompressorInputStream(Files.newInputStream(targetFile))) {
final ExtraField extra2 = gis.getMetaData().getExtraField();
+ assertEquals(subFieldCount == 0, extra2.isEmpty());
+ assertEquals(subFieldCount, extra2.getSize());
+ assertEquals(4 * subFieldCount + subFieldCount * payloadSize, extra2.getEncodedSize());
+ ArrayList<SubField> listCopy = new ArrayList<>();
+ extra2.forEach(listCopy::add);
+ assertEquals(subFieldCount, listCopy.size());
for (int i = 0; i < subFieldCount; i++) {
final SubField sf = extra2.getSubFieldAt(i);
+ assertSame(sf, listCopy.get(i));
+ assertSame(sf, extra2.findFirstSubField("z" + i));
assertEquals("z" + i, sf.getId()); // id was saved/loaded correctly
assertArrayEquals(payloads[i], sf.getPayload(), "field " + i + " has wrong payload");
}
- final AtomicInteger i = new AtomicInteger();
- gis.getMetaData().getExtraField().forEach(sf -> {
- assertEquals("z" + i, sf.getId()); // id was saved/loaded correctly
- assertArrayEquals(payloads[i.intValue()], sf.getPayload(), "field " + i + " has wrong payload");
- i.incrementAndGet();
- });
+ extra2.clear();
+ assertTrue(extra2.isEmpty());
}
}