From 005749b6ae8055aad89c0d59f3a59416f044952e Mon Sep 17 00:00:00 2001 From: xdark Date: Fri, 12 Apr 2024 01:17:01 +0300 Subject: [PATCH 1/9] Bump minimum required JDK to 22, migrate API to MemorySegment --- pom.xml | 8 +- .../java/software/coley/lljzip/ZipIO.java | 40 ++-- .../coley/lljzip/format/ZipPatterns.java | 4 +- .../format/compression/Decompressor.java | 4 +- .../compression/DeflateDecompressor.java | 9 +- .../UnsafeDeflateDecompressor.java | 9 +- .../format/compression/ZipCompressions.java | 4 +- .../format/model/AbstractZipFileHeader.java | 22 +-- .../format/model/AdaptingLocalFileHeader.java | 13 +- .../model/CentralDirectoryFileHeader.java | 50 ++--- .../format/model/EndOfCentralDirectory.java | 35 ++-- .../format/model/JvmLocalFileHeader.java | 12 +- .../lljzip/format/model/LocalFileHeader.java | 44 ++--- .../coley/lljzip/format/model/ZipRead.java | 5 +- .../lljzip/format/read/AdaptingZipReader.java | 20 +- .../format/read/ForwardScanZipReader.java | 16 +- .../lljzip/format/read/JvmZipReader.java | 26 +-- .../format/read/NaiveLocalFileZipReader.java | 8 +- .../coley/lljzip/format/read/ZipReader.java | 4 +- .../transform/JvmClassDirectoryMapper.java | 4 +- .../lljzip/format/write/DirectZipWriter.java | 16 +- .../write/ZipOutputStreamZipWriter.java | 6 +- .../coley/lljzip/util/BufferData.java | 170 ---------------- .../software/coley/lljzip/util/ByteData.java | 115 ----------- .../coley/lljzip/util/ExtraFieldTime.java | 30 +-- .../lljzip/util/FastWrapOutputStream.java | 7 +- .../coley/lljzip/util/FileMapUtil.java | 110 ----------- ...teDataUtil.java => MemorySegmentUtil.java} | 182 ++++++------------ .../coley/lljzip/util/NoopByteData.java | 66 ------- .../coley/lljzip/util/UnsafeMappedFile.java | 176 ----------------- .../coley/lljzip/util/lazy/LazyByteData.java | 17 +- .../software/coley/lljzip/JarInJarUtils.java | 8 +- .../software/coley/lljzip/PartParseTests.java | 14 +- .../java/software/coley/lljzip/Utils.java | 9 +- .../lljzip/ZipComparisonShowcaseTest.java | 8 +- 35 files changed, 293 insertions(+), 978 deletions(-) delete mode 100644 src/main/java/software/coley/lljzip/util/BufferData.java delete mode 100644 src/main/java/software/coley/lljzip/util/ByteData.java delete mode 100644 src/main/java/software/coley/lljzip/util/FileMapUtil.java rename src/main/java/software/coley/lljzip/util/{ByteDataUtil.java => MemorySegmentUtil.java} (60%) delete mode 100644 src/main/java/software/coley/lljzip/util/NoopByteData.java delete mode 100644 src/main/java/software/coley/lljzip/util/UnsafeMappedFile.java diff --git a/pom.xml b/pom.xml index 35f3122..106e36c 100644 --- a/pom.xml +++ b/pom.xml @@ -97,13 +97,9 @@ org.apache.maven.plugins maven-compiler-plugin - - 1.8 - 1.8 + 22 + 22 diff --git a/src/main/java/software/coley/lljzip/ZipIO.java b/src/main/java/software/coley/lljzip/ZipIO.java index 2ae2b1d..f359969 100644 --- a/src/main/java/software/coley/lljzip/ZipIO.java +++ b/src/main/java/software/coley/lljzip/ZipIO.java @@ -4,12 +4,12 @@ import software.coley.lljzip.format.model.EndOfCentralDirectory; import software.coley.lljzip.format.model.ZipArchive; import software.coley.lljzip.format.read.*; -import software.coley.lljzip.util.BufferData; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.FileMapUtil; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.channels.FileChannel; import java.nio.file.Files; import java.nio.file.Path; import java.util.zip.ZipFile; @@ -21,7 +21,7 @@ *
  • For regular ZIP files use {@link ForwardScanZipReader}.
  • *
  • For ZIP files without {@link CentralDirectoryFileHeader} or {@link EndOfCentralDirectory} items, use {@link NaiveLocalFileZipReader}
  • * - * You can fully control zip parsing via {@link #read(ByteData, ZipReader)} by passing a customized reader implementation. + * You can fully control zip parsing via {@link #read(MemorySegment, ZipReader)} by passing a customized reader implementation. * * @author Matt Coley */ @@ -37,7 +37,7 @@ public class ZipIO { * @throws IOException * When the archive bytes cannot be read from, usually indicating a malformed zip. */ - public static ZipArchive readStandard(ByteData data) throws IOException { + public static ZipArchive readStandard(MemorySegment data) throws IOException { return read(data, new ForwardScanZipReader()); } @@ -82,7 +82,7 @@ public static ZipArchive readStandard(Path data) throws IOException { * @throws IOException * When the archive bytes cannot be read from, usually indicating a malformed zip. */ - public static ZipArchive readNaive(ByteData data) throws IOException { + public static ZipArchive readNaive(MemorySegment data) throws IOException { return read(data, new NaiveLocalFileZipReader()); } @@ -128,7 +128,7 @@ public static ZipArchive readNaive(Path data) throws IOException { * @throws IOException * When the archive bytes cannot be read from, usually indicating a malformed zip. */ - public static ZipArchive readJvm(ByteData data) throws IOException { + public static ZipArchive readJvm(MemorySegment data) throws IOException { return read(data, new JvmZipReader()); } @@ -194,7 +194,7 @@ public static ZipArchive readAdaptingIO(Path path) throws IOException { public static ZipArchive read(byte[] data, ZipReader strategy) throws IOException { if (data == null) throw new IOException("Data is null!"); - return read(BufferData.wrap(data), strategy); + return read(MemorySegment.ofArray(data), strategy); } /** @@ -213,7 +213,23 @@ public static ZipArchive read(Path path, ZipReader strategy) throws IOException throw new IOException("Data is null!"); if (!Files.isRegularFile(path)) throw new FileNotFoundException(path.toString()); - return read(FileMapUtil.map(path), strategy); + FileChannel fc = FileChannel.open(path); + try { + long size = fc.size(); + // The fixed size elements of a CDFH is 22 bytes (plus the variable size bits which can be 0) + // - Even if we only want to read local/central file entries, those are even larger at a minimum + if (size < 22) + throw new IOException("Not enough bytes to read Central-Directory-File-Header, minimum=22"); + + ZipArchive zip = new ZipArchive(fc); + strategy.read(zip, fc.map(FileChannel.MapMode.READ_ONLY, 0L, size, Arena.ofAuto())); + fc = null; + return zip; + } finally { + if (fc != null) { + fc.close(); + } + } } /** @@ -227,17 +243,17 @@ public static ZipArchive read(Path path, ZipReader strategy) throws IOException * @throws IOException * When the archive bytes cannot be read from, usually indicating a malformed zip. */ - public static ZipArchive read(ByteData data, ZipReader strategy) throws IOException { + public static ZipArchive read(MemorySegment data, ZipReader strategy) throws IOException { if (data == null) throw new IOException("Data is null!"); // The fixed size elements of a CDFH is 22 bytes (plus the variable size bits which can be 0) // - Even if we only want to read local/central file entries, those are even larger at a minimum - if (data.length() < 22) + if (data.byteSize() < 22) throw new IOException("Not enough bytes to read Central-Directory-File-Header, minimum=22"); // Create instance - ZipArchive zip = new ZipArchive(data); + ZipArchive zip = new ZipArchive(); strategy.read(zip, data); return zip; } diff --git a/src/main/java/software/coley/lljzip/format/ZipPatterns.java b/src/main/java/software/coley/lljzip/format/ZipPatterns.java index bb02fce..9ee305b 100644 --- a/src/main/java/software/coley/lljzip/format/ZipPatterns.java +++ b/src/main/java/software/coley/lljzip/format/ZipPatterns.java @@ -3,10 +3,10 @@ import software.coley.lljzip.format.model.CentralDirectoryFileHeader; import software.coley.lljzip.format.model.EndOfCentralDirectory; import software.coley.lljzip.format.model.LocalFileHeader; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; /** - * Patterns for usage in {@link ByteDataUtil} methods. + * Patterns for usage in {@link MemorySegmentUtil} methods. * * @author Matt Coley */ diff --git a/src/main/java/software/coley/lljzip/format/compression/Decompressor.java b/src/main/java/software/coley/lljzip/format/compression/Decompressor.java index f0d52b5..d10b6b5 100644 --- a/src/main/java/software/coley/lljzip/format/compression/Decompressor.java +++ b/src/main/java/software/coley/lljzip/format/compression/Decompressor.java @@ -1,9 +1,9 @@ package software.coley.lljzip.format.compression; import software.coley.lljzip.format.model.LocalFileHeader; -import software.coley.lljzip.util.ByteData; import java.io.IOException; +import java.lang.foreign.MemorySegment; /** * Outlines decompression of {@link LocalFileHeader#getFileData()}. @@ -22,5 +22,5 @@ public interface Decompressor { * @throws IOException * Decompression failure. */ - ByteData decompress(LocalFileHeader header, ByteData bytes) throws IOException; + MemorySegment decompress(LocalFileHeader header, MemorySegment bytes) throws IOException; } diff --git a/src/main/java/software/coley/lljzip/format/compression/DeflateDecompressor.java b/src/main/java/software/coley/lljzip/format/compression/DeflateDecompressor.java index 7d884d2..f57319c 100644 --- a/src/main/java/software/coley/lljzip/format/compression/DeflateDecompressor.java +++ b/src/main/java/software/coley/lljzip/format/compression/DeflateDecompressor.java @@ -1,10 +1,10 @@ package software.coley.lljzip.format.compression; import software.coley.lljzip.format.model.LocalFileHeader; -import software.coley.lljzip.util.ByteData; import software.coley.lljzip.util.FastWrapOutputStream; import java.io.IOException; +import java.lang.foreign.MemorySegment; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import java.util.zip.ZipException; @@ -22,7 +22,7 @@ private DeflateDecompressor() { } @Override - public ByteData decompress(LocalFileHeader header, ByteData data) throws IOException { + public MemorySegment decompress(LocalFileHeader header, MemorySegment data) throws IOException { if (header.getCompressionMethod() != ZipCompressions.DEFLATED) throw new IOException("LocalFileHeader contents not using 'Deflated'!"); Inflater inflater = new Inflater(true); @@ -30,15 +30,16 @@ public ByteData decompress(LocalFileHeader header, ByteData data) throws IOExcep try { byte[] output = new byte[1024]; byte[] buffer = new byte[1024]; + MemorySegment bufferSegment = MemorySegment.ofArray(buffer); long position = 0L; - long length = data.length(); + long length = data.byteSize(); do { if (inflater.needsInput()) { int remaining = (int) Math.min(buffer.length, length); if (remaining == 0) { break; } - data.get(position, buffer, 0, remaining); + MemorySegment.copy(data, position, bufferSegment, 0, remaining); length -= remaining; position += remaining; inflater.setInput(buffer, 0, remaining); diff --git a/src/main/java/software/coley/lljzip/format/compression/UnsafeDeflateDecompressor.java b/src/main/java/software/coley/lljzip/format/compression/UnsafeDeflateDecompressor.java index d404a39..04b920a 100644 --- a/src/main/java/software/coley/lljzip/format/compression/UnsafeDeflateDecompressor.java +++ b/src/main/java/software/coley/lljzip/format/compression/UnsafeDeflateDecompressor.java @@ -1,11 +1,11 @@ package software.coley.lljzip.format.compression; import software.coley.lljzip.format.model.LocalFileHeader; -import software.coley.lljzip.util.ByteData; import software.coley.lljzip.util.FastWrapOutputStream; import software.coley.lljzip.util.InflaterHackery; import java.io.IOException; +import java.lang.foreign.MemorySegment; import java.util.ArrayDeque; import java.util.Deque; import java.util.zip.DataFormatException; @@ -38,7 +38,7 @@ private UnsafeDeflateDecompressor() { } @Override - public ByteData decompress(LocalFileHeader header, ByteData data) throws IOException { + public MemorySegment decompress(LocalFileHeader header, MemorySegment data) throws IOException { if (header.getCompressionMethod() != ZipCompressions.DEFLATED) throw new IOException("LocalFileHeader contents not using 'Deflated'!"); FastWrapOutputStream out = new FastWrapOutputStream(); @@ -55,16 +55,17 @@ public ByteData decompress(LocalFileHeader header, ByteData data) throws IOExcep try { byte[] output = entry.decompress; byte[] buffer = entry.buffer; + MemorySegment bufferSegment = MemorySegment.ofArray(buffer); Inflater inflater = entry.inflater; long position = 0L; - long length = data.length(); + long length = data.byteSize(); int remaining = 0; boolean needsInput = true; do { if (needsInput) { remaining = (int) Math.min(buffer.length, length); if (remaining != 0) { - data.get(position, buffer, 0, remaining); + MemorySegment.copy(data, position, bufferSegment, 0, remaining); length -= remaining; position += remaining; inflater.setInput(buffer, 0, remaining); diff --git a/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java b/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java index 9555b10..2b8434a 100644 --- a/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java +++ b/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java @@ -1,9 +1,9 @@ package software.coley.lljzip.format.compression; import software.coley.lljzip.format.model.LocalFileHeader; -import software.coley.lljzip.util.ByteData; import java.io.IOException; +import java.lang.foreign.MemorySegment; /** * Constants for {@link LocalFileHeader#getCompressionMethod()}. @@ -202,7 +202,7 @@ static String getName(int method) { * @throws IOException * When the decompression failed. */ - static ByteData decompress(LocalFileHeader header) throws IOException { + static MemorySegment decompress(LocalFileHeader header) throws IOException { int method = header.getCompressionMethod(); switch (method) { case STORED: diff --git a/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java b/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java index 471aa47..6bc5134 100644 --- a/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java @@ -2,14 +2,14 @@ import software.coley.lljzip.format.compression.Decompressor; import software.coley.lljzip.format.compression.ZipCompressions; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.lazy.LazyByteData; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.lang.foreign.MemorySegment; /** * Common base for shared elements of {@link CentralDirectoryFileHeader} and {@link LocalFileHeader}. @@ -35,7 +35,7 @@ public abstract class AbstractZipFileHeader implements ZipPart, ZipRead { protected transient long offset = -1L; // Data source that contents were read from. - protected transient ByteData data; + protected transient MemorySegment data; // String cache values private transient String fileNameCache; @@ -45,7 +45,7 @@ public abstract class AbstractZipFileHeader implements ZipPart, ZipRead { * @return The associated backing data that this file header was read from. */ @Nullable - public ByteData getBackingData() { + public MemorySegment getBackingData() { return data; } @@ -55,7 +55,7 @@ public long offset() { } @Override - public void read(@Nonnull ByteData data, long offset) { + public void read(@Nonnull MemorySegment data, long offset) { this.data = data; this.offset = offset; } @@ -230,7 +230,7 @@ public void setExtraFieldLength(int extraFieldLength) { * * @return File name. */ - public ByteData getFileName() { + public MemorySegment getFileName() { return fileName.get(); } @@ -238,7 +238,7 @@ public ByteData getFileName() { * @param fileName * File name. */ - public void setFileName(ByteData fileName) { + public void setFileName(MemorySegment fileName) { this.fileName.set(fileName); fileNameCache = null; } @@ -252,7 +252,7 @@ public void setFileName(ByteData fileName) { public String getFileNameAsString() { String fileNameCache = this.fileNameCache; if (fileNameCache == null && fileName != null) { - return this.fileNameCache = ByteDataUtil.toString(fileName.get()); + return this.fileNameCache = MemorySegmentUtil.toString(fileName.get()); } return fileNameCache; } @@ -261,7 +261,7 @@ public String getFileNameAsString() { * @return May be used for extra compression information, * depending on the {@link #getCompressionMethod() compression method} used. */ - public ByteData getExtraField() { + public MemorySegment getExtraField() { return extraField.get(); } @@ -269,7 +269,7 @@ public ByteData getExtraField() { * @param extraField * Extra field bytes. */ - public void setExtraField(ByteData extraField) { + public void setExtraField(MemorySegment extraField) { this.extraField.set(extraField); } @@ -279,7 +279,7 @@ public void setExtraField(ByteData extraField) { public String getExtraFieldAsString() { String fileCommentCache = this.extraFieldCache; if (fileCommentCache == null && extraField != null) { - return this.extraFieldCache = ByteDataUtil.toString(extraField.get()); + return this.extraFieldCache = MemorySegmentUtil.toString(extraField.get()); } return fileCommentCache; } diff --git a/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java index 6fb0f47..fb193a3 100644 --- a/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java @@ -1,7 +1,5 @@ package software.coley.lljzip.format.model; -import software.coley.lljzip.util.BufferData; -import software.coley.lljzip.util.NoopByteData; import software.coley.lljzip.util.lazy.LazyByteData; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; @@ -10,6 +8,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.foreign.MemorySegment; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -45,20 +44,20 @@ public AdaptingLocalFileHeader(@Nonnull ZipFile archive, @Nonnull ZipEntry entry lastModFileTime = new LazyInt(() -> 0); lastModFileDate = new LazyInt(() -> 0); fileNameLength = new LazyInt(entryName::length); - fileName = new LazyByteData(() -> BufferData.wrap(entryName.getBytes())); + fileName = new LazyByteData(() -> MemorySegment.ofArray(entryName.getBytes())); fileDataLength = new LazyLong(() -> entryData.length); - fileData = new LazyByteData(() -> BufferData.wrap(entryData)); + fileData = new LazyByteData(() -> MemorySegment.ofArray(entryData)); compressionMethod = new LazyInt(() -> 0); uncompressedSize = new LazyLong(() -> entryData.length); compressedSize = new LazyLong(() -> entryData.length); crc32 = new LazyInt(() -> (int) entry.getCrc()); if (extra != null) { extraFieldLength = new LazyInt(() -> extra.length); - extraField = new LazyByteData(() -> BufferData.wrap(extra)); + extraField = new LazyByteData(() -> MemorySegment.ofArray(extra)); } else { extraFieldLength = new LazyInt(() -> 0); - extraField = new LazyByteData(() -> NoopByteData.INSTANCE); + extraField = new LazyByteData(() -> MemorySegment.ofArray(new byte[0])); } - data = NoopByteData.INSTANCE; + data = MemorySegment.ofArray(new byte[0]); } } diff --git a/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java b/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java index d6cf159..c3f39bc 100644 --- a/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java @@ -1,12 +1,12 @@ package software.coley.lljzip.format.model; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.lazy.LazyByteData; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; import java.util.Objects; /** @@ -84,27 +84,27 @@ public CentralDirectoryFileHeader copy() { } @Override - public void read(@Nonnull ByteData data, long offset) { + public void read(@Nonnull MemorySegment data, long offset) { super.read(data, offset); - versionMadeBy = ByteDataUtil.readLazyWord(data, offset, 4).withId("versionMadeBy"); - versionNeededToExtract = ByteDataUtil.readLazyWord(data, offset, 6).withId("versionNeededToExtract"); - generalPurposeBitFlag = ByteDataUtil.readLazyWord(data, offset, 8).withId("generalPurposeBitFlag"); - compressionMethod = ByteDataUtil.readLazyWord(data, offset, 10).withId("compressionMethod"); - lastModFileTime = ByteDataUtil.readLazyWord(data, offset, 12).withId("lastModFileTime"); - lastModFileDate = ByteDataUtil.readLazyWord(data, offset, 14).withId("lastModFileDate"); - crc32 = ByteDataUtil.readLazyQuad(data, offset, 16).withId("crc32"); - compressedSize = ByteDataUtil.readLazyMaskedLongQuad(data, offset, 20).withId("compressedSize"); - uncompressedSize = ByteDataUtil.readLazyMaskedLongQuad(data, offset, 24).withId("uncompressedSize"); - fileNameLength = ByteDataUtil.readLazyWord(data, offset, 28).withId("fileNameLength"); - extraFieldLength = ByteDataUtil.readLazyWord(data, offset, 30).withId("extraFieldLength"); - fileCommentLength = ByteDataUtil.readLazyWord(data, offset, 32).withId("fileCommentLength"); - diskNumberStart = ByteDataUtil.readLazyWord(data, offset, 34).withId("diskNumberStart"); - internalFileAttributes = ByteDataUtil.readLazyWord(data, offset, 36).withId("internalFileAttributes"); - externalFileAttributes = ByteDataUtil.readLazyQuad(data, offset, 38).withId("externalFileAttributes"); - relativeOffsetOfLocalHeader = ByteDataUtil.readLazyMaskedLongQuad(data, offset, 42).withId("relativeOffsetOfLocalHeader"); - fileName = ByteDataUtil.readLazySlice(data, offset, new LazyInt(() -> 46), fileNameLength).withId("fileName"); - extraField = ByteDataUtil.readLazySlice(data, offset, fileNameLength.add(46), extraFieldLength).withId("extraField"); - fileComment = ByteDataUtil.readLazySlice(data, offset, fileNameLength.add(46).add(extraFieldLength), fileCommentLength).withId("fileComment"); + versionMadeBy = MemorySegmentUtil.readLazyWord(data, offset, 4).withId("versionMadeBy"); + versionNeededToExtract = MemorySegmentUtil.readLazyWord(data, offset, 6).withId("versionNeededToExtract"); + generalPurposeBitFlag = MemorySegmentUtil.readLazyWord(data, offset, 8).withId("generalPurposeBitFlag"); + compressionMethod = MemorySegmentUtil.readLazyWord(data, offset, 10).withId("compressionMethod"); + lastModFileTime = MemorySegmentUtil.readLazyWord(data, offset, 12).withId("lastModFileTime"); + lastModFileDate = MemorySegmentUtil.readLazyWord(data, offset, 14).withId("lastModFileDate"); + crc32 = MemorySegmentUtil.readLazyQuad(data, offset, 16).withId("crc32"); + compressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 20).withId("compressedSize"); + uncompressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 24).withId("uncompressedSize"); + fileNameLength = MemorySegmentUtil.readLazyWord(data, offset, 28).withId("fileNameLength"); + extraFieldLength = MemorySegmentUtil.readLazyWord(data, offset, 30).withId("extraFieldLength"); + fileCommentLength = MemorySegmentUtil.readLazyWord(data, offset, 32).withId("fileCommentLength"); + diskNumberStart = MemorySegmentUtil.readLazyWord(data, offset, 34).withId("diskNumberStart"); + internalFileAttributes = MemorySegmentUtil.readLazyWord(data, offset, 36).withId("internalFileAttributes"); + externalFileAttributes = MemorySegmentUtil.readLazyQuad(data, offset, 38).withId("externalFileAttributes"); + relativeOffsetOfLocalHeader = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 42).withId("relativeOffsetOfLocalHeader"); + fileName = MemorySegmentUtil.readLazySlice(data, offset, new LazyInt(() -> 46), fileNameLength).withId("fileName"); + extraField = MemorySegmentUtil.readLazySlice(data, offset, fileNameLength.add(46), extraFieldLength).withId("extraField"); + fileComment = MemorySegmentUtil.readLazySlice(data, offset, fileNameLength.add(46).add(extraFieldLength), fileCommentLength).withId("fileComment"); } @Override @@ -244,7 +244,7 @@ public void setFileCommentLength(int fileCommentLength) { /** * @return File comment. */ - public ByteData getFileComment() { + public MemorySegment getFileComment() { return fileComment.get(); } @@ -252,7 +252,7 @@ public ByteData getFileComment() { * @param fileComment * File comment. */ - public void setFileComment(ByteData fileComment) { + public void setFileComment(MemorySegment fileComment) { this.fileComment.set(fileComment); } @@ -262,7 +262,7 @@ public void setFileComment(ByteData fileComment) { public String getFileCommentAsString() { String fileCommentCache = this.fileCommentCache; if (fileCommentCache == null) { - return this.fileCommentCache = ByteDataUtil.toString(fileComment.get()); + return this.fileCommentCache = MemorySegmentUtil.toString(fileComment.get()); } return fileCommentCache; } diff --git a/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java b/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java index 3c7b092..db3069a 100644 --- a/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java +++ b/src/main/java/software/coley/lljzip/format/model/EndOfCentralDirectory.java @@ -1,10 +1,9 @@ package software.coley.lljzip.format.model; -import software.coley.lljzip.util.BufferData; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; import java.util.Objects; @@ -36,7 +35,7 @@ public class EndOfCentralDirectory implements ZipPart, ZipRead { private long centralDirectorySize; private long centralDirectoryOffset; private int zipCommentLength; - private ByteData zipComment; + private MemorySegment zipComment; private transient String zipCommentCache; /** @@ -59,21 +58,21 @@ public EndOfCentralDirectory copy() { } @Override - public void read(@Nonnull ByteData data, long offset) { + public void read(@Nonnull MemorySegment data, long offset) { this.offset = offset; - diskNumber = ByteDataUtil.readWord(data, offset + 4); - centralDirectoryStartDisk = ByteDataUtil.readWord(data, offset + 6); - centralDirectoryStartOffset = ByteDataUtil.readWord(data, offset + 8); - numEntries = ByteDataUtil.readWord(data, offset + 10); - setCentralDirectorySize(ByteDataUtil.readQuad(data, offset + 12)); - setCentralDirectoryOffset(ByteDataUtil.readQuad(data, offset + 16)); - setZipCommentLength(ByteDataUtil.readWord(data, offset + 20)); - zipComment = data.sliceOf(offset + 22, zipCommentLength); + diskNumber = MemorySegmentUtil.readWord(data, offset + 4); + centralDirectoryStartDisk = MemorySegmentUtil.readWord(data, offset + 6); + centralDirectoryStartOffset = MemorySegmentUtil.readWord(data, offset + 8); + numEntries = MemorySegmentUtil.readWord(data, offset + 10); + setCentralDirectorySize(MemorySegmentUtil.readQuad(data, offset + 12)); + setCentralDirectoryOffset(MemorySegmentUtil.readQuad(data, offset + 16)); + setZipCommentLength(MemorySegmentUtil.readWord(data, offset + 20)); + zipComment = data.asSlice(offset + 22, zipCommentLength); } @Override public long length() { - return 22L + zipComment.length(); + return 22L + zipComment.byteSize(); } @Nonnull @@ -197,7 +196,7 @@ public void setZipCommentLength(int zipCommentLength) { /** * @return Optional comment, or empty string. */ - public ByteData getZipComment() { + public MemorySegment getZipComment() { return zipComment; } @@ -205,9 +204,9 @@ public ByteData getZipComment() { * @param zipComment * Optional comment, or empty string. */ - public void setZipComment(ByteData zipComment) { + public void setZipComment(MemorySegment zipComment) { if (zipComment == null) - zipComment = BufferData.wrap(new byte[0]); + zipComment = MemorySegment.ofArray(new byte[0]); this.zipComment = zipComment; } @@ -217,7 +216,7 @@ public void setZipComment(ByteData zipComment) { public String getZipCommentAsString() { String zipCommentCache = this.zipCommentCache; if (zipCommentCache == null) { - return this.zipCommentCache = ByteDataUtil.toString(zipComment); + return this.zipCommentCache = MemorySegmentUtil.toString(zipComment); } return zipCommentCache; } diff --git a/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java index d18a504..5ecd074 100644 --- a/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/JvmLocalFileHeader.java @@ -2,11 +2,11 @@ import software.coley.lljzip.format.ZipPatterns; import software.coley.lljzip.format.compression.ZipCompressions; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.lazy.LazyLong; import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; import java.util.NavigableSet; @@ -32,7 +32,7 @@ public void setOffsets(@Nonnull NavigableSet offsets) { } @Override - public void read(@Nonnull ByteData data, long offset) { + public void read(@Nonnull MemorySegment data, long offset) { super.read(data, offset); // JVM file data reading does NOT use the compressed/uncompressed fields. @@ -61,7 +61,7 @@ public void read(@Nonnull ByteData data, long offset) { // The JVM technically allows the header to be excluded, so we split the offset fixing // into two parts. absoluteDataOffsetEnd -= 12; - if (data.getInt(absoluteDataOffsetEnd) == ZipPatterns.DATA_DESCRIPTOR_QUAD) { + if (MemorySegmentUtil.readQuad(data, absoluteDataOffsetEnd) == ZipPatterns.DATA_DESCRIPTOR_QUAD) { absoluteDataOffsetEnd -= 4; } } @@ -72,7 +72,7 @@ public void read(@Nonnull ByteData data, long offset) { this.relativeDataOffsetEnd = relativeDataOffsetEnd; fileDataLength = new LazyLong(() -> relativeDataOffsetEnd - relativeDataOffsetStart).withId("fileDataLength"); - fileData = ByteDataUtil.readLazyLongSlice(data, offset, + fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, new LazyLong(() -> relativeDataOffsetStart), fileDataLength).withId("fileData"); // Update sizes where possible @@ -130,7 +130,7 @@ public void adoptLinkedCentralDirectoryValues() { // Data should not be overflowing into adjacent header entries. // - If it is, the data here is likely intentionally tampered with to screw with parsers if (fileDataLength < relativeDataOffsetEnd) { - fileData = ByteDataUtil.readLazyLongSlice(data, offset, + fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, new LazyLong(() -> relativeDataOffsetStart - offset), new LazyLong(() -> fileDataLength)).withId("fileData"); } diff --git a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java index 8676ae3..18ed88c 100644 --- a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java @@ -2,14 +2,14 @@ import software.coley.lljzip.format.compression.Decompressor; import software.coley.lljzip.format.read.ZipReader; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.lazy.LazyByteData; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; import javax.annotation.Nonnull; import java.io.IOException; +import java.lang.foreign.MemorySegment; import java.util.Objects; import static software.coley.lljzip.format.compression.ZipCompressions.STORED; @@ -74,20 +74,20 @@ public LocalFileHeader copy() { } @Override - public void read(@Nonnull ByteData data, long offset) { + public void read(@Nonnull MemorySegment data, long offset) { super.read(data, offset); - versionNeededToExtract = ByteDataUtil.readLazyWord(data, offset, 4).withId("versionNeededToExtract"); - generalPurposeBitFlag = ByteDataUtil.readLazyWord(data, offset, 6).withId("generalPurposeBitFlag"); - compressionMethod = ByteDataUtil.readLazyWord(data, offset, 8).withId("compressionMethod"); - lastModFileTime = ByteDataUtil.readLazyWord(data, offset, 10).withId("lastModFileTime"); - lastModFileDate = ByteDataUtil.readLazyWord(data, offset, 12).withId("lastModFileDate"); - crc32 = ByteDataUtil.readLazyQuad(data, offset, 14).withId("crc32"); - compressedSize = ByteDataUtil.readLazyMaskedLongQuad(data, offset, 18).withId("compressedSize"); - uncompressedSize = ByteDataUtil.readLazyMaskedLongQuad(data, offset, 22).withId("uncompressedSize"); - fileNameLength = ByteDataUtil.readLazyWord(data, offset, 26).withId("fileNameLength"); - extraFieldLength = ByteDataUtil.readLazyWord(data, offset, 28).withId("extraFieldLength"); - fileName = ByteDataUtil.readLazySlice(data, offset, new LazyInt(() -> MIN_FIXED_SIZE), fileNameLength).withId("fileName"); - extraField = ByteDataUtil.readLazySlice(data, offset, fileNameLength.add(MIN_FIXED_SIZE), extraFieldLength).withId("extraField"); + versionNeededToExtract = MemorySegmentUtil.readLazyWord(data, offset, 4).withId("versionNeededToExtract"); + generalPurposeBitFlag = MemorySegmentUtil.readLazyWord(data, offset, 6).withId("generalPurposeBitFlag"); + compressionMethod = MemorySegmentUtil.readLazyWord(data, offset, 8).withId("compressionMethod"); + lastModFileTime = MemorySegmentUtil.readLazyWord(data, offset, 10).withId("lastModFileTime"); + lastModFileDate = MemorySegmentUtil.readLazyWord(data, offset, 12).withId("lastModFileDate"); + crc32 = MemorySegmentUtil.readLazyQuad(data, offset, 14).withId("crc32"); + compressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 18).withId("compressedSize"); + uncompressedSize = MemorySegmentUtil.readLazyMaskedLongQuad(data, offset, 22).withId("uncompressedSize"); + fileNameLength = MemorySegmentUtil.readLazyWord(data, offset, 26).withId("fileNameLength"); + extraFieldLength = MemorySegmentUtil.readLazyWord(data, offset, 28).withId("extraFieldLength"); + fileName = MemorySegmentUtil.readLazySlice(data, offset, new LazyInt(() -> MIN_FIXED_SIZE), fileNameLength).withId("fileName"); + extraField = MemorySegmentUtil.readLazySlice(data, offset, fileNameLength.add(MIN_FIXED_SIZE), extraFieldLength).withId("extraField"); fileDataLength = new LazyLong(() -> { long fileDataLength; if (compressionMethod.get() == STORED) { @@ -97,7 +97,7 @@ public void read(@Nonnull ByteData data, long offset) { } return fileDataLength; }).withId("fileDataLength"); - fileData = ByteDataUtil.readLazyLongSlice(data, offset, + fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), fileDataLength).withId("fileData"); } @@ -159,7 +159,7 @@ public void adoptLinkedCentralDirectoryValues() { return fileDataLength; }).withId("fileDataLength"); if (data != null) - fileData = ByteDataUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), fileDataLength).withId("fileData"); + fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), fileDataLength).withId("fileData"); } } @@ -179,7 +179,7 @@ public void setFileDataEndOffset(long endOffset) { */ public void setFileDataLength(long newLength) { fileDataLength.set(newLength); - fileData = ByteDataUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), newLength).withId("fileData"); + fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), newLength).withId("fileData"); } /** @@ -187,7 +187,7 @@ public void setFileDataLength(long newLength) { */ public void setFileDataLength(@Nonnull LazyLong newLength) { fileDataLength = newLength; - fileData = ByteDataUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), newLength).withId("fileData"); + fileData = MemorySegmentUtil.readLazyLongSlice(data, offset, fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), newLength).withId("fileData"); } @Override @@ -218,7 +218,7 @@ public long offset() { * @throws IOException * When the decompressor fails. */ - public ByteData decompress(Decompressor decompressor) throws IOException { + public MemorySegment decompress(Decompressor decompressor) throws IOException { return decompressor.decompress(this, fileData.get()); } @@ -242,7 +242,7 @@ public void link(CentralDirectoryFileHeader directoryFileHeader) { * * @see #decompress(Decompressor) Decompresses this data. */ - public ByteData getFileData() { + public MemorySegment getFileData() { return fileData.get(); } @@ -250,7 +250,7 @@ public ByteData getFileData() { * @param fileData * Compressed file contents. */ - public void setFileData(ByteData fileData) { + public void setFileData(MemorySegment fileData) { this.fileData.set(fileData); } diff --git a/src/main/java/software/coley/lljzip/format/model/ZipRead.java b/src/main/java/software/coley/lljzip/format/model/ZipRead.java index 984d52a..3773f45 100644 --- a/src/main/java/software/coley/lljzip/format/model/ZipRead.java +++ b/src/main/java/software/coley/lljzip/format/model/ZipRead.java @@ -1,8 +1,7 @@ package software.coley.lljzip.format.model; -import software.coley.lljzip.util.ByteData; - import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; /** * IO operations for children of {@link ZipPart}. @@ -16,5 +15,5 @@ public interface ZipRead { * @param offset * Initial offset in data to start at. */ - void read(@Nonnull ByteData data, long offset); + void read(@Nonnull MemorySegment data, long offset); } diff --git a/src/main/java/software/coley/lljzip/format/read/AdaptingZipReader.java b/src/main/java/software/coley/lljzip/format/read/AdaptingZipReader.java index 8ef5efa..66b4168 100644 --- a/src/main/java/software/coley/lljzip/format/read/AdaptingZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/AdaptingZipReader.java @@ -2,12 +2,12 @@ import software.coley.lljzip.format.model.AdaptingLocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; import javax.annotation.Nonnull; import java.io.File; import java.io.IOException; +import java.io.OutputStream; +import java.lang.foreign.MemorySegment; import java.nio.file.Files; import java.util.Enumeration; import java.util.zip.ZipEntry; @@ -24,11 +24,23 @@ */ public class AdaptingZipReader implements ZipReader { @Override - public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOException { + public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IOException { // Java's ZipFile requires the data be on-disk File temp = File.createTempFile("lljzip", ".tempzip"); try { - Files.write(temp.toPath(), ByteDataUtil.toByteArray(data)); + try (OutputStream os = Files.newOutputStream(temp.toPath())) { + final int BUFFER_SIZE = 16384; + byte[] buf = new byte[BUFFER_SIZE]; + MemorySegment wrapper = MemorySegment.ofArray(buf); + long offset = 0L; + long length = data.byteSize(); + while (offset < length) { + int copyable = (int) Math.min(BUFFER_SIZE, length - offset); + MemorySegment.copy(data, offset, wrapper, 0, copyable); + os.write(buf, 0, copyable); + offset += length; + } + } fill(zip, temp); } finally { temp.delete(); diff --git a/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java b/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java index cc20617..43d59dc 100644 --- a/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/ForwardScanZipReader.java @@ -7,12 +7,12 @@ import software.coley.lljzip.format.model.EndOfCentralDirectory; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.OffsetComparator; import javax.annotation.Nonnull; import java.io.IOException; +import java.lang.foreign.MemorySegment; import java.util.HashSet; import java.util.Set; @@ -41,9 +41,9 @@ public ForwardScanZipReader(@Nonnull ZipPartAllocator allocator) { } @Override - public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOException { + public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IOException { // Read scanning forwards - long endOfCentralDirectoryOffset = ByteDataUtil.indexOfQuad(data, 0, ZipPatterns.END_OF_CENTRAL_DIRECTORY_QUAD); + long endOfCentralDirectoryOffset = MemorySegmentUtil.indexOfQuad(data, 0, ZipPatterns.END_OF_CENTRAL_DIRECTORY_QUAD); if (endOfCentralDirectoryOffset < 0L) throw new IOException("No Central-Directory-File-Header found!"); @@ -53,12 +53,12 @@ public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOExcep zip.addPart(end); // Used for relative offsets as a base. - long zipStart = ByteDataUtil.indexOfQuad(data, 0, ZipPatterns.LOCAL_FILE_HEADER_QUAD); + long zipStart = MemorySegmentUtil.indexOfQuad(data, 0, ZipPatterns.LOCAL_FILE_HEADER_QUAD); // Read central directories - long len = data.length(); + long len = data.byteSize(); long centralDirectoryOffset = zipStart + end.getCentralDirectoryOffset(); - while (centralDirectoryOffset < len && data.getInt(centralDirectoryOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD) { + while (centralDirectoryOffset < len && MemorySegmentUtil.readQuad(data, centralDirectoryOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD) { CentralDirectoryFileHeader directory = new CentralDirectoryFileHeader(); directory.read(data, centralDirectoryOffset); centralDirectoryOffset += directory.length(); @@ -70,7 +70,7 @@ public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOExcep Set offsets = new HashSet<>(); for (CentralDirectoryFileHeader directory : zip.getCentralDirectories()) { long offset = zipStart + directory.getRelativeOffsetOfLocalHeader(); - if (!offsets.contains(offset) && data.getInt(offset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) { + if (!offsets.contains(offset) && MemorySegmentUtil.readQuad(data, offset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) { LocalFileHeader file = newLocalFileHeader(); directory.link(file); file.link(directory); diff --git a/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java b/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java index 692b45b..dcfad74 100644 --- a/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/JvmZipReader.java @@ -4,12 +4,12 @@ import org.slf4j.LoggerFactory; import software.coley.lljzip.format.ZipPatterns; import software.coley.lljzip.format.model.*; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import software.coley.lljzip.util.OffsetComparator; import javax.annotation.Nonnull; import java.io.IOException; +import java.lang.foreign.MemorySegment; import java.util.HashSet; import java.util.Set; import java.util.TreeSet; @@ -61,14 +61,14 @@ public JvmZipReader(@Nonnull ZipPartAllocator allocator, boolean skipRevisitedCe } @Override - public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOException { + public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IOException { // Read scanning backwards - long endOfCentralDirectoryOffset = ByteDataUtil.lastIndexOfQuad(data, data.length() - 4, ZipPatterns.END_OF_CENTRAL_DIRECTORY_QUAD); + long endOfCentralDirectoryOffset = MemorySegmentUtil.lastIndexOfQuad(data, data.byteSize() - 4, ZipPatterns.END_OF_CENTRAL_DIRECTORY_QUAD); if (endOfCentralDirectoryOffset < 0L) throw new IOException("No Central-Directory-File-Header found!"); // Check for a prior end, indicating a preceding ZIP file. - long precedingEndOfCentralDirectory = ByteDataUtil.lastIndexOfQuad(data, endOfCentralDirectoryOffset - 1, ZipPatterns.END_OF_CENTRAL_DIRECTORY_QUAD); + long precedingEndOfCentralDirectory = MemorySegmentUtil.lastIndexOfQuad(data, endOfCentralDirectoryOffset - 1, ZipPatterns.END_OF_CENTRAL_DIRECTORY_QUAD); // Read end header EndOfCentralDirectory end = newEndOfCentralDirectory(); @@ -77,12 +77,12 @@ public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOExcep // Read central directories (going from the back to the front) up until the preceding ZIP file (if any) // but not surpassing the declared cen directory offset in the end of central directory header. - long len = data.length(); + long len = data.byteSize(); long centralDirectoryOffset = len - ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER.length; long maxRelativeOffset = 0; long centralDirectoryOffsetScanEnd = Math.max(Math.max(precedingEndOfCentralDirectory, 0), end.getCentralDirectoryOffset()); while (centralDirectoryOffset > centralDirectoryOffsetScanEnd) { - centralDirectoryOffset = ByteDataUtil.lastIndexOfQuad(data, centralDirectoryOffset - 1L, ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD); + centralDirectoryOffset = MemorySegmentUtil.lastIndexOfQuad(data, centralDirectoryOffset - 1L, ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD); if (centralDirectoryOffset >= 0L) { CentralDirectoryFileHeader directory = newCentralDirectoryFileHeader(); directory.read(data, centralDirectoryOffset); @@ -130,13 +130,13 @@ public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOExcep jvmBaseFileOffset = (end.offset() - end.getCentralDirectorySize()) - end.getCentralDirectoryOffset(); // Now that we have the start offset, scan forward. We can match the current value as well. - jvmBaseFileOffset = ByteDataUtil.indexOfWord(data, jvmBaseFileOffset, ZipPatterns.PK_WORD); + jvmBaseFileOffset = MemorySegmentUtil.indexOfWord(data, jvmBaseFileOffset, ZipPatterns.PK_WORD); while (jvmBaseFileOffset >= 0L) { // Check that the PK discovered represents a valid zip part try { - if (data.getInt(jvmBaseFileOffset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) + if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) new LocalFileHeader().read(data, jvmBaseFileOffset); - else if (data.getInt(jvmBaseFileOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD) + else if (MemorySegmentUtil.readQuad(data, jvmBaseFileOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HEADER_QUAD) new CentralDirectoryFileHeader().read(data, jvmBaseFileOffset); else throw new IllegalStateException("No match for LocalFileHeader/CentralDirectoryFileHeader"); @@ -144,7 +144,7 @@ else if (data.getInt(jvmBaseFileOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HE break; } catch (Exception ex) { // Invalid, seek forward - jvmBaseFileOffset = ByteDataUtil.indexOfWord(data, jvmBaseFileOffset + 1L, ZipPatterns.PK_WORD); + jvmBaseFileOffset = MemorySegmentUtil.indexOfWord(data, jvmBaseFileOffset + 1L, ZipPatterns.PK_WORD); } } } @@ -161,7 +161,7 @@ else if (data.getInt(jvmBaseFileOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HE // Add associated local file header offset long offset = jvmBaseFileOffset + directory.getRelativeOffsetOfLocalHeader(); - if (data.getInt(offset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) { + if (MemorySegmentUtil.readQuad(data, offset) == ZipPatterns.LOCAL_FILE_HEADER_QUAD) { entryOffsets.add(offset); } } @@ -182,7 +182,7 @@ else if (data.getInt(jvmBaseFileOffset) == ZipPatterns.CENTRAL_DIRECTORY_FILE_HE continue; } - if (data.getInt(offset) != ZipPatterns.LOCAL_FILE_HEADER_QUAD) { + if (MemorySegmentUtil.readQuad(data, offset) != ZipPatterns.LOCAL_FILE_HEADER_QUAD) { logger.warn("Central-Directory-File-Header's offset[{}] to Local-File-Header does not match the Local-File-Header magic!", offset); continue; } diff --git a/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java b/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java index b74c58c..966dc11 100644 --- a/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/NaiveLocalFileZipReader.java @@ -3,11 +3,11 @@ import software.coley.lljzip.format.ZipPatterns; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import javax.annotation.Nonnull; import java.io.IOException; +import java.lang.foreign.MemorySegment; /** * A naive strategy that only looks for {@link LocalFileHeader} entries. @@ -33,9 +33,9 @@ public NaiveLocalFileZipReader(@Nonnull ZipPartAllocator allocator) { } @Override - public void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOException { + public void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IOException { long localFileOffset = -1; - while ((localFileOffset = ByteDataUtil.indexOfQuad(data, localFileOffset + 1, ZipPatterns.LOCAL_FILE_HEADER_QUAD)) >= 0) { + while ((localFileOffset = MemorySegmentUtil.indexOfQuad(data, localFileOffset + 1, ZipPatterns.LOCAL_FILE_HEADER_QUAD)) >= 0) { LocalFileHeader file = newLocalFileHeader(); file.read(data, localFileOffset); zip.addPart(file); diff --git a/src/main/java/software/coley/lljzip/format/read/ZipReader.java b/src/main/java/software/coley/lljzip/format/read/ZipReader.java index 67297db..2ae3465 100644 --- a/src/main/java/software/coley/lljzip/format/read/ZipReader.java +++ b/src/main/java/software/coley/lljzip/format/read/ZipReader.java @@ -2,10 +2,10 @@ import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteData; import javax.annotation.Nonnull; import java.io.IOException; +import java.lang.foreign.MemorySegment; /** * Outlines reading binary data into a ZIP data type. @@ -22,7 +22,7 @@ public interface ZipReader { * @throws IOException * When the data cannot be read (EOF, not matching expectations, etc) */ - void read(@Nonnull ZipArchive zip, @Nonnull ByteData data) throws IOException; + void read(@Nonnull ZipArchive zip, @Nonnull MemorySegment data) throws IOException; /** * @param file File to post-process. diff --git a/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java b/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java index fc626a1..42f0e10 100644 --- a/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java +++ b/src/main/java/software/coley/lljzip/format/transform/JvmClassDirectoryMapper.java @@ -28,7 +28,7 @@ public LocalFileHeader mapLocal(@Nonnull ZipArchive archive, @Nonnull LocalFileH if (name.endsWith(".class/")) { int newLength = name.length() - 1; LocalFileHeader copy = localFileHeader.copy(); - copy.setFileName(copy.getFileName().sliceOf(0, newLength)); + copy.setFileName(copy.getFileName().asSlice(0, newLength)); copy.setFileNameLength(newLength); localFileHeader = copy; } @@ -42,7 +42,7 @@ public CentralDirectoryFileHeader mapCentral(@Nonnull ZipArchive archive, @Nonnu if (name.endsWith(".class/")) { int newLength = name.length() - 1; CentralDirectoryFileHeader copy = centralDirectoryFileHeader.copy(); - copy.setFileName(copy.getFileName().sliceOf(0, newLength)); + copy.setFileName(copy.getFileName().asSlice(0, newLength)); copy.setFileNameLength(newLength); centralDirectoryFileHeader = copy; } diff --git a/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java b/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java index a7f5965..e76e65d 100644 --- a/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java +++ b/src/main/java/software/coley/lljzip/format/write/DirectZipWriter.java @@ -5,7 +5,7 @@ import software.coley.lljzip.format.model.EndOfCentralDirectory; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import javax.annotation.Nonnull; import java.io.IOException; @@ -33,9 +33,9 @@ public void write(@Nonnull ZipArchive archive, @Nonnull OutputStream os) throws writeIntLE(os, (int) fileHeader.getUncompressedSize()); writeShortLE(os, fileHeader.getFileNameLength()); writeShortLE(os, fileHeader.getExtraFieldLength()); - os.write(ByteDataUtil.toByteArray(fileHeader.getFileName())); - os.write(ByteDataUtil.toByteArray(fileHeader.getExtraField())); - os.write(ByteDataUtil.toByteArray(fileHeader.getFileData())); + os.write(MemorySegmentUtil.toByteArray(fileHeader.getFileName())); + os.write(MemorySegmentUtil.toByteArray(fileHeader.getExtraField())); + os.write(MemorySegmentUtil.toByteArray(fileHeader.getFileData())); } // Write central directory file headers. @@ -57,9 +57,9 @@ public void write(@Nonnull ZipArchive archive, @Nonnull OutputStream os) throws writeShortLE(os, directory.getInternalFileAttributes()); writeIntLE(os, directory.getExternalFileAttributes()); writeIntLE(os, (int) directory.getRelativeOffsetOfLocalHeader()); - os.write(ByteDataUtil.toByteArray(directory.getFileName())); - os.write(ByteDataUtil.toByteArray(directory.getExtraField())); - os.write(ByteDataUtil.toByteArray(directory.getFileComment())); + os.write(MemorySegmentUtil.toByteArray(directory.getFileName())); + os.write(MemorySegmentUtil.toByteArray(directory.getExtraField())); + os.write(MemorySegmentUtil.toByteArray(directory.getFileComment())); } // Write end of central directory record. @@ -73,7 +73,7 @@ public void write(@Nonnull ZipArchive archive, @Nonnull OutputStream os) throws writeIntLE(os, (int) end.getCentralDirectorySize()); writeIntLE(os, (int) end.getCentralDirectoryOffset()); writeShortLE(os, end.getZipCommentLength()); - os.write(ByteDataUtil.toByteArray(end.getZipComment())); + os.write(MemorySegmentUtil.toByteArray(end.getZipComment())); } } diff --git a/src/main/java/software/coley/lljzip/format/write/ZipOutputStreamZipWriter.java b/src/main/java/software/coley/lljzip/format/write/ZipOutputStreamZipWriter.java index 03534bc..299e1f2 100644 --- a/src/main/java/software/coley/lljzip/format/write/ZipOutputStreamZipWriter.java +++ b/src/main/java/software/coley/lljzip/format/write/ZipOutputStreamZipWriter.java @@ -3,7 +3,7 @@ import software.coley.lljzip.format.compression.ZipCompressions; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import javax.annotation.Nonnull; import java.io.IOException; @@ -44,9 +44,9 @@ public void write(@Nonnull ZipArchive archive, @Nonnull OutputStream os) throws try (ZipOutputStream zos = new ZipOutputStream(os)) { for (LocalFileHeader fileHeader : archive.getLocalFiles()) { String name = fileHeader.getFileNameAsString(); - if (fileHeader.getFileData().length() > 0L) { + if (fileHeader.getFileData().byteSize() > 0L) { zos.putNextEntry(new ZipEntry(name)); - zos.write(ByteDataUtil.toByteArray(ZipCompressions.decompress(fileHeader))); + zos.write(MemorySegmentUtil.toByteArray(ZipCompressions.decompress(fileHeader))); zos.closeEntry(); } else if (createDirectoryEntries) { // Directory, don't need to write anything diff --git a/src/main/java/software/coley/lljzip/util/BufferData.java b/src/main/java/software/coley/lljzip/util/BufferData.java deleted file mode 100644 index 3c4a646..0000000 --- a/src/main/java/software/coley/lljzip/util/BufferData.java +++ /dev/null @@ -1,170 +0,0 @@ -package software.coley.lljzip.util; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * File that is backed by a byte buffer. - * - * @author xDark - */ -public final class BufferData implements ByteData { - private final ByteBuffer buffer; - private final AtomicBoolean cleaned; - - private BufferData(ByteBuffer buffer, AtomicBoolean cleaned) { - this.buffer = buffer; - this.cleaned = cleaned; - } - - @Override - public int getInt(long position) { - ensureOpen(); - return buffer.getInt(validate(position)); - } - - @Override - public long getLong(long position) { - ensureOpen(); - return buffer.getLong(validate(position)); - } - - @Override - public short getShort(long position) { - ensureOpen(); - return buffer.getShort(validate(position)); - } - - @Override - public byte get(long position) { - ensureOpen(); - return buffer.get(validate(position)); - } - - @Override - @SuppressWarnings("all") - public void get(long position, byte[] b, int off, int len) { - ensureOpen(); - // Left intentionally as unchained calls due to API differences across Java versions - // and how the compiler changes output. - ByteBuffer buffer = this.buffer.slice(); - buffer.order(buffer.order()); - ((Buffer) buffer).position(validate(position)); - buffer.get(b, off, len); - } - - @Override - public void transferTo(OutputStream out, byte[] buf) throws IOException { - ensureOpen(); - ByteBuffer buffer = this.buffer; - int remaining = buffer.remaining(); - if (buffer.hasArray()) { - out.write(buffer.array(), buffer.arrayOffset() + buffer.position(), remaining); - } else { - ((Buffer)buffer).mark(); - int copyThreshold = buf.length; - while (remaining != 0) { - int length = Math.min(copyThreshold, remaining); - buffer.get(buf, 0, length); - out.write(buf, 0, length); - remaining -= length; - } - ((Buffer)buffer).reset(); - } - } - - @Override - public ByteData slice(long startIndex, long endIndex) { - ensureOpen(); - return new BufferData(ByteDataUtil.sliceExact(buffer, validate(startIndex), validate(endIndex)), cleaned); - } - - @Override - public long length() { - ensureOpen(); - return ByteDataUtil.length(buffer); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof BufferData)) return false; - BufferData that = (BufferData) o; - return buffer.equals(that.buffer); - } - - @Override - public int hashCode() { - return buffer.hashCode(); - } - - @Override - public void close() { - if (!cleaned.get()) { - synchronized (this) { - if (cleaned.get()) - return; - cleaned.set(true); - ByteBuffer buffer = this.buffer; - if (buffer.isDirect()) { - CleanerUtil.invokeCleaner(buffer); - } - } - } - } - - @Override - public boolean isClosed() { - return cleaned.get(); - } - - private void ensureOpen() { - if (cleaned.get()) - throw new IllegalStateException("Cannot access data after close"); - } - - private static int validate(long v) { - if (v < 0L || v > Integer.MAX_VALUE) { - throw new IllegalArgumentException(Long.toString(v)); - } - return (int) v; - } - - /** - * @param buffer - * Byte buffer to wrap. - * - * @return Buffer data. - */ - public static BufferData wrap(ByteBuffer buffer) { - return new BufferData(buffer, new AtomicBoolean()); - } - - /** - * @param array - * Byte array to wrap. - * - * @return Buffer data. - */ - public static BufferData wrap(byte[] array) { - return new BufferData(ByteBuffer.wrap(array).order(ByteOrder.LITTLE_ENDIAN), new AtomicBoolean()); - } - - /** - * @param array - * Byte array to wrap. - * @param offset - * Offset into the array to start at. - * @param length - * Length of content. - * - * @return Buffer data. - */ - public static BufferData wrap(byte[] array, int offset, int length) { - return new BufferData(ByteBuffer.wrap(array, offset, length).order(ByteOrder.LITTLE_ENDIAN), new AtomicBoolean()); - } -} diff --git a/src/main/java/software/coley/lljzip/util/ByteData.java b/src/main/java/software/coley/lljzip/util/ByteData.java deleted file mode 100644 index b750c67..0000000 --- a/src/main/java/software/coley/lljzip/util/ByteData.java +++ /dev/null @@ -1,115 +0,0 @@ -package software.coley.lljzip.util; - -import java.io.Closeable; -import java.io.IOException; -import java.io.OutputStream; - -/** - * Byte array data. - * - * @author xDark - */ -public interface ByteData extends Closeable { - /** - * @return {@code true} when the data is closed. - */ - boolean isClosed(); - - /** - * Gets int at specific position. - * - * @param position - * Position to read int from. - * - * @return Read int. - */ - int getInt(long position); - - /** - * Gets long at specific position. - * - * @param position - * Position to read long from. - * - * @return Read long. - */ - long getLong(long position); - - /** - * Gets short at specific position. - * - * @param position - * Position to short int from. - * - * @return Read short. - */ - short getShort(long position); - - /** - * Gets byte at specific position. - * - * @param position - * Position to read byte from. - * - * @return Read byte. - */ - byte get(long position); - - /** - * Copied array of bytes from position. - * - * @param position - * Position to copy from. - * @param buffer - * Buffer to store contents in. - * @param off - * Offset in buffer to start from. - * @param len - * Length of content. - */ - void get(long position, byte[] buffer, int off, int len); - - /** - * Transfers data to target output stream. - * - * @param out - * Stream to transfer data to. - * @param buffer - * Buffer to use for transferring. - * - * @throws IOException - * If any I/O error occurs. - */ - void transferTo(OutputStream out, byte[] buffer) throws IOException; - - /** - * Makes a slice of data. - * - * @param startIndex - * Starting index. - * @param endIndex - * End index. - * - * @return Slice of this data. - */ - ByteData slice(long startIndex, long endIndex); - - /** - * Same as above, but with relative length. - * - * @param startIndex - * Starting index. - * @param length - * Slice length. - * - * @return Slice of this data. - */ - default ByteData sliceOf(long startIndex, long length) { - return slice(startIndex, startIndex + length); - } - - /** - * @return Length of the data. - */ - long length(); -} diff --git a/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java b/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java index 3d8c671..51e534c 100644 --- a/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java +++ b/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java @@ -5,6 +5,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; import java.nio.file.attribute.FileTime; import java.util.concurrent.TimeUnit; @@ -24,7 +26,7 @@ public class ExtraFieldTime { public static TimeWrapper read(@Nonnull CentralDirectoryFileHeader header) { int extraLen = header.getExtraFieldLength(); if (extraLen > 0 && extraLen < 0xFFFF) { - ByteData extra = header.getExtraField(); + MemorySegment extra = header.getExtraField(); return read(extra); } return null; @@ -40,21 +42,21 @@ public static TimeWrapper read(@Nonnull CentralDirectoryFileHeader header) { public static TimeWrapper read(@Nonnull LocalFileHeader header) { int extraLen = header.getExtraFieldLength(); if (extraLen > 0 && extraLen < 0xFFFF) { - ByteData extra = header.getExtraField(); + MemorySegment extra = header.getExtraField(); return read(extra); } return null; } @Nonnull - private static TimeWrapper read(@Nonnull ByteData extra) { + private static TimeWrapper read(@Nonnull MemorySegment extra) { TimeWrapper wrapper = new TimeWrapper(); // Reimplementation of 'java.util.zip.ZipEntry#setExtra0(...)' int off = 0; - int len = (int) extra.length(); + int len = (int) extra.byteSize(); while (off + 4 < len) { - int tag = extra.getShort(off); - int size = extra.getShort(off + 2); + int tag = MemorySegmentUtil.readWord(extra, off); + int size = MemorySegmentUtil.readWord(extra, off + 2); off += 4; if (off + size > len) break; @@ -62,23 +64,23 @@ private static TimeWrapper read(@Nonnull ByteData extra) { if (size < 32) // reserved 4 bytes + tag 2 bytes + size 2 bytes break; // m[a|c]time 24 bytes int pos = off + 4; - if (extra.getShort(pos) != 0x0001 || extra.getShort(pos + 2) != 24) + if (MemorySegmentUtil.readWord(extra, pos) != 0x0001 || MemorySegmentUtil.readWord(extra, pos + 2) != 24) break; long wtime; - wtime = extra.getInt(pos + 4) | ((long) extra.getInt(pos + 8) << 32); + wtime = MemorySegmentUtil.readQuad(extra, pos + 4) | ((long) MemorySegmentUtil.readQuad(extra, pos + 8) << 32); if (wtime != Long.MIN_VALUE) { wrapper.modify = winTimeToFileTime(wtime).toMillis(); } - wtime = extra.getInt(pos + 12) | ((long) extra.getInt(pos + 16) << 32); + wtime = MemorySegmentUtil.readQuad(extra, pos + 12) | ((long) MemorySegmentUtil.readQuad(extra, pos + 16) << 32); if (wtime != Long.MIN_VALUE) { wrapper.access = winTimeToFileTime(wtime).toMillis(); } - wtime = extra.getInt(pos + 20) | ((long) extra.getInt(pos + 8) << 24); + wtime = MemorySegmentUtil.readQuad(extra, pos + 20) | ((long) MemorySegmentUtil.readQuad(extra, pos + 8) << 24); if (wtime != Long.MIN_VALUE) { wrapper.creation = winTimeToFileTime(wtime).toMillis(); } } else if (tag == /* EXTID_EXTT */ 0x5455) { - int flag = extra.get(off); + int flag = extra.get(ValueLayout.JAVA_BYTE, off) & 0xff; int localOff = 1; // The CEN-header extra field contains the modification // time only, or no timestamp at all. 'sz' is used to @@ -86,15 +88,15 @@ private static TimeWrapper read(@Nonnull ByteData extra) { // in LOC it must be present in CEN as well. if ((flag & 0x1) != 0 && (localOff + 4) <= size) { // get32S(extra, off + localOff) - wrapper.modify = unixTimeToFileTime(extra.getInt(off + localOff)).toMillis(); + wrapper.modify = unixTimeToFileTime(MemorySegmentUtil.readQuad(extra, off + localOff)).toMillis(); localOff += 4; } if ((flag & 0x2) != 0 && (localOff + 4) <= size) { - wrapper.access = unixTimeToFileTime(extra.getInt(off + localOff)).toMillis(); + wrapper.access = unixTimeToFileTime(MemorySegmentUtil.readQuad(extra, off + localOff)).toMillis(); localOff += 4; } if ((flag & 0x4) != 0 && (localOff + 4) <= size) { - wrapper.creation = unixTimeToFileTime(extra.getInt(off + localOff)).toMillis(); + wrapper.creation = unixTimeToFileTime(MemorySegmentUtil.readQuad(extra, off + localOff)).toMillis(); localOff += 4; } } diff --git a/src/main/java/software/coley/lljzip/util/FastWrapOutputStream.java b/src/main/java/software/coley/lljzip/util/FastWrapOutputStream.java index fb3a358..90bcb40 100644 --- a/src/main/java/software/coley/lljzip/util/FastWrapOutputStream.java +++ b/src/main/java/software/coley/lljzip/util/FastWrapOutputStream.java @@ -1,9 +1,10 @@ package software.coley.lljzip.util; import java.io.ByteArrayOutputStream; +import java.lang.foreign.MemorySegment; /** - * Byte output stream with {@link BufferData} wrapping provided, without the array-copy of {@link #toByteArray()}. + * Byte output stream with {@link MemorySegment} wrapping provided, without the array-copy of {@link #toByteArray()}. * * @author xDark */ @@ -11,7 +12,7 @@ public final class FastWrapOutputStream extends ByteArrayOutputStream { /** * @return Wrapper of the current buffer. */ - public BufferData wrap() { - return BufferData.wrap(buf, 0, count); + public MemorySegment wrap() { + return MemorySegment.ofArray(buf).asSlice(0, count); } } diff --git a/src/main/java/software/coley/lljzip/util/FileMapUtil.java b/src/main/java/software/coley/lljzip/util/FileMapUtil.java deleted file mode 100644 index 323d6e8..0000000 --- a/src/main/java/software/coley/lljzip/util/FileMapUtil.java +++ /dev/null @@ -1,110 +0,0 @@ -package software.coley.lljzip.util; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.ByteOrder; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Maps large files into memory. - * - * @author xDark - */ -public class FileMapUtil { - private static final Method MAP; - private static final Method UNMAP; - private static final boolean OLD_MAP; - - /** - * Maps file into memory. - * - * @param path - * Path to a file to map. - * - * @return Mapped file. - * - * @throws IOException - * If any I/O error occurs. - * @throws IllegalStateException - * If the environment is locked up and - * file is larger than 2GB. - */ - public static ByteData map(Path path) throws IOException { - if (MAP == null) { - long size = Files.size(path); - if (size <= Integer.MAX_VALUE) { - try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) { - long length = fc.size(); - MappedByteBuffer buffer = fc.map(FileChannel.MapMode.READ_ONLY, 0L, length); - buffer.order(ByteOrder.LITTLE_ENDIAN); - return BufferData.wrap(buffer); - } - } - throw new IllegalStateException("Cannot map more than 2GB of data in locked up environment"); - } - try (FileChannel fc = FileChannel.open(path, StandardOpenOption.READ)) { - long length = fc.size(); - long address; - try { - if (OLD_MAP) { - address = (long) MAP.invoke(fc, 0, 0L, length); - } else { - address = (long) MAP.invoke(fc, 0, 0L, length, false); - } - } catch (InvocationTargetException | IllegalAccessException ex) { - throw new IllegalStateException("Could not invoke map0", ex); - } - ByteData mappedFile = new UnsafeMappedFile(address, length, () -> { - try { - UNMAP.invoke(null, address, length); - } catch (IllegalAccessException | InvocationTargetException ex) { - throw new InternalError(ex); - } - }, new AtomicBoolean()); - return mappedFile; - } - } - - static { - boolean oldMap = false; - Method map = null; - Method unmap = null; - get: - { - Class c; - try { - c = Class.forName("sun.nio.ch.FileChannelImpl"); - } catch (ClassNotFoundException ignored) { - break get; - } - try { - map = c.getDeclaredMethod("map0", int.class, long.class, long.class, boolean.class); - } catch (NoSuchMethodException ex) { - try { - map = c.getDeclaredMethod("map0", int.class, long.class, long.class); - oldMap = true; - } catch (NoSuchMethodException ignored) { - break get; - } - } - try { - map.setAccessible(true); - unmap = c.getDeclaredMethod("unmap0", long.class, long.class); - unmap.setAccessible(true); - } catch (Exception ex) { - // Locked up environment, probably threw InaccessibleObjectException - map = null; - unmap = null; - } - } - MAP = map; - UNMAP = unmap; - OLD_MAP = oldMap; - } -} diff --git a/src/main/java/software/coley/lljzip/util/ByteDataUtil.java b/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java similarity index 60% rename from src/main/java/software/coley/lljzip/util/ByteDataUtil.java rename to src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java index e694924..909ba88 100644 --- a/src/main/java/software/coley/lljzip/util/ByteDataUtil.java +++ b/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java @@ -5,18 +5,20 @@ import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; -import java.nio.Buffer; -import java.nio.ByteBuffer; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; -import java.util.Objects; /** - * Buffer utils. + * {@link MemorySegment} utils. * * @author Matt Coley */ -public class ByteDataUtil { +public class MemorySegmentUtil { public static final int WILDCARD = Integer.MIN_VALUE; + private static final ValueLayout.OfInt LITTLE_INT = ValueLayout.JAVA_INT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); + private static final ValueLayout.OfShort LITTLE_SHORT = ValueLayout.JAVA_SHORT_UNALIGNED.withOrder(ByteOrder.LITTLE_ENDIAN); /** * @param data @@ -28,10 +30,10 @@ public class ByteDataUtil { * * @return First index of pattern in content, or {@code -1} for no match. */ - public static long indexOfWord(ByteData data, long offset, int pattern) { - long len = data.length() - 2; + public static long indexOfWord(MemorySegment data, long offset, int pattern) { + long len = data.byteSize() - 2; for (long i = offset; i < len; i++) { - if (pattern == data.getShort(i)) + if (pattern == (data.get(LITTLE_SHORT, i) & 0xFFFF)) return i; } return -1; @@ -47,11 +49,11 @@ public static long indexOfWord(ByteData data, long offset, int pattern) { * * @return First index of pattern in content, or {@code -1} for no match. */ - public static long indexOfQuad(ByteData data, long offset, int pattern) { - long len = data.length() - 4; + public static long indexOfQuad(MemorySegment data, long offset, int pattern) { + long len = data.byteSize() - 4; long i = offset; while (i < len) { - int value = data.getInt(i); + int value = data.get(LITTLE_INT, i); if (pattern == value) return i; @@ -80,12 +82,12 @@ public static long indexOfQuad(ByteData data, long offset, int pattern) { * * @return Last index of pattern in content, or {@code -1} for no match. */ - public static long lastIndexOfWord(ByteData data, long offset, int pattern) { + public static long lastIndexOfWord(MemorySegment data, long offset, int pattern) { long limit; - if (data == null || (limit = data.length()) < 2 || offset >= limit) + if (data == null || (limit = data.byteSize()) < 2 || offset >= limit) return -1; for (long i = offset; i >= 0; i--) { - if (pattern == data.getShort(i)) + if (pattern == data.get(LITTLE_SHORT, i)) return i; } return -1; @@ -102,13 +104,13 @@ public static long lastIndexOfWord(ByteData data, long offset, int pattern) { * * @return Last index of pattern in content, or {@code -1} for no match. */ - public static long lastIndexOfQuad(ByteData data, long offset, int pattern) { + public static long lastIndexOfQuad(MemorySegment data, long offset, int pattern) { long limit; - if (data == null || (limit = data.length()) < 4 || offset >= limit) + if (data == null || (limit = data.byteSize()) < 4 || offset >= limit) return -1; long i = offset; while (i >= 0) { - int value = data.getInt(i); + int value = data.get(LITTLE_INT, i); if (pattern == value) return i; @@ -135,7 +137,7 @@ public static long lastIndexOfQuad(ByteData data, long offset, int pattern) { * * @return First index of pattern in content, or {@code -1} for no match. */ - public static long indexOf(ByteData data, int[] pattern) { + public static long indexOf(MemorySegment data, int[] pattern) { return indexOf(data, 0, pattern); } @@ -149,10 +151,10 @@ public static long indexOf(ByteData data, int[] pattern) { * * @return First index of pattern in content, or {@code -1} for no match. */ - public static long indexOf(ByteData data, long offset, int[] pattern) { + public static long indexOf(MemorySegment data, long offset, int[] pattern) { // Remaining data must be as long as pattern long limit; - if (data == null || (limit = data.length()) < pattern.length || offset >= limit) + if (data == null || (limit = data.byteSize()) < pattern.length || offset >= limit) return -1; // Search from offset going forwards @@ -172,8 +174,8 @@ public static long indexOf(ByteData data, long offset, int[] pattern) { * * @return Last index of pattern in content, or {@code -1} for no match. */ - public static long lastIndexOf(ByteData data, int[] pattern) { - return lastIndexOf(data, (data.length() - pattern.length), pattern); + public static long lastIndexOf(MemorySegment data, int[] pattern) { + return lastIndexOf(data, (data.byteSize() - pattern.length), pattern); } /** @@ -186,9 +188,9 @@ public static long lastIndexOf(ByteData data, int[] pattern) { * * @return Last index of pattern in content, or {@code -1} for no match. */ - public static long lastIndexOf(ByteData data, long offset, int[] pattern) { + public static long lastIndexOf(MemorySegment data, long offset, int[] pattern) { // Remaining data must be as long as pattern - if (data == null || data.length() < pattern.length) + if (data == null || data.byteSize() < pattern.length) return -1; // Search from offset going backwards @@ -210,9 +212,9 @@ public static long lastIndexOf(ByteData data, long offset, int[] pattern) { * * @return {@code true} when the content of the array at the offset matches the pattern. */ - public static boolean startsWith(ByteData data, long offset, int[] pattern) { + public static boolean startsWith(MemorySegment data, long offset, int[] pattern) { // Remaining data must be as long as pattern and in the array bounds - if (data == null || (data.length() - offset) < pattern.length || offset < 0 || offset >= data.length()) + if (data == null || (data.byteSize() - offset) < pattern.length || offset < 0 || offset >= data.byteSize()) return false; // Check for mis-match @@ -220,7 +222,7 @@ public static boolean startsWith(ByteData data, long offset, int[] pattern) { int p = pattern[i]; if (p == WILDCARD) continue; - if (data.get(offset + i) != p) + if ((data.get(ValueLayout.JAVA_BYTE, offset + i) & 0xff) != p) return false; } @@ -236,8 +238,8 @@ public static boolean startsWith(ByteData data, long offset, int[] pattern) { * * @return Value of word. */ - public static int readWord(ByteData data, long i) { - return data.getShort(i) & 0xFFFF; + public static int readWord(MemorySegment data, long i) { + return data.get(LITTLE_SHORT, i) & 0xFFFF; } /** @@ -248,8 +250,8 @@ public static int readWord(ByteData data, long i) { * * @return Value of quad. */ - public static int readQuad(ByteData data, long i) { - return data.getInt(i); + public static int readQuad(MemorySegment data, long i) { + return data.get(LITTLE_INT, i); } /** @@ -262,11 +264,10 @@ public static int readQuad(ByteData data, long i) { * * @return Value of string. */ - public static String readString(ByteData data, long start, int len) { + public static String readString(MemorySegment data, long start, long len) { if (len == 0) return ""; - byte[] bytes = new byte[len]; - data.get(start, bytes, 0, len); + byte[] bytes = data.asSlice(start, len).toArray(ValueLayout.JAVA_BYTE); return new String(bytes, StandardCharsets.UTF_8); } @@ -280,51 +281,8 @@ public static String readString(ByteData data, long start, int len) { * * @return Copy of range. */ - public static byte[] readArray(ByteData data, int start, int len) { - byte[] bytes = new byte[len]; - data.get(start, bytes, 0, len); - return bytes; - } - - /** - * @param data - * Content to make slice of. - * @param start - * Start index. - * @param end - * Endex index. - * - * @return Buffer slice. - */ - public static ByteBuffer sliceExact(ByteBuffer data, int start, int end) { - ByteBuffer slice = data.slice(); - slice = ((ByteBuffer) ((Buffer) slice).position(start)).slice(); - ((Buffer) slice).limit(end - start); - return slice.order(data.order()); - } - - /** - * @param data - * Content to make slice of. - * @param start - * Start index. - * @param len - * Length of content to make slice of. - * - * @return Buffer slice. - */ - public static ByteData slice(ByteData data, long start, long len) { - return data.slice(start, start + len); - } - - /** - * @param data - * Content to get length of. - * - * @return Buffer length. - */ - public static int length(ByteBuffer data) { - return data.remaining() - data.position(); + public static byte[] readArray(MemorySegment data, long start, long len) { + return data.asSlice(start, len).toArray(ValueLayout.JAVA_BYTE); } /** @@ -333,14 +291,12 @@ public static int length(ByteBuffer data) { * * @return Buffer as byte array. */ - public static byte[] toByteArray(ByteData data) { - long length = data.length(); + public static byte[] toByteArray(MemorySegment data) { + long length = data.byteSize(); if (length > Integer.MAX_VALUE - 8) { throw new IllegalStateException("Data too big!"); } - byte[] bytes = new byte[(int) length]; - data.get(0L, bytes, 0, bytes.length); - return bytes; + return data.toArray(ValueLayout.JAVA_BYTE); } /** @@ -349,22 +305,10 @@ public static byte[] toByteArray(ByteData data) { * * @return Buffer as a string. */ - public static String toString(ByteData data) { + public static String toString(MemorySegment data) { return new String(toByteArray(data), StandardCharsets.UTF_8); } - /** - * @param a - * First buffer. - * @param b - * Second buffer. - * - * @return {@code true} if buffers are equal. - */ - public static boolean equals(ByteData a, ByteData b) { - return Objects.equals(a, b); - } - /** * @param data * Content to get word from. @@ -375,10 +319,8 @@ public static boolean equals(ByteData a, ByteData b) { * * @return Lazily populated word. */ - public static LazyInt readLazyWord(ByteData data, long headerOffset, int localOffset) { + public static LazyInt readLazyWord(MemorySegment data, long headerOffset, int localOffset) { return new LazyInt(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); return readWord(data, headerOffset + localOffset); }); } @@ -393,10 +335,8 @@ public static LazyInt readLazyWord(ByteData data, long headerOffset, int localOf * * @return Lazily populated quad. */ - public static LazyInt readLazyQuad(ByteData data, long headerOffset, int localOffset) { + public static LazyInt readLazyQuad(MemorySegment data, long headerOffset, int localOffset) { return new LazyInt(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); return readQuad(data, headerOffset + localOffset); }); } @@ -411,10 +351,8 @@ public static LazyInt readLazyQuad(ByteData data, long headerOffset, int localOf * * @return Lazily populated masked quad. */ - public static LazyInt readLazyMaskedQuad(ByteData data, long headerOffset, int localOffset) { + public static LazyInt readLazyMaskedQuad(MemorySegment data, long headerOffset, int localOffset) { return new LazyInt(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); return readQuad(data, headerOffset + localOffset) & 0xFFFF; }); } @@ -429,10 +367,8 @@ public static LazyInt readLazyMaskedQuad(ByteData data, long headerOffset, int l * * @return Lazily populated long word. */ - public static LazyLong readLazyLongWord(ByteData data, long headerOffset, int localOffset) { + public static LazyLong readLazyLongWord(MemorySegment data, long headerOffset, int localOffset) { return new LazyLong(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); return readWord(data, headerOffset + localOffset); }); } @@ -447,10 +383,8 @@ public static LazyLong readLazyLongWord(ByteData data, long headerOffset, int lo * * @return Lazily populated masked long quad. */ - public static LazyLong readLazyMaskedLongQuad(ByteData data, long headerOffset, int localOffset) { + public static LazyLong readLazyMaskedLongQuad(MemorySegment data, long headerOffset, int localOffset) { return new LazyLong(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); return readQuad(data, headerOffset + localOffset) & 0xFFFFFFFFL; }); } @@ -465,11 +399,9 @@ public static LazyLong readLazyMaskedLongQuad(ByteData data, long headerOffset, * * @return Lazily populated slice. */ - public static LazyByteData readLazySlice(ByteData data, long headerOffset, LazyInt localOffset, LazyInt length) { + public static LazyByteData readLazySlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyInt length) { return new LazyByteData(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); - return data.sliceOf(headerOffset + localOffset.get(), length.get()); + return data.asSlice(headerOffset + localOffset.get(), length.get()); }); } @@ -483,11 +415,9 @@ public static LazyByteData readLazySlice(ByteData data, long headerOffset, LazyI * * @return Lazily populated long slice. */ - public static LazyByteData readLazyLongSlice(ByteData data, long headerOffset, LazyInt localOffset, LazyLong length) { + public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyLong length) { return new LazyByteData(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); - return data.sliceOf(headerOffset + localOffset.get(), length.get()); + return data.asSlice(headerOffset + localOffset.get(), length.get()); }); } @@ -501,11 +431,9 @@ public static LazyByteData readLazyLongSlice(ByteData data, long headerOffset, L * * @return Lazily populated long slice. */ - public static LazyByteData readLazyLongSlice(ByteData data, long headerOffset, LazyLong localOffset, LazyLong length) { + public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffset, LazyLong localOffset, LazyLong length) { return new LazyByteData(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); - return data.sliceOf(headerOffset + localOffset.get(), length.get()); + return data.asSlice(headerOffset + localOffset.get(), length.get()); }); } @@ -519,11 +447,9 @@ public static LazyByteData readLazyLongSlice(ByteData data, long headerOffset, L * * @return Lazily populated long slice. */ - public static LazyByteData readLazyLongSlice(ByteData data, long headerOffset, LazyInt localOffset, long length) { + public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, long length) { return new LazyByteData(() -> { - if (data.isClosed()) - throw new IllegalStateException("Cannot read from closed data source"); - return data.sliceOf(headerOffset + localOffset.get(), length); + return data.asSlice(headerOffset + localOffset.get(), length); }); } } diff --git a/src/main/java/software/coley/lljzip/util/NoopByteData.java b/src/main/java/software/coley/lljzip/util/NoopByteData.java deleted file mode 100644 index ce96a28..0000000 --- a/src/main/java/software/coley/lljzip/util/NoopByteData.java +++ /dev/null @@ -1,66 +0,0 @@ -package software.coley.lljzip.util; - -import java.io.OutputStream; - -/** - * Empty file that yields {@code 0} for everything. - * - * @author Matt Coley - */ -public class NoopByteData implements ByteData { - /** - * Shared instance. - */ - public static final NoopByteData INSTANCE = new NoopByteData(); - - private NoopByteData() { - // deny construction - } - - @Override - public boolean isClosed() { - return false; - } - - @Override - public int getInt(long position) { - return 0; - } - - @Override - public long getLong(long position) { - return 0; - } - - @Override - public short getShort(long position) { - return 0; - } - - @Override - public byte get(long position) { - return 0; - } - - @Override - public void get(long position, byte[] buffer, int off, int len) { - } - - @Override - public void transferTo(OutputStream out, byte[] buffer) { - } - - @Override - public ByteData slice(long startIndex, long endIndex) { - return this; - } - - @Override - public long length() { - return 0; - } - - @Override - public void close() { - } -} diff --git a/src/main/java/software/coley/lljzip/util/UnsafeMappedFile.java b/src/main/java/software/coley/lljzip/util/UnsafeMappedFile.java deleted file mode 100644 index 86a729c..0000000 --- a/src/main/java/software/coley/lljzip/util/UnsafeMappedFile.java +++ /dev/null @@ -1,176 +0,0 @@ -package software.coley.lljzip.util; - -import sun.misc.Unsafe; - -import java.io.IOException; -import java.io.OutputStream; -import java.nio.ByteOrder; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Mapped file backed by address & length. - * - * @author xDark - */ -final class UnsafeMappedFile implements ByteData { - private static final boolean SWAP = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; - private static final Unsafe UNSAFE = UnsafeUtil.get(); - - private final AtomicBoolean cleaned; - private final long address; - private final long end; - private final Runnable deallocator; - @SuppressWarnings("unused") - private final Object attachment; - - private UnsafeMappedFile(Object attachment, long address, long end, AtomicBoolean cleaned) { - this.attachment = attachment; - this.address = address; - this.end = end; - this.cleaned = cleaned; - deallocator = null; - } - - UnsafeMappedFile(long address, long length, Runnable deallocator, AtomicBoolean cleaned) { - this.address = address; - this.end = address + length; - this.deallocator = deallocator; - this.cleaned = cleaned; - attachment = null; - } - - @Override - public int getInt(long position) { - ensureOpen(); - return swap(UNSAFE.getInt(validate(position))); - } - - @Override - public long getLong(long position) { - ensureOpen(); - return swap(UNSAFE.getLong(validate(position))); - } - - @Override - public short getShort(long position) { - ensureOpen(); - return swap(UNSAFE.getShort(validate(position))); - } - - @Override - public byte get(long position) { - ensureOpen(); - return UNSAFE.getByte(validate(position)); - } - - @Override - public void get(long position, byte[] buffer, int off, int len) { - ensureOpen(); - long address = validate(position); - if (address + len > end) - throw new IllegalArgumentException(); - UNSAFE.copyMemory(null, address, buffer, Unsafe.ARRAY_BYTE_BASE_OFFSET + off, len); - } - - @Override - public void transferTo(OutputStream out, byte[] buffer) throws IOException { - ensureOpen(); - int copyThreshold = buffer.length; - long address = this.address; - long remaining = end - address; - while (remaining != 0L) { - int length = (int) Math.min(copyThreshold, remaining); - UNSAFE.copyMemory(null, address, buffer, Unsafe.ARRAY_BYTE_BASE_OFFSET, length); - remaining -= length; - address += length; - out.write(buffer, 0, length); - } - } - - @Override - public ByteData slice(long startIndex, long endIndex) { - ensureOpen(); - if (startIndex > endIndex) - throw new IllegalArgumentException(); - return new UnsafeMappedFile(this, validate(startIndex), validate(endIndex), cleaned); - } - - @Override - public long length() { - ensureOpen(); - return end - address; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof UnsafeMappedFile)) return false; - - UnsafeMappedFile that = (UnsafeMappedFile) o; - - if (address != that.address) return false; - return end == that.end; - } - - @Override - public int hashCode() { - long address = this.address; - int result = Long.hashCode(address); - result = 31 * result + Long.hashCode((end - address)); - return result; - } - - @Override - public void close() { - if (!cleaned.get()) { - synchronized (this) { - if (cleaned.get()) - return; - cleaned.set(true); - Runnable deallocator = this.deallocator; - if (deallocator != null) - deallocator.run(); - } - } - } - - @Override - public boolean isClosed() { - return cleaned.get(); - } - - private void ensureOpen() { - if (cleaned.get()) - throw new IllegalStateException("Cannot access data after close"); - } - - private long validate(long position) { - if (position < 0L) { - throw new IllegalArgumentException(); - } - position += address; - if (position > end) { - long diff = position - end; - throw new IllegalArgumentException("positon beyond max bounds: " + position + " > " + end + " diff: " + diff); - } - return position; - } - - private static long swap(long x) { - if (SWAP) - return Long.reverseBytes(x); - return x; - } - - private static int swap(int x) { - if (SWAP) - return Integer.reverseBytes(x); - return x; - } - - private static short swap(short x) { - if (SWAP) - return (short) (((x >> 8) & 0xFF) | (x << 8)); - return x; - } -} diff --git a/src/main/java/software/coley/lljzip/util/lazy/LazyByteData.java b/src/main/java/software/coley/lljzip/util/lazy/LazyByteData.java index 3de6dc5..4aea330 100644 --- a/src/main/java/software/coley/lljzip/util/lazy/LazyByteData.java +++ b/src/main/java/software/coley/lljzip/util/lazy/LazyByteData.java @@ -1,22 +1,21 @@ package software.coley.lljzip.util.lazy; -import software.coley.lljzip.util.ByteData; - import javax.annotation.Nonnull; +import java.lang.foreign.MemorySegment; import java.util.Objects; import java.util.function.Supplier; /** - * Lazy {@link ByteData} getter. + * Lazy {@link MemorySegment} getter. */ -public class LazyByteData extends Lazy> { - private ByteData value; +public class LazyByteData extends Lazy> { + private MemorySegment value; /** * @param lookup * Lazy lookup. */ - public LazyByteData(@Nonnull Supplier lookup) { + public LazyByteData(@Nonnull Supplier lookup) { super(lookup); } @@ -35,7 +34,7 @@ public LazyByteData copy() { * @param value * Data value. */ - public void set(@Nonnull ByteData value) { + public void set(@Nonnull MemorySegment value) { set = true; this.value = value; } @@ -44,7 +43,7 @@ public void set(@Nonnull ByteData value) { * @return Data value. */ @Nonnull - public ByteData get() { + public MemorySegment get() { if (!set) { value = lookup.get(); set = true; @@ -54,7 +53,7 @@ public ByteData get() { @Override public String toString() { - return id + " data[" + get().length() + "]"; + return id + " data[" + get().byteSize() + "]"; } @Override diff --git a/src/test/java/software/coley/lljzip/JarInJarUtils.java b/src/test/java/software/coley/lljzip/JarInJarUtils.java index 6991b35..b978a40 100644 --- a/src/test/java/software/coley/lljzip/JarInJarUtils.java +++ b/src/test/java/software/coley/lljzip/JarInJarUtils.java @@ -5,10 +5,10 @@ import software.coley.lljzip.format.compression.ZipCompressions; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import java.io.IOException; +import java.lang.foreign.MemorySegment; import java.util.function.Supplier; /** @@ -38,7 +38,7 @@ public static void handleJar(Supplier archiveSupplier) throws IOExce // as we do on the root (that classes can be parsed) handleJar(() -> { try { - ByteData decompressed = ZipCompressions.decompress(lfh); + MemorySegment decompressed = ZipCompressions.decompress(lfh); return ZipIO.readStandard(decompressed); } catch (IOException ex) { throw new IllegalStateException("Failed to read inner jar: " + entryName, ex); @@ -58,7 +58,7 @@ public static void handleJar(Supplier archiveSupplier) throws IOExce * When the class couldn't be parsed. */ public static void handleClass(String name, LocalFileHeader localFileHeader) throws IOException { - byte[] entryData = ByteDataUtil.toByteArray(ZipCompressions.decompress(localFileHeader)); + byte[] entryData = MemorySegmentUtil.toByteArray(ZipCompressions.decompress(localFileHeader)); try { ClassReader reader = new ClassReader(entryData); reader.accept(new ClassWriter(0), 0); diff --git a/src/test/java/software/coley/lljzip/PartParseTests.java b/src/test/java/software/coley/lljzip/PartParseTests.java index 3b9b129..ee0ee11 100644 --- a/src/test/java/software/coley/lljzip/PartParseTests.java +++ b/src/test/java/software/coley/lljzip/PartParseTests.java @@ -10,7 +10,7 @@ import software.coley.lljzip.format.model.ZipArchive; import software.coley.lljzip.format.model.ZipPart; import software.coley.lljzip.format.read.ForwardScanZipReader; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import javax.annotation.Nonnull; import java.io.IOException; @@ -97,8 +97,8 @@ public void testConcatAndMerged(String name) { LocalFileHeader stdHello = zipStd.getLocalFileByName("Hello.class"); LocalFileHeader jvmHello = zipJvm.getLocalFileByName("Hello.class"); assertNotEquals(stdHello, jvmHello); - String stdHelloRaw = ByteDataUtil.toString(ZipCompressions.decompress(stdHello)); - String jvmHelloRaw = ByteDataUtil.toString(ZipCompressions.decompress(jvmHello)); + String stdHelloRaw = MemorySegmentUtil.toString(ZipCompressions.decompress(stdHello)); + String jvmHelloRaw = MemorySegmentUtil.toString(ZipCompressions.decompress(jvmHello)); assertFalse(stdHelloRaw.isEmpty()); assertFalse(jvmHelloRaw.isEmpty()); assertTrue(stdHelloRaw.contains("Hello world")); @@ -137,7 +137,7 @@ public void testJvmCanRecoverData(String name) { ZipArchive zip = ZipIO.readJvm(path); List localFiles = zip.getNameFilteredLocalFiles(n -> n.contains(".class")); assertEquals(1, localFiles.size(), "More than 1 class"); - byte[] decompressed = ByteDataUtil.toByteArray(ZipCompressions.decompress(localFiles.get(0))); + byte[] decompressed = MemorySegmentUtil.toByteArray(ZipCompressions.decompress(localFiles.get(0))); String decompressedStr = new String(decompressed); assertDoesNotThrow(() -> { ClassWriter cw = new ClassWriter(0); @@ -162,7 +162,7 @@ public void testLocalHeaderDetectMismatch() { LocalFileHeader hello = zipStd.getLocalFileByName("Hello.class"); assertNotNull(hello); - assertEquals(0, hello.getFileData().length()); // Should be empty + assertEquals(0, hello.getFileData().byteSize()); // Should be empty // The local file header says the contents are 0 bytes, but the central header has the real length assertTrue(hello.hasDifferentValuesThanCentralDirectoryHeader()); @@ -176,12 +176,12 @@ public void postProcessLocalFileHeader(@Nonnull LocalFileHeader file) { }); LocalFileHeader helloAdopted = zipStdAndAdopt.getLocalFileByName("Hello.class"); assertFalse(helloAdopted.hasDifferentValuesThanCentralDirectoryHeader()); - assertNotEquals(0, helloAdopted.getFileData().length()); // Should have data + assertNotEquals(0, helloAdopted.getFileData().byteSize()); // Should have data // The JVM strategy copies most properties, except for size. ZipArchive zipJvm = ZipIO.readJvm(path); helloAdopted = zipJvm.getLocalFileByName("Hello.class"); - assertNotEquals(0, helloAdopted.getFileData().length()); // Should have data, even if not sourced from values in the CEN + assertNotEquals(0, helloAdopted.getFileData().byteSize()); // Should have data, even if not sourced from values in the CEN } catch (IOException ex) { fail(ex); } diff --git a/src/test/java/software/coley/lljzip/Utils.java b/src/test/java/software/coley/lljzip/Utils.java index 612fa16..2f2b9e6 100644 --- a/src/test/java/software/coley/lljzip/Utils.java +++ b/src/test/java/software/coley/lljzip/Utils.java @@ -4,8 +4,9 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; -import software.coley.lljzip.util.ByteData; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; + +import java.lang.foreign.MemorySegment; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -50,7 +51,7 @@ public void visitLdcInsn(Object value) { * @param target * String instance to look for. */ - public static void assertDefinesString(ByteData code, String target) { - assertDefinesString(ByteDataUtil.toByteArray(code), target); + public static void assertDefinesString(MemorySegment code, String target) { + assertDefinesString(MemorySegmentUtil.toByteArray(code), target); } } diff --git a/src/test/java/software/coley/lljzip/ZipComparisonShowcaseTest.java b/src/test/java/software/coley/lljzip/ZipComparisonShowcaseTest.java index 2cc6d57..fcbe759 100644 --- a/src/test/java/software/coley/lljzip/ZipComparisonShowcaseTest.java +++ b/src/test/java/software/coley/lljzip/ZipComparisonShowcaseTest.java @@ -6,7 +6,7 @@ import software.coley.lljzip.format.compression.ZipCompressions; import software.coley.lljzip.format.model.LocalFileHeader; import software.coley.lljzip.format.model.ZipArchive; -import software.coley.lljzip.util.ByteDataUtil; +import software.coley.lljzip.util.MemorySegmentUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -72,7 +72,7 @@ public void test(String name) { ZipArchive zipJvm = ZipIO.readJvm(path); for (LocalFileHeader lfh : zipJvm.getLocalFiles()) { String entryName = lfh.getFileNameAsString(); - byte[] entryData = ByteDataUtil.toByteArray(ZipCompressions.decompress(lfh)); + byte[] entryData = MemorySegmentUtil.toByteArray(ZipCompressions.decompress(lfh)); handle(entryName, entryData); } } catch (Exception ex) { @@ -84,7 +84,7 @@ public void test(String name) { ZipArchive zipJvm = ZipIO.readStandard(path); for (LocalFileHeader lfh : zipJvm.getLocalFiles()) { String entryName = lfh.getFileNameAsString(); - byte[] entryData = ByteDataUtil.toByteArray(ZipCompressions.decompress(lfh)); + byte[] entryData = MemorySegmentUtil.toByteArray(ZipCompressions.decompress(lfh)); handle(entryName, entryData); } } catch (Exception ex) { @@ -96,7 +96,7 @@ public void test(String name) { ZipArchive zipJvm = ZipIO.readNaive(path); for (LocalFileHeader lfh : zipJvm.getLocalFiles()) { String entryName = lfh.getFileNameAsString(); - byte[] entryData = ByteDataUtil.toByteArray(ZipCompressions.decompress(lfh)); + byte[] entryData = MemorySegmentUtil.toByteArray(ZipCompressions.decompress(lfh)); handle(entryName, entryData); } } catch (Exception ex) { From c30bbcabd629b3c3d6cfffca0613751cff94926b Mon Sep 17 00:00:00 2001 From: xdark Date: Fri, 12 Apr 2024 01:20:35 +0300 Subject: [PATCH 2/9] Bump JDK to 22 in GitHub Actions --- .github/workflows/build.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 414f225..dceb555 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: fail-fast: false matrix: os: [ ubuntu-latest ] - java-version: [ 17 ] + java-version: [ 22 ] runs-on: ubuntu-latest timeout-minutes: 10 steps: @@ -33,7 +33,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 22 check-latest: true # The project version extract NEEDS to have the maven wrapper already downloaded. # So we have a dummy step here just to initialize it. @@ -90,7 +90,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: temurin - java-version: 17 + java-version: 22 # The project version extract NEEDS to have the maven wrapper already downloaded. # So we have a dummy step here just to initialize it. - name: Download Maven wrapper From 905d881564af4893b108221feab40909d99979d3 Mon Sep 17 00:00:00 2001 From: xdark Date: Fri, 12 Apr 2024 01:27:45 +0300 Subject: [PATCH 3/9] Rename LazyByteData to LazyMemorySegment --- .../format/model/AbstractZipFileHeader.java | 6 +++--- .../format/model/AdaptingLocalFileHeader.java | 10 +++++----- .../model/CentralDirectoryFileHeader.java | 4 ++-- .../lljzip/format/model/LocalFileHeader.java | 4 ++-- .../coley/lljzip/util/MemorySegmentUtil.java | 18 +++++++++--------- ...azyByteData.java => LazyMemorySegment.java} | 10 +++++----- 6 files changed, 26 insertions(+), 26 deletions(-) rename src/main/java/software/coley/lljzip/util/lazy/{LazyByteData.java => LazyMemorySegment.java} (78%) diff --git a/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java b/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java index 6bc5134..9063c9a 100644 --- a/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java @@ -3,7 +3,7 @@ import software.coley.lljzip.format.compression.Decompressor; import software.coley.lljzip.format.compression.ZipCompressions; import software.coley.lljzip.util.MemorySegmentUtil; -import software.coley.lljzip.util.lazy.LazyByteData; +import software.coley.lljzip.util.lazy.LazyMemorySegment; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; @@ -28,8 +28,8 @@ public abstract class AbstractZipFileHeader implements ZipPart, ZipRead { protected LazyLong uncompressedSize; protected LazyInt fileNameLength; protected LazyInt extraFieldLength; - protected LazyByteData fileName; - protected LazyByteData extraField; + protected LazyMemorySegment fileName; + protected LazyMemorySegment extraField; // Offset into the data this part is read from protected transient long offset = -1L; diff --git a/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java index fb193a3..3f9d6af 100644 --- a/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/AdaptingLocalFileHeader.java @@ -1,6 +1,6 @@ package software.coley.lljzip.format.model; -import software.coley.lljzip.util.lazy.LazyByteData; +import software.coley.lljzip.util.lazy.LazyMemorySegment; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; @@ -44,19 +44,19 @@ public AdaptingLocalFileHeader(@Nonnull ZipFile archive, @Nonnull ZipEntry entry lastModFileTime = new LazyInt(() -> 0); lastModFileDate = new LazyInt(() -> 0); fileNameLength = new LazyInt(entryName::length); - fileName = new LazyByteData(() -> MemorySegment.ofArray(entryName.getBytes())); + fileName = new LazyMemorySegment(() -> MemorySegment.ofArray(entryName.getBytes())); fileDataLength = new LazyLong(() -> entryData.length); - fileData = new LazyByteData(() -> MemorySegment.ofArray(entryData)); + fileData = new LazyMemorySegment(() -> MemorySegment.ofArray(entryData)); compressionMethod = new LazyInt(() -> 0); uncompressedSize = new LazyLong(() -> entryData.length); compressedSize = new LazyLong(() -> entryData.length); crc32 = new LazyInt(() -> (int) entry.getCrc()); if (extra != null) { extraFieldLength = new LazyInt(() -> extra.length); - extraField = new LazyByteData(() -> MemorySegment.ofArray(extra)); + extraField = new LazyMemorySegment(() -> MemorySegment.ofArray(extra)); } else { extraFieldLength = new LazyInt(() -> 0); - extraField = new LazyByteData(() -> MemorySegment.ofArray(new byte[0])); + extraField = new LazyMemorySegment(() -> MemorySegment.ofArray(new byte[0])); } data = MemorySegment.ofArray(new byte[0]); } diff --git a/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java b/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java index c3f39bc..399b844 100644 --- a/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java @@ -1,7 +1,7 @@ package software.coley.lljzip.format.model; import software.coley.lljzip.util.MemorySegmentUtil; -import software.coley.lljzip.util.lazy.LazyByteData; +import software.coley.lljzip.util.lazy.LazyMemorySegment; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; @@ -43,7 +43,7 @@ public class CentralDirectoryFileHeader extends AbstractZipFileHeader { // CentralDirectoryFileHeader spec (plus common elements between this and local file) private LazyInt versionMadeBy; private LazyInt fileCommentLength; - private LazyByteData fileComment; + private LazyMemorySegment fileComment; private LazyInt diskNumberStart; private LazyInt internalFileAttributes; private LazyInt externalFileAttributes; diff --git a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java index 18ed88c..e5fa62c 100644 --- a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java @@ -3,7 +3,7 @@ import software.coley.lljzip.format.compression.Decompressor; import software.coley.lljzip.format.read.ZipReader; import software.coley.lljzip.util.MemorySegmentUtil; -import software.coley.lljzip.util.lazy.LazyByteData; +import software.coley.lljzip.util.lazy.LazyMemorySegment; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; @@ -42,7 +42,7 @@ public class LocalFileHeader extends AbstractZipFileHeader { protected transient CentralDirectoryFileHeader linkedDirectoryFileHeader; // LocalFileHeader spec (plus common elements between this and central file) - protected LazyByteData fileData; + protected LazyMemorySegment fileData; // Caches protected transient LazyLong fileDataLength; diff --git a/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java b/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java index 909ba88..70b87c6 100644 --- a/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java +++ b/src/main/java/software/coley/lljzip/util/MemorySegmentUtil.java @@ -1,7 +1,7 @@ package software.coley.lljzip.util; import software.coley.lljzip.format.model.ZipPart; -import software.coley.lljzip.util.lazy.LazyByteData; +import software.coley.lljzip.util.lazy.LazyMemorySegment; import software.coley.lljzip.util.lazy.LazyInt; import software.coley.lljzip.util.lazy.LazyLong; @@ -399,8 +399,8 @@ public static LazyLong readLazyMaskedLongQuad(MemorySegment data, long headerOff * * @return Lazily populated slice. */ - public static LazyByteData readLazySlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyInt length) { - return new LazyByteData(() -> { + public static LazyMemorySegment readLazySlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyInt length) { + return new LazyMemorySegment(() -> { return data.asSlice(headerOffset + localOffset.get(), length.get()); }); } @@ -415,8 +415,8 @@ public static LazyByteData readLazySlice(MemorySegment data, long headerOffset, * * @return Lazily populated long slice. */ - public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyLong length) { - return new LazyByteData(() -> { + public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, LazyLong length) { + return new LazyMemorySegment(() -> { return data.asSlice(headerOffset + localOffset.get(), length.get()); }); } @@ -431,8 +431,8 @@ public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffs * * @return Lazily populated long slice. */ - public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffset, LazyLong localOffset, LazyLong length) { - return new LazyByteData(() -> { + public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long headerOffset, LazyLong localOffset, LazyLong length) { + return new LazyMemorySegment(() -> { return data.asSlice(headerOffset + localOffset.get(), length.get()); }); } @@ -447,8 +447,8 @@ public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffs * * @return Lazily populated long slice. */ - public static LazyByteData readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, long length) { - return new LazyByteData(() -> { + public static LazyMemorySegment readLazyLongSlice(MemorySegment data, long headerOffset, LazyInt localOffset, long length) { + return new LazyMemorySegment(() -> { return data.asSlice(headerOffset + localOffset.get(), length); }); } diff --git a/src/main/java/software/coley/lljzip/util/lazy/LazyByteData.java b/src/main/java/software/coley/lljzip/util/lazy/LazyMemorySegment.java similarity index 78% rename from src/main/java/software/coley/lljzip/util/lazy/LazyByteData.java rename to src/main/java/software/coley/lljzip/util/lazy/LazyMemorySegment.java index 4aea330..983f2fb 100644 --- a/src/main/java/software/coley/lljzip/util/lazy/LazyByteData.java +++ b/src/main/java/software/coley/lljzip/util/lazy/LazyMemorySegment.java @@ -8,14 +8,14 @@ /** * Lazy {@link MemorySegment} getter. */ -public class LazyByteData extends Lazy> { +public class LazyMemorySegment extends Lazy> { private MemorySegment value; /** * @param lookup * Lazy lookup. */ - public LazyByteData(@Nonnull Supplier lookup) { + public LazyMemorySegment(@Nonnull Supplier lookup) { super(lookup); } @@ -23,8 +23,8 @@ public LazyByteData(@Nonnull Supplier lookup) { * @return Copy. */ @Nonnull - public LazyByteData copy() { - LazyByteData copy = new LazyByteData(lookup); + public LazyMemorySegment copy() { + LazyMemorySegment copy = new LazyMemorySegment(lookup); copy.id = id; if (set) copy.set(value); return copy; @@ -61,7 +61,7 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; - LazyByteData that = (LazyByteData) o; + LazyMemorySegment that = (LazyMemorySegment) o; return Objects.equals(get(), that.get()); } From 0131d7e1bcb6ce4c055110089c6cc0bedccf288d Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 13 Apr 2024 00:52:27 -0400 Subject: [PATCH 4/9] Note on edge case test being disabled after JDK 22 update --- jreleaser.yml | 2 +- pom.xml | 4 ++-- .../coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java | 8 +++++--- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/jreleaser.yml b/jreleaser.yml index 36c0472..3d2c24d 100644 --- a/jreleaser.yml +++ b/jreleaser.yml @@ -10,7 +10,7 @@ project: inceptionYear: 2022 stereotype: none java: - version: 11 + version: 22 groupId: software.coley artifactId: lljzip diff --git a/pom.xml b/pom.xml index 106e36c..5de767e 100644 --- a/pom.xml +++ b/pom.xml @@ -97,9 +97,9 @@ org.apache.maven.plugins maven-compiler-plugin + 3.13.0 - 22 - 22 + 22 diff --git a/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java b/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java index 2fb25e1..80a9e19 100644 --- a/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java +++ b/src/test/java/software/coley/lljzip/JvmVsZipFileEqualityEdgeCaseTests.java @@ -1,5 +1,6 @@ package software.coley.lljzip; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import software.coley.lljzip.format.model.AbstractZipFileHeader; @@ -13,7 +14,8 @@ import java.util.stream.Collectors; import java.util.zip.ZipFile; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; /** * Some edge case tests where {@link JvmZipReader} needs to match a few quirky cases from {@link ZipFile}. @@ -21,7 +23,7 @@ * @author Matt Coley */ public class JvmVsZipFileEqualityEdgeCaseTests { - + @Disabled("Updating to JDK changes how ZipFile works, which no longer reads the test sample") @ParameterizedTest @ValueSource(strings = { "resource-pack-trick-header-N-to-1-cen-to-loc-mapping.zip", @@ -43,7 +45,7 @@ public void test(String name) { assertEquals(0, namesDifference.size()); assertEquals(sizeDel, sizeJvm); } catch (Exception ex) { - ex.printStackTrace(); + fail(ex); } } } From f33b7642f27eee58ee41728dc6596220e2f20488 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 13 Apr 2024 01:03:16 -0400 Subject: [PATCH 5/9] Specify name trust in javadocs for zip file header types --- .../format/model/AbstractZipFileHeader.java | 6 ---- .../model/CentralDirectoryFileHeader.java | 32 +++++++++++++++++ .../lljzip/format/model/LocalFileHeader.java | 36 +++++++++++++++++++ 3 files changed, 68 insertions(+), 6 deletions(-) diff --git a/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java b/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java index 9063c9a..8603548 100644 --- a/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/AbstractZipFileHeader.java @@ -225,9 +225,6 @@ public void setExtraFieldLength(int extraFieldLength) { } /** - * Should match {@link LocalFileHeader#getFileName()} but is not a strict requirement. - * If they do not match, trust this value instead. - * * @return File name. */ public MemorySegment getFileName() { @@ -244,9 +241,6 @@ public void setFileName(MemorySegment fileName) { } /** - * Should match {@link CentralDirectoryFileHeader#getFileName()} but is not a strict requirement. - * If they do not match, the central directory file name should be trusted instead. - * * @return File name. */ public String getFileNameAsString() { diff --git a/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java b/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java index 399b844..c101761 100644 --- a/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/CentralDirectoryFileHeader.java @@ -121,6 +121,38 @@ public PartType type() { return PartType.CENTRAL_DIRECTORY_FILE_HEADER; } + /** + * Should match {@link LocalFileHeader#getFileNameLength()} but is not a strict requirement. + * If they do not match, trust this value instead. + * + * @return File name length. + */ + @Override + public int getFileNameLength() { + return super.getFileNameLength(); + } + + /** + * Should match {@link LocalFileHeader#getFileName()} but is not a strict requirement. + * If they do not match, trust this value instead. + * + * @return File name. + */ + @Override + public MemorySegment getFileName() { + return super.getFileName(); + } + + /** + * Should match {@link LocalFileHeader#getFileName()} but is not a strict requirement. + * If they do not match, trust this value instead. + * + * @return File name. + */ + @Override + public String getFileNameAsString() { + return super.getFileNameAsString(); + } /** * @return The file header associated with {@link #getRelativeOffsetOfLocalHeader()}. May be {@code null}. diff --git a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java index e5fa62c..460b0ba 100644 --- a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java @@ -101,6 +101,42 @@ public void read(@Nonnull MemorySegment data, long offset) { fileNameLength.add(extraFieldLength).add(MIN_FIXED_SIZE), fileDataLength).withId("fileData"); } + + + /** + * Should match {@link CentralDirectoryFileHeader#getFileNameLength()} but is not a strict requirement. + * If they do not match, the central directory file name length should be trusted instead. + * + * @return File name length. + */ + @Override + public int getFileNameLength() { + return super.getFileNameLength(); + } + + /** + * Should match {@link CentralDirectoryFileHeader#getFileName()} but is not a strict requirement. + * If they do not match, the central directory file name should be trusted instead. + * + * @return File name. + */ + @Override + public MemorySegment getFileName() { + + return super.getFileName(); + } + + /** + * Should match {@link CentralDirectoryFileHeader#getFileName()} but is not a strict requirement. + * If they do not match, the central directory file name should be trusted instead. + * + * @return File name. + */ + @Override + public String getFileNameAsString() { + return super.getFileNameAsString(); + } + /** * Checks if the contents do not match those described in {@link CentralDirectoryFileHeader}. * If this is the case you will probably want to change your ZIP reading configuration. From 2bffed884de9af7d2ce4689b6137b6d55ccb6287 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 13 Apr 2024 01:03:25 -0400 Subject: [PATCH 6/9] Remove unused cleaner util --- .../coley/lljzip/util/CleanerUtil.java | 90 ------------------- 1 file changed, 90 deletions(-) delete mode 100644 src/main/java/software/coley/lljzip/util/CleanerUtil.java diff --git a/src/main/java/software/coley/lljzip/util/CleanerUtil.java b/src/main/java/software/coley/lljzip/util/CleanerUtil.java deleted file mode 100644 index 1e6a9c0..0000000 --- a/src/main/java/software/coley/lljzip/util/CleanerUtil.java +++ /dev/null @@ -1,90 +0,0 @@ -package software.coley.lljzip.util; - -import sun.misc.Unsafe; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.nio.ByteBuffer; - -/** - * Utility to invoke cleaners in a {@link ByteBuffer}. - * - * @author xDark - */ -public final class CleanerUtil { - private static final Method INVOKE_CLEANER; - private static final Method GET_CLEANER; - private static final boolean SUPPORTED; - - private CleanerUtil() { - } - - /** - * Attempts to clean direct buffer. - * - * @param buffer - * Buffer to clean. - * - * @throws IllegalStateException - * If buffer is not direct, slice or duplicate, or - * cleaner failed to invoke. - */ - public static void invokeCleaner(ByteBuffer buffer) { - if (!buffer.isDirect()) { - throw new IllegalStateException("buffer is not direct"); - } - if (!SUPPORTED) { - return; - } - Method getCleaner = GET_CLEANER; - Method invokeCleaner = INVOKE_CLEANER; - try { - if (getCleaner != null) { - Object cleaner = getCleaner.invoke(buffer); - if (cleaner == null) { - throw new IllegalStateException("slice or duplicate"); - } - invokeCleaner.invoke(cleaner); - } else { - invokeCleaner.invoke(UnsafeUtil.get(), buffer); - } - } catch (InvocationTargetException ex) { - throw new IllegalStateException("Failed to invoke clean method", ex.getTargetException()); - } catch (IllegalAccessException ex) { - throw new IllegalStateException("cleaner became inaccessible", ex); - } - } - - static { - boolean supported = false; - Method invokeCleaner; - Method getCleaner = null; - try { - invokeCleaner = Unsafe.class.getDeclaredMethod("invokeCleaner", ByteBuffer.class); - invokeCleaner.setAccessible(true); - ByteBuffer tmp = ByteBuffer.allocateDirect(1); - invokeCleaner.invoke(UnsafeUtil.get(), tmp); - supported = true; - } catch (NoSuchMethodException ignored) { - supported = true; - ByteBuffer tmp = ByteBuffer.allocateDirect(1); - try { - Class directBuffer = Class.forName("sun.nio.ch.DirectBuffer"); - getCleaner = directBuffer.getDeclaredMethod("cleaner"); - invokeCleaner = getCleaner.getReturnType().getDeclaredMethod("clean"); - invokeCleaner.setAccessible(true); - getCleaner.setAccessible(true); - invokeCleaner.invoke(getCleaner.invoke(tmp)); - } catch (Exception ignored1) { - invokeCleaner = null; - getCleaner = null; - supported = false; - } - } catch (Exception ex) { - invokeCleaner = null; - } - INVOKE_CLEANER = invokeCleaner; - GET_CLEANER = getCleaner; - SUPPORTED = supported; - } -} From 573baba2e2033395610688caf4349a93cb6d8bf1 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 13 Apr 2024 01:03:41 -0400 Subject: [PATCH 7/9] Update readme now that we use MemorySegment --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 3e4db01..d1a362b 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,7 @@ But that's not all it does. That's just what that one comment says. Some other f ## Additional features -- Reads ZIP files using `Unsafe` backed mapped files. - - Using `FileChannel.map` yields `MappedByteBuffer` which uses `int` values, limiting files up to about 2GB - - Our `UnsafeMappedFile` implementation uses `long` which far exceeds the GB file size range +- Reads ZIP files using `MemorySegment` backed mapped files. - Highly configurable, offering 3 ZIP reading strategies out of the box _(See `ZipIO` for convenience calls)_ - Std / Forward scanning: Scans for `EndOfCentralDirectory` from the front of the file, like many other tools - Naive: Scans only for `LocalFileHeader` values from the front of the file, the fastest implementation, but obviously naive From 2edc97862aa437e17130ad801cbb515eeea30625 Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 13 Apr 2024 01:11:21 -0400 Subject: [PATCH 8/9] Language migrations from 8 --> 22 --- .../java/software/coley/lljzip/ZipIO.java | 2 +- .../format/compression/ZipCompressions.java | 104 +++++++----------- .../lljzip/format/model/LocalFileHeader.java | 5 - .../coley/lljzip/format/model/ZipArchive.java | 2 +- .../format/transform/ZipPartMapper.java | 16 ++- .../coley/lljzip/util/ExtraFieldTime.java | 2 +- .../coley/lljzip/util/NameComparator.java | 8 +- 7 files changed, 49 insertions(+), 90 deletions(-) diff --git a/src/main/java/software/coley/lljzip/ZipIO.java b/src/main/java/software/coley/lljzip/ZipIO.java index f359969..f599900 100644 --- a/src/main/java/software/coley/lljzip/ZipIO.java +++ b/src/main/java/software/coley/lljzip/ZipIO.java @@ -172,7 +172,7 @@ public static ZipArchive readJvm(Path path) throws IOException { * * @return Archive from path. * - * @throws IOException + * @throws IOException When the archive cannot be read. */ public static ZipArchive readAdaptingIO(Path path) throws IOException { ZipArchive archive = new ZipArchive(); diff --git a/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java b/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java index 2b8434a..b4c7b7f 100644 --- a/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java +++ b/src/main/java/software/coley/lljzip/format/compression/ZipCompressions.java @@ -131,66 +131,37 @@ public interface ZipCompressions { * @return Name of method. */ static String getName(int method) { - switch (method) { - case STORED: - return "STORED"; - case SHRUNK: - return "SHRUNK"; - case REDUCED_F1: - return "REDUCED_F1"; - case REDUCED_F2: - return "REDUCED_F2"; - case REDUCED_F3: - return "REDUCED_F3"; - case REDUCED_F4: - return "REDUCED_F4"; - case IMPLODED: - return "IMPLODED"; - case RESERVED_TOKENIZING: - return "RESERVED_TOKENIZING"; - case DEFLATED: - return "DEFLATED"; - case DEFLATED_64: - return "DEFLATED_64"; - case PKWARE_IMPLODING: - return "PKWARE_IMPLODING"; - case PKWARE_RESERVED_11: - return "PKWARE_RESERVED_11"; - case BZIP2: - return "BZIP2"; - case PKWARE_RESERVED_13: - return "PKWARE_RESERVED_13"; - case LZMA: - return "LZMA"; - case PKWARE_RESERVED_15: - return "PKWARE_RESERVED_15"; - case CMPSC: - return "CMPSC"; - case PKWARE_RESERVED_17: - return "PKWARE_RESERVED_17"; - case IBM_TERSE: - return "IBM_TERSE"; - case IBM_LZ77: - return "IBM_LZ77"; - case DEPRECATED_ZSTD: - return "DEPRECATED_ZSTD"; - case ZSTANDARD: - return "ZSTANDARD"; - case MP3: - return "MP3"; - case XZ: - return "XZ"; - case JPEG: - return "JPEG"; - case WAVPACK: - return "WAVPACK"; - case PPMD: - return "PPMD"; - case AE_x: - return "AE_x"; - default: - return "Unknown[" + method + "]"; - } + return switch (method) { + case STORED -> "STORED"; + case SHRUNK -> "SHRUNK"; + case REDUCED_F1 -> "REDUCED_F1"; + case REDUCED_F2 -> "REDUCED_F2"; + case REDUCED_F3 -> "REDUCED_F3"; + case REDUCED_F4 -> "REDUCED_F4"; + case IMPLODED -> "IMPLODED"; + case RESERVED_TOKENIZING -> "RESERVED_TOKENIZING"; + case DEFLATED -> "DEFLATED"; + case DEFLATED_64 -> "DEFLATED_64"; + case PKWARE_IMPLODING -> "PKWARE_IMPLODING"; + case PKWARE_RESERVED_11 -> "PKWARE_RESERVED_11"; + case BZIP2 -> "BZIP2"; + case PKWARE_RESERVED_13 -> "PKWARE_RESERVED_13"; + case LZMA -> "LZMA"; + case PKWARE_RESERVED_15 -> "PKWARE_RESERVED_15"; + case CMPSC -> "CMPSC"; + case PKWARE_RESERVED_17 -> "PKWARE_RESERVED_17"; + case IBM_TERSE -> "IBM_TERSE"; + case IBM_LZ77 -> "IBM_LZ77"; + case DEPRECATED_ZSTD -> "DEPRECATED_ZSTD"; + case ZSTANDARD -> "ZSTANDARD"; + case MP3 -> "MP3"; + case XZ -> "XZ"; + case JPEG -> "JPEG"; + case WAVPACK -> "WAVPACK"; + case PPMD -> "PPMD"; + case AE_x -> "AE_x"; + default -> "Unknown[" + method + "]"; + }; } /** @@ -204,15 +175,14 @@ static String getName(int method) { */ static MemorySegment decompress(LocalFileHeader header) throws IOException { int method = header.getCompressionMethod(); - switch (method) { - case STORED: - return header.getFileData(); - case DEFLATED: - return header.decompress(UnsafeDeflateDecompressor.INSTANCE); - default: + return switch (method) { + case STORED -> header.getFileData(); + case DEFLATED -> header.decompress(UnsafeDeflateDecompressor.INSTANCE); + default -> { // TODO: Support other decompressing techniques String methodName = getName(method); throw new IOException("Unsupported compression method: " + methodName); - } + } + }; } } diff --git a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java index 460b0ba..6883c43 100644 --- a/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java +++ b/src/main/java/software/coley/lljzip/format/model/LocalFileHeader.java @@ -240,11 +240,6 @@ public PartType type() { return PartType.LOCAL_FILE_HEADER; } - @Override - public long offset() { - return offset; - } - /** * @param decompressor * Decompressor implementation. diff --git a/src/main/java/software/coley/lljzip/format/model/ZipArchive.java b/src/main/java/software/coley/lljzip/format/model/ZipArchive.java index 1f9245b..4ea84e5 100644 --- a/src/main/java/software/coley/lljzip/format/model/ZipArchive.java +++ b/src/main/java/software/coley/lljzip/format/model/ZipArchive.java @@ -157,7 +157,7 @@ public List getNameFilteredLocalFiles(Predicate nameFil public LocalFileHeader getLocalFileByName(String name) { List matches = getNameFilteredLocalFiles(name::equals); if (matches.isEmpty()) return null; - return matches.get(0); + return matches.getFirst(); } /** diff --git a/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java b/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java index c3cce57..7aed95d 100644 --- a/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java +++ b/src/main/java/software/coley/lljzip/format/transform/ZipPartMapper.java @@ -21,15 +21,13 @@ public interface ZipPartMapper { */ @Nullable default ZipPart map(@Nonnull ZipArchive archive, @Nonnull ZipPart part) { - if (part instanceof LocalFileHeader) { - return mapLocal(archive, (LocalFileHeader) part); - } else if (part instanceof CentralDirectoryFileHeader) { - return mapCentral(archive, (CentralDirectoryFileHeader) part); - } else if (part instanceof EndOfCentralDirectory) { - return mapEnd(archive, (EndOfCentralDirectory) part); - } - // Unknown part type, keep as-is. - return part; + return switch (part) { + case LocalFileHeader localFileHeader -> mapLocal(archive, localFileHeader); + case CentralDirectoryFileHeader centralDirectoryFileHeader -> + mapCentral(archive, centralDirectoryFileHeader); + case EndOfCentralDirectory endOfCentralDirectory -> mapEnd(archive, endOfCentralDirectory); + default -> part; // Unknown part type, keep as-is. + }; } /** diff --git a/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java b/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java index 51e534c..f7c48bd 100644 --- a/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java +++ b/src/main/java/software/coley/lljzip/util/ExtraFieldTime.java @@ -115,7 +115,7 @@ private static TimeWrapper read(@Nonnull MemorySegment extra) { */ @Nonnull public static FileTime winTimeToFileTime(long time) { - return FileTime.from(time / 10 + -11644473600000000L /* windows epoch */, TimeUnit.MICROSECONDS); + return FileTime.from(time / 10 - 11644473600000000L /* windows epoch */, TimeUnit.MICROSECONDS); } /** diff --git a/src/main/java/software/coley/lljzip/util/NameComparator.java b/src/main/java/software/coley/lljzip/util/NameComparator.java index 722c9d6..3027d95 100644 --- a/src/main/java/software/coley/lljzip/util/NameComparator.java +++ b/src/main/java/software/coley/lljzip/util/NameComparator.java @@ -27,9 +27,7 @@ public NameComparator(Comparator fallback) { @Override public int compare(ZipPart o1, ZipPart o2) { - if (o1 instanceof LocalFileHeader && o2 instanceof LocalFileHeader) { - LocalFileHeader header1 = (LocalFileHeader) o1; - LocalFileHeader header2 = (LocalFileHeader) o2; + if (o1 instanceof LocalFileHeader header1 && o2 instanceof LocalFileHeader header2) { String name1 = Optional.ofNullable(header1.getLinkedDirectoryFileHeader()) .map(CentralDirectoryFileHeader::getFileNameAsString) .orElse(header1.getFileNameAsString()); @@ -37,9 +35,7 @@ public int compare(ZipPart o1, ZipPart o2) { .map(CentralDirectoryFileHeader::getFileNameAsString) .orElse(header2.getFileNameAsString()); return name1.compareTo(name2); - } else if (o1 instanceof CentralDirectoryFileHeader && o2 instanceof CentralDirectoryFileHeader) { - CentralDirectoryFileHeader header1 = (CentralDirectoryFileHeader) o1; - CentralDirectoryFileHeader header2 = (CentralDirectoryFileHeader) o2; + } else if (o1 instanceof CentralDirectoryFileHeader header1 && o2 instanceof CentralDirectoryFileHeader header2) { String name1 = header1.getFileNameAsString(); String name2 = header2.getFileNameAsString(); return name1.compareTo(name2); From 99db173cf707b55bb7cb6230cb0fd118094941bf Mon Sep 17 00:00:00 2001 From: Matt Date: Sat, 13 Apr 2024 00:53:41 -0400 Subject: [PATCH 9/9] Bump version --> 2.4.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5de767e..90d5722 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ software.coley lljzip - 2.3.0 + 2.4.0 LL Java ZIP Lower level ZIP support for Java