diff --git a/pom.xml b/pom.xml
index be742e2..f9c57c6 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
moe.yo3explorer
dotnet4j
- 1.2.4
+ 1.2.5
jar
@@ -15,7 +15,7 @@
org.apache.maven.plugins
maven-compiler-plugin
- 3.11.0
+ 3.12.1
17
@@ -23,7 +23,7 @@
org.apache.maven.plugins
maven-surefire-plugin
- 3.2.2
+ 3.2.5
-Djava.util.logging.config.file=${project.build.testOutputDirectory}/logging.properties
@@ -34,7 +34,7 @@
org.apache.maven.plugins
maven-source-plugin
- 3.2.1
+ 3.3.0
attach-sources
@@ -47,7 +47,7 @@
org.apache.maven.plugins
maven-javadoc-plugin
- 3.5.0
+ 3.7.0
attach-javadocs
@@ -63,7 +63,7 @@
-Xdoclint:-missing
en_US
- true
+ false
false
false
false
@@ -80,7 +80,7 @@
org.junit
junit-bom
- 5.10.2
+ 5.10.3
pom
import
@@ -104,7 +104,7 @@
com.github.umjammer
vavi-commons
- 1.1.11
+ 1.1.14
diff --git a/src/main/java/dotnet4j/io/BinaryReader.java b/src/main/java/dotnet4j/io/BinaryReader.java
index ba4d634..d421bf7 100644
--- a/src/main/java/dotnet4j/io/BinaryReader.java
+++ b/src/main/java/dotnet4j/io/BinaryReader.java
@@ -425,14 +425,14 @@ private int internalReadOneChar() {
charsRead = this.singleChar.length;
assert charsRead < 2 : "InternalReadOneChar - assuming we only got 0 or 1 char, not 2!";
-// System.err.println("That became: " + charsRead + " characters.");
+//logger.log(Level.TRACE, "That became: " + charsRead + " characters.");
}
if (charsRead == 0)
return -1;
return this.singleChar[0];
}
- //[SecuritySafeCritical]
+ // [SecuritySafeCritical]
public char[] readChars(int count) throws java.io.IOException {
if (count < 0) {
throw new IndexOutOfBoundsException("count: " + count);
diff --git a/src/main/java/dotnet4j/io/FileStream.java b/src/main/java/dotnet4j/io/FileStream.java
index 971dc4f..a859e82 100644
--- a/src/main/java/dotnet4j/io/FileStream.java
+++ b/src/main/java/dotnet4j/io/FileStream.java
@@ -3,16 +3,21 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
-import vavi.util.Debug;
+import static java.lang.System.getLogger;
/**
* Created by FT on 27.11.14.
*/
public class FileStream extends Stream {
+
+ private static final Logger logger = getLogger(FileStream.class.getName());
+
private FileChannel channel;
private final FileMode myMode;
private final FileAccess myAccess;
@@ -156,12 +161,12 @@ public int read(byte[] buffer, int offset, int length) {
int m;
try {
m = channel.read(tmp);
-//Debug.println(m + ", " + offset + ", " + length + " / " + channel.size() + ", " + channel.size() + ", " + channel.position());
+//logger.log(Level.TRACE, m + ", " + offset + ", " + length + " / " + channel.size() + ", " + channel.size() + ", " + channel.position());
if (m == -1) {
return 0;
}
} catch (IOException e) {
- Debug.printStackTrace(e);
+ logger.log(Level.TRACE, e.getMessage(), e);
throw new dotnet4j.io.IOException(e);
}
return m;
diff --git a/src/main/java/dotnet4j/io/MemoryStream.java b/src/main/java/dotnet4j/io/MemoryStream.java
index 410d968..00c7329 100644
--- a/src/main/java/dotnet4j/io/MemoryStream.java
+++ b/src/main/java/dotnet4j/io/MemoryStream.java
@@ -273,8 +273,6 @@ public int read(byte[] buffer, int offset, int count) {
return n;
}
-//public static boolean debug;
-
@Override
public void write(byte[] buffer, int offset, int count) {
if (buffer == null)
@@ -289,7 +287,7 @@ public void write(byte[] buffer, int offset, int count) {
throw new dotnet4j.io.IOException("object disposed");
if (!canWrite())
throw new dotnet4j.io.IOException("not writable");
-//if (debug) { Debug.println(offset + ", " + count + "\n" + StringUtil.getDump(buffer, offset, Math.min(count, 64))); new Exception().printStackTrace(); }
+//logger.log(Level.TRACE, offset + ", " + count + "\n" + StringUtil.getDump(buffer, offset, Math.min(count, 64))); new Exception().printStackTrace(); }
int i = position + count;
// Check for overflow
diff --git a/src/main/java/dotnet4j/io/compat/JavaIOStream.java b/src/main/java/dotnet4j/io/compat/JavaIOStream.java
index c2ae189..a3016b0 100644
--- a/src/main/java/dotnet4j/io/compat/JavaIOStream.java
+++ b/src/main/java/dotnet4j/io/compat/JavaIOStream.java
@@ -15,7 +15,7 @@
/**
- * JavaIOStream.
+ * InputStream + OutputStream = Stream.
*
* @author Naohide Sano (umjammer)
* @version 0.00 2019/10/09 umjammer initial version
@@ -118,6 +118,9 @@ public void setLength(long value) {
throw new UnsupportedOperationException();
}
+ /**
+ * @return 0 when EOF (it's C# spec) ⚠️⚠️⚠️ CAUTION not same as the java specs. ⚠️⚠️⚠️
+ */
@Override
public int read(byte[] buffer, int offset, int length) {
if (is == null) {
@@ -125,17 +128,17 @@ public int read(byte[] buffer, int offset, int length) {
}
try {
-//Debug.println(buffer.length + ", " + offset + ", " + length + ", " + is.available());
+//logger.log(Level.TRACE, buffer.length + ", " + offset + ", " + length + ", " + is.available());
int r = is.read(buffer, offset, length);
-//Debug.println(StringUtil.getDump(buffer, 16));
+//logger.log(Level.TRACE, StringUtil.getDump(buffer, 16));
if (r > 0) {
position += r;
}
if (r == -1) {
-//Debug.println("EOF");
+//logger.log(Level.TRACE, "EOF");
return 0; // C# Spec.
}
-//Debug.println("position: " + position);
+//logger.log(Level.TRACE, "position: " + position);
return r;
} catch (IOException e) {
throw new dotnet4j.io.IOException(e);
@@ -166,7 +169,7 @@ public void write(byte[] buffer, int offset, int count) {
}
try {
-//Debug.println("w: " + count + ", " + os);
+//logger.log(Level.TRACE, "w: " + count + ", " + os);
os.write(buffer, offset, count);
position += count;
} catch (IOException e) {
diff --git a/src/main/java/dotnet4j/io/compat/StreamInputStream.java b/src/main/java/dotnet4j/io/compat/StreamInputStream.java
index 6859403..ce78586 100644
--- a/src/main/java/dotnet4j/io/compat/StreamInputStream.java
+++ b/src/main/java/dotnet4j/io/compat/StreamInputStream.java
@@ -14,7 +14,7 @@
/**
- * StreamInputStream.
+ * Treats Stream as InputStream.
*
* @author Naohide Sano (umjammer)
* @version 0.00 2019/09/30 umjammer initial version
diff --git a/src/main/java/dotnet4j/io/compat/StreamOutputStream.java b/src/main/java/dotnet4j/io/compat/StreamOutputStream.java
index a3c0039..0630d32 100644
--- a/src/main/java/dotnet4j/io/compat/StreamOutputStream.java
+++ b/src/main/java/dotnet4j/io/compat/StreamOutputStream.java
@@ -14,7 +14,7 @@
/**
- * StreamOutputStream.
+ * Treats Stream as OutputStream.
*
* @author Naohide Sano (umjammer)
* @version 0.00 2019/09/30 umjammer initial version
@@ -34,7 +34,7 @@ public void write(int b) {
@Override
public void write(byte[] buffer, int offset, int count) {
-//Debug.println("w: " + count + ", " + stream);
+//logger.log(Level.TRACE, "w: " + count + ", " + stream);
stream.write(buffer, offset, count);
}
diff --git a/src/main/java/dotnet4j/io/compression/GZipStream.java b/src/main/java/dotnet4j/io/compression/GZipStream.java
index 44fe4bc..15badd4 100644
--- a/src/main/java/dotnet4j/io/compression/GZipStream.java
+++ b/src/main/java/dotnet4j/io/compression/GZipStream.java
@@ -20,6 +20,7 @@
import vavi.io.InputEngineOutputStream;
import vavi.io.OutputEngine;
import vavi.io.OutputEngineInputStream;
+import vavi.util.Debug;
/**
@@ -30,10 +31,14 @@
*/
public class GZipStream extends JavaIOStream {
+ /**
+ * @param stream assume as input stream
+ * @param compressionMode {@link CompressionMode}
+ */
static InputStream toInputStream(Stream stream, CompressionMode compressionMode) {
try {
InputStream is = new StreamInputStream(stream);
- return compressionMode == CompressionMode.Decompress ? new GZIPInputStream(is)
+ return compressionMode == CompressionMode.Decompress ? new GZIPInputStream(is) // TODO should be null stream? this maybe no mean
: new OutputEngineInputStream(new OutputEngine() {
OutputStream out;
@@ -45,7 +50,8 @@ static InputStream toInputStream(Stream stream, CompressionMode compressionMode)
@Override public void execute() throws IOException {
int r = is.read(buf);
- out.write(buf, 0, r);
+ if (r < 0) out.close();
+ else out.write(buf, 0, r);
}
@Override public void finish() {
@@ -56,24 +62,30 @@ static InputStream toInputStream(Stream stream, CompressionMode compressionMode)
}
}
+ /**
+ * @param stream assume as output stream
+ * @param compressionMode {@link CompressionMode}
+ */
static OutputStream toOutputStream(Stream stream, CompressionMode compressionMode) {
try {
OutputStream os = new StreamOutputStream(stream);
return compressionMode == CompressionMode.Compress ? new GZIPOutputStream(os)
- : new InputEngineOutputStream(new InputEngine() {
+ : new InputEngineOutputStream(new InputEngine() { // TODO should be null stream? this maybe no mean
InputStream in;
- @Override public void initialize(InputStream in) {
+ @Override public void initialize(InputStream in) throws IOException {
+ if (this.in == null && in.available() > 0 /* means stream is for input */) {
+Debug.println(in + ", " + in.available());
+ this.in = new GZIPInputStream(in);
+ }
}
final byte[] buf = new byte[8192];
@Override public void execute() throws IOException {
- if (in == null) {
- this.in = new GZIPInputStream(in);
- }
int r = in.read(buf);
- os.write(buf, 0, r);
+ if (r < 0) in.close();
+ else os.write(buf, 0, r);
}
@Override public void finish() {
@@ -84,9 +96,7 @@ static OutputStream toOutputStream(Stream stream, CompressionMode compressionMod
}
}
- /**
- *
- */
+ /** @throws dotnet4j.io.IOException when an error occurs */
public GZipStream(Stream stream, CompressionMode compressionMode) {
super(toInputStream(stream, compressionMode), toOutputStream(stream, compressionMode));
}
diff --git a/src/main/java/dotnet4j/security/accessControl/AccessControlSections.java b/src/main/java/dotnet4j/security/accessControl/AccessControlSections.java
index 0b7f631..7b07598 100644
--- a/src/main/java/dotnet4j/security/accessControl/AccessControlSections.java
+++ b/src/main/java/dotnet4j/security/accessControl/AccessControlSections.java
@@ -4,15 +4,15 @@
public enum AccessControlSections {
- /** 随意アクセス制御リスト (DACL: Discretionary Access Control List)。 */
+ /** DACL: Discretionary Access Control List */
Access(0x2),
- /** システム アクセス制御リスト (SACL: System Access Control List)。 */
+ /** SACL: System Access Control List */
Audit(0x1),
- /** プライマリ グループ。 */
+ /** primary group */
Group(0x8),
- /** セクションを指定しません。 */
+ /** no section specified */
None(0x0),
- /** 所有者。 */
+ /** owner */
Owner(0x4);
final int value;
@@ -21,6 +21,6 @@ public enum AccessControlSections {
this.value = value;
}
- /** セキュリティ記述子全体。 */
+ /** entire security descriptor */
public static final EnumSet All = EnumSet.of(Audit, Access, Owner, Group);
}
diff --git a/src/main/java/dotnet4j/security/accessControl/RegistrySecurity.java b/src/main/java/dotnet4j/security/accessControl/RegistrySecurity.java
index 2355aaf..e10a30c 100644
--- a/src/main/java/dotnet4j/security/accessControl/RegistrySecurity.java
+++ b/src/main/java/dotnet4j/security/accessControl/RegistrySecurity.java
@@ -72,7 +72,7 @@ public String getSecurityDescriptorSddlForm(EnumSet secti
/** TODO impl */
public void setSecurityDescriptorSddlForm(String form, EnumSet sections) {
-//System.err.println(form);
+//logger.log(Level.TRACE, form);
binaryForm = form.getBytes(StandardCharsets.US_ASCII);
}
}
diff --git a/src/main/java/dotnet4j/win32/RegistryValueOptions.java b/src/main/java/dotnet4j/win32/RegistryValueOptions.java
index 462848f..c1cb31e 100644
--- a/src/main/java/dotnet4j/win32/RegistryValueOptions.java
+++ b/src/main/java/dotnet4j/win32/RegistryValueOptions.java
@@ -1,10 +1,10 @@
package dotnet4j.win32;
public enum RegistryValueOptions {
- /** オプションの動作は指定されていません。 */
+ /** optional behavior is unspecified */
None,
/**
- * 型の値が、埋め込まれた環境変数を展開せずに取得されます。
+ * the value of the type is retrieved without expanding embedded environment variables.
*
* @see "F:Microsoft.Win32.RegistryValueKind.ExpandString"
*/
diff --git a/src/test/java/dotnet4j/io/compat/JavaIOStreamTest.java b/src/test/java/dotnet4j/io/compat/JavaIOStreamTest.java
new file mode 100644
index 0000000..eb1b8c2
--- /dev/null
+++ b/src/test/java/dotnet4j/io/compat/JavaIOStreamTest.java
@@ -0,0 +1,270 @@
+/*
+ * https://claude.ai/chat/5cb0054b-dc67-4dbd-91d7-960966aec653
+ */
+
+package dotnet4j.io.compat;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import dotnet4j.io.SeekOrigin;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/**
+ * JavaIOStreamTest.
+ *
+ * @author Naohide Sano (nsano)
+ * @version 0.00 2024-11-25 nsano initial version
+ */
+class JavaIOStreamTest {
+
+ private ByteArrayInputStream inputStream;
+ private ByteArrayOutputStream outputStream;
+ private JavaIOStream stream;
+ private final byte[] testData = "Hello, World!".getBytes();
+
+ @BeforeEach
+ void setUp() {
+ inputStream = new ByteArrayInputStream(testData);
+ outputStream = new ByteArrayOutputStream();
+ }
+
+ @AfterEach
+ void tearDown() throws IOException {
+ if (stream != null) {
+ stream.close();
+ }
+ inputStream.close();
+ outputStream.close();
+ }
+
+ @Nested
+ @DisplayName("Constructor Tests")
+ class ConstructorTests {
+ @Test
+ @DisplayName("Input-only constructor should initialize correctly")
+ void testInputOnlyConstructor() {
+ stream = new JavaIOStream(inputStream);
+ assertTrue(stream.canRead());
+ assertFalse(stream.canWrite());
+ }
+
+ @Test
+ @DisplayName("Input-output constructor should initialize correctly")
+ void testInputOutputConstructor() {
+ stream = new JavaIOStream(inputStream, outputStream);
+ assertTrue(stream.canRead());
+ assertTrue(stream.canWrite());
+ }
+
+ @Test
+ @DisplayName("Constructor with leaveOpen should initialize correctly")
+ void testLeaveOpenConstructor() {
+ stream = new JavaIOStream(inputStream, outputStream, true);
+ assertTrue(stream.canRead());
+ assertTrue(stream.canWrite());
+ }
+ }
+
+ @Nested
+ @DisplayName("Stream Capability Tests")
+ class StreamCapabilityTests {
+ @Test
+ @DisplayName("Stream capabilities should be correctly reported")
+ void testStreamCapabilities() {
+ stream = new JavaIOStream(inputStream, outputStream);
+ assertAll(
+ () -> assertTrue(stream.canRead()),
+ () -> assertTrue(stream.canWrite()),
+ () -> assertFalse(stream.canSeek())
+ );
+ }
+
+ @Test
+ @DisplayName("Stream length should match input data")
+ void testGetLength() {
+ stream = new JavaIOStream(inputStream);
+ assertEquals(testData.length, stream.getLength());
+ }
+
+ @Test
+ @DisplayName("Position should be tracked correctly")
+ void testPosition() {
+ stream = new JavaIOStream(inputStream);
+ assertEquals(0, stream.position());
+
+ byte[] buffer = new byte[5];
+ stream.read(buffer, 0, 5);
+ assertEquals(5, stream.position());
+ }
+ }
+
+ @Nested
+ @DisplayName("Unsupported Operation Tests")
+ class UnsupportedOperationTests {
+ @BeforeEach
+ void init() {
+ stream = new JavaIOStream(inputStream);
+ }
+
+ @Test
+ @DisplayName("Setting position should throw UnsupportedOperationException")
+ void testSetPositionThrowsException() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> stream.position(5));
+ }
+
+ @Test
+ @DisplayName("Seek should throw UnsupportedOperationException")
+ void testSeekThrowsException() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> stream.seek(5, SeekOrigin.Begin));
+ }
+
+ @Test
+ @DisplayName("SetLength should throw UnsupportedOperationException")
+ void testSetLengthThrowsException() {
+ assertThrows(UnsupportedOperationException.class,
+ () -> stream.setLength(100));
+ }
+ }
+
+ @Nested
+ @DisplayName("Read Operation Tests")
+ class ReadOperationTests {
+ @Test
+ @DisplayName("Reading buffer should work correctly")
+ void testRead() {
+ stream = new JavaIOStream(inputStream);
+ byte[] buffer = new byte[testData.length];
+ int bytesRead = stream.read(buffer, 0, buffer.length);
+
+ assertAll(
+ () -> assertEquals(testData.length, bytesRead),
+ () -> assertArrayEquals(testData, buffer)
+ );
+ }
+
+ @Test
+ @DisplayName("Reading single byte should work correctly")
+ void testReadByte() {
+ stream = new JavaIOStream(inputStream);
+ assertAll(
+ () -> assertEquals(testData[0], stream.readByte()),
+ () -> assertEquals(1, stream.position())
+ );
+ }
+
+ @Test
+ @DisplayName("Reading from closed stream should throw exception")
+ void testReadOnClosedStream() throws IOException {
+ stream = new JavaIOStream(inputStream);
+ stream.close();
+
+ Exception exception = assertThrows(dotnet4j.io.IOException.class,
+ () -> stream.read(new byte[1], 0, 1));
+ assertEquals("closed", exception.getMessage());
+ }
+ }
+
+ @Nested
+ @DisplayName("Write Operation Tests")
+ class WriteOperationTests {
+ @Test
+ @DisplayName("Writing buffer should work correctly")
+ void testWrite() {
+ stream = new JavaIOStream(new ByteArrayInputStream(new byte[0]), outputStream);
+ stream.write(testData, 0, testData.length);
+
+ assertAll(
+ () -> assertArrayEquals(testData, outputStream.toByteArray()),
+ () -> assertEquals(testData.length, stream.position())
+ );
+ }
+
+ @Test
+ @DisplayName("Writing single byte should work correctly")
+ void testWriteByte() {
+ stream = new JavaIOStream(new ByteArrayInputStream(new byte[0]), outputStream);
+ stream.writeByte((byte) 65); // ASCII 'A'
+
+ assertAll(
+ () -> assertArrayEquals(new byte[] { 65 }, outputStream.toByteArray()),
+ () -> assertEquals(2, stream.position(), "position must be count up 1")
+ );
+ }
+
+ @Test
+ @DisplayName("Writing to closed stream should throw exception")
+ void testWriteOnClosedStream() throws IOException {
+ stream = new JavaIOStream(inputStream, outputStream);
+ stream.close();
+
+ Exception exception = assertThrows(dotnet4j.io.IOException.class,
+ () -> stream.write(new byte[1], 0, 1));
+ assertEquals("closed", exception.getMessage());
+ }
+ }
+
+ @Nested
+ @DisplayName("Stream Management Tests")
+ class StreamManagementTests {
+ @Test
+ @DisplayName("Flush should work correctly")
+ void testFlush() {
+ stream = new JavaIOStream(inputStream, outputStream);
+ stream.write(testData, 0, testData.length);
+ stream.flush();
+ assertArrayEquals(testData, outputStream.toByteArray());
+ }
+
+ @Test
+ @DisplayName("Flush on closed stream should throw exception")
+ void testFlushOnClosedStream() throws IOException {
+ stream = new JavaIOStream(inputStream, outputStream);
+ stream.close();
+
+ Exception exception = assertThrows(dotnet4j.io.IOException.class,
+ () -> stream.flush());
+ assertEquals("closed", exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Close with leaveOpen=false should close streams")
+ void testClose() throws IOException {
+ stream = new JavaIOStream(inputStream, outputStream, false);
+ stream.close();
+
+ Exception exception = assertThrows(dotnet4j.io.IOException.class,
+ () -> stream.read(new byte[1], 0, 1));
+ assertEquals("closed", exception.getMessage());
+ }
+
+ @Test
+ @DisplayName("Close with leaveOpen=true should keep streams open")
+ void testLeaveOpenTrue() throws IOException {
+ stream = new JavaIOStream(inputStream, outputStream, true);
+ stream.close();
+
+ // Verify that the underlying streams are still usable
+ assertAll(
+ () -> assertDoesNotThrow(() -> inputStream.read()),
+ () -> assertDoesNotThrow(() -> outputStream.write(65))
+ );
+ }
+ }
+}
diff --git a/src/test/java/dotnet4j/io/compression/GZipStreamTest.java b/src/test/java/dotnet4j/io/compression/GZipStreamTest.java
new file mode 100644
index 0000000..bf16818
--- /dev/null
+++ b/src/test/java/dotnet4j/io/compression/GZipStreamTest.java
@@ -0,0 +1,301 @@
+/*
+ * https://claude.ai/chat/5cb0054b-dc67-4dbd-91d7-960966aec653
+ */
+
+package dotnet4j.io.compression;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.zip.GZIPOutputStream;
+
+import dotnet4j.io.MemoryStream;
+import dotnet4j.io.SeekOrigin;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.opentest4j.AssertionFailedError;
+import vavi.util.Debug;
+import vavi.util.StringUtil;
+
+import static org.junit.jupiter.api.Assertions.assertAll;
+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.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+
+/**
+ * GZipStreamTest.
+ *
+ * @author Naohide Sano (nsano)
+ * @version 0.00 2024-11-25 nsano initial version
+ */
+class GZipStreamTest {
+
+ private MemoryStream memoryStream;
+ private final String testString = """
+ This test suite covers:
+
+ Compression Tests
+
+
+ Basic compression functionality
+ Compression followed by decompression
+ Handling empty data
+ Handling large data sets
+
+
+ Decompression Tests
+
+
+ Error handling for invalid data
+ Multiple read operations
+ Partial reads
+
+
+ Stream Capability Tests
+
+
+ Verification of supported operations
+ Testing unsupported operations throw correct exceptions
+
+
+ Resource Management Tests
+
+
+ Proper stream closing behavior
+ Flush operations
+
+ Key features of the test suite:
+
+ Organized using nested test classes for better readability and organization
+ Comprehensive test coverage for both compression and decompression
+ Testing of edge cases and error conditions
+ Resource cleanup using try-with-resources and @AfterEach
+ Use of assertAll() for multiple related assertions
+ Clear test names using @DisplayName
+
+ The tests use a MemoryStream as the underlying stream for predictable behavior and easy verification. Error conditions and edge cases are also tested to ensure robust behavior.
+ Would you like me to add any additional test cases or modify the existing ones?
+ """;
+ private final byte[] testData = testString.getBytes();
+
+ @BeforeEach
+ void setUp() {
+ memoryStream = new MemoryStream();
+ }
+
+ @AfterEach
+ void tearDown() throws IOException {
+ memoryStream.close();
+ }
+
+ @Nested
+ @DisplayName("Compression Tests")
+ class CompressionTests {
+
+ @Test
+ @Disabled("just confirmation")
+ void testX() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ GZIPOutputStream gzos = new GZIPOutputStream(baos);
+ gzos.write(testData, 0, testData.length);
+ gzos.flush();
+ gzos.close();
+Debug.print("compressed size: " + baos.size() + "\n" + StringUtil.getDump(baos.toByteArray()));
+ }
+
+ @Test
+ @DisplayName("Should compress data correctly")
+ void testCompression() throws IOException {
+ // Write test data to memory stream using GZipStream
+ try (GZipStream compressionStream = new GZipStream(memoryStream, CompressionMode.Compress)) {
+ compressionStream.write(testData, 0, testData.length);
+ compressionStream.flush();
+ }
+
+ byte[] compressedData = memoryStream.toArray();
+//Debug.print("compressed size: " + compressedData.length + "\n" + StringUtil.getDump(compressedData));
+
+ // Verify compression actually occurred
+ assertAll(
+ () -> assertTrue(compressedData.length > 0),
+ () -> assertTrue(compressedData.length < testData.length, "expect " + compressedData.length + " < " + testData.length),
+ () -> assertThrows(AssertionFailedError.class, () -> assertArrayEquals(testData, compressedData)) // TODO there isn't assertArrayNotEquals?
+ );
+ }
+
+ @Test
+ @DisplayName("Should compress and decompress data correctly")
+ void testCompressionDecompression() throws IOException {
+ // First compress the data
+ // TODO leave open doesn't work, GZIPOutputStream outputs something when close?
+ try (GZipStream compressionStream = new GZipStream(memoryStream, CompressionMode.Compress)) {
+ compressionStream.write(testData, 0, testData.length);
+ compressionStream.flush();
+ }
+
+// memoryStream.position(0); // TODO ditto
+ MemoryStream memoryStream2 = new MemoryStream(memoryStream.toArray());
+
+ // Now decompress the data
+ byte[] decompressedData = new byte[testData.length];
+ try (GZipStream decompressionStream = new GZipStream(memoryStream2, CompressionMode.Decompress)) {
+ int bytesRead = 0;
+ while (true) {
+ int r = decompressionStream.read(decompressedData, bytesRead, decompressedData.length - bytesRead);
+Debug.println("decompressionStream: " + r);
+ if (r <= 0) break; // TODO GZipStream doesn't return -1 (not java spec), EOF is 0 (c# spec)
+ bytesRead += r;
+ }
+ assertEquals(testData.length, bytesRead);
+ }
+
+ assertArrayEquals(testData, decompressedData);
+ }
+
+ @Test
+ @DisplayName("Should compress empty data correctly")
+ void testCompressEmptyData() throws IOException {
+ byte[] emptyData = new byte[0];
+
+ try (GZipStream compressionStream = new GZipStream(memoryStream, CompressionMode.Compress)) {
+ compressionStream.write(emptyData, 0, 0);
+ }
+
+ byte[] compressedData = memoryStream.toArray();
+ assertTrue(compressedData.length > 0); // GZip header should still be present
+ }
+
+ @Test
+ @DisplayName("Should compress large data correctly")
+ void testCompressLargeData() throws IOException {
+ // Create large data set
+ byte[] largeData = new byte[1000000]; // 1MB of data
+ Arrays.fill(largeData, (byte) 'A');
+
+ try (GZipStream compressionStream = new GZipStream(memoryStream, CompressionMode.Compress)) {
+ compressionStream.write(largeData, 0, largeData.length);
+ }
+
+ byte[] compressedData = memoryStream.toArray();
+
+ // Highly compressible data should compress well
+ assertTrue(compressedData.length < largeData.length / 10);
+ }
+ }
+
+ @Nested
+ @DisplayName("Decompression Tests")
+ class DecompressionTests {
+
+ @Test
+ @DisplayName("Should throw exception for invalid compressed data")
+ void testDecompressInvalidData() {
+ byte[] invalidData = {1, 2, 3, 4, 5}; // Invalid GZip data
+ memoryStream.write(invalidData, 0, invalidData.length);
+ memoryStream.position(0);
+
+ assertThrows(dotnet4j.io.IOException.class, () -> {
+ try (GZipStream decompressionStream = new GZipStream(memoryStream, CompressionMode.Decompress)) {
+ decompressionStream.read(new byte[10], 0, 10);
+ }
+ });
+ }
+
+ @Test
+ @DisplayName("Should handle multiple read operations correctly")
+ void testMultipleReads() throws IOException {
+ // First compress some data
+ // TODO leave open doesn't work, GZIPOutputStream outputs something when close?
+ try (GZipStream compressionStream = new GZipStream(memoryStream, CompressionMode.Compress)) {
+ compressionStream.write(testData, 0, testData.length);
+ compressionStream.flush();
+ }
+
+// memoryStream.position(0); // TODO ditto
+ MemoryStream memoryStream2 = new MemoryStream(memoryStream.toArray());
+
+ // Read in small chunks
+ try (GZipStream decompressionStream = new GZipStream(memoryStream2, CompressionMode.Decompress)) {
+ byte[] decompressedData = new byte[testData.length];
+ int totalBytesRead = 0;
+ int bytesRead;
+ int chunkSize = 4;
+
+ while ((bytesRead = decompressionStream.read(decompressedData, totalBytesRead,
+ Math.min(chunkSize, testData.length - totalBytesRead))) > 0) {
+ totalBytesRead += bytesRead;
+ }
+
+ assertEquals(testData.length, totalBytesRead);
+ assertArrayEquals(testData, decompressedData);
+ }
+ }
+ }
+
+ @Nested
+ @DisplayName("Stream Capability Tests")
+ class StreamCapabilityTests {
+
+ @Test
+ @DisplayName("Should report correct stream capabilities")
+ void testStreamCapabilities() throws IOException {
+ try (GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress)) {
+ assertAll(
+ () -> assertTrue(gzipStream.canWrite()),
+ () -> assertFalse(gzipStream.canSeek())
+ );
+ }
+ }
+
+ @Test
+ @DisplayName("Should throw exception for unsupported operations")
+ void testUnsupportedOperations() {
+ GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress);
+
+ assertAll(
+ () -> assertThrows(UnsupportedOperationException.class,
+ () -> gzipStream.seek(0, SeekOrigin.Begin)),
+ () -> assertThrows(UnsupportedOperationException.class,
+ () -> gzipStream.setLength(100)),
+ () -> assertThrows(UnsupportedOperationException.class,
+ () -> gzipStream.position(50))
+ );
+ }
+ }
+
+ @Nested
+ @DisplayName("Resource Management Tests")
+ class ResourceManagementTests {
+
+ @Test
+ @DisplayName("Should close underlying streams correctly")
+ void testStreamClosing() throws IOException {
+ GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress);
+ gzipStream.write(testData, 0, testData.length);
+ gzipStream.close();
+
+ // Verify that operations on closed stream throw exceptions
+ assertThrows(dotnet4j.io.IOException.class,
+ () -> gzipStream.write(testData, 0, testData.length));
+ }
+
+ @Test
+ @DisplayName("Should flush compressed data correctly")
+ void testFlush() throws IOException {
+ try (GZipStream gzipStream = new GZipStream(memoryStream, CompressionMode.Compress)) {
+ gzipStream.write(testData, 0, testData.length);
+ gzipStream.flush();
+
+ // Verify that data was written to underlying stream
+ assertTrue(memoryStream.getLength() > 0);
+ }
+ }
+ }
+}