From eb10c296165790b75d773ee5bd41d501931e4319 Mon Sep 17 00:00:00 2001 From: a2geek Date: Sat, 10 Oct 2026 15:00:12 -0500 Subject: [PATCH 1/5] Removing ProDOS requirements on the Real Name entry (entry number 3). Closes #64. --- .../applesingle/AppleSingle.java | 37 ++++++------------- .../applesingle/AppleSingleTest.java | 31 ++-------------- 2 files changed, 16 insertions(+), 52 deletions(-) diff --git a/api/src/main/java/org/applecommander/applesingle/AppleSingle.java b/api/src/main/java/org/applecommander/applesingle/AppleSingle.java index 258859f..1a07401 100644 --- a/api/src/main/java/org/applecommander/applesingle/AppleSingle.java +++ b/api/src/main/java/org/applecommander/applesingle/AppleSingle.java @@ -17,22 +17,13 @@ */ package org.applecommander.applesingle; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; +import java.io.*; import java.nio.ByteBuffer; 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; +import java.util.*; import java.util.function.Consumer; /** @@ -269,30 +260,21 @@ private Builder() { this.as = new AppleSingle(); } private Builder(AppleSingle original) { + Objects.requireNonNull(original); this.as = original; } public Builder realName(String realName) { - if (!Character.isAlphabetic(realName.charAt(0))) { - throw new IllegalArgumentException("ProDOS file names must begin with a letter"); - } - as.realName = realName.chars() - .map(this::sanitize) - .limit(15) - .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append) - .toString(); + Objects.requireNonNull(realName); + as.realName = realName; return this; } - private int sanitize(int ch) { - if (Character.isAlphabetic(ch) || Character.isDigit(ch)) { - return Character.toUpperCase(ch); - } - return '.'; - } public Builder dataFork(byte[] dataFork) { + Objects.requireNonNull(dataFork); as.dataFork = dataFork; return this; } public Builder resourceFork(byte[] resourceFork) { + Objects.requireNonNull(resourceFork); as.resourceFork = resourceFork; return this; } @@ -313,6 +295,7 @@ public Builder creationDate(int creation) { return this; } public Builder creationDate(Instant creation) { + Objects.requireNonNull(creation); as.fileDatesInfo.creation = FileDatesInfo.fromInstant(creation); return this; } @@ -321,6 +304,7 @@ public Builder modificationDate(int modification) { return this; } public Builder modificationDate(Instant modification) { + Objects.requireNonNull(modification); as.fileDatesInfo.modification = FileDatesInfo.fromInstant(modification); return this; } @@ -329,6 +313,7 @@ public Builder backupDate(int backup) { return this; } public Builder backupDate(Instant backup) { + Objects.requireNonNull(backup); as.fileDatesInfo.backup = FileDatesInfo.fromInstant(backup); return this; } @@ -337,10 +322,12 @@ public Builder accessDate(int access) { return this; } public Builder accessDate(Instant access) { + Objects.requireNonNull(access); as.fileDatesInfo.access = FileDatesInfo.fromInstant(access); return this; } public Builder allDates(Instant instant) { + Objects.requireNonNull(instant); return creationDate(instant).modificationDate(instant).backupDate(instant).accessDate(instant); } public AppleSingle build() { diff --git a/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java b/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java index 43f4a78..3e3efee 100644 --- a/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java +++ b/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java @@ -17,19 +17,14 @@ */ package org.applecommander.applesingle; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; +import org.junit.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; -import org.junit.Test; +import static org.junit.Assert.*; public class AppleSingleTest { private static final String AS_HELLO_BIN = "/hello.applesingle.bin"; @@ -63,7 +58,7 @@ public void testCreateAndReadAppleSingle() throws IOException { .allDates(instant) .build(); assertNotNull(createdAS); - assertEquals(realName.toUpperCase(), createdAS.getRealName()); + assertEquals(realName, createdAS.getRealName()); assertArrayEquals(dataFork, createdAS.getDataFork()); assertNull(createdAS.getResourceFork()); assertNotNull(createdAS.getProdosFileInfo()); @@ -74,7 +69,7 @@ public void testCreateAndReadAppleSingle() throws IOException { AppleSingle readAS = AppleSingle.read(actualBytes.toByteArray()); assertNotNull(readAS); - assertEquals(realName.toUpperCase(), readAS.getRealName()); + assertEquals(realName, readAS.getRealName()); assertArrayEquals(dataFork, readAS.getDataFork()); assertNull(readAS.getResourceFork()); assertNotNull(readAS.getProdosFileInfo()); @@ -85,24 +80,6 @@ public void testCreateAndReadAppleSingle() throws IOException { assertEquals(instant, readAS.getFileDatesInfo().getBackupInstant()); } - @Test - public void testProdosFileNameLengthRequirements() { - AppleSingle as = AppleSingle.builder().realName("superlongnamethatneedstobetruncated").build(); - assertEquals(15, as.getRealName().length()); - } - - @Test - public void testProdosFileNameCharacterRequirements() { - AppleSingle as = AppleSingle.builder().realName("bad-~@").build(); - assertEquals("BAD...", as.getRealName()); - } - - @Test(expected = IllegalArgumentException.class) - public void testProdosFileNameFirstCharacter() { - // Fails due to the first character being a digit. - AppleSingle.builder().realName("1st-file").build(); - } - @Test public void testTest() throws IOException { // Known valid From 6cf5fbd258763384249378021f2a8b11f3f9b6d7 Mon Sep 17 00:00:00 2001 From: a2geek Date: Sat, 10 Oct 2026 15:01:19 -0500 Subject: [PATCH 2/5] Adding 'env.sh' script. --- scripts/env.sh | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 scripts/env.sh diff --git a/scripts/env.sh b/scripts/env.sh new file mode 100644 index 0000000..3941c04 --- /dev/null +++ b/scripts/env.sh @@ -0,0 +1,9 @@ +# Usage: 'shell env.sh' + +# Try to accommodate Mac and Linux +platform=$(uname | tr '[:upper:]' '[:lower:]' | sed 's/darwin/macosx/') +machine=$(uname -m | tr '[:upper:]' '[:lower:]' | sed 's/arm64/aarch64/' | sed 's/x86_64/amd64/') + +# These are useful aliases while developing +alias asu="java -jar ${PWD}/tools/asu/build/libs/applesingle-tools-asu-*.jar" +alias asun="${PWD}/tools/asu/build/native/nativeCompile/asu-${platform}-${machine}-*" From c8a214119707483c53efbd969b4590b7508e98fe Mon Sep 17 00:00:00 2001 From: a2geek Date: Sat, 10 Oct 2026 15:27:36 -0500 Subject: [PATCH 3/5] Bumping version. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index e1373c0..6720baa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ # Universal applesingle version number. Used for: # - Naming JAR file. # - The build will insert this into a file that is read at run time as well. -version=2.0 +version=2.1 From 4eca8b01d29ba702fa8864aa4915026bcc5b17e3 Mon Sep 17 00:00:00 2001 From: a2geek Date: Sat, 10 Oct 2026 15:28:32 -0500 Subject: [PATCH 4/5] Updated JUnit version. (Didn't need to, but it was in the path!) --- api/build.gradle | 9 ++++++++- .../applesingle/AppleSingleReaderTest.java | 12 ++++++++---- .../applecommander/applesingle/AppleSingleTest.java | 4 ++-- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/api/build.gradle b/api/build.gradle index 8562fc4..eaa600e 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -12,7 +12,10 @@ repositories { } dependencies { - testImplementation 'junit:junit:4.13.2' + testImplementation platform('org.junit:junit-bom:6.1.3') + testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'org.junit.jupiter:junit-jupiter-params' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } jar { @@ -22,6 +25,10 @@ jar { } } +test { + useJUnitPlatform() +} + javadoc { title = "applesingle-api ${project.version}" source = sourceSets.main.allJava diff --git a/api/src/test/java/org/applecommander/applesingle/AppleSingleReaderTest.java b/api/src/test/java/org/applecommander/applesingle/AppleSingleReaderTest.java index 0b7db47..5163632 100644 --- a/api/src/test/java/org/applecommander/applesingle/AppleSingleReaderTest.java +++ b/api/src/test/java/org/applecommander/applesingle/AppleSingleReaderTest.java @@ -17,15 +17,19 @@ */ package org.applecommander.applesingle; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; import java.io.IOException; +import static org.junit.jupiter.api.Assertions.*; + public class AppleSingleReaderTest { - @Test(expected = NullPointerException.class) + @Test public void testDoesNotAcceptNull() { - AppleSingleReader.builder(null); + assertThrows(NullPointerException.class, () -> { + AppleSingleReader.builder(null); + }); + } @Test diff --git a/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java b/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java index 3e3efee..a803d76 100644 --- a/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java +++ b/api/src/test/java/org/applecommander/applesingle/AppleSingleTest.java @@ -17,14 +17,14 @@ */ package org.applecommander.applesingle; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Instant; import java.time.temporal.ChronoUnit; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; public class AppleSingleTest { private static final String AS_HELLO_BIN = "/hello.applesingle.bin"; From e3dc65c62a75e7548de670518bb1064bbf036b02 Mon Sep 17 00:00:00 2001 From: a2geek Date: Sat, 10 Oct 2026 15:28:55 -0500 Subject: [PATCH 5/5] Adding module version detection. --- api/build.gradle | 5 +++++ .../applecommander/applesingle/AppleSingle.java | 15 ++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/api/build.gradle b/api/build.gradle index eaa600e..c347345 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -25,6 +25,11 @@ jar { } } +tasks.withType(JavaCompile).configureEach { + options.compilerArgs += ['--module-path', classpath.asPath] + options.javaModuleVersion = project.version.toString() +} + test { useJUnitPlatform() } diff --git a/api/src/main/java/org/applecommander/applesingle/AppleSingle.java b/api/src/main/java/org/applecommander/applesingle/AppleSingle.java index 1a07401..97a7f11 100644 --- a/api/src/main/java/org/applecommander/applesingle/AppleSingle.java +++ b/api/src/main/java/org/applecommander/applesingle/AppleSingle.java @@ -47,7 +47,20 @@ public class AppleSingle { public static final String VERSION; static { - VERSION = AppleSingle.class.getPackage().getImplementationVersion(); + String version = AppleSingle.class.getPackage().getImplementationVersion(); + if (version == null && AppleSingle.class.getModule().getDescriptor() != null) { + // When run under the Java Module system (JPMS) we need to get it a different way. + version = AppleSingle.class.getModule() + .getDescriptor() + .version() + .map(Object::toString) + .orElse("Development Build"); + } + // For unit tests, the descriptor (apparently) doesn't exist, so we skip it. + if (version == null) { + version = "Unit Testing"; + } + VERSION = version; } private Map> entryConsumers = new HashMap<>();