8225763: Inflater and Deflater should implement AutoCloseable

Reviewed-by: lancea, rriggs, alanb, smarks
This commit is contained in:
Jaikiran Pai 2025-01-15 01:04:44 +00:00
parent d6d45c6eae
commit 36b7abd617
7 changed files with 647 additions and 87 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -49,29 +49,30 @@ import static java.util.zip.ZipUtils.NIO_ACCESS;
* thrown.
* <p>
* This class deflates sequences of bytes into ZLIB compressed data format.
* The input byte sequence is provided in either byte array or byte buffer,
* The input byte sequence is provided in either a byte array or a {@link ByteBuffer},
* via one of the {@code setInput()} methods. The output byte sequence is
* written to the output byte array or byte buffer passed to the
* written to the output byte array or {@code ByteBuffer} passed to the
* {@code deflate()} methods.
* <p>
* The following code fragment demonstrates a trivial compression
* and decompression of a string using {@code Deflater} and
* {@code Inflater}.
* {@snippet id="compdecomp" lang="java" class="Snippets" region="DeflaterInflaterExample"}
* To release the resources used by a {@code Deflater}, an application must close it
* by invoking its {@link #end()} or {@link #close()} method.
*
* @apiNote
* To release resources used by this {@code Deflater}, the {@link #end()} method
* should be called explicitly. Subclasses are responsible for the cleanup of resources
* acquired by the subclass. Subclasses that override {@link #finalize()} in order
* to perform cleanup should be modified to use alternative cleanup mechanisms such
* as {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method.
* This class implements {@link AutoCloseable} to facilitate its usage with
* {@code try}-with-resources statement. The {@linkplain Deflater#close() close() method} simply
* calls {@code end()}.
*
* <p>
* The following code fragment demonstrates a trivial compression
* and decompression of a string using {@code Deflater} and {@code Inflater}.
* {@snippet id="compdecomp" lang="java" class="Snippets" region="DeflaterInflaterExample"}
*
* @see Inflater
* @author David Connelly
* @since 1.1
*/
public class Deflater {
public class Deflater implements AutoCloseable {
private final DeflaterZStreamRef zsRef;
private ByteBuffer input = ZipUtils.defaultBuf;
@ -269,6 +270,7 @@ public class Deflater {
* @param dictionary the dictionary data bytes
* @param off the start offset of the data
* @param len the length of the data
* @throws IllegalStateException if the Deflater is closed
* @see Inflater#inflate
* @see Inflater#getAdler()
*/
@ -287,6 +289,7 @@ public class Deflater {
* in order to get the Adler-32 value of the dictionary required for
* decompression.
* @param dictionary the dictionary data bytes
* @throws IllegalStateException if the Deflater is closed
* @see Inflater#inflate
* @see Inflater#getAdler()
*/
@ -305,6 +308,7 @@ public class Deflater {
* return, its position will equal its limit.
*
* @param dictionary the dictionary data bytes
* @throws IllegalStateException if the Deflater is closed
* @see Inflater#inflate
* @see Inflater#getAdler()
*
@ -437,6 +441,7 @@ public class Deflater {
* @param len the maximum number of bytes of compressed data
* @return the actual number of bytes of compressed data written to the
* output buffer
* @throws IllegalStateException if the Deflater is closed
*/
public int deflate(byte[] output, int off, int len) {
return deflate(output, off, len, NO_FLUSH);
@ -456,6 +461,7 @@ public class Deflater {
* @param output the buffer for the compressed data
* @return the actual number of bytes of compressed data written to the
* output buffer
* @throws IllegalStateException if the Deflater is closed
*/
public int deflate(byte[] output) {
return deflate(output, 0, output.length, NO_FLUSH);
@ -476,6 +482,7 @@ public class Deflater {
* @return the actual number of bytes of compressed data written to the
* output buffer
* @throws ReadOnlyBufferException if the given output buffer is read-only
* @throws IllegalStateException if the Deflater is closed
* @since 11
*/
public int deflate(ByteBuffer output) {
@ -531,6 +538,7 @@ public class Deflater {
* the output buffer
*
* @throws IllegalArgumentException if the flush mode is invalid
* @throws IllegalStateException if the Deflater is closed
* @since 1.7
*/
public int deflate(byte[] output, int off, int len, int flush) {
@ -657,6 +665,7 @@ public class Deflater {
*
* @throws IllegalArgumentException if the flush mode is invalid
* @throws ReadOnlyBufferException if the given output buffer is read-only
* @throws IllegalStateException if the Deflater is closed
* @since 11
*/
public int deflate(ByteBuffer output, int flush) {
@ -783,6 +792,7 @@ public class Deflater {
/**
* {@return the ADLER-32 value of the uncompressed data}
* @throws IllegalStateException if the Deflater is closed
*/
public int getAdler() {
synchronized (zsRef) {
@ -802,6 +812,7 @@ public class Deflater {
* @deprecated Use {@link #getBytesRead()} instead
*
* @return the total number of uncompressed bytes input so far
* @throws IllegalStateException if the Deflater is closed
*/
@Deprecated(since = "23")
public int getTotalIn() {
@ -812,6 +823,7 @@ public class Deflater {
* Returns the total number of uncompressed bytes input so far.
*
* @return the total (non-negative) number of uncompressed bytes input so far
* @throws IllegalStateException if the Deflater is closed
* @since 1.5
*/
public long getBytesRead() {
@ -832,6 +844,7 @@ public class Deflater {
* @deprecated Use {@link #getBytesWritten()} instead
*
* @return the total number of compressed bytes output so far
* @throws IllegalStateException if the Deflater is closed
*/
@Deprecated(since = "23")
public int getTotalOut() {
@ -842,6 +855,7 @@ public class Deflater {
* Returns the total number of compressed bytes output so far.
*
* @return the total (non-negative) number of compressed bytes output so far
* @throws IllegalStateException if the Deflater is closed
* @since 1.5
*/
public long getBytesWritten() {
@ -854,6 +868,7 @@ public class Deflater {
/**
* Resets deflater so that a new set of input data can be processed.
* Keeps current compression level and strategy settings.
* @throws IllegalStateException if the Deflater is closed
*/
public void reset() {
synchronized (zsRef) {
@ -868,23 +883,45 @@ public class Deflater {
}
/**
* Closes the compressor and discards any unprocessed input.
* Closes and releases the resources held by this {@code Deflater}
* and discards any unprocessed input.
* <p>
* If the {@code Deflater} is already closed then invoking this method has no effect.
*
* This method should be called when the compressor is no longer
* being used. Once this method is called, the behavior of the
* Deflater object is undefined.
* @implSpec Subclasses should override this method to clean up the resources
* acquired by the subclass.
*
* @see #close()
*/
public void end() {
synchronized (zsRef) {
// check if already closed
if (zsRef.address() == 0) {
return;
}
zsRef.clean();
input = ZipUtils.defaultBuf;
inputArray = null;
}
}
/**
* Closes and releases the resources held by this {@code Deflater}
* and discards any unprocessed input.
*
* @implSpec This method calls the {@link #end()} method.
* @since 25
*/
@Override
public void close() {
end();
}
private void ensureOpen() {
assert Thread.holdsLock(zsRef);
if (zsRef.address() == 0)
throw new NullPointerException("Deflater has been closed");
if (zsRef.address() == 0) {
throw new IllegalStateException("Deflater has been closed");
}
}
/**
@ -928,10 +965,11 @@ public class Deflater {
*/
static class DeflaterZStreamRef implements Runnable {
private long address;
private long address; // will be a non-zero value when the native resource is in use
private final Cleanable cleanable;
private DeflaterZStreamRef(Deflater owner, long addr) {
assert addr != 0 : "native address is 0";
this.cleanable = (owner != null) ? CleanerFactory.cleaner().register(owner, this) : null;
this.address = addr;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -49,21 +49,22 @@ import static java.util.zip.ZipUtils.NIO_ACCESS;
* thrown.
* <p>
* This class inflates sequences of ZLIB compressed bytes. The input byte
* sequence is provided in either byte array or byte buffer, via one of the
* sequence is provided in either a byte array or a {@link ByteBuffer}, via one of the
* {@code setInput()} methods. The output byte sequence is written to the
* output byte array or byte buffer passed to the {@code inflate()} methods.
* output byte array or {@code ByteBuffer} passed to the {@code inflate()} methods.
* <p>
* The following code fragment demonstrates a trivial compression
* and decompression of a string using {@code Deflater} and
* {@code Inflater}.
* {@snippet id="compdecomp" lang="java" class="Snippets" region="DeflaterInflaterExample"}
* To release the resources used by an {@code Inflater}, an application must close it
* by invoking its {@link #end()} or {@link #close()} method.
*
* @apiNote
* To release resources used by this {@code Inflater}, the {@link #end()} method
* should be called explicitly. Subclasses are responsible for the cleanup of resources
* acquired by the subclass. Subclasses that override {@link #finalize()} in order
* to perform cleanup should be modified to use alternative cleanup mechanisms such
* as {@link java.lang.ref.Cleaner} and remove the overriding {@code finalize} method.
* This class implements {@link AutoCloseable} to facilitate its usage with
* {@code try}-with-resources statement. The {@linkplain Inflater#close() close() method} simply
* calls {@code end()}.
*
* <p>
* The following code fragment demonstrates a trivial compression
* and decompression of a string using {@code Deflater} and {@code Inflater}.
* {@snippet id="compdecomp" lang="java" class="Snippets" region="DeflaterInflaterExample"}
*
* @see Deflater
* @author David Connelly
@ -71,7 +72,7 @@ import static java.util.zip.ZipUtils.NIO_ACCESS;
*
*/
public class Inflater {
public class Inflater implements AutoCloseable {
private final InflaterZStreamRef zsRef;
private ByteBuffer input = ZipUtils.defaultBuf;
@ -192,6 +193,7 @@ public class Inflater {
* @param dictionary the dictionary data bytes
* @param off the start offset of the data
* @param len the length of the data
* @throws IllegalStateException if the Inflater is closed
* @see Inflater#needsDictionary
* @see Inflater#getAdler
*/
@ -210,6 +212,7 @@ public class Inflater {
* indicating that a preset dictionary is required. The method getAdler()
* can be used to get the Adler-32 value of the dictionary needed.
* @param dictionary the dictionary data bytes
* @throws IllegalStateException if the Inflater is closed
* @see Inflater#needsDictionary
* @see Inflater#getAdler
*/
@ -227,6 +230,7 @@ public class Inflater {
* return, its position will equal its limit.
*
* @param dictionary the dictionary data bytes
* @throws IllegalStateException if the Inflater is closed
* @see Inflater#needsDictionary
* @see Inflater#getAdler
* @since 11
@ -331,6 +335,7 @@ public class Inflater {
* @param len the maximum number of uncompressed bytes
* @return the actual number of uncompressed bytes
* @throws DataFormatException if the compressed data format is invalid
* @throws IllegalStateException if the Inflater is closed
* @see Inflater#needsInput
* @see Inflater#needsDictionary
*/
@ -437,6 +442,7 @@ public class Inflater {
* @param output the buffer for the uncompressed data
* @return the actual number of uncompressed bytes
* @throws DataFormatException if the compressed data format is invalid
* @throws IllegalStateException if the Inflater is closed
* @see Inflater#needsInput
* @see Inflater#needsDictionary
*/
@ -474,6 +480,7 @@ public class Inflater {
* @return the actual number of uncompressed bytes
* @throws DataFormatException if the compressed data format is invalid
* @throws ReadOnlyBufferException if the given output buffer is read-only
* @throws IllegalStateException if the Inflater is closed
* @see Inflater#needsInput
* @see Inflater#needsDictionary
* @since 11
@ -600,6 +607,7 @@ public class Inflater {
/**
* {@return the ADLER-32 value of the uncompressed data}
* @throws IllegalStateException if the Inflater is closed
*/
public int getAdler() {
synchronized (zsRef) {
@ -619,6 +627,7 @@ public class Inflater {
* @deprecated Use {@link #getBytesRead()} instead
*
* @return the total number of compressed bytes input so far
* @throws IllegalStateException if the Inflater is closed
*/
@Deprecated(since = "23")
public int getTotalIn() {
@ -629,6 +638,7 @@ public class Inflater {
* Returns the total number of compressed bytes input so far.
*
* @return the total (non-negative) number of compressed bytes input so far
* @throws IllegalStateException if the Inflater is closed
* @since 1.5
*/
public long getBytesRead() {
@ -649,6 +659,7 @@ public class Inflater {
* @deprecated Use {@link #getBytesWritten()} instead
*
* @return the total number of uncompressed bytes output so far
* @throws IllegalStateException if the Inflater is closed
*/
@Deprecated(since = "23")
public int getTotalOut() {
@ -659,6 +670,7 @@ public class Inflater {
* Returns the total number of uncompressed bytes output so far.
*
* @return the total (non-negative) number of uncompressed bytes output so far
* @throws IllegalStateException if the Inflater is closed
* @since 1.5
*/
public long getBytesWritten() {
@ -670,6 +682,7 @@ public class Inflater {
/**
* Resets inflater so that a new set of input data can be processed.
* @throws IllegalStateException if the Inflater is closed
*/
public void reset() {
synchronized (zsRef) {
@ -684,14 +697,22 @@ public class Inflater {
}
/**
* Closes the decompressor and discards any unprocessed input.
* Closes and releases the resources held by this {@code Inflater}
* and discards any unprocessed input.
* <p>
* If the {@code Inflater} is already closed then invoking this method has no effect.
*
* This method should be called when the decompressor is no longer
* being used. Once this method is called, the behavior of the
* Inflater object is undefined.
* @implSpec Subclasses should override this method to clean up the resources
* acquired by the subclass.
*
* @see #close()
*/
public void end() {
synchronized (zsRef) {
// check if already closed
if (zsRef.address() == 0) {
return;
}
zsRef.clean();
input = ZipUtils.defaultBuf;
inputArray = null;
@ -699,10 +720,23 @@ public class Inflater {
}
/**
* Closes and releases the resources held by this {@code Inflater}
* and discards any unprocessed input.
*
* @implSpec This method calls the {@link #end()} method.
* @since 25
*/
@Override
public void close() {
end();
}
private void ensureOpen () {
assert Thread.holdsLock(zsRef);
if (zsRef.address() == 0)
throw new NullPointerException("Inflater has been closed");
if (zsRef.address() == 0) {
throw new IllegalStateException("Inflater has been closed");
}
}
boolean hasPendingOutput() {
@ -737,10 +771,11 @@ public class Inflater {
*/
static class InflaterZStreamRef implements Runnable {
private long address;
private long address; // will be a non-zero value when the native resource is in use
private final Cleanable cleanable;
private InflaterZStreamRef(Inflater owner, long addr) {
assert addr != 0 : "native address is 0";
this.cleanable = (owner != null) ? CleanerFactory.cleaner().register(owner, this) : null;
this.address = addr;
}

View file

@ -38,8 +38,7 @@ class Snippets {
// Compress the bytes
ByteArrayOutputStream compressedBaos = new ByteArrayOutputStream();
Deflater compressor = new Deflater();
try {
try (Deflater compressor = new Deflater()) {
compressor.setInput(input);
// Let the compressor know that the complete input
// has been made available
@ -55,15 +54,11 @@ class Snippets {
// buffer into the final byte array
compressedBaos.write(tmpBuffer, 0, numCompressed);
}
} finally {
// Release the resources held by the compressor
compressor.end();
}
// Decompress the bytes
Inflater decompressor = new Inflater();
ByteArrayOutputStream decompressedBaos = new ByteArrayOutputStream();
try {
try (Inflater decompressor = new Inflater()) {
byte[] compressed = compressedBaos.toByteArray();
decompressor.setInput(compressed, 0, compressed.length);
while (!decompressor.finished()) {
@ -83,9 +78,6 @@ class Snippets {
// buffer into the final byte array
decompressedBaos.write(tmpBuffer, 0, numDecompressed);
}
} finally {
// Release the resources held by the decompressor
decompressor.end();
}
// Decode the bytes into a String
String outputString = decompressedBaos.toString(StandardCharsets.UTF_8);