From 4f34a8a2891071e90daa195c155fdc1c4d7406d5 Mon Sep 17 00:00:00 2001 From: Rob Greene Date: Sun, 3 Jun 2018 12:33:09 -0500 Subject: [PATCH] Introducing Entry/EntryType/AppleSingleReader to make reading aspects more common across tools. --- .../applesingle/AppleSingle.java | 165 +++++++----------- .../applesingle/AppleSingleReader.java | 91 ++++++++++ .../applecommander/applesingle/Entry.java | 52 ++++++ .../applecommander/applesingle/EntryType.java | 44 +++++ .../applesingle/FileDatesInfo.java | 9 + .../applesingle/ProdosFileInfo.java | 9 + .../applesingle/tools/asu/AnalyzeCommand.java | 154 ++++++---------- 7 files changed, 321 insertions(+), 203 deletions(-) create mode 100644 api/src/main/java/io/github/applecommander/applesingle/AppleSingleReader.java create mode 100644 api/src/main/java/io/github/applecommander/applesingle/Entry.java create mode 100644 api/src/main/java/io/github/applecommander/applesingle/EntryType.java diff --git a/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java b/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java index 2510a0a..b0f5c80 100644 --- a/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java +++ b/api/src/main/java/io/github/applecommander/applesingle/AppleSingle.java @@ -1,6 +1,5 @@ package io.github.applecommander.applesingle; -import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -11,7 +10,9 @@ import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Path; import java.time.Instant; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; @@ -34,35 +35,16 @@ public class AppleSingle { public static final int MAGIC_NUMBER = 0x0051600; public static final int VERSION_NUMBER1 = 0x00010000; public static final int VERSION_NUMBER2 = 0x00020000; - public static final Map ENTRY_TYPE_NAMES = new HashMap() { - private static final long serialVersionUID = 7142066556402030814L; - { - put(1, "Data Fork"); - put(2, "Resource Fork"); - put(3, "Real Name"); - put(4, "Comment"); - put(5, "Icon, B&W"); - put(6, "Icon, Color"); - put(7, "File Info"); - put(8, "File Dates Info"); - put(9, "Finder Info"); - put(10, "Macintosh File Info"); - put(11, "ProDOS File Info"); - put(12, "MS-DOS File Info"); - put(13, "Short Name"); - put(14, "AFP File Info"); - put(15, "Directory ID"); - }}; - private Map> entryConsumers = new HashMap<>(); + private Map> entryConsumers = new HashMap<>(); { - entryConsumers.put(1, this::setDataFork); - entryConsumers.put(2, this::setResourceFork); - entryConsumers.put(3, this::setRealName); - entryConsumers.put(8, this::setFileDatesInfo); - entryConsumers.put(11, this::setProdosFileInfo); + entryConsumers.put(1, entry -> this.dataFork = entry.getData()); + entryConsumers.put(2, entry -> this.resourceFork = entry.getData()); + entryConsumers.put(3, entry -> this.realName = Utilities.entryToAsciiString(entry)); + entryConsumers.put(8, entry -> this.fileDatesInfo = FileDatesInfo.fromEntry(entry)); + entryConsumers.put(11, entry -> this.prodosFileInfo = ProdosFileInfo.fromEntry(entry)); } - + private byte[] dataFork; private byte[] resourceFork; private String realName; @@ -72,66 +54,13 @@ public class AppleSingle { private AppleSingle() { // Allow Builder construction } - private AppleSingle(byte[] data) throws IOException { - ByteBuffer buffer = ByteBuffer.wrap(data) - .order(ByteOrder.BIG_ENDIAN) - .asReadOnlyBuffer(); - required(buffer, MAGIC_NUMBER, "Not an AppleSingle file - magic number does not match."); - required(buffer, VERSION_NUMBER2, "Only AppleSingle version 2 supported."); - buffer.position(buffer.position() + 16); // Skip filler - int entries = buffer.getShort(); - for (int i = 0; i < entries; i++) { - int entryId = buffer.getInt(); - int offset = buffer.getInt(); - int length = buffer.getInt(); - buffer.mark(); - buffer.position(offset); - byte[] entryData = new byte[length]; - buffer.get(entryData); - // Defer to the proper set method or crash if we don't support that type of entry - Optional.ofNullable(entryConsumers.get(entryId)) - .orElseThrow(() -> new IOException(String.format("Unsupported entry type of %04X (%s)", entryId, - ENTRY_TYPE_NAMES.getOrDefault(entryId, "Unknown")))) - .accept(entryData); - buffer.reset(); - } - } - private void required(ByteBuffer buffer, int expected, String message) throws IOException { - int actual = buffer.getInt(); - if (actual != expected) { - throw new IOException(String.format("%s Expected 0x%08x but read 0x%08x.", message, expected, actual)); - } - } - private void setDataFork(byte[] entryData) { - this.dataFork = entryData; - } - private void setResourceFork(byte[] entryData) { - this.resourceFork = entryData; - } - private void setRealName(byte[] entryData) { - for (int i=0; i entries) throws IOException { + entries.forEach(entry -> { + Optional.ofNullable(entry) + .map(Entry::getEntryId) + .map(entryConsumers::get) + .ifPresent(c -> c.accept(entry)); + }); } public byte[] getDataFork() { @@ -228,7 +157,7 @@ public class AppleSingle { public static AppleSingle read(InputStream inputStream) throws IOException { Objects.requireNonNull(inputStream, "Please supply an input stream"); - return read(AppleSingle.toByteArray(inputStream)); + return read(Utilities.toByteArray(inputStream)); } public static AppleSingle read(File file) throws IOException { Objects.requireNonNull(file, "Please supply a file"); @@ -236,11 +165,54 @@ public class AppleSingle { } public static AppleSingle read(Path path) throws IOException { Objects.requireNonNull(path, "Please supply a file"); - return new AppleSingle(Files.readAllBytes(path)); + return read(Files.readAllBytes(path)); } public static AppleSingle read(byte[] data) throws IOException { Objects.requireNonNull(data); - return new AppleSingle(data); + return new AppleSingle(asEntries(data)); + } + + public static List asEntries(InputStream inputStream) throws IOException { + Objects.requireNonNull(inputStream); + return asEntries(Utilities.toByteArray(inputStream)); + } + public static List asEntries(File file) throws IOException { + Objects.requireNonNull(file); + return asEntries(file.toPath()); + } + public static List asEntries(Path path) throws IOException { + Objects.requireNonNull(path); + return asEntries(Files.readAllBytes(path)); + } + public static List asEntries(byte[] data) throws IOException { + Objects.requireNonNull(data); + return asEntries(AppleSingleReader.builder().data(data).build()); + } + public static List asEntries(AppleSingleReader reader) throws IOException { + Objects.requireNonNull(reader); + List entries = new ArrayList<>(); + required(reader, "Magic number", "Not an AppleSingle file - magic number does not match.", MAGIC_NUMBER); + int version = required(reader, "Version", "Only AppleSingle version 1 and 2 supported.", VERSION_NUMBER1, VERSION_NUMBER2); + reader.reportVersion(version); + reader.read(16, "Filler"); + int numberOfEntries = reader.read(Short.BYTES, "Number of entries").getShort(); + reader.reportNumberOfEntries(numberOfEntries); + for (int i = 0; i < numberOfEntries; i++) { + Entry entry = Entry.create(reader); + entries.add(entry); + reader.reportEntry(entry); + } + return entries; + } + private static int required(AppleSingleReader reader, String description, String message, int... expecteds) throws IOException { + int actual = reader.read(Integer.BYTES, description).getInt(); + for (int expected : expecteds) { + if (actual == expected) return actual; + } + List versions = new ArrayList<>(); + for (int expected : expecteds) versions.add(String.format("0x%08x", expected)); + throw new IOException(String.format("%s Expected %s but read 0x%08x.", + message, String.join(",", versions), actual)); } public static Builder builder() { @@ -324,17 +296,4 @@ public class AppleSingle { return as; } } - - /** Utility method to read all bytes from an InputStream. May move if more utility methods appear. */ - public static byte[] toByteArray(InputStream inputStream) throws IOException { - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - while (true) { - byte[] buf = new byte[1024]; - int len = inputStream.read(buf); - if (len == -1) break; - outputStream.write(buf, 0, len); - } - outputStream.flush(); - return outputStream.toByteArray(); - } } diff --git a/api/src/main/java/io/github/applecommander/applesingle/AppleSingleReader.java b/api/src/main/java/io/github/applecommander/applesingle/AppleSingleReader.java new file mode 100644 index 0000000..f4ccb27 --- /dev/null +++ b/api/src/main/java/io/github/applecommander/applesingle/AppleSingleReader.java @@ -0,0 +1,91 @@ +package io.github.applecommander.applesingle; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; +import java.util.function.Consumer; + +public final class AppleSingleReader { + private byte[] data; + private int pos = 0; + private Consumer versionReporter = v -> {}; + private Consumer numberOfEntriesReporter = n -> {}; + private Consumer entryReporter = e -> {}; + private ReadAtReporter readAtReporter = (s,l,b,d) -> {}; + + public ByteBuffer read(int len, String description) { + try { + return readAt(pos, len, description); + } finally { + pos += len; + } + } + public ByteBuffer readAt(int start, int len, String description) { + byte[] chunk = new byte[len]; + System.arraycopy(data, start, chunk, 0, len); + readAtReporter.accept(start, len, chunk, description); + ByteBuffer buffer = ByteBuffer.wrap(chunk) + .order(ByteOrder.BIG_ENDIAN); + return buffer; + } + public void reportVersion(int version) { + versionReporter.accept(version); + } + public void reportNumberOfEntries(int numberOfEntries) { + numberOfEntriesReporter.accept(numberOfEntries); + } + public void reportEntry(Entry entry) { + entryReporter.accept(entry); + } + + public static Builder builder() { + return new Builder(); + } + public static class Builder { + private AppleSingleReader reader = new AppleSingleReader(); + private Builder() { + // Prevent construction + } + public Builder data(byte[] data) { + Objects.requireNonNull(data); + reader.data = data; + return this; + } + public Builder versionReporter(Consumer consumer) { + Objects.requireNonNull(consumer); + reader.versionReporter = reader.versionReporter.andThen(consumer); + return this; + } + public Builder numberOfEntriesReporter(Consumer consumer) { + Objects.requireNonNull(consumer); + reader.numberOfEntriesReporter = reader.numberOfEntriesReporter.andThen(consumer); + return this; + } + public Builder entryReporter(Consumer consumer) { + Objects.requireNonNull(consumer); + reader.entryReporter = reader.entryReporter.andThen(consumer); + return this; + } + public Builder readAtReporter(ReadAtReporter consumer) { + Objects.requireNonNull(consumer); + reader.readAtReporter = reader.readAtReporter.andThen(consumer); + return this; + } + public AppleSingleReader build() { + Objects.requireNonNull(reader.data, "You must supply a byte[] of data"); + return reader; + } + } + + /** + * A reporter for the {@code AppleSingleReader#readAt(int, int, String)} method, + * heaviliy modeled on the {@code Consumer} interface. + */ + public interface ReadAtReporter { + public void accept(int start, int len, byte[] data, String description); + default ReadAtReporter andThen(ReadAtReporter after) { + Objects.requireNonNull(after); + return (s,l,b,d) -> { accept(s,l,b,d); after.accept(s,l,b,d); }; + } + } +} \ No newline at end of file diff --git a/api/src/main/java/io/github/applecommander/applesingle/Entry.java b/api/src/main/java/io/github/applecommander/applesingle/Entry.java new file mode 100644 index 0000000..15c4f22 --- /dev/null +++ b/api/src/main/java/io/github/applecommander/applesingle/Entry.java @@ -0,0 +1,52 @@ +package io.github.applecommander.applesingle; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.Objects; + +public class Entry { + public static final int LENGTH = 12; + private int entryId; + private int offset; + private int length; + private byte[] data; + + public static Entry create(AppleSingleReader reader) { + Objects.requireNonNull(reader); + + ByteBuffer buffer = reader.read(LENGTH, "Entry header"); + Entry entry = new Entry(); + entry.entryId = buffer.getInt(); + entry.offset = buffer.getInt(); + entry.length = buffer.getInt(); + + entry.data = reader.readAt(entry.offset, entry.length, EntryType.findNameOrUnknown(entry)).array(); + return entry; + } + public static Entry create(EntryType type, byte[] data) { + Objects.requireNonNull(type); + Objects.requireNonNull(data); + Entry entry = new Entry(); + entry.entryId = type.entryId; + entry.offset = -1; + entry.length = data.length; + entry.data = data; + return entry; + } + + public int getEntryId() { + return entryId; + } + public int getOffset() { + return offset; + } + public int getLength() { + return length; + } + public byte[] getData() { + return data; + } + public ByteBuffer getBuffer() { + return ByteBuffer.wrap(data).order(ByteOrder.BIG_ENDIAN).asReadOnlyBuffer(); + } +} diff --git a/api/src/main/java/io/github/applecommander/applesingle/EntryType.java b/api/src/main/java/io/github/applecommander/applesingle/EntryType.java new file mode 100644 index 0000000..4bcff70 --- /dev/null +++ b/api/src/main/java/io/github/applecommander/applesingle/EntryType.java @@ -0,0 +1,44 @@ +package io.github.applecommander.applesingle; + +public enum EntryType { + DATA_FORK(1, "Data Fork"), + RESOURCE_FORK(2, "Resource Fork"), + REAL_NAME(3, "Real Name"), + COMMENT(4, "Comment"), + ICON_BW(5, "Icon, B&W"), + ICON_COLOR(6, "Icon, Color"), + FILE_INFO(7, "File Info"), + FILE_DATES_INFO(8, "File Dates Info"), + FINDER_INFO(9, "Finder Info"), + MACINTOSH_FILE_INFO(10, "Macintosh File Info"), + PRODOS_FILE_INFO(11, "ProDOS File Info"), + MSDOS_FILE_INFO(12, "MS-DOS File Info"), + SHORT_NAME(13, "Short Name"), + AFP_FILE_INFO(14, "AFP File Info"), + DIRECTORY_ID(15, "Directory ID"); + + public static final String findNameOrUnknown(Entry entry) { + for (EntryType et : values()) { + if (et.entryId == entry.getEntryId()) { + return et.name; + } + } + return "Unknown"; + } + public static final EntryType find(int entryId) { + for (EntryType et : values()) { + if (et.entryId == entryId) { + return et; + } + } + throw new IllegalArgumentException(String.format("Unable to find EntryType # %d", entryId)); + } + + public final int entryId; + public final String name; + + private EntryType(int entryId, String name) { + this.entryId = entryId; + this.name= name; + } +} diff --git a/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java b/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java index 0268527..8ce7f72 100644 --- a/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java +++ b/api/src/main/java/io/github/applecommander/applesingle/FileDatesInfo.java @@ -1,5 +1,6 @@ package io.github.applecommander.applesingle; +import java.nio.ByteBuffer; import java.time.Instant; import java.util.function.IntSupplier; @@ -18,6 +19,14 @@ public class FileDatesInfo { public static int fromInstant(Instant instant) { return (int)(instant.getEpochSecond() - EPOCH_INSTANT.getEpochSecond()); } + public static FileDatesInfo fromEntry(Entry entry) { + ByteBuffer infoData = entry.getBuffer(); + int creation = infoData.getInt(); + int modification = infoData.getInt(); + int backup = infoData.getInt(); + int access = infoData.getInt(); + return new FileDatesInfo(creation, modification, backup, access); + } public FileDatesInfo() { int current = FileDatesInfo.fromInstant(Instant.now()); diff --git a/api/src/main/java/io/github/applecommander/applesingle/ProdosFileInfo.java b/api/src/main/java/io/github/applecommander/applesingle/ProdosFileInfo.java index 7497f7c..4620393 100644 --- a/api/src/main/java/io/github/applecommander/applesingle/ProdosFileInfo.java +++ b/api/src/main/java/io/github/applecommander/applesingle/ProdosFileInfo.java @@ -1,5 +1,7 @@ package io.github.applecommander.applesingle; +import java.nio.ByteBuffer; + import io.github.applecommander.applesingle.AppleSingle.Builder; /** @@ -16,6 +18,13 @@ public class ProdosFileInfo { public static ProdosFileInfo standardBIN() { return new ProdosFileInfo(0xc3, 0x06, 0x0000); } + public static ProdosFileInfo fromEntry(Entry entry) { + ByteBuffer infoData = entry.getBuffer(); + int access = infoData.getShort(); + int fileType = infoData.getShort(); + int auxType = infoData.getInt(); + return new ProdosFileInfo(access, fileType, auxType); + } public ProdosFileInfo(int access, int fileType, int auxType) { this.access = access; diff --git a/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java b/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java index bd06436..383ef10 100644 --- a/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java +++ b/tools/asu/src/main/java/io/github/applecommander/applesingle/tools/asu/AnalyzeCommand.java @@ -2,18 +2,23 @@ package io.github.applecommander.applesingle.tools.asu; import java.io.IOException; import java.io.PrintStream; -import java.nio.ByteBuffer; -import java.nio.ByteOrder; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; +import java.util.function.BiConsumer; import io.github.applecommander.applesingle.AppleSingle; +import io.github.applecommander.applesingle.AppleSingleReader; +import io.github.applecommander.applesingle.Entry; +import io.github.applecommander.applesingle.EntryType; import io.github.applecommander.applesingle.FileDatesInfo; import io.github.applecommander.applesingle.ProdosFileInfo; +import io.github.applecommander.applesingle.Utilities; import picocli.CommandLine.Command; import picocli.CommandLine.Option; import picocli.CommandLine.Parameters; @@ -43,29 +48,22 @@ public class AnalyzeCommand implements Callable { @Override public Void call() throws IOException { - byte[] fileData = stdinFlag ? AppleSingle.toByteArray(System.in) : Files.readAllBytes(path); + byte[] fileData = stdinFlag ? Utilities.toByteArray(System.in) : Files.readAllBytes(path); if (verboseFlag) this.verbose = System.out; - State state = new State(fileData); - match(state, "Magic number", "Not an AppleSingle file - magic number does not match.", - AppleSingle.MAGIC_NUMBER); - int version = match(state, "Version", "Only recognize AppleSingle versions 1 and 2.", - AppleSingle.VERSION_NUMBER1, AppleSingle.VERSION_NUMBER2); - verbose.printf(" .. Version 0x%08x\n", version); - state.read(16, "Filler"); - int numberOfEntries = state.read(Short.BYTES, "Number of entries").getShort(); - verbose.printf(" .. Entries = %d\n", numberOfEntries); - List entries = new ArrayList<>(); - for (int i = 0; i < numberOfEntries; i++) { - ByteBuffer buffer = state.read(12, String.format("Entry #%d", i+1)); - Entry entry = new Entry(i+1, buffer); - entry.print(verbose); - entries.add(entry); - } - entries.sort((a,b) -> Integer.compare(a.offset, b.offset)); - for (Entry entry : entries) entryReport(state, entry); + List used = new ArrayList<>(); + HexDumper dumper = HexDumper.standard(); + AppleSingleReader reader = AppleSingleReader.builder() + .data(fileData) + .readAtReporter((start,len,b,d) -> used.add(IntRange.of(start, start+len))) + .readAtReporter((start,len,chunk,desc) -> dumper.dump(start, chunk, desc)) + .versionReporter(this::reportVersion) + .numberOfEntriesReporter(this::reportNumberOfEntries) + .entryReporter(this::reportEntry) + .build(); + AppleSingle.asEntries(reader); - List ranges = IntRange.normalize(state.used); + List ranges = IntRange.normalize(used); if (ranges.size() == 1 && ranges.get(0).getLow() == 0 && ranges.get(0).getHigh() == fileData.length) { verbose.printf("The entirety of the file was used.\n"); } else { @@ -74,99 +72,55 @@ public class AnalyzeCommand implements Callable { } return null; } - - public int match(State state, String description, String message, int... expecteds) throws IOException { - ByteBuffer buffer = state.read(Integer.BYTES, description); - int actual = buffer.getInt(); - for (int expected : expecteds) { - if (actual == expected) return actual; - } - throw new IOException(String.format("%s Aborting.", message)); + + public void reportVersion(int version) { + verbose.printf(" .. %s\n", VERSION_TEXT.getOrDefault(version, "Unrecognized version!")); } - - public void entryReport(State state, Entry entry) throws IOException { - String entryName = AppleSingle.ENTRY_TYPE_NAMES.getOrDefault(entry.entryId, "Unknown"); - ByteBuffer buffer = state.readAt(entry.offset, entry.length, - String.format("Entry #%d data (%s)", entry.index, entryName)); - switch (entry.entryId) { - case 3: - case 4: - case 13: - displayEntryString(buffer, entryName); - break; - case 8: - displayFileDatesInfo(buffer, entryName); - break; - case 11: - displayProdosFileInfo(buffer, entryName); - break; - default: - verbose.printf(" .. No further details for this entry type (%s).\n", entryName); - break; - } + public void reportNumberOfEntries(int numberOfEntries) { + verbose.printf(" .. Number of entries = %d\n", numberOfEntries); } - public void displayEntryString(ByteBuffer buffer, String entryName) { - StringBuilder sb = new StringBuilder(); - while (buffer.hasRemaining()) { - int ch = Byte.toUnsignedInt(buffer.get()) & 0x7f; - sb.append((char)ch); - } - verbose.printf(" .. %s: '%s'\n", entryName, sb.toString()); + public void reportEntry(Entry entry) { + String entryName = EntryType.findNameOrUnknown(entry); + verbose.printf(" .. Entry: entryId=%d (%s), offset=%d, length=%d\n", entry.getEntryId(), + entryName, entry.getOffset(), entry.getLength()); + REPORTERS.getOrDefault(entry.getEntryId(), this::reportDefaultEntry) + .accept(entry, entryName); } - public void displayFileDatesInfo(ByteBuffer buffer, String entryName) { - FileDatesInfo info = new FileDatesInfo(buffer.getInt(), buffer.getInt(), buffer.getInt(), buffer.getInt()); + private void reportDefaultEntry(Entry entry, String entryName) { + verbose.printf(" .. No further details for this entry type (%s).\n", entryName); + } + private void reportStringEntry(Entry entry, String entryName) { + verbose.printf(" .. %s: '%s'\n", entryName, Utilities.entryToAsciiString(entry)); + } + private void reportFileDatesInfoEntry(Entry entry, String entryName) { + FileDatesInfo info = FileDatesInfo.fromEntry(entry); verbose.printf(" .. %s -\n", entryName); verbose.printf(" Creation: %s\n", info.getCreationInstant().toString()); verbose.printf(" Modification: %s\n", info.getModificationInstant().toString()); verbose.printf(" Backup: %s\n", info.getBackupInstant().toString()); verbose.printf(" Access: %s\n", info.getAccessInstant().toString()); } - public void displayProdosFileInfo(ByteBuffer buffer, String entryName) { - ProdosFileInfo info = new ProdosFileInfo(buffer.getShort(), buffer.getShort(), buffer.getInt()); + private void reportProdosFileInfoEntry(Entry entry, String entryName) { + ProdosFileInfo info = ProdosFileInfo.fromEntry(entry); verbose.printf(" .. %s -\n", entryName); verbose.printf(" Access: %02X\n", info.getAccess()); verbose.printf(" File Type: %04X\n", info.getFileType()); verbose.printf(" Aux. Type: %04X\n", info.getAuxType()); } - public static class State { - private final byte[] data; - private int pos = 0; - private List used = new ArrayList<>(); - private HexDumper dumper = HexDumper.standard(); - - public State(byte[] data) { - this.data = data; - } - public ByteBuffer read(int len, String description) throws IOException { - return readAt(pos, len, description); - } - public ByteBuffer readAt(int start, int len, String description) throws IOException { - byte[] chunk = new byte[len]; - System.arraycopy(data, start, chunk, 0, len); - ByteBuffer buffer = ByteBuffer.wrap(chunk) - .order(ByteOrder.BIG_ENDIAN) - .asReadOnlyBuffer(); - dumper.dump(start, chunk, description); - used.add(IntRange.of(start, start+len)); - pos= start+len; - return buffer; - } - } - public static class Entry { - private int index; - private int entryId; - private int offset; - private int length; - public Entry(int index, ByteBuffer buffer) { - this.index = index; - this.entryId = buffer.getInt(); - this.offset = buffer.getInt(); - this.length = buffer.getInt(); - } - public void print(PrintStream ps) { - ps.printf(" .. Entry #%d, entryId=%d (%s), offset=%d, length=%d\n", index, entryId, - AppleSingle.ENTRY_TYPE_NAMES.getOrDefault(entryId, "Unknown"), offset, length); + private static final Map VERSION_TEXT = new HashMap() { + private static final long serialVersionUID = 7142066556402030814L; + { + put(AppleSingle.VERSION_NUMBER1, "Version 1"); + put(AppleSingle.VERSION_NUMBER2, "Version 2"); } + }; + private final Map> REPORTERS = new HashMap>(); + { + REPORTERS.put(EntryType.REAL_NAME.entryId, this::reportStringEntry); + REPORTERS.put(EntryType.COMMENT.entryId, this::reportStringEntry); + REPORTERS.put(EntryType.SHORT_NAME.entryId, this::reportStringEntry); + REPORTERS.put(EntryType.FILE_DATES_INFO.entryId, this::reportFileDatesInfoEntry); + REPORTERS.put(EntryType.PRODOS_FILE_INFO.entryId, this::reportProdosFileInfoEntry); } }