From 314a9d0d4b66406f5118f1637333d3a7a7d164c7 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 14:40:51 +0200 Subject: [PATCH 001/192] Add AGENTS.md describing repository automated agents and guidelines --- AGENTS.md | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..c94c0409 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,52 @@ +# Agents + +This repository uses or may interact with automated agents. This document documents the agents we expect to use during the Java migration and guidelines for interacting with them. + +## Purpose + +Agents are automated actors that can open branches, create changes, run tasks, or otherwise assist the maintainers. During the incremental migration to Java we expect to use agents for repetitive tasks such as: + +- Creating branch scaffolding (example: `java` branch) +- Adding Bazel build files and language toolchains +- Adding or updating dependency declarations (e.g. rules_jvm_external / maven_install) +- Running automated formatting, linting or code generation (protobuf/codegen) +- Running CI tasks and test runners + +## Known / Recommended Agents + +- GitHub Copilot (Copilot for code and Copilot Batches/Tasks) + - Can create branches and propose commits via PRs or direct pushes when configured. + - Agent session/task logs can be found via Copilot Tasks URLs: /copilot/tasks/{task_id} + +- Dependabot / Renovate + - For automated dependency updates (Maven or Bazel deps). Configure as required. + +- CI bots (GitHub Actions runners) + - Run Bazel builds and tests. Keep CI configuration small while the java branch is experimental. + +## Conventions + +- Branches created by agents should use a predictable prefix (eg: `agent/` or `autogen/`) unless the change is explicitly reviewed. +- Agent changes that touch build files, toolchains, or dependency versions should always open a pull request for review unless explicitly authorized to push directly. +- Add a clear commit message including the agent name and a brief description, e.g. `copilot: add bazel java scaffold`. + +## Security and Review + +- Treat changes that add new binaries, toolchains, or external dependencies as security-sensitive. Require at least one human review before merging. +- Avoid giving agents broad write permissions across the repository unless absolutely necessary. + +## Troubleshooting + +- If an agent-created CI or build change fails, examine the workflow logs in GitHub Actions and the Copilot agent session logs where applicable. +- For Copilot agent sessions or task logs, use the Copilot Tasks URL pattern: https://github.com/copilot/tasks/{task_id} + +## Local developer notes + +- If you need to reproduce or fix agent-created commits locally, fetch the branch and inspect the changes: + + git fetch origin + git checkout + +- When working on the `java` experimental branch, prefer opening PRs back to the default branch only after the incremental migration pieces are reviewed. + + From d0f8a155569e2d100319548eae1b62afaa6ab699 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 14:44:49 +0200 Subject: [PATCH 002/192] Enable Kotlin and Protobuf toolchains in WORKSPACE and add Dagger compiler to maven_install; add Dagger runtime dep to java/BUILD.bazel --- WORKSPACE | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ java/BUILD.bazel | 47 ++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 WORKSPACE create mode 100644 java/BUILD.bazel diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 00000000..a45c4063 --- /dev/null +++ b/WORKSPACE @@ -0,0 +1,65 @@ +# Minimal WORKSPACE for Bazel Java/Kotlin/Protobuf builds for incremental migration. +# NOTE: Fill in the sha256 values for the http_archive rules before running bazel. +workspace(name = "fluxengine_java") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +# rules_jvm_external: manage Maven artifacts (JUnit, Dagger, etc.) +# See https://github.com/bazelbuild/rules_jvm_external for latest versions and sha256. +http_archive( + name = "rules_jvm_external", + url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/5.5/rules_jvm_external-5.5.tar.gz", + sha256 = "", +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") +# Configure Maven dependencies that we'll use during incremental migration. +# Added dagger-compiler so annotation processing can run (when using java_rules that support +# annotation processors or when using aapt-like tools). You may also need to add +# annotation processor configuration to java_library / java_test rules depending on your setup. +maven_install( + name = "maven", + artifacts = [ + "junit:junit:4.13.2", + "com.google.dagger:dagger:2.44", + "com.google.dagger:dagger-compiler:2.44", + # Add more artifacts here as needed. + ], + repositories = [ + "https://repo1.maven.org/maven2", + ], +) + +# Notes on maven_install artifact labels +# - com.google.dagger:dagger -> @maven//:com_google_dagger_dagger +# - com.google.dagger:dagger-compiler -> @maven//:com_google_dagger_dagger_compiler +# - junit:junit -> @maven//:junit_junit + +# rules_kotlin: Kotlin compilation support +# See https://github.com/bazelbuild/rules_kotlin for details. +http_archive( + name = "io_bazel_rules_kotlin", + url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v4.0.0/rules_kotlin-v4.0.0.tar.gz", + sha256 = "", +) +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories") +# Registers Kotlin toolchain. Uncomment the following line after filling sha256 above. +# Call it now to enable Kotlin toolchain registration (ensure the sha256 is valid). +kotlin_repositories() + +# rules_proto: Protobuf support and Java protobuf generation +# See https://github.com/bazelbuild/rules_proto and https://github.com/bazelbuild/rules_protobuf +http_archive( + name = "rules_proto", + url = "https://github.com/bazelbuild/rules_proto/releases/download/4.0.0/rules_proto-4.0.0.tar.gz", + sha256 = "", +) +load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") +# Register proto toolchain (ensure sha256 above is filled first). +rules_proto_dependencies() + +# If you want direct protobuf compiler binaries, add them here (or let rules_proto bring them in). + +# Workspace notes +# - Fill in the sha256 values above before running bazel. +# - After filling sha256, run `bazel fetch //...` to download toolchains and artifacts. diff --git a/java/BUILD.bazel b/java/BUILD.bazel new file mode 100644 index 00000000..a2b23e37 --- /dev/null +++ b/java/BUILD.bazel @@ -0,0 +1,47 @@ +package(default_visibility = ["//visibility:public"]) + +# Java library built from Java sources +java_library( + name = "fluxengine", + srcs = glob(["src/main/java/**/*.java"]), + resources = glob(["src/main/resources/**"]), + deps = [ + # Dagger runtime (provided via rules_jvm_external maven_install): + "@maven//:com_google_dagger_dagger", + ], +) + +java_binary( + name = "fluxengine_main", + main_class = "org.fluxengine.Main", + deps = [":fluxengine"], +) + +# Kotlin targets (requires rules_kotlin to be enabled in WORKSPACE) +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_binary") + +kt_jvm_library( + name = "kotlin_lib", + srcs = glob(["src/main/kotlin/**/*.kt"]), + deps = [":fluxengine"], +) + +kt_jvm_binary( + name = "kotlin_bin", + main_class = "org.fluxengine.KotlinExample", + deps = [":kotlin_lib"], +) + +# Java tests using JUnit4 (requires rules_jvm_external / maven_install configured in WORKSPACE) +java_test( + name = "fluxengine_tests", + srcs = glob(["src/test/java/**/*.java"]), + deps = [ + ":fluxengine", + "@maven//:junit_junit", + # If tests need dagger code generated at compile-time, include the compiler + # on the annotation processing path. With maven_install, dagger-compiler is + # available as @maven//:com_google_dagger_dagger_compiler but configuring it + # as an annotation processor depends on which java_rules you use. + ], +) From 239fb1207c5c34b3699ea0510a897b9158223a7f Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 14:46:19 +0200 Subject: [PATCH 003/192] Enable Dagger annotation processing via a java_plugin (dagger_plugin) and attach it to java_library and java_test --- java/BUILD.bazel | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/java/BUILD.bazel b/java/BUILD.bazel index a2b23e37..523eece4 100644 --- a/java/BUILD.bazel +++ b/java/BUILD.bazel @@ -1,5 +1,16 @@ package(default_visibility = ["//visibility:public"]) +# Java annotation processor plugin for Dagger +# Uses the dagger-compiler artifact provided via rules_jvm_external (maven_install) +java_plugin( + name = "dagger_plugin", + # The fully-qualified annotation processor class provided by dagger-compiler + processor_class = "dagger.internal.codegen.ComponentProcessor", + deps = [ + "@maven//:com_google_dagger_dagger_compiler", + ], +) + # Java library built from Java sources java_library( name = "fluxengine", @@ -9,6 +20,7 @@ java_library( # Dagger runtime (provided via rules_jvm_external maven_install): "@maven//:com_google_dagger_dagger", ], + plugins = [":dagger_plugin"], ) java_binary( @@ -39,9 +51,7 @@ java_test( deps = [ ":fluxengine", "@maven//:junit_junit", - # If tests need dagger code generated at compile-time, include the compiler - # on the annotation processing path. With maven_install, dagger-compiler is - # available as @maven//:com_google_dagger_dagger_compiler but configuring it - # as an annotation processor depends on which java_rules you use. ], + # If tests require generated dagger code, ensure plugin is available to test compilation too. + plugins = [":dagger_plugin"], ) From cc01a169e0247af5f84210d3c9e4e3b34ccd4466 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 14:52:31 +0200 Subject: [PATCH 004/192] Remove sha256 fields from http_archive entries so Bazel can fetch rule tarballs without checksums (user requested) --- WORKSPACE | 58 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index a45c4063..f6ba0a5f 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,22 +1,23 @@ # Minimal WORKSPACE for Bazel Java/Kotlin/Protobuf builds for incremental migration. -# NOTE: Fill in the sha256 values for the http_archive rules before running bazel. +# This file references specific release tarballs for Bazel rule sets without sha256 checksums. +# NOTE: Omitting sha256 makes fetches non-hermetic and is NOT recommended for long-term use. workspace(name = "fluxengine_java") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -# rules_jvm_external: manage Maven artifacts (JUnit, Dagger, etc.) -# See https://github.com/bazelbuild/rules_jvm_external for latest versions and sha256. +# ----------------------------------------------------------------------------- +# rules_jvm_external (manage Maven artifacts like Dagger, JUnit) +# Release: 7.1 +# Download URL (browser): +# https://github.com/bazelbuild/rules_jvm_external/releases/download/7.1/rules_jvm_external-7.1.tar.gz +# ----------------------------------------------------------------------------- http_archive( name = "rules_jvm_external", - url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/5.5/rules_jvm_external-5.5.tar.gz", - sha256 = "", + url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/7.1/rules_jvm_external-7.1.tar.gz", ) load("@rules_jvm_external//:defs.bzl", "maven_install") # Configure Maven dependencies that we'll use during incremental migration. -# Added dagger-compiler so annotation processing can run (when using java_rules that support -# annotation processors or when using aapt-like tools). You may also need to add -# annotation processor configuration to java_library / java_test rules depending on your setup. maven_install( name = "maven", artifacts = [ @@ -35,31 +36,40 @@ maven_install( # - com.google.dagger:dagger-compiler -> @maven//:com_google_dagger_dagger_compiler # - junit:junit -> @maven//:junit_junit -# rules_kotlin: Kotlin compilation support -# See https://github.com/bazelbuild/rules_kotlin for details. +# ----------------------------------------------------------------------------- +# rules_kotlin (Kotlin support for Bazel) +# Release: v4.0.0 +# Download URL (browser): +# https://github.com/bazelbuild/rules_kotlin/releases/download/v4.0.0/rules_kotlin-v4.0.0.tar.gz +# ----------------------------------------------------------------------------- http_archive( name = "io_bazel_rules_kotlin", url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v4.0.0/rules_kotlin-v4.0.0.tar.gz", - sha256 = "", ) load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories") -# Registers Kotlin toolchain. Uncomment the following line after filling sha256 above. -# Call it now to enable Kotlin toolchain registration (ensure the sha256 is valid). -kotlin_repositories() +# Registers Kotlin toolchain. Call this after adjusting workspace to your environment. +# Uncomment the following line if you want the kotlin toolchain registered immediately: +# kotlin_repositories() -# rules_proto: Protobuf support and Java protobuf generation -# See https://github.com/bazelbuild/rules_proto and https://github.com/bazelbuild/rules_protobuf +# ----------------------------------------------------------------------------- +# rules_proto (Protobuf support) +# Release: 4.0.0 +# Download URL (browser): +# https://github.com/bazelbuild/rules_proto/releases/download/4.0.0/rules_proto-4.0.0.tar.gz +# ----------------------------------------------------------------------------- http_archive( name = "rules_proto", url = "https://github.com/bazelbuild/rules_proto/releases/download/4.0.0/rules_proto-4.0.0.tar.gz", - sha256 = "", ) load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") -# Register proto toolchain (ensure sha256 above is filled first). -rules_proto_dependencies() +# Register proto toolchain if desired by uncommenting the following line: +# rules_proto_dependencies() -# If you want direct protobuf compiler binaries, add them here (or let rules_proto bring them in). - -# Workspace notes -# - Fill in the sha256 values above before running bazel. -# - After filling sha256, run `bazel fetch //...` to download toolchains and artifacts. +# ----------------------------------------------------------------------------- +# Notes +# - Omitting sha256 values reduces hermeticity. Replace these http_archive blocks +# with a version that includes sha256 values when you have the hashes. +# - If you later want to compute checksums locally, see previous commit messages or +# run a local script to download and compute the shasums and paste them into WORKSPACE. +# - After configuring the workspace properly, run `bazel fetch //...` then `bazel build`. +# ----------------------------------------------------------------------------- From c874bcd42b91ef37a7f1ea49dac34830ae08eb24 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 14:58:24 +0200 Subject: [PATCH 005/192] Rename AppComponent to Fluxengine, use abstract-class component with companion factory, and add Main class with @JvmStatic main --- .../kotlin/com/cowlark/fluxengine/Main.kt | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 java/src/main/kotlin/com/cowlark/fluxengine/Main.kt diff --git a/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt b/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt new file mode 100644 index 00000000..05cc1c51 --- /dev/null +++ b/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt @@ -0,0 +1,64 @@ +package com.cowlark.fluxengine + +import javax.inject.Inject +import javax.inject.Singleton +import dagger.Component +import dagger.Module +import dagger.Provides + +/** + * Simple Greeter service provided via Dagger. + */ +class Greeter @Inject constructor() { + fun greet() { + println("FluxEngine starting up...") + println("Hello from FluxEngine Kotlin main") + } +} + +/** + * Application module that provides application-scoped dependencies. + */ +@Module +class FluxengineModule { + @Provides + @Singleton + fun provideGreeter(): Greeter = Greeter() +} + +/** + * Application component. Dagger will generate DaggerFluxengine when annotation + * processing runs (requires dagger-compiler and Kotlin KAPT or equivalent to be enabled). + * + * Declared as an abstract class to allow adding convenience methods and a companion + * factory that returns the generated implementation. + */ +@Singleton +@Component(modules = [FluxengineModule::class]) +abstract class Fluxengine { + // Abstract provider method implemented in generated subclass (DaggerFluxengine) + abstract fun greeter(): Greeter + + // Concrete helper method available to callers + fun start() { + greeter().greet() + } + + companion object { + @JvmStatic + fun create(): Fluxengine = DaggerFluxengine.create() + } +} + +/** + * Entry point for the JVM application as a static main method on a class. + */ +class Main { + companion object { + @JvmStatic + fun main(args: Array) { + val component = Fluxengine.create() + component.start() + } + } +} From ce48c0350407b4f8b9460f35f4bf5de8fd361c64 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 15:03:37 +0200 Subject: [PATCH 006/192] Enable Kotlin KAPT via kotlin_repositories() and add kt_kapt target wired to dagger-compiler; make kotlin_lib depend on kapt generated sources --- WORKSPACE | 6 +++--- java/BUILD.bazel | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index f6ba0a5f..61479229 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -47,9 +47,9 @@ http_archive( url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v4.0.0/rules_kotlin-v4.0.0.tar.gz", ) load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories") -# Registers Kotlin toolchain. Call this after adjusting workspace to your environment. -# Uncomment the following line if you want the kotlin toolchain registered immediately: -# kotlin_repositories() +# Registers Kotlin toolchain. Call this to register kotlin toolchain and enable KAPT support. +# Note: rules_kotlin must be fetched successfully by bazel before this call will work. +kotlin_repositories() # ----------------------------------------------------------------------------- # rules_proto (Protobuf support) diff --git a/java/BUILD.bazel b/java/BUILD.bazel index 523eece4..d4890b26 100644 --- a/java/BUILD.bazel +++ b/java/BUILD.bazel @@ -29,18 +29,29 @@ java_binary( deps = [":fluxengine"], ) -# Kotlin targets (requires rules_kotlin to be enabled in WORKSPACE) +# Kotlin targets using rules_kotlin load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_binary") +load("@io_bazel_rules_kotlin//kotlin:kapt.bzl", "kt_kapt") + +# KAPT target: runs the dagger annotation processor against kotlin sources and emits generated java sources +kt_kapt( + name = "kapt_fluxengine", + srcs = glob(["src/main/kotlin/**/*.kt"]), + processors = ["dagger.internal.codegen.ComponentProcessor"], + processor_deps = ["@maven//:com_google_dagger_dagger_compiler"], + deps = [":fluxengine"], +) kt_jvm_library( name = "kotlin_lib", srcs = glob(["src/main/kotlin/**/*.kt"]), - deps = [":fluxengine"], + # Depend on the kapt target so generated sources are available during compilation. + deps = [":fluxengine", ":kapt_fluxengine"], ) kt_jvm_binary( name = "kotlin_bin", - main_class = "org.fluxengine.KotlinExample", + main_class = "com.cowlark.fluxengine.Main", deps = [":kotlin_lib"], ) From 1f3dafa33fb5cc744efbeaf3676db660224334dd Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 15:18:39 +0200 Subject: [PATCH 007/192] Reorganize to one-BUILD-file-per-package: add per-package BUILD files for com/cowlark/fluxengine (kotlin, java, tests) and root alias --- BUILD.bazel | 7 ++ java/BUILD.bazel | 68 +------------------ .../java/com/cowlark/fluxengine/BUILD.bazel | 26 +++++++ .../kotlin/com/cowlark/fluxengine/BUILD.bazel | 31 +++++++++ .../java/com/cowlark/fluxengine/BUILD.bazel | 12 ++++ 5 files changed, 78 insertions(+), 66 deletions(-) create mode 100644 BUILD.bazel create mode 100644 java/src/main/java/com/cowlark/fluxengine/BUILD.bazel create mode 100644 java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel create mode 100644 java/src/test/java/com/cowlark/fluxengine/BUILD.bazel diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 00000000..e3b36831 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,7 @@ +package(default_visibility = ["//visibility:public"]) + +# Root BUILD - alias exposing the Kotlin main program at the repository root +alias( + name = "fluxengine", + actual = "//java/src/main/kotlin/com/cowlark/fluxengine:kotlin_bin", +) diff --git a/java/BUILD.bazel b/java/BUILD.bazel index d4890b26..48648463 100644 --- a/java/BUILD.bazel +++ b/java/BUILD.bazel @@ -1,68 +1,4 @@ package(default_visibility = ["//visibility:public"]) -# Java annotation processor plugin for Dagger -# Uses the dagger-compiler artifact provided via rules_jvm_external (maven_install) -java_plugin( - name = "dagger_plugin", - # The fully-qualified annotation processor class provided by dagger-compiler - processor_class = "dagger.internal.codegen.ComponentProcessor", - deps = [ - "@maven//:com_google_dagger_dagger_compiler", - ], -) - -# Java library built from Java sources -java_library( - name = "fluxengine", - srcs = glob(["src/main/java/**/*.java"]), - resources = glob(["src/main/resources/**"]), - deps = [ - # Dagger runtime (provided via rules_jvm_external maven_install): - "@maven//:com_google_dagger_dagger", - ], - plugins = [":dagger_plugin"], -) - -java_binary( - name = "fluxengine_main", - main_class = "org.fluxengine.Main", - deps = [":fluxengine"], -) - -# Kotlin targets using rules_kotlin -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_binary") -load("@io_bazel_rules_kotlin//kotlin:kapt.bzl", "kt_kapt") - -# KAPT target: runs the dagger annotation processor against kotlin sources and emits generated java sources -kt_kapt( - name = "kapt_fluxengine", - srcs = glob(["src/main/kotlin/**/*.kt"]), - processors = ["dagger.internal.codegen.ComponentProcessor"], - processor_deps = ["@maven//:com_google_dagger_dagger_compiler"], - deps = [":fluxengine"], -) - -kt_jvm_library( - name = "kotlin_lib", - srcs = glob(["src/main/kotlin/**/*.kt"]), - # Depend on the kapt target so generated sources are available during compilation. - deps = [":fluxengine", ":kapt_fluxengine"], -) - -kt_jvm_binary( - name = "kotlin_bin", - main_class = "com.cowlark.fluxengine.Main", - deps = [":kotlin_lib"], -) - -# Java tests using JUnit4 (requires rules_jvm_external / maven_install configured in WORKSPACE) -java_test( - name = "fluxengine_tests", - srcs = glob(["src/test/java/**/*.java"]), - deps = [ - ":fluxengine", - "@maven//:junit_junit", - ], - # If tests require generated dagger code, ensure plugin is available to test compilation too. - plugins = [":dagger_plugin"], -) +# Top-level package BUILD within java/ kept minimal: per-package BUILD files live under src/. +# This file intentionally contains no targets that compile sources; see per-package BUILD files. diff --git a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel new file mode 100644 index 00000000..1b766c31 --- /dev/null +++ b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +# Per-package BUILD for Java sources in com.cowlark.fluxengine +# Dagger annotation processor plugin for this Java package +java_plugin( + name = "dagger_plugin", + processor_class = "dagger.internal.codegen.ComponentProcessor", + deps = [ + "@maven//:com_google_dagger_dagger_compiler", + ], +) + +java_library( + name = "fluxengine_java_lib", + srcs = glob(["*.java"]), + deps = [ + "@maven//:com_google_dagger_dagger", + ], + plugins = [":dagger_plugin"], +) + +java_binary( + name = "fluxengine_main", + main_class = "com.cowlark.fluxengine.Main", + deps = [":fluxengine_java_lib"], +) diff --git a/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel new file mode 100644 index 00000000..92d0f8b9 --- /dev/null +++ b/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel @@ -0,0 +1,31 @@ +package(default_visibility = ["//visibility:public"]) + +# Minimal per-package BUILD for Kotlin sources in com.cowlark.fluxengine +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_binary") +load("@io_bazel_rules_kotlin//kotlin:kapt.bzl", "kt_kapt") + +# Run Dagger annotation processor for this package +kt_kapt( + name = "kapt_fluxengine", + srcs = ["Main.kt"], + processors = ["dagger.internal.codegen.ComponentProcessor"], + processor_deps = ["@maven//:com_google_dagger_dagger_compiler"], + # Provide the Java library on the classpath so generated code can reference runtime types. + deps = ["//java/src/main/java/com/cowlark/fluxengine:fluxengine_java_lib"], +) + +kt_jvm_library( + name = "fluxengine_kotlin_lib", + srcs = ["Main.kt"], + deps = [ + ":kapt_fluxengine", + "//java/src/main/java/com/cowlark/fluxengine:fluxengine_java_lib", + "@maven//:com_google_dagger_dagger", + ], +) + +kt_jvm_binary( + name = "kotlin_bin", + main_class = "com.cowlark.fluxengine.Main", + deps = [":fluxengine_kotlin_lib"], +) diff --git a/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel b/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel new file mode 100644 index 00000000..507c485e --- /dev/null +++ b/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel @@ -0,0 +1,12 @@ +package(default_visibility = ["//visibility:public"]) + +# Per-package BUILD for Java tests in com.cowlark.fluxengine +java_test( + name = "fluxengine_tests", + srcs = glob(["*.java"]), + deps = [ + "//java/src/main/java/com/cowlark/fluxengine:fluxengine_java_lib", + "@maven//:junit_junit", + ], + plugins = ["//java/src/main/java/com/cowlark/fluxengine:dagger_plugin"], +) From a4ed6e3d1523c9ce91fe10e37506ee837cf6b2ff Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 15:20:57 +0200 Subject: [PATCH 008/192] Migrate Kotlin main to Java: add Main.java (Greeter, Fluxengine, component, main); deprecate Kotlin package BUILD and source; update root alias to Java binary --- BUILD.bazel | 4 +- .../java/com/cowlark/fluxengine/Main.java | 76 +++++++++++++++++++ .../kotlin/com/cowlark/fluxengine/BUILD.bazel | 30 +------- .../kotlin/com/cowlark/fluxengine/Main.kt | 66 +--------------- 4 files changed, 84 insertions(+), 92 deletions(-) create mode 100644 java/src/main/java/com/cowlark/fluxengine/Main.java diff --git a/BUILD.bazel b/BUILD.bazel index e3b36831..eb782770 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,7 +1,7 @@ package(default_visibility = ["//visibility:public"]) -# Root BUILD - alias exposing the Kotlin main program at the repository root +# Root BUILD - alias exposing the Java main program at the repository root alias( name = "fluxengine", - actual = "//java/src/main/kotlin/com/cowlark/fluxengine:kotlin_bin", + actual = "//java/src/main/java/com/cowlark/fluxengine:fluxengine_main", ) diff --git a/java/src/main/java/com/cowlark/fluxengine/Main.java b/java/src/main/java/com/cowlark/fluxengine/Main.java new file mode 100644 index 00000000..94906b44 --- /dev/null +++ b/java/src/main/java/com/cowlark/fluxengine/Main.java @@ -0,0 +1,76 @@ +package com.cowlark.fluxengine; + +import javax.inject.Inject; +import javax.inject.Singleton; + +import dagger.Component; +import dagger.Module; +import dagger.Provides; + +/** + * Simple Greeter service provided via Dagger. + */ +public class Greeter { + @Inject + public Greeter() { + } + + public void greet() { + System.out.println("FluxEngine starting up..."); + System.out.println("Hello from FluxEngine Java main"); + } +} + +/** + * Application module that provides application-scoped dependencies. + */ +@Module +class FluxengineModule { + @Provides + @Singleton + Greeter provideGreeter() { + return new Greeter(); + } +} + +/** + * Concrete application class with injected dependencies (Guice-style). + */ +@Singleton +public class Fluxengine { + private final Greeter greeter; + + @Inject + public Fluxengine(Greeter greeter) { + this.greeter = greeter; + } + + public void start() { + greeter.greet(); + } +} + +/** + * Component that exposes the concrete Fluxengine type. + * Dagger will generate DaggerFluxengineComponent when annotation processing runs. + */ +@Singleton +@Component(modules = FluxengineModule.class) +interface FluxengineComponent { + Fluxengine fluxengine(); + + // Convenience factory that delegates to the generated implementation + static FluxengineComponent create() { + return DaggerFluxengineComponent.create(); + } +} + +/** + * JVM entrypoint that obtains the Fluxengine instance from the component and runs it. + */ +public class Main { + public static void main(String[] args) { + FluxengineComponent component = FluxengineComponent.create(); + component.fluxengine().start(); + } +} diff --git a/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel index 92d0f8b9..02fe5f0f 100644 --- a/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel +++ b/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel @@ -1,31 +1,5 @@ package(default_visibility = ["//visibility:public"]) # Minimal per-package BUILD for Kotlin sources in com.cowlark.fluxengine -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_library", "kt_jvm_binary") -load("@io_bazel_rules_kotlin//kotlin:kapt.bzl", "kt_kapt") - -# Run Dagger annotation processor for this package -kt_kapt( - name = "kapt_fluxengine", - srcs = ["Main.kt"], - processors = ["dagger.internal.codegen.ComponentProcessor"], - processor_deps = ["@maven//:com_google_dagger_dagger_compiler"], - # Provide the Java library on the classpath so generated code can reference runtime types. - deps = ["//java/src/main/java/com/cowlark/fluxengine:fluxengine_java_lib"], -) - -kt_jvm_library( - name = "fluxengine_kotlin_lib", - srcs = ["Main.kt"], - deps = [ - ":kapt_fluxengine", - "//java/src/main/java/com/cowlark/fluxengine:fluxengine_java_lib", - "@maven//:com_google_dagger_dagger", - ], -) - -kt_jvm_binary( - name = "kotlin_bin", - main_class = "com.cowlark.fluxengine.Main", - deps = [":fluxengine_kotlin_lib"], -) +# This package is deprecated: the project has been migrated to Java. +# No Kotlin compilation targets are provided here to avoid accidental builds. diff --git a/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt b/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt index 05cc1c51..88cde7ee 100644 --- a/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt +++ b/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt @@ -1,64 +1,6 @@ -package com.cowlark.fluxengine - -import javax.inject.Inject -import javax.inject.Singleton -import dagger.Component -import dagger.Module -import dagger.Provides - -/** - * Simple Greeter service provided via Dagger. - */ -class Greeter @Inject constructor() { - fun greet() { - println("FluxEngine starting up...") - println("Hello from FluxEngine Kotlin main") - } -} - -/** - * Application module that provides application-scoped dependencies. - */ -@Module -class FluxengineModule { - @Provides - @Singleton - fun provideGreeter(): Greeter = Greeter() -} +// Main.kt deprecated - replaced by java/src/main/java/com/cowlark/fluxengine/Main.java +// Remove this file when you are ready to fully delete Kotlin sources. -/** - * Application component. Dagger will generate DaggerFluxengine when annotation - * processing runs (requires dagger-compiler and Kotlin KAPT or equivalent to be enabled). - * - * Declared as an abstract class to allow adding convenience methods and a companion - * factory that returns the generated implementation. - */ -@Singleton -@Component(modules = [FluxengineModule::class]) -abstract class Fluxengine { - // Abstract provider method implemented in generated subclass (DaggerFluxengine) - abstract fun greeter(): Greeter - - // Concrete helper method available to callers - fun start() { - greeter().greet() - } - - companion object { - @JvmStatic - fun create(): Fluxengine = DaggerFluxengine.create() - } -} +package com.cowlark.fluxengine -/** - * Entry point for the JVM application as a static main method on a class. - */ -class Main { - companion object { - @JvmStatic - fun main(args: Array) { - val component = Fluxengine.create() - component.start() - } - } -} +// Deprecated placeholder From 216d5cc85d556d3c4b3958829a66a06648c9f8ee Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 15:55:15 +0200 Subject: [PATCH 009/192] Add root alias //:fluxengine_native pointing at Java native_image target; keep //:fluxengine for JVM binary --- BUILD.bazel | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/BUILD.bazel b/BUILD.bazel index eb782770..b2cf67a2 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,7 +1,12 @@ package(default_visibility = ["//visibility:public"]) -# Root BUILD - alias exposing the Java main program at the repository root +# Root aliases for running/building the application alias( name = "fluxengine", actual = "//java/src/main/java/com/cowlark/fluxengine:fluxengine_main", ) + +alias( + name = "fluxengine_native", + actual = "//java/src/main/java/com/cowlark/fluxengine:fluxengine_native", +) From 712b4ba95b2dc03d45512ace1fcd813e9df3bbe0 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 16:03:06 +0200 Subject: [PATCH 010/192] Add rules_graalvm git_repository to WORKSPACE and add native_image target next to java_binary; document GraalVM SDK pinning and toolchain registration --- WORKSPACE | 45 ++++++++++++++++--- .../java/com/cowlark/fluxengine/BUILD.bazel | 19 ++++++++ 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 61479229..87816fb4 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -65,11 +65,46 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") # Register proto toolchain if desired by uncommenting the following line: # rules_proto_dependencies() +# ----------------------------------------------------------------------------- +# rules_graalvm (GraalVM native-image integration) +# We fetch the rules_graalvm repository so we can use the `native_image` rule and +# toolchain helpers. This is pinned to a specific commit to keep the workspace +# reproducible; replace the commit with a release tag if you prefer. +# ----------------------------------------------------------------------------- +load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") + +git_repository( + name = "rules_graalvm", + remote = "https://github.com/sgammon/rules_graalvm.git", + commit = "35d47821696b4d96aa579b184338fb5e1ea20885", +) + +# NOTE: The rules_graalvm repository provides helper repository rules (graalvm_repository +# or graal_bindist_repository) to declare an SDK repository (commonly named @graalvm). +# You must declare a GraalVM SDK repository appropriate for your host platform and pin +# it to a specific GraalVM CE build in order for `native_image` targets to be fully +# functional. See the rules_graalvm docs for examples: +# https://github.com/sgammon/rules_graalvm/blob/main/docs/native-image.md + +# Example (commented) — replace URL and sha256 with the GraalVM build you want to pin: +# load("@rules_graalvm//graalvm:repositories.bzl", "graalvm_bindist_repository") +# graalvm_bindist_repository( +# name = "graalvm", +# version = "22.3.0", +# url_template = "https://github.com/graalvm/graalvm-ce-builds/releases/download/v{version}/graalvm-ce-java17-linux-amd64-{version}.tar.gz", +# sha256 = "", +# ) + +# After declaring a @graalvm SDK repository, register the GraalVM toolchains so +# the `native_image` rule resolves the native-image tool for the host/exec +# platform automatically: +# load("@rules_graalvm//graalvm:toolchain.bzl", "register_graalvm_toolchains") +# register_graalvm_toolchains(name = "@graalvm") + # ----------------------------------------------------------------------------- # Notes -# - Omitting sha256 values reduces hermeticity. Replace these http_archive blocks -# with a version that includes sha256 values when you have the hashes. -# - If you later want to compute checksums locally, see previous commit messages or -# run a local script to download and compute the shasums and paste them into WORKSPACE. -# - After configuring the workspace properly, run `bazel fetch //...` then `bazel build`. +# - Omitting sha256 values reduces hermeticity. Replace these http_archive/git_repository +# blocks with versions that include sha256 values when you have the hashes. +# - After configuring the workspace properly (including a @graalvm SDK repo), run +# `bazel fetch //...` then `bazel build` to populate external dependencies. # ----------------------------------------------------------------------------- diff --git a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel index 1b766c31..d04f6bc3 100644 --- a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel @@ -24,3 +24,22 @@ java_binary( main_class = "com.cowlark.fluxengine.Main", deps = [":fluxengine_java_lib"], ) + +# Native image integration (GraalVM rules) +# The native_image rule is provided by the @rules_graalvm repository. It will +# attempt to resolve a native-image tool via toolchains; for that to work you +# must declare a GraalVM SDK repository (commonly @graalvm) and register its +# toolchains in WORKSPACE (see comments in WORKSPACE). + +load("@rules_graalvm//graal:graal.bzl", "native_image") + +native_image( + name = "fluxengine_native", + deps = [":fluxengine_java_lib"], + main_class = "com.cowlark.fluxengine.Main", + # executable_name defaults to -bin; override if desired. + executable_name = "fluxengine", + # If you have internal native archives (cc_library with linkstatic = True), + # add them here (or use extra_args) so native-image links them into the + # produced binary. We do not force --static linking of system libraries. +) From c94acc6f9d41c8e78a1b9baebd95bc98ec8a1907 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 16:22:57 +0200 Subject: [PATCH 011/192] Format Bazel files with buildifier: WORKSPACE, BUILD.bazel, java/* BUILD.bazel, native/BUILD.bazel --- BUILD.bazel | 2 +- WORKSPACE | 8 ++++--- java/BUILD.bazel | 2 +- .../java/com/cowlark/fluxengine/BUILD.bazel | 8 +------ .../kotlin/com/cowlark/fluxengine/BUILD.bazel | 2 +- .../java/com/cowlark/fluxengine/BUILD.bazel | 2 +- native/BUILD.bazel | 24 +++++++++++++++++++ 7 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 native/BUILD.bazel diff --git a/BUILD.bazel b/BUILD.bazel index b2cf67a2..ade8bbbe 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,4 +1,4 @@ -package(default_visibility = ["//visibility:public"]) +package(default_visibility = ["//visibility:public"]) # Root aliases for running/building the application alias( diff --git a/WORKSPACE b/WORKSPACE index 87816fb4..1a8935d5 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -1,6 +1,3 @@ -# Minimal WORKSPACE for Bazel Java/Kotlin/Protobuf builds for incremental migration. -# This file references specific release tarballs for Bazel rule sets without sha256 checksums. -# NOTE: Omitting sha256 makes fetches non-hermetic and is NOT recommended for long-term use. workspace(name = "fluxengine_java") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") @@ -17,6 +14,7 @@ http_archive( ) load("@rules_jvm_external//:defs.bzl", "maven_install") + # Configure Maven dependencies that we'll use during incremental migration. maven_install( name = "maven", @@ -46,7 +44,9 @@ http_archive( name = "io_bazel_rules_kotlin", url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v4.0.0/rules_kotlin-v4.0.0.tar.gz", ) + load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories") + # Registers Kotlin toolchain. Call this to register kotlin toolchain and enable KAPT support. # Note: rules_kotlin must be fetched successfully by bazel before this call will work. kotlin_repositories() @@ -61,7 +61,9 @@ http_archive( name = "rules_proto", url = "https://github.com/bazelbuild/rules_proto/releases/download/4.0.0/rules_proto-4.0.0.tar.gz", ) + load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") + # Register proto toolchain if desired by uncommenting the following line: # rules_proto_dependencies() diff --git a/java/BUILD.bazel b/java/BUILD.bazel index 48648463..4fa910b4 100644 --- a/java/BUILD.bazel +++ b/java/BUILD.bazel @@ -1,4 +1,4 @@ -package(default_visibility = ["//visibility:public"]) +package(default_visibility = ["//visibility:public"]) # Top-level package BUILD within java/ kept minimal: per-package BUILD files live under src/. # This file intentionally contains no targets that compile sources; see per-package BUILD files. diff --git a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel index d04f6bc3..7a21b303 100644 --- a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,4 +1,4 @@ -package(default_visibility = ["//visibility:public"]) +package(default_visibility = ["//visibility:public"]) # Per-package BUILD for Java sources in com.cowlark.fluxengine # Dagger annotation processor plugin for this Java package @@ -25,12 +25,6 @@ java_binary( deps = [":fluxengine_java_lib"], ) -# Native image integration (GraalVM rules) -# The native_image rule is provided by the @rules_graalvm repository. It will -# attempt to resolve a native-image tool via toolchains; for that to work you -# must declare a GraalVM SDK repository (commonly @graalvm) and register its -# toolchains in WORKSPACE (see comments in WORKSPACE). - load("@rules_graalvm//graal:graal.bzl", "native_image") native_image( diff --git a/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel index 02fe5f0f..6ba6a209 100644 --- a/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel +++ b/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel @@ -1,4 +1,4 @@ -package(default_visibility = ["//visibility:public"]) +package(default_visibility = ["//visibility:public"]) # Minimal per-package BUILD for Kotlin sources in com.cowlark.fluxengine # This package is deprecated: the project has been migrated to Java. diff --git a/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel b/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel index 507c485e..856ce588 100644 --- a/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,4 +1,4 @@ -package(default_visibility = ["//visibility:public"]) +package(default_visibility = ["//visibility:public"]) # Per-package BUILD for Java tests in com.cowlark.fluxengine java_test( diff --git a/native/BUILD.bazel b/native/BUILD.bazel new file mode 100644 index 00000000..52ff2458 --- /dev/null +++ b/native/BUILD.bazel @@ -0,0 +1,24 @@ +package(default_visibility = ["//visibility:public"]) + +# Placeholder cc_library for future JNI/native code. +# Do NOT add source files here yet if you don't want a native library built. +# Populate srcs/hdrs/copts when you add real native sources. +cc_library( + name = "fluxjni", + srcs = [], # e.g. ["fluxjni.cc"] + hdrs = [], # e.g. ["fluxjni.h"] + copts = [], # e.g. ["-I/path/to/jdk/include", "-I/path/to/jdk/include/linux"] + linkstatic = False, + visibility = ["//visibility:public"], +) + +# Example: to build a shared library suitable for System.loadLibrary(), add a +# cc_binary target like this (uncomment and adapt when you add sources): +# +# cc_binary( +# name = "fluxjni_so", +# srcs = ["fluxjni.cc"], +# copts = ["-fPIC", "-I$JAVA_HOME/include", "-I$JAVA_HOME/include/linux"], +# linkshared = 1, +# visibility = ["//visibility:public"], +# ) From 48bc6e34bf6f8246e5cd73040b3c815cb1d9d8b5 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 17:24:17 +0200 Subject: [PATCH 012/192] Use Bazel to build all targets in the root BUILD, remove package installs and NSIS, upload bazel artifacts (java branch) --- .github/workflows/ccpp.yml | 77 +++++++++++++++----------------------- 1 file changed, 30 insertions(+), 47 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 9fe71508..e7367795 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -26,9 +26,20 @@ jobs: with: repository: 'davidgiven/fluxengine-testdata' path: 'fluxengine-testdata' - - name: make + - name: Setup Bazel + uses: bazelbuild/setup-bazel@v4 + with: + install-bazelisk: true + - name: Build with Bazel run: | - cd fluxengine && docker build -t ${{ matrix.variant }} -f tests/docker/Dockerfile.${{ matrix.variant }} . + cd fluxengine + bazel build //:all + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts + path: | + fluxengine/bazel-bin/** build-macos-current: strategy: @@ -44,47 +55,25 @@ jobs: with: repository: 'davidgiven/fluxengine-testdata' path: 'fluxengine-testdata' - - name: brew - run: | - brew install sqlite pkg-config libusb protobuf wxwidgets fmt make coreutils dylibbundler libjpeg libmagic nlohmann-json cli11 boost glfw3 md4c ninja python freetype2 mbedtls@3 lunasvg - brew link mbedtls@3 - brew upgrade - - name: make + - name: Setup Bazel + uses: bazelbuild/setup-bazel@v4 + with: + install-bazelisk: true + - name: Build with Bazel run: | - g++ -v - gmake -C fluxengine + cd fluxengine + bazel build //:all - name: Upload build artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.event.repository.name }}.${{ github.sha }}.fluxengine.${{ runner.arch }}.pkg + name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts.${{ runner.arch }} path: | - fluxengine/FluxEngine.pkg - fluxengine/FluxEngine.app.zip + fluxengine/bazel-bin/** build-windows: runs-on: windows-latest - defaults: - run: - shell: msys2 {0} steps: - - uses: msys2/setup-msys2@v2 - with: - msystem: mingw64 - update: true - install: | - python diffutils ninja make zip git - pacboy: | - protobuf:p pkgconf:p curl-winssl:p file:p glfw:p mbedtls:p - sqlite:p freetype:p boost:p gcc:p binutils:p nsis:p abseil-cpp:p - - - name: debug - run: | - pacboy -Q --info protobuf:p - cat /mingw64/lib/pkgconfig/protobuf.pc - /mingw64/bin/pkg-config.exe protobuf --cflags - /mingw64/bin/pkg-config.exe protobuf --cflags --static - - uses: actions/checkout@v4 with: repository: 'davidgiven/fluxengine' @@ -95,24 +84,18 @@ jobs: repository: 'davidgiven/fluxengine-testdata' path: 'fluxengine-testdata' - - name: run - run: | - g++ -v - make -C fluxengine BUILDTYPE=windows AB_SANDBOX=no + - name: Setup Bazel + uses: bazelbuild/setup-bazel@v4 + with: + install-bazelisk: true - - name: nsis + - name: Build with Bazel run: | cd fluxengine - strip fluxengine.exe -o fluxengine-stripped.exe - strip fluxengine-gui.exe -o fluxengine-gui-stripped.exe - makensis -v2 -nocd -dOUTFILE=fluxengine-installer.exe extras/windows-installer.nsi - - - name: zip - run: | - cd fluxengine && zip -9 fluxengine-windows.zip fluxengine.exe fluxengine-gui.exe upgrade-flux-file.exe brother120tool.exe brother240tool.exe FluxEngine.cydsn/CortexM3/ARM_GCC_541/Release/FluxEngine.hex fluxengine-installer.exe + bazel build //:all - name: Upload build artifacts uses: actions/upload-artifact@v4 with: - name: ${{ github.event.repository.name }}.${{ github.sha }}.windows.zip - path: fluxengine/fluxengine-windows.zip + name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts.windows + path: fluxengine/bazel-bin/** From c05383afad1e00c91ed7f6b8707b44e9b40cb269 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 17:35:12 +0200 Subject: [PATCH 013/192] Use bazel-contrib/setup-bazel action for Bazel setup (fix hallucinated action) on java branch --- .github/workflows/ccpp.yml | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index e7367795..ecb675a9 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -27,9 +27,7 @@ jobs: repository: 'davidgiven/fluxengine-testdata' path: 'fluxengine-testdata' - name: Setup Bazel - uses: bazelbuild/setup-bazel@v4 - with: - install-bazelisk: true + uses: bazel-contrib/setup-bazel@v2 - name: Build with Bazel run: | cd fluxengine @@ -56,9 +54,7 @@ jobs: repository: 'davidgiven/fluxengine-testdata' path: 'fluxengine-testdata' - name: Setup Bazel - uses: bazelbuild/setup-bazel@v4 - with: - install-bazelisk: true + uses: bazel-contrib/setup-bazel@v2 - name: Build with Bazel run: | cd fluxengine @@ -85,9 +81,7 @@ jobs: path: 'fluxengine-testdata' - name: Setup Bazel - uses: bazelbuild/setup-bazel@v4 - with: - install-bazelisk: true + uses: bazel-contrib/setup-bazel@v2 - name: Build with Bazel run: | From a4ca78d5170a0d0acf8c7c7c96eb97ba563511ac Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 17:37:14 +0200 Subject: [PATCH 014/192] Fix bazel setup action version to bazel-contrib/setup-bazel@0.19.0 (java branch) --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index ecb675a9..bf251257 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -27,7 +27,7 @@ jobs: repository: 'davidgiven/fluxengine-testdata' path: 'fluxengine-testdata' - name: Setup Bazel - uses: bazel-contrib/setup-bazel@v2 + uses: bazel-contrib/setup-bazel@0.19.0 - name: Build with Bazel run: | cd fluxengine @@ -54,7 +54,7 @@ jobs: repository: 'davidgiven/fluxengine-testdata' path: 'fluxengine-testdata' - name: Setup Bazel - uses: bazel-contrib/setup-bazel@v2 + uses: bazel-contrib/setup-bazel@0.19.0 - name: Build with Bazel run: | cd fluxengine @@ -81,7 +81,7 @@ jobs: path: 'fluxengine-testdata' - name: Setup Bazel - uses: bazel-contrib/setup-bazel@v2 + uses: bazel-contrib/setup-bazel@0.19.0 - name: Build with Bazel run: | From 4d72c0ef1ec9f80c6ce3067d1fa6adbd1631a1f6 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 17:43:47 +0200 Subject: [PATCH 015/192] Use rules_graalvm release archive and register rules_graalvm_repositories (java branch) --- WORKSPACE | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 1a8935d5..8486ced9 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -69,43 +69,43 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") # ----------------------------------------------------------------------------- # rules_graalvm (GraalVM native-image integration) -# We fetch the rules_graalvm repository so we can use the `native_image` rule and -# toolchain helpers. This is pinned to a specific commit to keep the workspace -# reproducible; replace the commit with a release tag if you prefer. +# Use the official release archive rather than a git_repository so Bazel exposes +# the @rules_graalvm repository correctly during repository evaluation. +# See https://github.com/sgammon/rules_graalvm README for details. # ----------------------------------------------------------------------------- -load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository") - -git_repository( +http_archive( name = "rules_graalvm", - remote = "https://github.com/sgammon/rules_graalvm.git", - commit = "35d47821696b4d96aa579b184338fb5e1ea20885", + urls = [ + "https://github.com/sgammon/rules_graalvm/releases/download/v0.12.0/rules_graalvm-0.12.0.zip", + ], + strip_prefix = "rules_graalvm-0.12.0", + sha256 = "", # TODO: add the release sha256 for hermeticity ) -# NOTE: The rules_graalvm repository provides helper repository rules (graalvm_repository -# or graal_bindist_repository) to declare an SDK repository (commonly named @graalvm). -# You must declare a GraalVM SDK repository appropriate for your host platform and pin -# it to a specific GraalVM CE build in order for `native_image` targets to be fully -# functional. See the rules_graalvm docs for examples: -# https://github.com/sgammon/rules_graalvm/blob/main/docs/native-image.md +# Load helper repository functions from rules_graalvm and register the +# repository-level dependencies. This makes @rules_graalvm visible to the +# main workspace and avoids the "unknown repo 'rules_graalvm'" error. +load("@rules_graalvm//graalvm:workspace.bzl", "rules_graalvm_repositories") -# Example (commented) — replace URL and sha256 with the GraalVM build you want to pin: +# Register the rules' own external repositories (examples, toolchain helpers, etc.). +rules_graalvm_repositories() + +# NOTE: To use GraalVM as an SDK/toolchain you must declare a @graalvm SDK +# repository and register its toolchains. Example (uncomment and adjust): # load("@rules_graalvm//graalvm:repositories.bzl", "graalvm_bindist_repository") # graalvm_bindist_repository( # name = "graalvm", -# version = "22.3.0", +# version = "23.0.0", # url_template = "https://github.com/graalvm/graalvm-ce-builds/releases/download/v{version}/graalvm-ce-java17-linux-amd64-{version}.tar.gz", # sha256 = "", # ) - -# After declaring a @graalvm SDK repository, register the GraalVM toolchains so -# the `native_image` rule resolves the native-image tool for the host/exec -# platform automatically: -# load("@rules_graalvm//graalvm:toolchain.bzl", "register_graalvm_toolchains") +# +# load("@rules_graalvm//graalvm:workspace.bzl", "register_graalvm_toolchains") # register_graalvm_toolchains(name = "@graalvm") # ----------------------------------------------------------------------------- # Notes -# - Omitting sha256 values reduces hermeticity. Replace these http_archive/git_repository +# - Omitting sha256 values reduces hermeticity. Replace these http_archive # blocks with versions that include sha256 values when you have the hashes. # - After configuring the workspace properly (including a @graalvm SDK repo), run # `bazel fetch //...` then `bazel build` to populate external dependencies. From c22791a952d9e0041d5c074aa9aa819270acde92 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 3 Aug 2026 18:00:56 +0200 Subject: [PATCH 016/192] Configure rules_graalvm and graalvm_repository per README (java branch) --- WORKSPACE | 37 +++++++++++++++---------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/WORKSPACE b/WORKSPACE index 8486ced9..3cc85dab 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -69,39 +69,32 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") # ----------------------------------------------------------------------------- # rules_graalvm (GraalVM native-image integration) -# Use the official release archive rather than a git_repository so Bazel exposes -# the @rules_graalvm repository correctly during repository evaluation. -# See https://github.com/sgammon/rules_graalvm README for details. +# Use the official release archive and register the SDK via graalvm_repository +# as requested. Follow the rules_graalvm README for details. # ----------------------------------------------------------------------------- http_archive( name = "rules_graalvm", + sha256 = "", + strip_prefix = "rules_graalvm-0.12.0", urls = [ "https://github.com/sgammon/rules_graalvm/releases/download/v0.12.0/rules_graalvm-0.12.0.zip", ], - strip_prefix = "rules_graalvm-0.12.0", - sha256 = "", # TODO: add the release sha256 for hermeticity ) -# Load helper repository functions from rules_graalvm and register the -# repository-level dependencies. This makes @rules_graalvm visible to the -# main workspace and avoids the "unknown repo 'rules_graalvm'" error. -load("@rules_graalvm//graalvm:workspace.bzl", "rules_graalvm_repositories") +load("@rules_graalvm//graalvm:repositories.bzl", "graalvm_repository") + +graalvm_repository( + name = "graalvm", + distribution = "ce", # `oracle`, `ce`, or `community` + java_version = "23", # `17`, `20`, `22`, `23`, etc. + version = "23.0.0", # pass graalvm or specific jdk version supported by gvm +) + +load("@rules_graalvm//graalvm:workspace.bzl", "register_graalvm_toolchains", "rules_graalvm_repositories") -# Register the rules' own external repositories (examples, toolchain helpers, etc.). rules_graalvm_repositories() -# NOTE: To use GraalVM as an SDK/toolchain you must declare a @graalvm SDK -# repository and register its toolchains. Example (uncomment and adjust): -# load("@rules_graalvm//graalvm:repositories.bzl", "graalvm_bindist_repository") -# graalvm_bindist_repository( -# name = "graalvm", -# version = "23.0.0", -# url_template = "https://github.com/graalvm/graalvm-ce-builds/releases/download/v{version}/graalvm-ce-java17-linux-amd64-{version}.tar.gz", -# sha256 = "", -# ) -# -# load("@rules_graalvm//graalvm:workspace.bzl", "register_graalvm_toolchains") -# register_graalvm_toolchains(name = "@graalvm") +register_graalvm_toolchains() # ----------------------------------------------------------------------------- # Notes From 9876d4a154a12bce11ebcecacd9a7aa2f20b9de4 Mon Sep 17 00:00:00 2001 From: dtrg Date: Tue, 4 Aug 2026 14:01:10 +0200 Subject: [PATCH 017/192] At least the Java bits build now. --- .gitignore | 4 + MODULE.bazel | 24 + MODULE.bazel.lock | 450 ++++++++++++++++++ WORKSPACE | 62 +-- .../java/com/cowlark/fluxengine/BUILD.bazel | 40 +- .../java/com/cowlark/fluxengine/Main.java | 4 +- 6 files changed, 513 insertions(+), 71 deletions(-) create mode 100644 MODULE.bazel create mode 100644 MODULE.bazel.lock diff --git a/.gitignore b/.gitignore index 46868929..abd78000 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +bazel-fluxengine +bazel-testlogs +bazel-out +bazel-bin .obj .project /.ninja* diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 00000000..f30c205f --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,24 @@ +bazel_dep(name = "rules_java", version = "9.1.0") +bazel_dep(name = "rules_graalvm", version = "0.11.1") +bazel_dep(name = "rules_jvm_external", version = "6.7") + +git_override( + module_name = "rules_graalvm", + remote = "https://github.com/sgammon/rules_graalvm.git", + branch = "main", +) + +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") +maven.install( + artifacts = [ + "org.projectlombok:lombok:1.18.30", + "com.google.dagger:dagger:2.51.1", + "com.google.dagger:dagger-compiler:2.51.1", + # Add any other Maven artifacts your plugins or libraries need here + ], + repositories = [ + "https://repo1.maven.org/maven2", + ], +) +use_repo(maven, "maven") + diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 00000000..3bd56e26 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,450 @@ +{ + "lockFileVersion": 28, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.1/MODULE.bazel": "c4a89e7ceb9bf1e25cf84a9f830ff6b817b72874088bf5141b314726e46a57c1", + "https://bcr.bazel.build/modules/abseil-cpp/20250512.1/MODULE.bazel": "d209fdb6f36ffaf61c509fcc81b19e81b411a999a934a032e10cd009a0226215", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/MODULE.bazel": "51f2312901470cdab0dbdf3b88c40cd21c62a7ed58a3de45b365ddc5b11bcab2", + "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/bazel_features/1.0.0/MODULE.bazel": "d7f022dc887efb96e1ee51cec7b2e48d41e36ff59a6e4f216c40e4029e1585bf", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.27.0/MODULE.bazel": "621eeee06c4458a9121d1f104efb80f39d34deff4984e778359c60eaf1a8cb65", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", + "https://bcr.bazel.build/modules/googletest/1.17.0/MODULE.bazel": "dbec758171594a705933a29fcf69293d2468c49ec1f2ebca65c36f504d72df46", + "https://bcr.bazel.build/modules/googletest/1.17.0/source.json": "38e4454b25fc30f15439c0378e57909ab1fd0a443158aa35aec685da727cd713", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/platforms/1.0.0/MODULE.bazel": "f05feb42b48f1b3c225e4ccf351f367be0371411a803198ec34a389fb22aa580", + "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", + "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", + "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", + "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_java/9.1.0/MODULE.bazel": "ee63f27e36a3fada80342869361182f120a9819c74320e8e65b1e04ba0cd7a9d", + "https://bcr.bazel.build/modules/rules_java/9.1.0/source.json": "da589573c1dee2c9ac4a568b301269a2e8191110ff0345c1a959fa7ea6c4dfd6", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", + "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", + "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "+Kp6j204mBZ3mxlIDDR0gBoP45BZ4jYRhRAcB8sU0qc=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedInputs": [ + "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" + ], + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + } + } + }, + "@@rules_python+//python/extensions:config.bzl%config": { + "general": { + "bzlTransitiveDigest": "dzD8Q2YmrP3fz8saWLHPmlwPLO91ImtTmP/c9JKTStM=", + "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", + "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", + "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", + "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", + "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", + "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", + "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", + "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", + "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", + "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", + "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", + "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", + "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", + "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", + "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" + ], + "generatedRepoSpecs": { + "rules_python_internal": { + "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", + "attributes": { + "transition_setting_generators": {}, + "transition_settings": [] + } + }, + "pypi__build": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__click": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__colorama": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__packaging": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + } + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", + "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + } + } + } + }, + "facts": {}, + "factsVersions": {} +} diff --git a/WORKSPACE b/WORKSPACE index 3cc85dab..f37c95f5 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -2,54 +2,6 @@ workspace(name = "fluxengine_java") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -# ----------------------------------------------------------------------------- -# rules_jvm_external (manage Maven artifacts like Dagger, JUnit) -# Release: 7.1 -# Download URL (browser): -# https://github.com/bazelbuild/rules_jvm_external/releases/download/7.1/rules_jvm_external-7.1.tar.gz -# ----------------------------------------------------------------------------- -http_archive( - name = "rules_jvm_external", - url = "https://github.com/bazelbuild/rules_jvm_external/releases/download/7.1/rules_jvm_external-7.1.tar.gz", -) - -load("@rules_jvm_external//:defs.bzl", "maven_install") - -# Configure Maven dependencies that we'll use during incremental migration. -maven_install( - name = "maven", - artifacts = [ - "junit:junit:4.13.2", - "com.google.dagger:dagger:2.44", - "com.google.dagger:dagger-compiler:2.44", - # Add more artifacts here as needed. - ], - repositories = [ - "https://repo1.maven.org/maven2", - ], -) - -# Notes on maven_install artifact labels -# - com.google.dagger:dagger -> @maven//:com_google_dagger_dagger -# - com.google.dagger:dagger-compiler -> @maven//:com_google_dagger_dagger_compiler -# - junit:junit -> @maven//:junit_junit - -# ----------------------------------------------------------------------------- -# rules_kotlin (Kotlin support for Bazel) -# Release: v4.0.0 -# Download URL (browser): -# https://github.com/bazelbuild/rules_kotlin/releases/download/v4.0.0/rules_kotlin-v4.0.0.tar.gz -# ----------------------------------------------------------------------------- -http_archive( - name = "io_bazel_rules_kotlin", - url = "https://github.com/bazelbuild/rules_kotlin/releases/download/v4.0.0/rules_kotlin-v4.0.0.tar.gz", -) - -load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kotlin_repositories") - -# Registers Kotlin toolchain. Call this to register kotlin toolchain and enable KAPT support. -# Note: rules_kotlin must be fetched successfully by bazel before this call will work. -kotlin_repositories() # ----------------------------------------------------------------------------- # rules_proto (Protobuf support) @@ -74,7 +26,7 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") # ----------------------------------------------------------------------------- http_archive( name = "rules_graalvm", - sha256 = "", + sha256 = "3ef2f1583a4849d03209a43b0b507f172299c3045e585b6ffa7144a2bc12ae18", strip_prefix = "rules_graalvm-0.12.0", urls = [ "https://github.com/sgammon/rules_graalvm/releases/download/v0.12.0/rules_graalvm-0.12.0.zip", @@ -85,12 +37,16 @@ load("@rules_graalvm//graalvm:repositories.bzl", "graalvm_repository") graalvm_repository( name = "graalvm", - distribution = "ce", # `oracle`, `ce`, or `community` - java_version = "23", # `17`, `20`, `22`, `23`, etc. - version = "23.0.0", # pass graalvm or specific jdk version supported by gvm + distribution = "ce", + java_version = "23", + version = "23.0.0", ) -load("@rules_graalvm//graalvm:workspace.bzl", "register_graalvm_toolchains", "rules_graalvm_repositories") +load( + "@rules_graalvm//graalvm:workspace.bzl", + "register_graalvm_toolchains", + "rules_graalvm_repositories", +) rules_graalvm_repositories() diff --git a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel index 7a21b303..38323d66 100644 --- a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,3 +1,5 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin") + package(default_visibility = ["//visibility:public"]) # Per-package BUILD for Java sources in com.cowlark.fluxengine @@ -11,29 +13,35 @@ java_plugin( ) java_library( - name = "fluxengine_java_lib", + name = "dagger", + exported_plugins = [":dagger_plugin"], +) + +java_library( + name = "fluxengine_lib", srcs = glob(["*.java"]), deps = [ + ":dagger", "@maven//:com_google_dagger_dagger", + "@maven//:javax_inject_javax_inject", ], - plugins = [":dagger_plugin"], ) java_binary( - name = "fluxengine_main", + name = "fluxengine", main_class = "com.cowlark.fluxengine.Main", - deps = [":fluxengine_java_lib"], + runtime_deps = [":fluxengine_lib"], ) -load("@rules_graalvm//graal:graal.bzl", "native_image") - -native_image( - name = "fluxengine_native", - deps = [":fluxengine_java_lib"], - main_class = "com.cowlark.fluxengine.Main", - # executable_name defaults to -bin; override if desired. - executable_name = "fluxengine", - # If you have internal native archives (cc_library with linkstatic = True), - # add them here (or use extra_args) so native-image links them into the - # produced binary. We do not force --static linking of system libraries. -) +#load("@rules_graalvm//graal:graal.bzl", "native_image") +# +#native_image( +# name = "fluxengine_native", +# deps = [":fluxengine_lib"], +# main_class = "com.cowlark.fluxengine.Main", +# # executable_name defaults to -bin; override if desired. +# executable_name = "fluxengine", +# # If you have internal native archives (cc_library with linkstatic = True), +# # add them here (or use extra_args) so native-image links them into the +# # produced binary. We do not force --static linking of system libraries. +#) diff --git a/java/src/main/java/com/cowlark/fluxengine/Main.java b/java/src/main/java/com/cowlark/fluxengine/Main.java index 94906b44..390d930c 100644 --- a/java/src/main/java/com/cowlark/fluxengine/Main.java +++ b/java/src/main/java/com/cowlark/fluxengine/Main.java @@ -10,7 +10,7 @@ /** * Simple Greeter service provided via Dagger. */ -public class Greeter { +class Greeter { @Inject public Greeter() { } @@ -37,7 +37,7 @@ Greeter provideGreeter() { * Concrete application class with injected dependencies (Guice-style). */ @Singleton -public class Fluxengine { +class Fluxengine { private final Greeter greeter; @Inject From caceb06144057867e04d7ecf368130061266bf6a Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 16:35:53 +0200 Subject: [PATCH 018/192] Finally get a native binary made! --- .bazelversion | 2 + BUILD.bazel | 2 +- MODULE.bazel | 23 +- MODULE.bazel.lock | 270 ++++-------------- WORKSPACE | 40 --- .../java/com/cowlark/fluxengine/BUILD.bazel | 13 +- native_image.bzl | 51 ++++ 7 files changed, 135 insertions(+), 266 deletions(-) create mode 100644 .bazelversion create mode 100644 native_image.bzl diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 00000000..599d14aa --- /dev/null +++ b/.bazelversion @@ -0,0 +1,2 @@ +8.7.0 + diff --git a/BUILD.bazel b/BUILD.bazel index ade8bbbe..6ec6191d 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,7 +3,7 @@ package(default_visibility = ["//visibility:public"]) # Root aliases for running/building the application alias( name = "fluxengine", - actual = "//java/src/main/java/com/cowlark/fluxengine:fluxengine_main", + actual = "//java/src/main/java/com/cowlark/fluxengine", ) alias( diff --git a/MODULE.bazel b/MODULE.bazel index f30c205f..58299b95 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,11 +1,24 @@ bazel_dep(name = "rules_java", version = "9.1.0") -bazel_dep(name = "rules_graalvm", version = "0.11.1") bazel_dep(name = "rules_jvm_external", version = "6.7") -git_override( - module_name = "rules_graalvm", - remote = "https://github.com/sgammon/rules_graalvm.git", - branch = "main", +http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "graalvm", + build_file_content = """ +package(default_visibility = ["//visibility:public"]) +exports_files(glob(["**/*"])) + +filegroup( + name = "java_home", + srcs = glob(["**/*"]), +) +""", + # Update URLs / SHA256 per platform as needed, or register via local_jdk + strip_prefix = "graalvm-jdk-21+35.1", + urls = [ + "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21_linux-x64_bin.tar.gz", + ], ) maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 3bd56e26..0dbe895f 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 28, + "lockFileVersion": 24, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -16,13 +16,9 @@ "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", - "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", - "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", - "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", - "https://bcr.bazel.build/modules/bazel_features/1.0.0/MODULE.bazel": "d7f022dc887efb96e1ee51cec7b2e48d41e36ff59a6e4f216c40e4029e1585bf", + "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", + "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", - "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", @@ -34,10 +30,8 @@ "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", - "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", - "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", @@ -51,10 +45,9 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", - "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", - "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -80,13 +73,14 @@ "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", - "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", - "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", + "https://bcr.bazel.build/modules/protobuf/32.1/source.json": "bd2664e90875c0cd755d1d9b7a103a4b027893ac8eafa3bba087557ffc244ad4", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", @@ -97,11 +91,11 @@ "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", @@ -110,22 +104,22 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", - "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", - "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/source.json": "f872e892c5265c5532e526857532f4868708f88d64e5ebe517ea72e09da61bdb", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", @@ -134,9 +128,12 @@ "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", @@ -150,6 +147,7 @@ "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", @@ -159,28 +157,24 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", - "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", - "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", - "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_python/1.4.1/source.json": "8ec8c90c70ccacc4de8ca1b97f599e756fb59173e898ee08b733006650057c07", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", - "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", - "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", @@ -191,11 +185,11 @@ "moduleExtensions": { "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "+Kp6j204mBZ3mxlIDDR0gBoP45BZ4jYRhRAcB8sU0qc=", + "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedInputs": [ - "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" - ], + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, "generatedRepoSpecs": { "com_github_jetbrains_kotlin_git": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", @@ -243,185 +237,23 @@ ] } } - } - } - }, - "@@rules_python+//python/extensions:config.bzl%config": { - "general": { - "bzlTransitiveDigest": "dzD8Q2YmrP3fz8saWLHPmlwPLO91ImtTmP/c9JKTStM=", - "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", - "recordedInputs": [ - "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", - "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", - "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", - "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", - "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", - "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", - "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", - "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", - "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", - "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", - "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", - "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", - "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", - "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", - "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" - ], - "generatedRepoSpecs": { - "rules_python_internal": { - "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", - "attributes": { - "transition_setting_generators": {}, - "transition_settings": [] - } - }, - "pypi__build": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", - "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__click": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", - "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__colorama": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", - "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__importlib_metadata": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", - "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__installer": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", - "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__more_itertools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", - "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__packaging": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", - "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pep517": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", - "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", - "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip_tools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", - "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pyproject_hooks": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", - "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__setuptools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", - "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__tomli": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", - "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__wheel": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", - "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__zipp": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", - "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - } - } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin+", + "bazel_tools", + "bazel_tools" + ] + ] } }, "@@rules_python+//python/uv:uv.bzl%uv": { "general": { - "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", - "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", - "recordedInputs": [ - "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_python+,platforms platforms" - ], + "bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=", + "usagesDigest": "4JapxcpS0mL3524k0TZJffAtVyuRjDHZvN9kBRxxF1U=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, "generatedRepoSpecs": { "uv": { "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", @@ -441,10 +273,16 @@ "toolchain_target_settings": {} } } - } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python+", + "platforms", + "platforms" + ] + ] } } }, - "facts": {}, - "factsVersions": {} + "facts": {} } diff --git a/WORKSPACE b/WORKSPACE index f37c95f5..d3501540 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -19,43 +19,3 @@ load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") # Register proto toolchain if desired by uncommenting the following line: # rules_proto_dependencies() -# ----------------------------------------------------------------------------- -# rules_graalvm (GraalVM native-image integration) -# Use the official release archive and register the SDK via graalvm_repository -# as requested. Follow the rules_graalvm README for details. -# ----------------------------------------------------------------------------- -http_archive( - name = "rules_graalvm", - sha256 = "3ef2f1583a4849d03209a43b0b507f172299c3045e585b6ffa7144a2bc12ae18", - strip_prefix = "rules_graalvm-0.12.0", - urls = [ - "https://github.com/sgammon/rules_graalvm/releases/download/v0.12.0/rules_graalvm-0.12.0.zip", - ], -) - -load("@rules_graalvm//graalvm:repositories.bzl", "graalvm_repository") - -graalvm_repository( - name = "graalvm", - distribution = "ce", - java_version = "23", - version = "23.0.0", -) - -load( - "@rules_graalvm//graalvm:workspace.bzl", - "register_graalvm_toolchains", - "rules_graalvm_repositories", -) - -rules_graalvm_repositories() - -register_graalvm_toolchains() - -# ----------------------------------------------------------------------------- -# Notes -# - Omitting sha256 values reduces hermeticity. Replace these http_archive -# blocks with versions that include sha256 values when you have the hashes. -# - After configuring the workspace properly (including a @graalvm SDK repo), run -# `bazel fetch //...` then `bazel build` to populate external dependencies. -# ----------------------------------------------------------------------------- diff --git a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel index 38323d66..8f5d8501 100644 --- a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin") +load("//:native_image.bzl", "native_image") package(default_visibility = ["//visibility:public"]) @@ -33,10 +34,14 @@ java_binary( runtime_deps = [":fluxengine_lib"], ) -#load("@rules_graalvm//graal:graal.bzl", "native_image") -# -#native_image( -# name = "fluxengine_native", +native_image( + name = "fluxengine_native", + jar = ":fluxengine_deploy.jar", + extra_args = [ + "--no-fallback", + "-O2" + ] +) # deps = [":fluxengine_lib"], # main_class = "com.cowlark.fluxengine.Main", # # executable_name defaults to -bin; override if desired. diff --git a/native_image.bzl b/native_image.bzl new file mode 100644 index 00000000..758adf82 --- /dev/null +++ b/native_image.bzl @@ -0,0 +1,51 @@ +def _native_image_impl(ctx): + # Determine binary extension based on OS + is_windows = ctx.configuration.host_path_separator == ";" + out_name = ctx.label.name + (".exe" if is_windows else "") + out_binary = ctx.actions.declare_file(out_name) + + # Collect JAR files from deploy_jar or java_binary input + jar_file = ctx.file.jar + + # Build native-image arguments + args = ctx.actions.args() + args.add("-jar", jar_file.path) + args.add("-H:Name=" + out_binary.path) + + for extra_arg in ctx.attr.extra_args: + args.add(extra_arg) + + # Execute the native-image tool from the GraalVM toolchain/repo + ctx.actions.run( + outputs = [out_binary], + inputs = [jar_file], + executable = ctx.executable._native_image_tool, + arguments = [args], + mnemonic = "GraalVMNativeImage", + progress_message = "Building GraalVM native image %s" % ctx.label.name, + use_default_shell_env=True, + ) + + return [DefaultInfo(executable = out_binary)] + +native_image = rule( + implementation = _native_image_impl, + executable = True, + attrs = { + "jar": attr.label( + mandatory = True, + allow_single_file = [".jar"], + doc = "The deployable JAR file (e.g. :app_deploy.jar)", + ), + "extra_args": attr.string_list( + default = [], + doc = "Additional flags passed to native-image (e.g. --no-fallback)", + ), + "_native_image_tool": attr.label( + default = Label("@graalvm//:bin/native-image"), + allow_single_file = True, + executable = True, + cfg = "exec", + ), + }, +) From 2332c662e1a7c97c1657878bbedfdd8661ef603a Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 16:57:15 +0200 Subject: [PATCH 019/192] Try to make binaries on all platforms. --- MODULE.bazel | 19 ++---------- MODULE.bazel.lock | 16 ++++++++++ graalvm_extension.bzl | 8 +++++ graalvm_repository.bzl | 70 ++++++++++++++++++++++++++++++++++++++++++ native_image.bzl | 15 +++------ 5 files changed, 100 insertions(+), 28 deletions(-) create mode 100644 graalvm_extension.bzl create mode 100644 graalvm_repository.bzl diff --git a/MODULE.bazel b/MODULE.bazel index 58299b95..fdfe42a6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -3,23 +3,8 @@ bazel_dep(name = "rules_jvm_external", version = "6.7") http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -http_archive( - name = "graalvm", - build_file_content = """ -package(default_visibility = ["//visibility:public"]) -exports_files(glob(["**/*"])) - -filegroup( - name = "java_home", - srcs = glob(["**/*"]), -) -""", - # Update URLs / SHA256 per platform as needed, or register via local_jdk - strip_prefix = "graalvm-jdk-21+35.1", - urls = [ - "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21_linux-x64_bin.tar.gz", - ], -) +graalvm_ext = use_extension("//:graalvm_extension.bzl", "graalvm_ext") +use_repo(graalvm_ext, "graalvm") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 0dbe895f..09940401 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -183,6 +183,22 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { + "//:graalvm_extension.bzl%graalvm_ext": { + "general": { + "bzlTransitiveDigest": "YNdJVd0z2Zz/CwXyMp9xfCg0hQuOo4r6jJkynvrByNk=", + "usagesDigest": "iUXd/3jCJaegO0Dllj33xgb8pqISEwNiOB/aoc6/sBQ=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "graalvm": { + "repoRuleId": "@@//:graalvm_repository.bzl%graalvm_repository", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", diff --git a/graalvm_extension.bzl b/graalvm_extension.bzl new file mode 100644 index 00000000..5199881d --- /dev/null +++ b/graalvm_extension.bzl @@ -0,0 +1,8 @@ +load("//:graalvm_repository.bzl", "graalvm_repository") + +def _graalvm_ext_impl(mctx): + graalvm_repository(name = "graalvm") + +graalvm_ext = module_extension( + implementation = _graalvm_ext_impl, +) diff --git a/graalvm_repository.bzl b/graalvm_repository.bzl new file mode 100644 index 00000000..bb047eb6 --- /dev/null +++ b/graalvm_repository.bzl @@ -0,0 +1,70 @@ +_GRAALVM_URLS = { + "linux_x86_64": { + "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_linux-x64_bin.tar.gz", + "strip_prefix": "graalvm-jdk-21.0.2+13.1", + }, + "linux_aarch64": { + "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_linux-aarch64_bin.tar.gz", + "strip_prefix": "graalvm-jdk-21.0.2+13.1", + }, + "macos_x86_64": { + "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_macos-x64_bin.tar.gz", + "strip_prefix": "graalvm-jdk-21.0.2+13.1/Contents/Home", + }, + "macos_aarch64": { + "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_macos-aarch64_bin.tar.gz", + "strip_prefix": "graalvm-jdk-21.0.2+13.1/Contents/Home", + }, + "windows_x86_64": { + "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_windows-x64_bin.zip", + "strip_prefix": "graalvm-jdk-21.0.2+13.1", + }, +} + +def _graalvm_repository_impl(ctx): + os_name = ctx.os.name.lower() + arch = ctx.os.arch.lower() + + # Normalize OS name + if "mac" in os_name or "darwin" in os_name: + os_key = "macos" + elif "win" in os_name: + os_key = "windows" + else: + os_key = "linux" + + # Normalize architecture + if arch in ["aarch64", "arm64"]: + arch_key = "aarch64" + else: + arch_key = "x86_64" + + key = "%s_%s" % (os_key, arch_key) + if key not in _GRAALVM_URLS: + fail("Unsupported platform for GraalVM: %s" % key) + + info = _GRAALVM_URLS[key] + + # Download and extract the platform archive + ctx.download_and_extract( + url = info["url"], + stripPrefix = info["strip_prefix"], + ) + + # Expose binary files and executables to Bazel + ctx.file( + "BUILD.bazel", + """ +package(default_visibility = ["//visibility:public"]) +exports_files(glob(["**/*"])) + +filegroup( + name = "java_home", + srcs = glob(["**/*"]), +) +""", + ) + +graalvm_repository = repository_rule( + implementation = _graalvm_repository_impl, +) diff --git a/native_image.bzl b/native_image.bzl index 758adf82..24580eee 100644 --- a/native_image.bzl +++ b/native_image.bzl @@ -1,21 +1,17 @@ def _native_image_impl(ctx): - # Determine binary extension based on OS is_windows = ctx.configuration.host_path_separator == ";" out_name = ctx.label.name + (".exe" if is_windows else "") out_binary = ctx.actions.declare_file(out_name) - # Collect JAR files from deploy_jar or java_binary input jar_file = ctx.file.jar - # Build native-image arguments args = ctx.actions.args() args.add("-jar", jar_file.path) args.add("-H:Name=" + out_binary.path) - + for extra_arg in ctx.attr.extra_args: args.add(extra_arg) - # Execute the native-image tool from the GraalVM toolchain/repo ctx.actions.run( outputs = [out_binary], inputs = [jar_file], @@ -23,7 +19,7 @@ def _native_image_impl(ctx): arguments = [args], mnemonic = "GraalVMNativeImage", progress_message = "Building GraalVM native image %s" % ctx.label.name, - use_default_shell_env=True, + use_default_shell_env = True, ) return [DefaultInfo(executable = out_binary)] @@ -35,12 +31,8 @@ native_image = rule( "jar": attr.label( mandatory = True, allow_single_file = [".jar"], - doc = "The deployable JAR file (e.g. :app_deploy.jar)", - ), - "extra_args": attr.string_list( - default = [], - doc = "Additional flags passed to native-image (e.g. --no-fallback)", ), + "extra_args": attr.string_list(default = []), "_native_image_tool": attr.label( default = Label("@graalvm//:bin/native-image"), allow_single_file = True, @@ -49,3 +41,4 @@ native_image = rule( ), }, ) + From f42acd251f8b776b385ecfbda90e3ba7153336a6 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 17:01:29 +0200 Subject: [PATCH 020/192] Try and build native images on Windows. --- native_image.bzl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/native_image.bzl b/native_image.bzl index 24580eee..c839ec46 100644 --- a/native_image.bzl +++ b/native_image.bzl @@ -1,8 +1,13 @@ def _native_image_impl(ctx): is_windows = ctx.configuration.host_path_separator == ";" + + # 1. Determine output binary file name out_name = ctx.label.name + (".exe" if is_windows else "") out_binary = ctx.actions.declare_file(out_name) + # 2. Select the correct tool executable depending on host OS + tool_file = ctx.file._native_image_win if is_windows else ctx.file._native_image_unix + jar_file = ctx.file.jar args = ctx.actions.args() @@ -15,7 +20,7 @@ def _native_image_impl(ctx): ctx.actions.run( outputs = [out_binary], inputs = [jar_file], - executable = ctx.executable._native_image_tool, + executable = tool_file, arguments = [args], mnemonic = "GraalVMNativeImage", progress_message = "Building GraalVM native image %s" % ctx.label.name, @@ -33,12 +38,17 @@ native_image = rule( allow_single_file = [".jar"], ), "extra_args": attr.string_list(default = []), - "_native_image_tool": attr.label( + "_native_image_unix": attr.label( default = Label("@graalvm//:bin/native-image"), allow_single_file = True, executable = True, cfg = "exec", ), + "_native_image_win": attr.label( + default = Label("@graalvm//:bin/native-image.cmd"), + allow_single_file = True, + executable = True, + cfg = "exec", + ), }, ) - From ee3da192c676755e0fed8378ad1edd21120f5bae Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 17:14:23 +0200 Subject: [PATCH 021/192] Try and fix the Windows builds. --- graalvm_repository.bzl | 14 +++++++++----- native_image.bzl | 17 +++-------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/graalvm_repository.bzl b/graalvm_repository.bzl index bb047eb6..328d2600 100644 --- a/graalvm_repository.bzl +++ b/graalvm_repository.bzl @@ -25,7 +25,6 @@ def _graalvm_repository_impl(ctx): os_name = ctx.os.name.lower() arch = ctx.os.arch.lower() - # Normalize OS name if "mac" in os_name or "darwin" in os_name: os_key = "macos" elif "win" in os_name: @@ -33,7 +32,6 @@ def _graalvm_repository_impl(ctx): else: os_key = "linux" - # Normalize architecture if arch in ["aarch64", "arm64"]: arch_key = "aarch64" else: @@ -45,24 +43,30 @@ def _graalvm_repository_impl(ctx): info = _GRAALVM_URLS[key] - # Download and extract the platform archive ctx.download_and_extract( url = info["url"], stripPrefix = info["strip_prefix"], ) - # Expose binary files and executables to Bazel + # Alias target points to .cmd on Windows, standard executable on Linux/macOS + launcher = "bin/native-image.cmd" if os_key == "windows" else "bin/native-image" + ctx.file( "BUILD.bazel", """ package(default_visibility = ["//visibility:public"]) exports_files(glob(["**/*"])) +alias( + name = "native_image_tool", + actual = "%s", +) + filegroup( name = "java_home", srcs = glob(["**/*"]), ) -""", +""" % launcher, ) graalvm_repository = repository_rule( diff --git a/native_image.bzl b/native_image.bzl index c839ec46..ef4ee315 100644 --- a/native_image.bzl +++ b/native_image.bzl @@ -1,13 +1,8 @@ def _native_image_impl(ctx): is_windows = ctx.configuration.host_path_separator == ";" - - # 1. Determine output binary file name out_name = ctx.label.name + (".exe" if is_windows else "") out_binary = ctx.actions.declare_file(out_name) - # 2. Select the correct tool executable depending on host OS - tool_file = ctx.file._native_image_win if is_windows else ctx.file._native_image_unix - jar_file = ctx.file.jar args = ctx.actions.args() @@ -20,7 +15,7 @@ def _native_image_impl(ctx): ctx.actions.run( outputs = [out_binary], inputs = [jar_file], - executable = tool_file, + executable = ctx.executable._native_image_tool, arguments = [args], mnemonic = "GraalVMNativeImage", progress_message = "Building GraalVM native image %s" % ctx.label.name, @@ -38,14 +33,8 @@ native_image = rule( allow_single_file = [".jar"], ), "extra_args": attr.string_list(default = []), - "_native_image_unix": attr.label( - default = Label("@graalvm//:bin/native-image"), - allow_single_file = True, - executable = True, - cfg = "exec", - ), - "_native_image_win": attr.label( - default = Label("@graalvm//:bin/native-image.cmd"), + "_native_image_tool": attr.label( + default = Label("@graalvm//:native_image_tool"), allow_single_file = True, executable = True, cfg = "exec", From bb2d48519b5f43cb8d69705f1ba488891d3bca2a Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 17:18:37 +0200 Subject: [PATCH 022/192] Initialise Visual Studio. --- .github/workflows/ccpp.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index bf251257..a88e41e2 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -83,10 +83,19 @@ jobs: - name: Setup Bazel uses: bazel-contrib/setup-bazel@0.19.0 + - name: Set up MSVC Developer Environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 + - name: Build with Bazel run: | cd fluxengine - bazel build //:all + bazel build //:all ` + --action_env=PATH ` + --action_env=INCLUDE ` + --action_env=LIB ` + --action_env=LIBPATH - name: Upload build artifacts uses: actions/upload-artifact@v4 From 99f1764dc69c88f1846658577e2665d7393b2c80 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 17:22:00 +0200 Subject: [PATCH 023/192] Remove the old Kotlin stuff. --- .../kotlin/com/cowlark/fluxengine/BUILD.bazel | 5 ---- .../kotlin/com/cowlark/fluxengine/Main.kt | 6 ----- native/BUILD.bazel | 24 ------------------- 3 files changed, 35 deletions(-) delete mode 100644 java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel delete mode 100644 java/src/main/kotlin/com/cowlark/fluxengine/Main.kt delete mode 100644 native/BUILD.bazel diff --git a/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel b/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel deleted file mode 100644 index 6ba6a209..00000000 --- a/java/src/main/kotlin/com/cowlark/fluxengine/BUILD.bazel +++ /dev/null @@ -1,5 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -# Minimal per-package BUILD for Kotlin sources in com.cowlark.fluxengine -# This package is deprecated: the project has been migrated to Java. -# No Kotlin compilation targets are provided here to avoid accidental builds. diff --git a/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt b/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt deleted file mode 100644 index 88cde7ee..00000000 --- a/java/src/main/kotlin/com/cowlark/fluxengine/Main.kt +++ /dev/null @@ -1,6 +0,0 @@ -// Main.kt deprecated - replaced by java/src/main/java/com/cowlark/fluxengine/Main.java -// Remove this file when you are ready to fully delete Kotlin sources. - -package com.cowlark.fluxengine - -// Deprecated placeholder diff --git a/native/BUILD.bazel b/native/BUILD.bazel deleted file mode 100644 index 52ff2458..00000000 --- a/native/BUILD.bazel +++ /dev/null @@ -1,24 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -# Placeholder cc_library for future JNI/native code. -# Do NOT add source files here yet if you don't want a native library built. -# Populate srcs/hdrs/copts when you add real native sources. -cc_library( - name = "fluxjni", - srcs = [], # e.g. ["fluxjni.cc"] - hdrs = [], # e.g. ["fluxjni.h"] - copts = [], # e.g. ["-I/path/to/jdk/include", "-I/path/to/jdk/include/linux"] - linkstatic = False, - visibility = ["//visibility:public"], -) - -# Example: to build a shared library suitable for System.loadLibrary(), add a -# cc_binary target like this (uncomment and adapt when you add sources): -# -# cc_binary( -# name = "fluxjni_so", -# srcs = ["fluxjni.cc"], -# copts = ["-fPIC", "-I$JAVA_HOME/include", "-I$JAVA_HOME/include/linux"], -# linkshared = 1, -# visibility = ["//visibility:public"], -# ) From 129443ed8c8166d3287cf3991e63a321870d3866 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 17:29:35 +0200 Subject: [PATCH 024/192] Another Windows fix. --- native_image.bzl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/native_image.bzl b/native_image.bzl index ef4ee315..fcb68ae8 100644 --- a/native_image.bzl +++ b/native_image.bzl @@ -1,13 +1,25 @@ def _native_image_impl(ctx): is_windows = ctx.configuration.host_path_separator == ";" - out_name = ctx.label.name + (".exe" if is_windows else "") + + # 1. Ensure out_name ends with .exe on Windows (without duplicating it) + base_name = ctx.label.name + if is_windows and not base_name.lower().endswith(".exe"): + out_name = base_name + ".exe" + else: + out_name = base_name + out_binary = ctx.actions.declare_file(out_name) + # 2. Strip .exe for -H:Name on Windows because native-image auto-appends .exe on Windows + h_name_path = out_binary.path + if is_windows and h_name_path.lower().endswith(".exe"): + h_name_path = h_name_path[:-4] + jar_file = ctx.file.jar args = ctx.actions.args() args.add("-jar", jar_file.path) - args.add("-H:Name=" + out_binary.path) + args.add("-H:Name=" + h_name_path) for extra_arg in ctx.attr.extra_args: args.add(extra_arg) From 7b159fa43701f37e7ca4524d474623d68367ac37 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 17:47:19 +0200 Subject: [PATCH 025/192] Add some more Maven dependencies. --- MODULE.bazel | 9 ++++++++- MODULE.bazel.lock | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index fdfe42a6..62bd7eaf 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -12,7 +12,14 @@ maven.install( "org.projectlombok:lombok:1.18.30", "com.google.dagger:dagger:2.51.1", "com.google.dagger:dagger-compiler:2.51.1", - # Add any other Maven artifacts your plugins or libraries need here + "info.picocli:picocli:4.7.7", + "org.openjfx:javafx-controls:23.0.2", + "org.openjfx:javafx-fxml:23.0.2", + "org.openjfx:javafx-graphics:23.0.2", + "org.openjfx:javafx-base:23.0.2", + "org.openjfx:javafx-graphics:23.0.2:linux", + "org.openjfx:javafx-graphics:23.0.2:mac", + "org.openjfx:javafx-graphics:23.0.2:win", ], repositories = [ "https://repo1.maven.org/maven2", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 09940401..c0f8ca12 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -185,7 +185,7 @@ "moduleExtensions": { "//:graalvm_extension.bzl%graalvm_ext": { "general": { - "bzlTransitiveDigest": "YNdJVd0z2Zz/CwXyMp9xfCg0hQuOo4r6jJkynvrByNk=", + "bzlTransitiveDigest": "j3Qz7w1ruIOY8oFfCFAgWWjr/ev5O+1F8Jebvo5QHGo=", "usagesDigest": "iUXd/3jCJaegO0Dllj33xgb8pqISEwNiOB/aoc6/sBQ=", "recordedFileInputs": {}, "recordedDirentsInputs": {}, From c2bc3b529393f0b4afd1caa616f542dbe52634f8 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 20:39:40 +0200 Subject: [PATCH 026/192] Rearrange the source to make more sense. --- .gitignore | 1 + BUILD.bazel | 4 +-- fluxengine.iml | 30 +++++++++++++++++++ java/BUILD.bazel | 2 +- .../com/cowlark/fluxengine/BUILD.bazel | 0 .../com/cowlark/fluxengine/Main.java | 0 .../com/cowlark/fluxengine/BUILD.bazel | 4 +-- 7 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 fluxengine.iml rename java/{src/main/java => }/com/cowlark/fluxengine/BUILD.bazel (100%) rename java/{src/main/java => }/com/cowlark/fluxengine/Main.java (100%) rename {java/src/test/java => javatests}/com/cowlark/fluxengine/BUILD.bazel (61%) diff --git a/.gitignore b/.gitignore index abd78000..8e734fc8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ bazel-out bazel-bin .obj .project +.idea/ /.ninja* /brother120tool /brother120tool-* diff --git a/BUILD.bazel b/BUILD.bazel index 6ec6191d..f9c721c5 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -3,10 +3,10 @@ package(default_visibility = ["//visibility:public"]) # Root aliases for running/building the application alias( name = "fluxengine", - actual = "//java/src/main/java/com/cowlark/fluxengine", + actual = "//java/com/cowlark/fluxengine", ) alias( name = "fluxengine_native", - actual = "//java/src/main/java/com/cowlark/fluxengine:fluxengine_native", + actual = "//java/com/cowlark/fluxengine:fluxengine_native", ) diff --git a/fluxengine.iml b/fluxengine.iml new file mode 100644 index 00000000..a5816c00 --- /dev/null +++ b/fluxengine.iml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java/BUILD.bazel b/java/BUILD.bazel index 4fa910b4..3819242a 100644 --- a/java/BUILD.bazel +++ b/java/BUILD.bazel @@ -1,4 +1,4 @@ package(default_visibility = ["//visibility:public"]) -# Top-level package BUILD within java/ kept minimal: per-package BUILD files live under src/. +# Top-level package BUILD within java/ kept minimal: per-package BUILD files live under com/. # This file intentionally contains no targets that compile sources; see per-package BUILD files. diff --git a/java/src/main/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel similarity index 100% rename from java/src/main/java/com/cowlark/fluxengine/BUILD.bazel rename to java/com/cowlark/fluxengine/BUILD.bazel diff --git a/java/src/main/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java similarity index 100% rename from java/src/main/java/com/cowlark/fluxengine/Main.java rename to java/com/cowlark/fluxengine/Main.java diff --git a/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel b/javatests/com/cowlark/fluxengine/BUILD.bazel similarity index 61% rename from java/src/test/java/com/cowlark/fluxengine/BUILD.bazel rename to javatests/com/cowlark/fluxengine/BUILD.bazel index 856ce588..0c738959 100644 --- a/java/src/test/java/com/cowlark/fluxengine/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/BUILD.bazel @@ -5,8 +5,8 @@ java_test( name = "fluxengine_tests", srcs = glob(["*.java"]), deps = [ - "//java/src/main/java/com/cowlark/fluxengine:fluxengine_java_lib", + "//java/com/cowlark/fluxengine:fluxengine_lib", "@maven//:junit_junit", ], - plugins = ["//java/src/main/java/com/cowlark/fluxengine:dagger_plugin"], + plugins = ["//java/com/cowlark/fluxengine:dagger_plugin"], ) From 3cc76a090c17a2b281f355ff00866530d5772ed4 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 20:51:36 +0200 Subject: [PATCH 027/192] Make the tests work. --- MODULE.bazel | 1 + MODULE.bazel.lock | 309 ++++++++++++++++++ fluxengine.iml | 5 +- java/com/cowlark/fluxengine/BUILD.bazel | 8 - java/com/cowlark/fluxengine/Fluxengine.java | 24 ++ .../fluxengine/FluxengineComponent.java | 21 ++ .../cowlark/fluxengine/FluxengineModule.java | 19 ++ java/com/cowlark/fluxengine/Greeter.java | 20 ++ java/com/cowlark/fluxengine/Main.java | 71 +--- javatests/com/cowlark/fluxengine/BUILD.bazel | 7 +- .../com/cowlark/fluxengine/EmptyTest.java | 11 + 11 files changed, 412 insertions(+), 84 deletions(-) create mode 100644 java/com/cowlark/fluxengine/Fluxengine.java create mode 100644 java/com/cowlark/fluxengine/FluxengineComponent.java create mode 100644 java/com/cowlark/fluxengine/FluxengineModule.java create mode 100644 java/com/cowlark/fluxengine/Greeter.java create mode 100644 javatests/com/cowlark/fluxengine/EmptyTest.java diff --git a/MODULE.bazel b/MODULE.bazel index 62bd7eaf..a22cf9fc 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -13,6 +13,7 @@ maven.install( "com.google.dagger:dagger:2.51.1", "com.google.dagger:dagger-compiler:2.51.1", "info.picocli:picocli:4.7.7", + "junit:junit:4.13.2", "org.openjfx:javafx-controls:23.0.2", "org.openjfx:javafx-fxml:23.0.2", "org.openjfx:javafx-graphics:23.0.2", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index c0f8ca12..7c2ac4d7 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -199,6 +199,156 @@ "recordedRepoMappingEntries": [] } }, + "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { + "general": { + "bzlTransitiveDigest": "7zBsfo5dyMqKT23rXrvWqJMx0AugwL6NyirkmvzKcqU=", + "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", + "recordedFileInputs": { + "@@pybind11_bazel+//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "pybind11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", + "strip_prefix": "pybind11-2.12.0", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.12.0.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "pybind11_bazel+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_apple+//apple:apple.bzl%provisioning_profile_repository_extension": { + "general": { + "bzlTransitiveDigest": "1iB2eTWEbKFmYWbNwIrIqDBmoZNB/WCnK13e0QG7qcw=", + "usagesDigest": "vsJl8Rw5NL+5Ag2wdUDoTeRF/5klkXO8545Iy7U1Q08=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_provisioning_profiles": { + "repoRuleId": "@@rules_apple+//apple/internal:local_provisioning_profiles.bzl%provisioning_profile_repository", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "bazel_tools", + "rules_cc", + "rules_cc+" + ], + [ + "rules_apple+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "rules_apple+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_apple+", + "build_bazel_apple_support", + "apple_support+" + ], + [ + "rules_apple+", + "build_bazel_rules_swift", + "rules_swift+" + ], + [ + "rules_cc+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_cc+", + "cc_compatibility_proxy", + "rules_cc++compatibility_proxy+cc_compatibility_proxy" + ], + [ + "rules_cc+", + "rules_cc", + "rules_cc+" + ], + [ + "rules_cc++compatibility_proxy+cc_compatibility_proxy", + "rules_cc", + "rules_cc+" + ], + [ + "rules_swift+", + "bazel_skylib", + "bazel_skylib+" + ], + [ + "rules_swift+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_swift+", + "build_bazel_apple_support", + "apple_support+" + ], + [ + "rules_swift+", + "build_bazel_rules_swift", + "rules_swift+" + ], + [ + "rules_swift+", + "build_bazel_rules_swift_local_config", + "rules_swift++non_module_deps+build_bazel_rules_swift_local_config" + ] + ] + } + }, + "@@rules_apple+//apple:extensions.bzl%non_module_deps": { + "general": { + "bzlTransitiveDigest": "ul8vHGy74hBD66XzLuB09UmLKPv7O6yFI8pwN7jMWdc=", + "usagesDigest": "M3VqFpeTCo4qmrNKGZw0dxBHvTYDrfV3cscGzlSAhQ4=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "xctestrunner": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/google/xctestrunner/archive/b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6.tar.gz" + ], + "strip_prefix": "xctestrunner-b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6", + "sha256": "ae3a063c985a8633cb7eb566db21656f8db8eb9a0edb8c182312c7f0db53730d" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_apple+", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", @@ -298,6 +448,165 @@ ] ] } + }, + "@@rules_swift+//swift:extensions.bzl%non_module_deps": { + "general": { + "bzlTransitiveDigest": "TdyDy4TBpjOHwrF4hSiQwGdsTAvssvyD6vUJBx+7nt4=", + "usagesDigest": "mhACFnrdMv9Wi0Mt67bxocJqviRkDSV+Ee5Mqdj5akA=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_apple_swift_protobuf": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-protobuf/archive/1.20.2.tar.gz" + ], + "sha256": "3fb50bd4d293337f202d917b6ada22f9548a0a0aed9d9a4d791e6fbd8a246ebb", + "strip_prefix": "swift-protobuf-1.20.2/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_protobuf/BUILD.overlay" + } + }, + "com_github_grpc_grpc_swift": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/grpc/grpc-swift/archive/1.16.0.tar.gz" + ], + "sha256": "58b60431d0064969f9679411264b82e40a217ae6bd34e17096d92cc4e47556a5", + "strip_prefix": "grpc-swift-1.16.0/", + "build_file": "@@rules_swift+//third_party:com_github_grpc_grpc_swift/BUILD.overlay" + } + }, + "com_github_apple_swift_docc_symbolkit": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-docc-symbolkit/archive/refs/tags/swift-5.10-RELEASE.tar.gz" + ], + "sha256": "de1d4b6940468ddb53b89df7aa1a81323b9712775b0e33e8254fa0f6f7469a97", + "strip_prefix": "swift-docc-symbolkit-swift-5.10-RELEASE", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_docc_symbolkit/BUILD.overlay" + } + }, + "com_github_apple_swift_nio": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio/archive/2.42.0.tar.gz" + ], + "sha256": "e3304bc3fb53aea74a3e54bd005ede11f6dc357117d9b1db642d03aea87194a0", + "strip_prefix": "swift-nio-2.42.0/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_http2": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-http2/archive/1.26.0.tar.gz" + ], + "sha256": "f0edfc9d6a7be1d587e5b403f2d04264bdfae59aac1d74f7d974a9022c6d2b25", + "strip_prefix": "swift-nio-http2-1.26.0/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_http2/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_transport_services": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-transport-services/archive/1.15.0.tar.gz" + ], + "sha256": "f3498dafa633751a52b9b7f741f7ac30c42bcbeb3b9edca6d447e0da8e693262", + "strip_prefix": "swift-nio-transport-services-1.15.0/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_transport_services/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_extras": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-extras/archive/1.4.0.tar.gz" + ], + "sha256": "4684b52951d9d9937bb3e8ccd6b5daedd777021ef2519ea2f18c4c922843b52b", + "strip_prefix": "swift-nio-extras-1.4.0/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_extras/BUILD.overlay" + } + }, + "com_github_apple_swift_log": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-log/archive/1.4.4.tar.gz" + ], + "sha256": "48fe66426c784c0c20031f15dc17faf9f4c9037c192bfac2f643f65cb2321ba0", + "strip_prefix": "swift-log-1.4.4/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_log/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_ssl": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-ssl/archive/2.23.0.tar.gz" + ], + "sha256": "4787c63f61dd04d99e498adc3d1a628193387e41efddf8de19b8db04544d016d", + "strip_prefix": "swift-nio-ssl-2.23.0/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_ssl/BUILD.overlay" + } + }, + "com_github_apple_swift_collections": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-collections/archive/1.0.4.tar.gz" + ], + "sha256": "d9e4c8a91c60fb9c92a04caccbb10ded42f4cb47b26a212bc6b39cc390a4b096", + "strip_prefix": "swift-collections-1.0.4/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_collections/BUILD.overlay" + } + }, + "com_github_apple_swift_atomics": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-atomics/archive/1.1.0.tar.gz" + ], + "sha256": "1bee7f469f7e8dc49f11cfa4da07182fbc79eab000ec2c17bfdce468c5d276fb", + "strip_prefix": "swift-atomics-1.1.0/", + "build_file": "@@rules_swift+//third_party:com_github_apple_swift_atomics/BUILD.overlay" + } + }, + "build_bazel_rules_swift_index_import": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@rules_swift+//third_party:build_bazel_rules_swift_index_import/BUILD.overlay", + "canonical_id": "index-import-5.8", + "urls": [ + "https://github.com/MobileNativeFoundation/index-import/releases/download/5.8.0.1/index-import.tar.gz" + ], + "sha256": "28c1ffa39d99e74ed70623899b207b41f79214c498c603915aef55972a851a15" + } + }, + "build_bazel_rules_swift_local_config": { + "repoRuleId": "@@rules_swift+//swift/internal:swift_autoconfiguration.bzl%swift_autoconfiguration", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_swift+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_swift+", + "build_bazel_rules_swift", + "rules_swift+" + ] + ] + } } }, "facts": {} diff --git a/fluxengine.iml b/fluxengine.iml index a5816c00..0e3e7da1 100644 --- a/fluxengine.iml +++ b/fluxengine.iml @@ -2,9 +2,10 @@ - - + + + diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 8f5d8501..c09fe2ab 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -42,11 +42,3 @@ native_image( "-O2" ] ) -# deps = [":fluxengine_lib"], -# main_class = "com.cowlark.fluxengine.Main", -# # executable_name defaults to -bin; override if desired. -# executable_name = "fluxengine", -# # If you have internal native archives (cc_library with linkstatic = True), -# # add them here (or use extra_args) so native-image links them into the -# # produced binary. We do not force --static linking of system libraries. -#) diff --git a/java/com/cowlark/fluxengine/Fluxengine.java b/java/com/cowlark/fluxengine/Fluxengine.java new file mode 100644 index 00000000..fef6a648 --- /dev/null +++ b/java/com/cowlark/fluxengine/Fluxengine.java @@ -0,0 +1,24 @@ +package com.cowlark.fluxengine; + +import javax.inject.Inject; +import javax.inject.Singleton; + +/** + * Concrete application class with injected dependencies (Guice-style). + */ +@Singleton +class Fluxengine +{ + private final Greeter greeter; + + @Inject + public Fluxengine(Greeter greeter) + { + this.greeter = greeter; + } + + public void start() + { + greeter.greet(); + } +} diff --git a/java/com/cowlark/fluxengine/FluxengineComponent.java b/java/com/cowlark/fluxengine/FluxengineComponent.java new file mode 100644 index 00000000..abce605a --- /dev/null +++ b/java/com/cowlark/fluxengine/FluxengineComponent.java @@ -0,0 +1,21 @@ +package com.cowlark.fluxengine; + +import dagger.Component; +import javax.inject.Singleton; + +/** + * Component that exposes the concrete Fluxengine type. + * Dagger will generate DaggerFluxengineComponent when annotation processing runs. + */ +@Singleton +@Component(modules = FluxengineModule.class) +interface FluxengineComponent +{ + Fluxengine fluxengine(); + + // Convenience factory that delegates to the generated implementation + static FluxengineComponent create() + { + return DaggerFluxengineComponent.create(); + } +} diff --git a/java/com/cowlark/fluxengine/FluxengineModule.java b/java/com/cowlark/fluxengine/FluxengineModule.java new file mode 100644 index 00000000..9c2b8014 --- /dev/null +++ b/java/com/cowlark/fluxengine/FluxengineModule.java @@ -0,0 +1,19 @@ +package com.cowlark.fluxengine; + +import dagger.Module; +import dagger.Provides; +import javax.inject.Singleton; + +/** + * Application module that provides application-scoped dependencies. + */ +@Module +class FluxengineModule +{ + @Provides + @Singleton + Greeter provideGreeter() + { + return new Greeter(); + } +} diff --git a/java/com/cowlark/fluxengine/Greeter.java b/java/com/cowlark/fluxengine/Greeter.java new file mode 100644 index 00000000..c29907a2 --- /dev/null +++ b/java/com/cowlark/fluxengine/Greeter.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine; + +import javax.inject.Inject; + +/** + * Simple Greeter service provided via Dagger. + */ +class Greeter +{ + @Inject + public Greeter() + { + } + + public void greet() + { + System.out.println("FluxEngine starting up..."); + System.out.println("Hello from FluxEngine Java main"); + } +} diff --git a/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java index 390d930c..c3517970 100644 --- a/java/com/cowlark/fluxengine/Main.java +++ b/java/com/cowlark/fluxengine/Main.java @@ -1,76 +1,7 @@ package com.cowlark.fluxengine; -import javax.inject.Inject; -import javax.inject.Singleton; - -import dagger.Component; -import dagger.Module; -import dagger.Provides; - -/** - * Simple Greeter service provided via Dagger. - */ -class Greeter { - @Inject - public Greeter() { - } - - public void greet() { - System.out.println("FluxEngine starting up..."); - System.out.println("Hello from FluxEngine Java main"); - } -} - -/** - * Application module that provides application-scoped dependencies. - */ -@Module -class FluxengineModule { - @Provides - @Singleton - Greeter provideGreeter() { - return new Greeter(); - } -} - -/** - * Concrete application class with injected dependencies (Guice-style). - */ -@Singleton -class Fluxengine { - private final Greeter greeter; - - @Inject - public Fluxengine(Greeter greeter) { - this.greeter = greeter; - } - - public void start() { - greeter.greet(); - } -} - -/** - * Component that exposes the concrete Fluxengine type. - * Dagger will generate DaggerFluxengineComponent when annotation processing runs. - */ -@Singleton -@Component(modules = FluxengineModule.class) -interface FluxengineComponent { - Fluxengine fluxengine(); - - // Convenience factory that delegates to the generated implementation - static FluxengineComponent create() { - return DaggerFluxengineComponent.create(); - } -} - -/** - * JVM entrypoint that obtains the Fluxengine instance from the component and runs it. - */ public class Main { public static void main(String[] args) { - FluxengineComponent component = FluxengineComponent.create(); - component.fluxengine().start(); + FluxengineComponent.create().fluxengine().start(); } } diff --git a/javatests/com/cowlark/fluxengine/BUILD.bazel b/javatests/com/cowlark/fluxengine/BUILD.bazel index 0c738959..1e05a79b 100644 --- a/javatests/com/cowlark/fluxengine/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/BUILD.bazel @@ -1,12 +1,11 @@ package(default_visibility = ["//visibility:public"]) -# Per-package BUILD for Java tests in com.cowlark.fluxengine java_test( - name = "fluxengine_tests", - srcs = glob(["*.java"]), + name = "EmptyTest", + srcs = ["EmptyTest.java"], + plugins = ["//java/com/cowlark/fluxengine:dagger_plugin"], deps = [ "//java/com/cowlark/fluxengine:fluxengine_lib", "@maven//:junit_junit", ], - plugins = ["//java/com/cowlark/fluxengine:dagger_plugin"], ) diff --git a/javatests/com/cowlark/fluxengine/EmptyTest.java b/javatests/com/cowlark/fluxengine/EmptyTest.java new file mode 100644 index 00000000..70f9e3c8 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/EmptyTest.java @@ -0,0 +1,11 @@ +package com.cowlark.fluxengine; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class EmptyTest{ + @Test + public void empty(){} +} From 4f7f754869e003fce61b8997f3a3b00c892dc44e Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 20:56:50 +0200 Subject: [PATCH 028/192] Start figuring out Dagger modules. --- java/com/cowlark/fluxengine/BUILD.bazel | 1 + java/com/cowlark/fluxengine/Fluxengine.java | 6 +++++- .../cowlark/fluxengine/FluxengineComponent.java | 15 +++++++++++++-- java/com/cowlark/fluxengine/Main.java | 2 +- java/com/cowlark/fluxengine/wiring/BUILD.bazel | 10 ++++++++++ .../cowlark/fluxengine/wiring/CliParameters.java | 14 ++++++++++++++ 6 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 java/com/cowlark/fluxengine/wiring/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/wiring/CliParameters.java diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index c09fe2ab..c2b67df1 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -23,6 +23,7 @@ java_library( srcs = glob(["*.java"]), deps = [ ":dagger", + "//java/com/cowlark/fluxengine/wiring", "@maven//:com_google_dagger_dagger", "@maven//:javax_inject_javax_inject", ], diff --git a/java/com/cowlark/fluxengine/Fluxengine.java b/java/com/cowlark/fluxengine/Fluxengine.java index fef6a648..7d788528 100644 --- a/java/com/cowlark/fluxengine/Fluxengine.java +++ b/java/com/cowlark/fluxengine/Fluxengine.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine; +import com.cowlark.fluxengine.wiring.CliParameters; import javax.inject.Inject; import javax.inject.Singleton; @@ -10,15 +11,18 @@ class Fluxengine { private final Greeter greeter; + private final String[] args; @Inject - public Fluxengine(Greeter greeter) + public Fluxengine(Greeter greeter, @CliParameters String[] args) { this.greeter = greeter; + this.args = args; } public void start() { greeter.greet(); + System.out.println("CLI arguments: " + String.join(" ", args)); } } diff --git a/java/com/cowlark/fluxengine/FluxengineComponent.java b/java/com/cowlark/fluxengine/FluxengineComponent.java index abce605a..8daee72b 100644 --- a/java/com/cowlark/fluxengine/FluxengineComponent.java +++ b/java/com/cowlark/fluxengine/FluxengineComponent.java @@ -1,5 +1,7 @@ package com.cowlark.fluxengine; +import com.cowlark.fluxengine.wiring.CliParameters; +import dagger.BindsInstance; import dagger.Component; import javax.inject.Singleton; @@ -13,9 +15,18 @@ interface FluxengineComponent { Fluxengine fluxengine(); + @Component.Builder + interface Builder + { + @BindsInstance + Builder cliParameters(@CliParameters String[] args); + + FluxengineComponent build(); + } + // Convenience factory that delegates to the generated implementation - static FluxengineComponent create() + static FluxengineComponent create(String[] args) { - return DaggerFluxengineComponent.create(); + return DaggerFluxengineComponent.builder().cliParameters(args).build(); } } diff --git a/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java index c3517970..c88513c0 100644 --- a/java/com/cowlark/fluxengine/Main.java +++ b/java/com/cowlark/fluxengine/Main.java @@ -2,6 +2,6 @@ public class Main { public static void main(String[] args) { - FluxengineComponent.create().fluxengine().start(); + FluxengineComponent.create(args).fluxengine().start(); } } diff --git a/java/com/cowlark/fluxengine/wiring/BUILD.bazel b/java/com/cowlark/fluxengine/wiring/BUILD.bazel new file mode 100644 index 00000000..d217b149 --- /dev/null +++ b/java/com/cowlark/fluxengine/wiring/BUILD.bazel @@ -0,0 +1,10 @@ +package(default_visibility = ["//visibility:public"]) + +# Per-package BUILD for Java sources in com.cowlark.fluxengine.wiring +java_library( + name = "wiring", + srcs = glob(["*.java"]), + deps = [ + "@maven//:javax_inject_javax_inject", + ], +) diff --git a/java/com/cowlark/fluxengine/wiring/CliParameters.java b/java/com/cowlark/fluxengine/wiring/CliParameters.java new file mode 100644 index 00000000..b21b4eb1 --- /dev/null +++ b/java/com/cowlark/fluxengine/wiring/CliParameters.java @@ -0,0 +1,14 @@ +package com.cowlark.fluxengine.wiring; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.inject.Qualifier; + +/** + * Qualifier marking a {@code String[]} as the command-line parameters passed + * to {@link com.cowlark.fluxengine.Main}. + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface CliParameters { +} From b3f522cbd6f077dd8a9c04a252e92baca11a5027 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 20:58:07 +0200 Subject: [PATCH 029/192] Rename for clarity. --- .../{Fluxengine.java => FluxEngine.java} | 4 ++-- ...neComponent.java => FluxEngineComponent.java} | 16 ++++++++-------- ...uxengineModule.java => FluxEngineModule.java} | 2 +- java/com/cowlark/fluxengine/Main.java | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) rename java/com/cowlark/fluxengine/{Fluxengine.java => FluxEngine.java} (86%) rename java/com/cowlark/fluxengine/{FluxengineComponent.java => FluxEngineComponent.java} (54%) rename java/com/cowlark/fluxengine/{FluxengineModule.java => FluxEngineModule.java} (92%) diff --git a/java/com/cowlark/fluxengine/Fluxengine.java b/java/com/cowlark/fluxengine/FluxEngine.java similarity index 86% rename from java/com/cowlark/fluxengine/Fluxengine.java rename to java/com/cowlark/fluxengine/FluxEngine.java index 7d788528..83f923df 100644 --- a/java/com/cowlark/fluxengine/Fluxengine.java +++ b/java/com/cowlark/fluxengine/FluxEngine.java @@ -8,13 +8,13 @@ * Concrete application class with injected dependencies (Guice-style). */ @Singleton -class Fluxengine +class FluxEngine { private final Greeter greeter; private final String[] args; @Inject - public Fluxengine(Greeter greeter, @CliParameters String[] args) + public FluxEngine(Greeter greeter, @CliParameters String[] args) { this.greeter = greeter; this.args = args; diff --git a/java/com/cowlark/fluxengine/FluxengineComponent.java b/java/com/cowlark/fluxengine/FluxEngineComponent.java similarity index 54% rename from java/com/cowlark/fluxengine/FluxengineComponent.java rename to java/com/cowlark/fluxengine/FluxEngineComponent.java index 8daee72b..ca3c2414 100644 --- a/java/com/cowlark/fluxengine/FluxengineComponent.java +++ b/java/com/cowlark/fluxengine/FluxEngineComponent.java @@ -6,14 +6,14 @@ import javax.inject.Singleton; /** - * Component that exposes the concrete Fluxengine type. - * Dagger will generate DaggerFluxengineComponent when annotation processing runs. + * Component that exposes the concrete FluxEngine type. + * Dagger will generate DaggerFluxEngineComponent when annotation processing runs. */ @Singleton -@Component(modules = FluxengineModule.class) -interface FluxengineComponent +@Component(modules = FluxEngineModule.class) +interface FluxEngineComponent { - Fluxengine fluxengine(); + FluxEngine fluxengine(); @Component.Builder interface Builder @@ -21,12 +21,12 @@ interface Builder @BindsInstance Builder cliParameters(@CliParameters String[] args); - FluxengineComponent build(); + FluxEngineComponent build(); } // Convenience factory that delegates to the generated implementation - static FluxengineComponent create(String[] args) + static FluxEngineComponent create(String[] args) { - return DaggerFluxengineComponent.builder().cliParameters(args).build(); + return DaggerFluxEngineComponent.builder().cliParameters(args).build(); } } diff --git a/java/com/cowlark/fluxengine/FluxengineModule.java b/java/com/cowlark/fluxengine/FluxEngineModule.java similarity index 92% rename from java/com/cowlark/fluxengine/FluxengineModule.java rename to java/com/cowlark/fluxengine/FluxEngineModule.java index 9c2b8014..61cfe956 100644 --- a/java/com/cowlark/fluxengine/FluxengineModule.java +++ b/java/com/cowlark/fluxengine/FluxEngineModule.java @@ -8,7 +8,7 @@ * Application module that provides application-scoped dependencies. */ @Module -class FluxengineModule +class FluxEngineModule { @Provides @Singleton diff --git a/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java index c88513c0..2a9ed914 100644 --- a/java/com/cowlark/fluxengine/Main.java +++ b/java/com/cowlark/fluxengine/Main.java @@ -2,6 +2,6 @@ public class Main { public static void main(String[] args) { - FluxengineComponent.create(args).fluxengine().start(); + FluxEngineComponent.create(args).fluxengine().start(); } } From c3de5662b41763c90cbb098c1b1f415934364218 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 21:21:03 +0200 Subject: [PATCH 030/192] Enable LSPs. --- opencode.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 opencode.json diff --git a/opencode.json b/opencode.json new file mode 100644 index 00000000..9cc8361e --- /dev/null +++ b/opencode.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://opencode.ai/config.json", + "lsp": true +} From a4a40e269b5360b11f44df7ed02e0332bf433d7e Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 21:21:08 +0200 Subject: [PATCH 031/192] Now I understand things some more, rearrange the CLI code. --- MODULE.bazel | 2 +- java/com/cowlark/fluxengine/BUILD.bazel | 26 ++++------------- java/com/cowlark/fluxengine/FluxEngine.java | 28 ------------------- .../fluxengine/FluxEngineComponent.java | 25 ++++------------- .../cowlark/fluxengine/FluxEngineModule.java | 19 ------------- java/com/cowlark/fluxengine/Greeter.java | 20 ------------- java/com/cowlark/fluxengine/Main.java | 10 +++++-- java/com/cowlark/fluxengine/cli/BUILD.bazel | 21 ++++++++++++++ .../cowlark/fluxengine/cli/MainCommand.java | 20 +++++++++++++ .../com/cowlark/fluxengine/wiring/BUILD.bazel | 22 +++++++++++---- .../fluxengine/wiring/CliParameters.java | 14 ---------- javatests/com/cowlark/fluxengine/BUILD.bazel | 2 +- 12 files changed, 78 insertions(+), 131 deletions(-) delete mode 100644 java/com/cowlark/fluxengine/FluxEngine.java delete mode 100644 java/com/cowlark/fluxengine/FluxEngineModule.java delete mode 100644 java/com/cowlark/fluxengine/Greeter.java create mode 100644 java/com/cowlark/fluxengine/cli/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/cli/MainCommand.java delete mode 100644 java/com/cowlark/fluxengine/wiring/CliParameters.java diff --git a/MODULE.bazel b/MODULE.bazel index a22cf9fc..981f8cf9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -13,6 +13,7 @@ maven.install( "com.google.dagger:dagger:2.51.1", "com.google.dagger:dagger-compiler:2.51.1", "info.picocli:picocli:4.7.7", + "info.picocli:picocli-codegen:4.7.7", "junit:junit:4.13.2", "org.openjfx:javafx-controls:23.0.2", "org.openjfx:javafx-fxml:23.0.2", @@ -27,4 +28,3 @@ maven.install( ], ) use_repo(maven, "maven") - diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index c2b67df1..3e186a05 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,30 +1,16 @@ -load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin") +load("@rules_java//java:defs.bzl", "java_binary", "java_library") load("//:native_image.bzl", "native_image") package(default_visibility = ["//visibility:public"]) -# Per-package BUILD for Java sources in com.cowlark.fluxengine -# Dagger annotation processor plugin for this Java package -java_plugin( - name = "dagger_plugin", - processor_class = "dagger.internal.codegen.ComponentProcessor", - deps = [ - "@maven//:com_google_dagger_dagger_compiler", - ], -) - -java_library( - name = "dagger", - exported_plugins = [":dagger_plugin"], -) - java_library( name = "fluxengine_lib", srcs = glob(["*.java"]), deps = [ - ":dagger", + "//java/com/cowlark/fluxengine/cli", "//java/com/cowlark/fluxengine/wiring", "@maven//:com_google_dagger_dagger", + "@maven//:info_picocli_picocli", "@maven//:javax_inject_javax_inject", ], ) @@ -37,9 +23,9 @@ java_binary( native_image( name = "fluxengine_native", - jar = ":fluxengine_deploy.jar", extra_args = [ "--no-fallback", - "-O2" - ] + "-O2", + ], + jar = ":fluxengine_deploy.jar", ) diff --git a/java/com/cowlark/fluxengine/FluxEngine.java b/java/com/cowlark/fluxengine/FluxEngine.java deleted file mode 100644 index 83f923df..00000000 --- a/java/com/cowlark/fluxengine/FluxEngine.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.cowlark.fluxengine; - -import com.cowlark.fluxengine.wiring.CliParameters; -import javax.inject.Inject; -import javax.inject.Singleton; - -/** - * Concrete application class with injected dependencies (Guice-style). - */ -@Singleton -class FluxEngine -{ - private final Greeter greeter; - private final String[] args; - - @Inject - public FluxEngine(Greeter greeter, @CliParameters String[] args) - { - this.greeter = greeter; - this.args = args; - } - - public void start() - { - greeter.greet(); - System.out.println("CLI arguments: " + String.join(" ", args)); - } -} diff --git a/java/com/cowlark/fluxengine/FluxEngineComponent.java b/java/com/cowlark/fluxengine/FluxEngineComponent.java index ca3c2414..69248367 100644 --- a/java/com/cowlark/fluxengine/FluxEngineComponent.java +++ b/java/com/cowlark/fluxengine/FluxEngineComponent.java @@ -1,32 +1,17 @@ package com.cowlark.fluxengine; -import com.cowlark.fluxengine.wiring.CliParameters; -import dagger.BindsInstance; +import com.cowlark.fluxengine.cli.MainCommand; import dagger.Component; import javax.inject.Singleton; -/** - * Component that exposes the concrete FluxEngine type. - * Dagger will generate DaggerFluxEngineComponent when annotation processing runs. - */ @Singleton -@Component(modules = FluxEngineModule.class) +@Component interface FluxEngineComponent { - FluxEngine fluxengine(); - - @Component.Builder - interface Builder + static FluxEngineComponent create() { - @BindsInstance - Builder cliParameters(@CliParameters String[] args); - - FluxEngineComponent build(); + return DaggerFluxEngineComponent.create(); } - // Convenience factory that delegates to the generated implementation - static FluxEngineComponent create(String[] args) - { - return DaggerFluxEngineComponent.builder().cliParameters(args).build(); - } + MainCommand mainCommand(); } diff --git a/java/com/cowlark/fluxengine/FluxEngineModule.java b/java/com/cowlark/fluxengine/FluxEngineModule.java deleted file mode 100644 index 61cfe956..00000000 --- a/java/com/cowlark/fluxengine/FluxEngineModule.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.cowlark.fluxengine; - -import dagger.Module; -import dagger.Provides; -import javax.inject.Singleton; - -/** - * Application module that provides application-scoped dependencies. - */ -@Module -class FluxEngineModule -{ - @Provides - @Singleton - Greeter provideGreeter() - { - return new Greeter(); - } -} diff --git a/java/com/cowlark/fluxengine/Greeter.java b/java/com/cowlark/fluxengine/Greeter.java deleted file mode 100644 index c29907a2..00000000 --- a/java/com/cowlark/fluxengine/Greeter.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.cowlark.fluxengine; - -import javax.inject.Inject; - -/** - * Simple Greeter service provided via Dagger. - */ -class Greeter -{ - @Inject - public Greeter() - { - } - - public void greet() - { - System.out.println("FluxEngine starting up..."); - System.out.println("Hello from FluxEngine Java main"); - } -} diff --git a/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java index 2a9ed914..d2338279 100644 --- a/java/com/cowlark/fluxengine/Main.java +++ b/java/com/cowlark/fluxengine/Main.java @@ -1,7 +1,11 @@ package com.cowlark.fluxengine; -public class Main { - public static void main(String[] args) { - FluxEngineComponent.create(args).fluxengine().start(); +import picocli.CommandLine; + +public class Main +{ + public static void main(String[] args) + { + new CommandLine(FluxEngineComponent.create().mainCommand()).execute(args); } } diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel new file mode 100644 index 00000000..dd536ad9 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_plugin( + name = "picocli", + processor_class = "picocli.codegen.aot.graalvm.processor.NativeImageConfigGeneratorProcessor", + deps = ["@maven//:info_picocli_picocli_codegen"], +) + +java_library( + name = "cli", + srcs = glob(["*.java"]), + plugins = [":picocli"], + deps = [ + "//java/com/cowlark/fluxengine/wiring", + "@maven//:com_google_dagger_dagger", + "@maven//:info_picocli_picocli", + "@maven//:javax_inject_javax_inject", + ], +) diff --git a/java/com/cowlark/fluxengine/cli/MainCommand.java b/java/com/cowlark/fluxengine/cli/MainCommand.java new file mode 100644 index 00000000..bb140bfd --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/MainCommand.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.cli; + +import picocli.CommandLine.Command; +import javax.inject.Inject; + +@Command(name = "fluxengine", mixinStandardHelpOptions = true, subcommands = {}, + description = "FluxEngine CLI") +public class MainCommand implements Runnable +{ + @Inject + MainCommand() + { + } + + @Override + public void run() + { + System.out.println("run!"); + } +} diff --git a/java/com/cowlark/fluxengine/wiring/BUILD.bazel b/java/com/cowlark/fluxengine/wiring/BUILD.bazel index d217b149..646f1e31 100644 --- a/java/com/cowlark/fluxengine/wiring/BUILD.bazel +++ b/java/com/cowlark/fluxengine/wiring/BUILD.bazel @@ -1,10 +1,22 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") + package(default_visibility = ["//visibility:public"]) -# Per-package BUILD for Java sources in com.cowlark.fluxengine.wiring -java_library( - name = "wiring", - srcs = glob(["*.java"]), +java_plugin( + name = "dagger_plugin", + processor_class = "dagger.internal.codegen.ComponentProcessor", deps = [ - "@maven//:javax_inject_javax_inject", + "@maven//:com_google_dagger_dagger_compiler", ], ) + +java_library( + name = "dagger", + exported_plugins = [":dagger_plugin"], +) + +java_library( + name = "wiring", + srcs = [], + exported_plugins = [":dagger_plugin"], +) diff --git a/java/com/cowlark/fluxengine/wiring/CliParameters.java b/java/com/cowlark/fluxengine/wiring/CliParameters.java deleted file mode 100644 index b21b4eb1..00000000 --- a/java/com/cowlark/fluxengine/wiring/CliParameters.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cowlark.fluxengine.wiring; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import javax.inject.Qualifier; - -/** - * Qualifier marking a {@code String[]} as the command-line parameters passed - * to {@link com.cowlark.fluxengine.Main}. - */ -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface CliParameters { -} diff --git a/javatests/com/cowlark/fluxengine/BUILD.bazel b/javatests/com/cowlark/fluxengine/BUILD.bazel index 1e05a79b..896ee2b1 100644 --- a/javatests/com/cowlark/fluxengine/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/BUILD.bazel @@ -3,7 +3,7 @@ package(default_visibility = ["//visibility:public"]) java_test( name = "EmptyTest", srcs = ["EmptyTest.java"], - plugins = ["//java/com/cowlark/fluxengine:dagger_plugin"], + plugins = ["//java/com/cowlark/fluxengine/wiring:dagger_plugin"], deps = [ "//java/com/cowlark/fluxengine:fluxengine_lib", "@maven//:junit_junit", From ae4073b4902f2689cec3e9fe8d4c7669579f913d Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 21:33:29 +0200 Subject: [PATCH 032/192] Clean up bazel workspace. --- MODULE.bazel | 1 + MODULE.bazel.lock | 521 +++++++------------ WORKSPACE | 21 - java/com/cowlark/fluxengine/cli/BUILD.bazel | 2 +- javatests/com/cowlark/fluxengine/BUILD.bazel | 2 + 5 files changed, 189 insertions(+), 358 deletions(-) delete mode 100644 WORKSPACE diff --git a/MODULE.bazel b/MODULE.bazel index 981f8cf9..e6fcfa75 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,5 +1,6 @@ bazel_dep(name = "rules_java", version = "9.1.0") bazel_dep(name = "rules_jvm_external", version = "6.7") +bazel_dep(name = "rules_proto", version = "7.1.0") http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 7c2ac4d7..bc8d6983 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 24, + "lockFileVersion": 28, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -16,9 +16,12 @@ "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", - "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", @@ -30,8 +33,10 @@ "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", - "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", @@ -45,9 +50,10 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", - "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", - "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -73,14 +79,13 @@ "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", - "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", - "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", - "https://bcr.bazel.build/modules/protobuf/32.1/source.json": "bd2664e90875c0cd755d1d9b7a103a4b027893ac8eafa3bba087557ffc244ad4", + "https://bcr.bazel.build/modules/protobuf/33.4/MODULE.bazel": "114775b816b38b6d0ca620450d6b02550c60ceedfdc8d9a229833b34a223dc42", + "https://bcr.bazel.build/modules/protobuf/33.4/source.json": "555f8686b4c7d6b5ba731fbea13bf656b4bfd9a7ff629c1d9d3f6e1d6155de79", "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", @@ -91,11 +96,11 @@ "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", - "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", @@ -104,22 +109,22 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", - "https://bcr.bazel.build/modules/rules_cc/0.2.13/source.json": "f872e892c5265c5532e526857532f4868708f88d64e5ebe517ea72e09da61bdb", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", - "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", - "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", - "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", @@ -128,12 +133,9 @@ "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", - "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", - "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", @@ -147,7 +149,6 @@ "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", @@ -157,24 +158,28 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", - "https://bcr.bazel.build/modules/rules_python/1.4.1/source.json": "8ec8c90c70ccacc4de8ca1b97f599e756fb59173e898ee08b733006650057c07", + "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", - "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", - "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", - "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", @@ -187,175 +192,22 @@ "general": { "bzlTransitiveDigest": "j3Qz7w1ruIOY8oFfCFAgWWjr/ev5O+1F8Jebvo5QHGo=", "usagesDigest": "iUXd/3jCJaegO0Dllj33xgb8pqISEwNiOB/aoc6/sBQ=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "recordedInputs": [], "generatedRepoSpecs": { "graalvm": { "repoRuleId": "@@//:graalvm_repository.bzl%graalvm_repository", "attributes": {} } - }, - "recordedRepoMappingEntries": [] - } - }, - "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { - "general": { - "bzlTransitiveDigest": "7zBsfo5dyMqKT23rXrvWqJMx0AugwL6NyirkmvzKcqU=", - "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", - "recordedFileInputs": { - "@@pybind11_bazel+//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" - }, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "pybind11": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", - "strip_prefix": "pybind11-2.12.0", - "urls": [ - "https://github.com/pybind/pybind11/archive/v2.12.0.zip" - ] - } - } - }, - "recordedRepoMappingEntries": [ - [ - "pybind11_bazel+", - "bazel_tools", - "bazel_tools" - ] - ] - } - }, - "@@rules_apple+//apple:apple.bzl%provisioning_profile_repository_extension": { - "general": { - "bzlTransitiveDigest": "1iB2eTWEbKFmYWbNwIrIqDBmoZNB/WCnK13e0QG7qcw=", - "usagesDigest": "vsJl8Rw5NL+5Ag2wdUDoTeRF/5klkXO8545Iy7U1Q08=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "local_provisioning_profiles": { - "repoRuleId": "@@rules_apple+//apple/internal:local_provisioning_profiles.bzl%provisioning_profile_repository", - "attributes": {} - } - }, - "recordedRepoMappingEntries": [ - [ - "apple_support+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "bazel_tools", - "rules_cc", - "rules_cc+" - ], - [ - "rules_apple+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "rules_apple+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_apple+", - "build_bazel_apple_support", - "apple_support+" - ], - [ - "rules_apple+", - "build_bazel_rules_swift", - "rules_swift+" - ], - [ - "rules_cc+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_cc+", - "cc_compatibility_proxy", - "rules_cc++compatibility_proxy+cc_compatibility_proxy" - ], - [ - "rules_cc+", - "rules_cc", - "rules_cc+" - ], - [ - "rules_cc++compatibility_proxy+cc_compatibility_proxy", - "rules_cc", - "rules_cc+" - ], - [ - "rules_swift+", - "bazel_skylib", - "bazel_skylib+" - ], - [ - "rules_swift+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_swift+", - "build_bazel_apple_support", - "apple_support+" - ], - [ - "rules_swift+", - "build_bazel_rules_swift", - "rules_swift+" - ], - [ - "rules_swift+", - "build_bazel_rules_swift_local_config", - "rules_swift++non_module_deps+build_bazel_rules_swift_local_config" - ] - ] - } - }, - "@@rules_apple+//apple:extensions.bzl%non_module_deps": { - "general": { - "bzlTransitiveDigest": "ul8vHGy74hBD66XzLuB09UmLKPv7O6yFI8pwN7jMWdc=", - "usagesDigest": "M3VqFpeTCo4qmrNKGZw0dxBHvTYDrfV3cscGzlSAhQ4=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "xctestrunner": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "urls": [ - "https://github.com/google/xctestrunner/archive/b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6.tar.gz" - ], - "strip_prefix": "xctestrunner-b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6", - "sha256": "ae3a063c985a8633cb7eb566db21656f8db8eb9a0edb8c182312c7f0db53730d" - } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_apple+", - "bazel_tools", - "bazel_tools" - ] - ] + } } }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", + "bzlTransitiveDigest": "+Kp6j204mBZ3mxlIDDR0gBoP45BZ4jYRhRAcB8sU0qc=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "recordedInputs": [ + "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" + ], "generatedRepoSpecs": { "com_github_jetbrains_kotlin_git": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", @@ -403,211 +255,208 @@ ] } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_kotlin+", - "bazel_tools", - "bazel_tools" - ] - ] + } } }, - "@@rules_python+//python/uv:uv.bzl%uv": { + "@@rules_python+//python/extensions:config.bzl%config": { "general": { - "bzlTransitiveDigest": "Xpqjnjzy6zZ90Es9Wa888ZLHhn7IsNGbph/e6qoxzw8=", - "usagesDigest": "4JapxcpS0mL3524k0TZJffAtVyuRjDHZvN9kBRxxF1U=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "dzD8Q2YmrP3fz8saWLHPmlwPLO91ImtTmP/c9JKTStM=", + "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", + "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", + "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", + "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", + "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", + "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", + "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", + "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", + "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", + "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", + "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", + "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", + "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", + "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", + "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" + ], "generatedRepoSpecs": { - "uv": { - "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "rules_python_internal": { + "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", "attributes": { - "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", - "toolchain_names": [ - "none" - ], - "toolchain_implementations": { - "none": "'@@rules_python+//python:none'" - }, - "toolchain_compatible_with": { - "none": [ - "@platforms//:incompatible" - ] - }, - "toolchain_target_settings": {} + "transition_setting_generators": {}, + "transition_settings": [] } - } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python+", - "platforms", - "platforms" - ] - ] - } - }, - "@@rules_swift+//swift:extensions.bzl%non_module_deps": { - "general": { - "bzlTransitiveDigest": "TdyDy4TBpjOHwrF4hSiQwGdsTAvssvyD6vUJBx+7nt4=", - "usagesDigest": "mhACFnrdMv9Wi0Mt67bxocJqviRkDSV+Ee5Mqdj5akA=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, - "generatedRepoSpecs": { - "com_github_apple_swift_protobuf": { + }, + "pypi__build": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-protobuf/archive/1.20.2.tar.gz" - ], - "sha256": "3fb50bd4d293337f202d917b6ada22f9548a0a0aed9d9a4d791e6fbd8a246ebb", - "strip_prefix": "swift-protobuf-1.20.2/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_protobuf/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_grpc_grpc_swift": { + "pypi__click": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/grpc/grpc-swift/archive/1.16.0.tar.gz" - ], - "sha256": "58b60431d0064969f9679411264b82e40a217ae6bd34e17096d92cc4e47556a5", - "strip_prefix": "grpc-swift-1.16.0/", - "build_file": "@@rules_swift+//third_party:com_github_grpc_grpc_swift/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_docc_symbolkit": { + "pypi__colorama": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-docc-symbolkit/archive/refs/tags/swift-5.10-RELEASE.tar.gz" - ], - "sha256": "de1d4b6940468ddb53b89df7aa1a81323b9712775b0e33e8254fa0f6f7469a97", - "strip_prefix": "swift-docc-symbolkit-swift-5.10-RELEASE", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_docc_symbolkit/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_nio": { + "pypi__importlib_metadata": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-nio/archive/2.42.0.tar.gz" - ], - "sha256": "e3304bc3fb53aea74a3e54bd005ede11f6dc357117d9b1db642d03aea87194a0", - "strip_prefix": "swift-nio-2.42.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_nio_http2": { + "pypi__installer": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-nio-http2/archive/1.26.0.tar.gz" - ], - "sha256": "f0edfc9d6a7be1d587e5b403f2d04264bdfae59aac1d74f7d974a9022c6d2b25", - "strip_prefix": "swift-nio-http2-1.26.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_http2/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_nio_transport_services": { + "pypi__more_itertools": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-nio-transport-services/archive/1.15.0.tar.gz" - ], - "sha256": "f3498dafa633751a52b9b7f741f7ac30c42bcbeb3b9edca6d447e0da8e693262", - "strip_prefix": "swift-nio-transport-services-1.15.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_transport_services/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_nio_extras": { + "pypi__packaging": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-nio-extras/archive/1.4.0.tar.gz" - ], - "sha256": "4684b52951d9d9937bb3e8ccd6b5daedd777021ef2519ea2f18c4c922843b52b", - "strip_prefix": "swift-nio-extras-1.4.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_extras/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_log": { + "pypi__pep517": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-log/archive/1.4.4.tar.gz" - ], - "sha256": "48fe66426c784c0c20031f15dc17faf9f4c9037c192bfac2f643f65cb2321ba0", - "strip_prefix": "swift-log-1.4.4/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_log/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_nio_ssl": { + "pypi__pip": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-nio-ssl/archive/2.23.0.tar.gz" - ], - "sha256": "4787c63f61dd04d99e498adc3d1a628193387e41efddf8de19b8db04544d016d", - "strip_prefix": "swift-nio-ssl-2.23.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_nio_ssl/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_collections": { + "pypi__pip_tools": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-collections/archive/1.0.4.tar.gz" - ], - "sha256": "d9e4c8a91c60fb9c92a04caccbb10ded42f4cb47b26a212bc6b39cc390a4b096", - "strip_prefix": "swift-collections-1.0.4/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_collections/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "com_github_apple_swift_atomics": { + "pypi__pyproject_hooks": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "urls": [ - "https://github.com/apple/swift-atomics/archive/1.1.0.tar.gz" - ], - "sha256": "1bee7f469f7e8dc49f11cfa4da07182fbc79eab000ec2c17bfdce468c5d276fb", - "strip_prefix": "swift-atomics-1.1.0/", - "build_file": "@@rules_swift+//third_party:com_github_apple_swift_atomics/BUILD.overlay" + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "build_bazel_rules_swift_index_import": { + "pypi__setuptools": { "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", "attributes": { - "build_file": "@@rules_swift+//third_party:build_bazel_rules_swift_index_import/BUILD.overlay", - "canonical_id": "index-import-5.8", - "urls": [ - "https://github.com/MobileNativeFoundation/index-import/releases/download/5.8.0.1/index-import.tar.gz" - ], - "sha256": "28c1ffa39d99e74ed70623899b207b41f79214c498c603915aef55972a851a15" + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" } }, - "build_bazel_rules_swift_local_config": { - "repoRuleId": "@@rules_swift+//swift/internal:swift_autoconfiguration.bzl%swift_autoconfiguration", - "attributes": {} + "pypi__tomli": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + } + } + }, + "@@rules_python+//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", + "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], + "generatedRepoSpecs": { + "uv": { + "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python+//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python+//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_swift+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_swift+", - "build_bazel_rules_swift", - "rules_swift+" - ] - ] + } } } }, - "facts": {} + "facts": {}, + "factsVersions": {} } diff --git a/WORKSPACE b/WORKSPACE deleted file mode 100644 index d3501540..00000000 --- a/WORKSPACE +++ /dev/null @@ -1,21 +0,0 @@ -workspace(name = "fluxengine_java") - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") - - -# ----------------------------------------------------------------------------- -# rules_proto (Protobuf support) -# Release: 4.0.0 -# Download URL (browser): -# https://github.com/bazelbuild/rules_proto/releases/download/4.0.0/rules_proto-4.0.0.tar.gz -# ----------------------------------------------------------------------------- -http_archive( - name = "rules_proto", - url = "https://github.com/bazelbuild/rules_proto/releases/download/4.0.0/rules_proto-4.0.0.tar.gz", -) - -load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies") - -# Register proto toolchain if desired by uncommenting the following line: -# rules_proto_dependencies() - diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index dd536ad9..e4ad8b41 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_java//java:defs.bzl", "java_binary", "java_library") +load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin") package(default_visibility = ["//visibility:public"]) diff --git a/javatests/com/cowlark/fluxengine/BUILD.bazel b/javatests/com/cowlark/fluxengine/BUILD.bazel index 896ee2b1..491ce55a 100644 --- a/javatests/com/cowlark/fluxengine/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/BUILD.bazel @@ -1,3 +1,5 @@ +load("@rules_java//java:defs.bzl", "java_test") + package(default_visibility = ["//visibility:public"]) java_test( From f40f355ea0747a0df21cf58f72cbd73fc3c6e9aa Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 21:38:07 +0200 Subject: [PATCH 033/192] Add USB libraries. --- MODULE.bazel | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MODULE.bazel b/MODULE.bazel index e6fcfa75..b5a8051e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -11,6 +11,7 @@ maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( artifacts = [ "org.projectlombok:lombok:1.18.30", + "com.fazecast:jSerialComm:2.11.4", "com.google.dagger:dagger:2.51.1", "com.google.dagger:dagger-compiler:2.51.1", "info.picocli:picocli:4.7.7", @@ -23,6 +24,7 @@ maven.install( "org.openjfx:javafx-graphics:23.0.2:linux", "org.openjfx:javafx-graphics:23.0.2:mac", "org.openjfx:javafx-graphics:23.0.2:win", + "org.usb4java:usb4java:1.3.0", ], repositories = [ "https://repo1.maven.org/maven2", From 2bc7bb1ca9c86d667be744848f4a1c421c749283 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 21:55:02 +0200 Subject: [PATCH 034/192] Wire in some cheap-and-nasty USB integration. --- MODULE.bazel | 2 + MODULE.bazel.lock | 22 +++ java/BUILD.bazel | 2 + java/com/cowlark/fluxengine/BUILD.bazel | 4 + .../fluxengine/FluxEngineComponent.java | 6 + java/com/cowlark/fluxengine/Main.java | 28 +++- java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + .../cowlark/fluxengine/cli/MainCommand.java | 2 +- .../cowlark/fluxengine/cli/TestCommand.java | 13 ++ .../fluxengine/cli/TestDevicesCommand.java | 27 ++++ java/com/cowlark/fluxengine/jni-config.json | 55 ++++++++ .../cowlark/fluxengine/reflect-config.json | 129 ++++++++++++++++++ .../cowlark/fluxengine/resource-config.json | 11 ++ .../fluxengine/serialization-config.json | 8 ++ java/com/cowlark/fluxengine/usb/BUILD.bazel | 14 ++ .../com/cowlark/fluxengine/usb/UsbFinder.java | 128 +++++++++++++++++ java/javax.usb.properties | 1 + native_image.bzl | 33 ++++- 18 files changed, 483 insertions(+), 3 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/TestCommand.java create mode 100644 java/com/cowlark/fluxengine/cli/TestDevicesCommand.java create mode 100644 java/com/cowlark/fluxengine/jni-config.json create mode 100644 java/com/cowlark/fluxengine/reflect-config.json create mode 100644 java/com/cowlark/fluxengine/resource-config.json create mode 100644 java/com/cowlark/fluxengine/serialization-config.json create mode 100644 java/com/cowlark/fluxengine/usb/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/usb/UsbFinder.java create mode 100644 java/javax.usb.properties diff --git a/MODULE.bazel b/MODULE.bazel index b5a8051e..afda7fff 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,6 +16,7 @@ maven.install( "com.google.dagger:dagger-compiler:2.51.1", "info.picocli:picocli:4.7.7", "info.picocli:picocli-codegen:4.7.7", + "javax.usb:usb-api:1.0.2", "junit:junit:4.13.2", "org.openjfx:javafx-controls:23.0.2", "org.openjfx:javafx-fxml:23.0.2", @@ -25,6 +26,7 @@ maven.install( "org.openjfx:javafx-graphics:23.0.2:mac", "org.openjfx:javafx-graphics:23.0.2:win", "org.usb4java:usb4java:1.3.0", + "org.usb4java:usb4java-javax:1.3.0", ], repositories = [ "https://repo1.maven.org/maven2", diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index bc8d6983..86e94867 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -201,6 +201,28 @@ } } }, + "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { + "general": { + "bzlTransitiveDigest": "NRXra7941UfmNUyIxnLt82V5hULluVGL2nBsijTl4j4=", + "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", + "recordedInputs": [ + "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools", + "FILE:@@pybind11_bazel+//MODULE.bazel e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" + ], + "generatedRepoSpecs": { + "pybind11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", + "strip_prefix": "pybind11-2.12.0", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.12.0.zip" + ] + } + } + } + } + }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { "bzlTransitiveDigest": "+Kp6j204mBZ3mxlIDDR0gBoP45BZ4jYRhRAcB8sU0qc=", diff --git a/java/BUILD.bazel b/java/BUILD.bazel index 3819242a..6200e1a7 100644 --- a/java/BUILD.bazel +++ b/java/BUILD.bazel @@ -1,4 +1,6 @@ package(default_visibility = ["//visibility:public"]) +exports_files(["javax.usb.properties"]) + # Top-level package BUILD within java/ kept minimal: per-package BUILD files live under com/. # This file intentionally contains no targets that compile sources; see per-package BUILD files. diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 3e186a05..734b2df5 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -27,5 +27,9 @@ native_image( "--no-fallback", "-O2", ], + jni_config = ["jni-config.json"], + reflection_config = ["reflect-config.json"], + resource_config = ["resource-config.json"], + serialization_config = ["serialization-config.json"], jar = ":fluxengine_deploy.jar", ) diff --git a/java/com/cowlark/fluxengine/FluxEngineComponent.java b/java/com/cowlark/fluxengine/FluxEngineComponent.java index 69248367..9eac474b 100644 --- a/java/com/cowlark/fluxengine/FluxEngineComponent.java +++ b/java/com/cowlark/fluxengine/FluxEngineComponent.java @@ -1,6 +1,8 @@ package com.cowlark.fluxengine; import com.cowlark.fluxengine.cli.MainCommand; +import com.cowlark.fluxengine.cli.TestCommand; +import com.cowlark.fluxengine.cli.TestDevicesCommand; import dagger.Component; import javax.inject.Singleton; @@ -14,4 +16,8 @@ static FluxEngineComponent create() } MainCommand mainCommand(); + + TestCommand testCommand(); + + TestDevicesCommand testDevicesCommand(); } diff --git a/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java index d2338279..a0e4a627 100644 --- a/java/com/cowlark/fluxengine/Main.java +++ b/java/com/cowlark/fluxengine/Main.java @@ -1,11 +1,37 @@ package com.cowlark.fluxengine; +import com.cowlark.fluxengine.cli.TestCommand; +import com.cowlark.fluxengine.cli.TestDevicesCommand; import picocli.CommandLine; public class Main { public static void main(String[] args) { - new CommandLine(FluxEngineComponent.create().mainCommand()).execute(args); + FluxEngineComponent component = FluxEngineComponent.create(); + CommandLine commandLine = + new CommandLine(component.mainCommand(), new CommandFactory(component)); + commandLine.execute(args); + } + + private static final class CommandFactory implements CommandLine.IFactory + { + private final FluxEngineComponent component; + + CommandFactory(FluxEngineComponent component) + { + this.component = component; + } + + @Override + @SuppressWarnings("unchecked") + public K create(Class cls) throws Exception + { + if (cls == TestCommand.class) + return (K) component.testCommand(); + if (cls == TestDevicesCommand.class) + return (K) component.testDevicesCommand(); + return CommandLine.defaultFactory().create(cls); + } } } diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index e4ad8b41..114d7864 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -13,6 +13,7 @@ java_library( srcs = glob(["*.java"]), plugins = [":picocli"], deps = [ + "//java/com/cowlark/fluxengine/usb", "//java/com/cowlark/fluxengine/wiring", "@maven//:com_google_dagger_dagger", "@maven//:info_picocli_picocli", diff --git a/java/com/cowlark/fluxengine/cli/MainCommand.java b/java/com/cowlark/fluxengine/cli/MainCommand.java index bb140bfd..92b678c4 100644 --- a/java/com/cowlark/fluxengine/cli/MainCommand.java +++ b/java/com/cowlark/fluxengine/cli/MainCommand.java @@ -3,7 +3,7 @@ import picocli.CommandLine.Command; import javax.inject.Inject; -@Command(name = "fluxengine", mixinStandardHelpOptions = true, subcommands = {}, +@Command(name = "fluxengine", mixinStandardHelpOptions = true, subcommands = {TestCommand.class}, description = "FluxEngine CLI") public class MainCommand implements Runnable { diff --git a/java/com/cowlark/fluxengine/cli/TestCommand.java b/java/com/cowlark/fluxengine/cli/TestCommand.java new file mode 100644 index 00000000..bd4d4869 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/TestCommand.java @@ -0,0 +1,13 @@ +package com.cowlark.fluxengine.cli; + +import picocli.CommandLine.Command; +import javax.inject.Inject; + +@Command(name = "test", description = "Test commands", subcommands = {TestDevicesCommand.class}) +public class TestCommand +{ + @Inject + TestCommand() + { + } +} diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java new file mode 100644 index 00000000..c3312820 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -0,0 +1,27 @@ +package com.cowlark.fluxengine.cli; + +import com.cowlark.fluxengine.usb.UsbFinder; +import picocli.CommandLine.Command; +import javax.inject.Inject; + +@Command(name = "devices", description = "List attached USB devices") +public class TestDevicesCommand implements Runnable +{ + private final UsbFinder usbFinder; + + @Inject + TestDevicesCommand(UsbFinder usbFinder) + { + this.usbFinder = usbFinder; + } + + @Override + public void run() + { + for (UsbFinder.CandidateDevice device : usbFinder.findUsbDevices()) + { + System.out.println(String.format("%s (0x%08x) serial %s", + usbFinder.getDeviceName(device.type), device.id, device.serial)); + } + } +} diff --git a/java/com/cowlark/fluxengine/jni-config.json b/java/com/cowlark/fluxengine/jni-config.json new file mode 100644 index 00000000..e18b81fd --- /dev/null +++ b/java/com/cowlark/fluxengine/jni-config.json @@ -0,0 +1,55 @@ +[ +{ + "name":"[Lorg.usb4java.EndpointDescriptor;" +}, +{ + "name":"[Lorg.usb4java.Interface;" +}, +{ + "name":"[Lorg.usb4java.InterfaceDescriptor;" +}, +{ + "name":"org.usb4java.ConfigDescriptor", + "fields":[{"name":"configDescriptorPointer"}] +}, +{ + "name":"org.usb4java.Context", + "fields":[{"name":"contextPointer"}] +}, +{ + "name":"org.usb4java.Device", + "fields":[{"name":"devicePointer"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.usb4java.DeviceDescriptor", + "fields":[{"name":"deviceDescriptorBuffer"}, {"name":"deviceDescriptorPointer"}] +}, +{ + "name":"org.usb4java.DeviceHandle", + "fields":[{"name":"deviceHandlePointer"}] +}, +{ + "name":"org.usb4java.DeviceList", + "fields":[{"name":"deviceListPointer"}, {"name":"size"}] +}, +{ + "name":"org.usb4java.EndpointDescriptor", + "fields":[{"name":"endpointDescriptorPointer"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.usb4java.Interface", + "fields":[{"name":"interfacePointer"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.usb4java.InterfaceDescriptor", + "fields":[{"name":"interfaceDescriptorPointer"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"org.usb4java.LibUsb", + "methods":[{"name":"hotplugCallback","parameterTypes":["org.usb4java.Context","org.usb4java.Device","int","long"] }, {"name":"triggerPollfdAdded","parameterTypes":["java.io.FileDescriptor","int","long"] }, {"name":"triggerPollfdRemoved","parameterTypes":["java.io.FileDescriptor","long"] }] +} +] diff --git a/java/com/cowlark/fluxengine/reflect-config.json b/java/com/cowlark/fluxengine/reflect-config.json new file mode 100644 index 00000000..4b35ca56 --- /dev/null +++ b/java/com/cowlark/fluxengine/reflect-config.json @@ -0,0 +1,129 @@ +[ +{ + "name":"com.cowlark.fluxengine.cli.MainCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"com.cowlark.fluxengine.cli.TestCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"com.cowlark.fluxengine.cli.TestDevicesCommand", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"groovy.lang.Closure" +}, +{ + "name":"java.lang.Object", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"java.nio.file.Path" +}, +{ + "name":"java.nio.file.Paths", + "methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }] +}, +{ + "name":"java.security.SecureRandomParameters" +}, +{ + "name":"java.sql.Connection" +}, +{ + "name":"java.sql.Driver" +}, +{ + "name":"java.sql.DriverManager", + "methods":[{"name":"getConnection","parameterTypes":["java.lang.String"] }, {"name":"getDriver","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.sql.Time", + "methods":[{"name":"","parameterTypes":["long"] }] +}, +{ + "name":"java.sql.Timestamp", + "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.time.Duration", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.Instant", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.LocalDate", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.LocalDateTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.LocalTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.MonthDay", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.OffsetDateTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.OffsetTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.Period", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.Year", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.YearMonth", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"java.time.ZoneId", + "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.time.ZoneOffset", + "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] +}, +{ + "name":"java.time.ZonedDateTime", + "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] +}, +{ + "name":"javax.usb.UsbHostManager" +}, +{ + "name":"org.usb4java.javax.Services", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"picocli.CommandLine$AutoHelpMixin", + "allDeclaredFields":true, + "queryAllDeclaredMethods":true +}, +{ + "name":"sun.security.provider.NativePRNG", + "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] +}, +{ + "name":"sun.security.provider.SHA", + "methods":[{"name":"","parameterTypes":[] }] +} +] diff --git a/java/com/cowlark/fluxengine/resource-config.json b/java/com/cowlark/fluxengine/resource-config.json new file mode 100644 index 00000000..4f1c7bfe --- /dev/null +++ b/java/com/cowlark/fluxengine/resource-config.json @@ -0,0 +1,11 @@ +{ + "resources":{ + "includes":[{ + "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, { + "pattern":"\\Qjavax.usb.properties\\E" + }, { + "pattern":"\\Qorg/usb4java/linux-x86-64/libusb4java.so\\E" + }]}, + "bundles":[] +} diff --git a/java/com/cowlark/fluxengine/serialization-config.json b/java/com/cowlark/fluxengine/serialization-config.json new file mode 100644 index 00000000..f3d7e06e --- /dev/null +++ b/java/com/cowlark/fluxengine/serialization-config.json @@ -0,0 +1,8 @@ +{ + "types":[ + ], + "lambdaCapturingTypes":[ + ], + "proxies":[ + ] +} diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel new file mode 100644 index 00000000..7de41f7e --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "usb", + srcs = glob(["*.java"]), + resources = ["//java:javax.usb.properties"], + deps = [ + "@maven//:javax_inject_javax_inject", + "@maven//:javax_usb_usb_api", + "@maven//:org_usb4java_usb4java_javax", + ], +) diff --git a/java/com/cowlark/fluxengine/usb/UsbFinder.java b/java/com/cowlark/fluxengine/usb/UsbFinder.java new file mode 100644 index 00000000..2a2c1427 --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/UsbFinder.java @@ -0,0 +1,128 @@ +package com.cowlark.fluxengine.usb; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import javax.inject.Inject; +import javax.usb.UsbDevice; +import javax.usb.UsbDeviceDescriptor; +import javax.usb.UsbException; +import javax.usb.UsbHub; +import javax.usb.UsbServices; +import org.usb4java.javax.Services; + +/** + * USB device finder, ported from lib/usb/usbfinder.cc. + */ +public final class UsbFinder +{ + public enum DeviceType + { + FLUXENGINE, + GREASEWEAZLE, + APPLESAUCE, + } + + public static final class CandidateDevice + { + public DeviceType type; + public UsbDevice device; + public int id; + public String serial; + public String serialPort; + } + + private static final int GREASEWEAZLE_ID = 0x12094d69; + private static final int FLUXENGINE_ID = 0x12096e00; + private static final int APPLESAUCE_ID = 0x16c00483; + + private static final Set VALID_DEVICES = + Set.of(GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID); + + @Inject + public UsbFinder() + { + } + + public String getDeviceName(DeviceType type) + { + switch (type) + { + case GREASEWEAZLE: + return "Greaseweazle"; + + case FLUXENGINE: + return "FluxEngine"; + + case APPLESAUCE: + return "Applesauce"; + + default: + return "unknown"; + } + } + + private static String getSerialNumber(UsbDevice device) + { + try + { + return device.getSerialNumberString(); + } + catch (UsbException | java.io.UnsupportedEncodingException e) + { + return "n/a"; + } + } + + public List findUsbDevices() + { + List candidates = new ArrayList<>(); + try + { + UsbServices services = new Services(); + UsbHub rootHub = services.getRootUsbHub(); + walkHub(rootHub, candidates); + } + catch (UsbException e) + { + System.err.println("USB error: " + e.getMessage()); + } + return candidates; + } + + private static void walkHub(UsbHub hub, List candidates) + { + for (Object o : hub.getAttachedUsbDevices()) + { + UsbDevice usbDevice = (UsbDevice) o; + if (usbDevice.isUsbHub()) + walkHub((UsbHub) usbDevice, candidates); + + UsbDeviceDescriptor descriptor = usbDevice.getUsbDeviceDescriptor(); + int id = ((descriptor.idVendor() & 0xffff) << 16) | + (descriptor.idProduct() & 0xffff); + if (!VALID_DEVICES.contains(id)) + continue; + + CandidateDevice candidate = new CandidateDevice(); + candidate.device = usbDevice; + candidate.id = id; + candidate.serial = getSerialNumber(usbDevice); + + if (id == GREASEWEAZLE_ID) + candidate.type = DeviceType.GREASEWEAZLE; + else if (id == APPLESAUCE_ID) + candidate.type = DeviceType.APPLESAUCE; + else + candidate.type = DeviceType.FLUXENGINE; + + if (id == GREASEWEAZLE_ID || id == APPLESAUCE_ID) + { + // TODO: map the USB device to an OS serial port (CDC-ACM). + candidate.serialPort = null; + } + + candidates.add(candidate); + } + } +} diff --git a/java/javax.usb.properties b/java/javax.usb.properties new file mode 100644 index 00000000..fd8c2f41 --- /dev/null +++ b/java/javax.usb.properties @@ -0,0 +1 @@ +javax.usb.services=org.usb4java.javax.Services diff --git a/native_image.bzl b/native_image.bzl index fcb68ae8..b2539e17 100644 --- a/native_image.bzl +++ b/native_image.bzl @@ -17,16 +17,35 @@ def _native_image_impl(ctx): jar_file = ctx.file.jar + inputs = [jar_file] + args = ctx.actions.args() args.add("-jar", jar_file.path) args.add("-H:Name=" + h_name_path) + sep = ctx.configuration.host_path_separator + + # 3. Pass GraalVM configuration files (JNI / reflection / resources / serialization) + # generated by the native-image tracing agent. + config_specs = [ + ("jni_config", "JNIConfigurationFiles"), + ("reflection_config", "ReflectionConfigurationFiles"), + ("resource_config", "ResourceConfigurationFiles"), + ("serialization_config", "SerializationConfigurationFiles"), + ] + + for attr_name, flag_name in config_specs: + files = getattr(ctx.files, attr_name) + if files: + inputs.extend(files) + args.add("-H:%s=%s" % (flag_name, sep.join([f.path for f in files]))) + for extra_arg in ctx.attr.extra_args: args.add(extra_arg) ctx.actions.run( outputs = [out_binary], - inputs = [jar_file], + inputs = inputs, executable = ctx.executable._native_image_tool, arguments = [args], mnemonic = "GraalVMNativeImage", @@ -44,6 +63,18 @@ native_image = rule( mandatory = True, allow_single_file = [".jar"], ), + "jni_config": attr.label_list( + allow_files = [".json"], + ), + "reflection_config": attr.label_list( + allow_files = [".json"], + ), + "resource_config": attr.label_list( + allow_files = [".json"], + ), + "serialization_config": attr.label_list( + allow_files = [".json"], + ), "extra_args": attr.string_list(default = []), "_native_image_tool": attr.label( default = Label("@graalvm//:native_image_tool"), From 4ffcd3f70b8d55aadb479fd0ecad6ed306ddf768 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 22:26:42 +0200 Subject: [PATCH 035/192] Cleanup. --- .../fluxengine/cli/TestDevicesCommand.java | 29 +++++++++++++-- .../com/cowlark/fluxengine/usb/UsbFinder.java | 36 ++++++++----------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java index c3312820..a1c4c108 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.cli; import com.cowlark.fluxengine.usb.UsbFinder; +import java.util.List; import picocli.CommandLine.Command; import javax.inject.Inject; @@ -18,10 +19,32 @@ public class TestDevicesCommand implements Runnable @Override public void run() { - for (UsbFinder.CandidateDevice device : usbFinder.findUsbDevices()) + List candidates = usbFinder.findUsbDevices(); + switch (candidates.size()) { - System.out.println(String.format("%s (0x%08x) serial %s", - usbFinder.getDeviceName(device.type), device.id, device.serial)); + case 0: + System.out.println("Detected no devices."); + break; + + case 1: + System.out.println("Detected one device:"); + break; + + default: + System.out.println(String.format("Detected %d devices:", candidates.size())); + } + + if (!candidates.isEmpty()) + { + System.out.println(String.format("%-15s %-30s %s", + "Type", "Serial number", "Port (if any)")); + for (UsbFinder.CandidateDevice candidate : candidates) + { + System.out.println(String.format("%-15s %-30s %s", + candidate.type.getDeviceName(), + candidate.serial, + candidate.serialPort == null ? "" : candidate.serialPort)); + } } } } diff --git a/java/com/cowlark/fluxengine/usb/UsbFinder.java b/java/com/cowlark/fluxengine/usb/UsbFinder.java index 2a2c1427..177095a3 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFinder.java +++ b/java/com/cowlark/fluxengine/usb/UsbFinder.java @@ -18,9 +18,21 @@ public final class UsbFinder { public enum DeviceType { - FLUXENGINE, - GREASEWEAZLE, - APPLESAUCE, + FLUXENGINE("FluxEngine"), + GREASEWEAZLE("Greaseweazle"), + APPLESAUCE("Applesauce"); + + private final String deviceName; + + DeviceType(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } } public static final class CandidateDevice @@ -44,24 +56,6 @@ public UsbFinder() { } - public String getDeviceName(DeviceType type) - { - switch (type) - { - case GREASEWEAZLE: - return "Greaseweazle"; - - case FLUXENGINE: - return "FluxEngine"; - - case APPLESAUCE: - return "Applesauce"; - - default: - return "unknown"; - } - } - private static String getSerialNumber(UsbDevice device) { try From 4ec70dba17c39a3ce7ab679ceb81320ea235c0e1 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 22:29:53 +0200 Subject: [PATCH 036/192] Use Guava. --- MODULE.bazel | 1 + java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + .../com/cowlark/fluxengine/cli/TestDevicesCommand.java | 5 ++++- java/com/cowlark/fluxengine/usb/BUILD.bazel | 1 + java/com/cowlark/fluxengine/usb/UsbFinder.java | 10 +++++----- 5 files changed, 12 insertions(+), 6 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index afda7fff..2028a32a 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -14,6 +14,7 @@ maven.install( "com.fazecast:jSerialComm:2.11.4", "com.google.dagger:dagger:2.51.1", "com.google.dagger:dagger-compiler:2.51.1", + "com.google.guava:guava:33.6.0-jre", "info.picocli:picocli:4.7.7", "info.picocli:picocli-codegen:4.7.7", "javax.usb:usb-api:1.0.2", diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 114d7864..3d175692 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -16,6 +16,7 @@ java_library( "//java/com/cowlark/fluxengine/usb", "//java/com/cowlark/fluxengine/wiring", "@maven//:com_google_dagger_dagger", + "@maven//:com_google_guava_guava", "@maven//:info_picocli_picocli", "@maven//:javax_inject_javax_inject", ], diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java index a1c4c108..62f7526e 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -1,6 +1,9 @@ package com.cowlark.fluxengine.cli; +import static com.google.common.base.Strings.nullToEmpty; + import com.cowlark.fluxengine.usb.UsbFinder; +import com.google.common.base.Strings; import java.util.List; import picocli.CommandLine.Command; import javax.inject.Inject; @@ -43,7 +46,7 @@ public void run() System.out.println(String.format("%-15s %-30s %s", candidate.type.getDeviceName(), candidate.serial, - candidate.serialPort == null ? "" : candidate.serialPort)); + nullToEmpty(candidate.serialPort))); } } } diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index 7de41f7e..23020dba 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -7,6 +7,7 @@ java_library( srcs = glob(["*.java"]), resources = ["//java:javax.usb.properties"], deps = [ + "@maven//:com_google_guava_guava", "@maven//:javax_inject_javax_inject", "@maven//:javax_usb_usb_api", "@maven//:org_usb4java_usb4java_javax", diff --git a/java/com/cowlark/fluxengine/usb/UsbFinder.java b/java/com/cowlark/fluxengine/usb/UsbFinder.java index 177095a3..de0d40e4 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFinder.java +++ b/java/com/cowlark/fluxengine/usb/UsbFinder.java @@ -1,6 +1,6 @@ package com.cowlark.fluxengine.usb; -import java.util.ArrayList; +import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Set; import javax.inject.Inject; @@ -68,9 +68,9 @@ private static String getSerialNumber(UsbDevice device) } } - public List findUsbDevices() + public ImmutableList findUsbDevices() { - List candidates = new ArrayList<>(); + ImmutableList.Builder candidates = ImmutableList.builder(); try { UsbServices services = new Services(); @@ -81,10 +81,10 @@ public List findUsbDevices() { System.err.println("USB error: " + e.getMessage()); } - return candidates; + return candidates.build(); } - private static void walkHub(UsbHub hub, List candidates) + private static void walkHub(UsbHub hub, ImmutableList.Builder candidates) { for (Object o : hub.getAttachedUsbDevices()) { From 3e88fa7bfd20ac793fe7616d9cd62608113b23b8 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 22:31:22 +0200 Subject: [PATCH 037/192] Replace agents file. --- AGENTS.md | 142 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 52 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index c94c0409..407bb52e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,52 +1,90 @@ -# Agents - -This repository uses or may interact with automated agents. This document documents the agents we expect to use during the Java migration and guidelines for interacting with them. - -## Purpose - -Agents are automated actors that can open branches, create changes, run tasks, or otherwise assist the maintainers. During the incremental migration to Java we expect to use agents for repetitive tasks such as: - -- Creating branch scaffolding (example: `java` branch) -- Adding Bazel build files and language toolchains -- Adding or updating dependency declarations (e.g. rules_jvm_external / maven_install) -- Running automated formatting, linting or code generation (protobuf/codegen) -- Running CI tasks and test runners - -## Known / Recommended Agents - -- GitHub Copilot (Copilot for code and Copilot Batches/Tasks) - - Can create branches and propose commits via PRs or direct pushes when configured. - - Agent session/task logs can be found via Copilot Tasks URLs: /copilot/tasks/{task_id} - -- Dependabot / Renovate - - For automated dependency updates (Maven or Bazel deps). Configure as required. - -- CI bots (GitHub Actions runners) - - Run Bazel builds and tests. Keep CI configuration small while the java branch is experimental. - -## Conventions - -- Branches created by agents should use a predictable prefix (eg: `agent/` or `autogen/`) unless the change is explicitly reviewed. -- Agent changes that touch build files, toolchains, or dependency versions should always open a pull request for review unless explicitly authorized to push directly. -- Add a clear commit message including the agent name and a brief description, e.g. `copilot: add bazel java scaffold`. - -## Security and Review - -- Treat changes that add new binaries, toolchains, or external dependencies as security-sensitive. Require at least one human review before merging. -- Avoid giving agents broad write permissions across the repository unless absolutely necessary. - -## Troubleshooting - -- If an agent-created CI or build change fails, examine the workflow logs in GitHub Actions and the Copilot agent session logs where applicable. -- For Copilot agent sessions or task logs, use the Copilot Tasks URL pattern: https://github.com/copilot/tasks/{task_id} - -## Local developer notes - -- If you need to reproduce or fix agent-created commits locally, fetch the branch and inspect the changes: - - git fetch origin - git checkout - -- When working on the `java` experimental branch, prefer opening PRs back to the default branch only after the incremental migration pieces are reviewed. - - +# AGENTS.md + +This repo is FluxEngine, a USB floppy-disk drive tool. The existing codebase is C++, +and there is an active, incremental migration of components to Java. The Java side is +the current focus of development. This document describes the Java build structure and +the coding conventions used. Follow it when making changes. + +## Build system + +Bazel with bzlmod. There is **no WORKSPACE file** — all dependency declarations live in +`MODULE.bazel` (rules_java, rules_jvm_external for Maven deps, rules_proto, plus custom +GraalVM extension/rule). + +- Java sources: `java/` (standard Bazel layout, `com` is a direct child of `java`) +- Java tests: `javatests/` +- Packages (Java): `com.cowlark.fluxengine` (Main, FluxEngineComponent), + `com.cowlark.fluxengine.cli`, `com.cowlark.fluxengine.usb`, `com.cowlark.fluxengine.wiring` +- Each package directory has its own `BUILD.bazel`. + +Useful commands: + +- `bazel build //java/...` +- `bazel test //javatests/...` +- `bazel run //java/com/cowlark/fluxengine:fluxengine -- ` (JVM binary) +- `bazel run //:fluxengine_native -- ` (GraalVM native binary; root aliases + `//:fluxengine` and `//:fluxengine_native` exist) + +## Gotchas + +- Because there is no WORKSPACE, Java rules are **not autoloaded**. Every BUILD file must + explicitly load what it uses, e.g. + `load("@rules_java//java:defs.bzl", "java_library", "java_binary", "java_plugin", "java_test")`. +- `javax.usb.properties` must sit at the **classpath root** (the usb4java `Services` + constructor requires it via `UsbHostManager.getProperties()`). It lives at + `java/javax.usb.properties`, is exported from `java/BUILD.bazel`, and is pulled in as a + resource (`resources = ["//java:javax.usb.properties"]`) by the usb library. Bazel's + resource jarring strips the leading `java/`, so it lands at the jar root. Do not move it + into the package directory. +- GraalVM native-image requires reachability config generated by the tracing agent: + `jni-config.json`, `reflect-config.json`, `resource-config.json`, + `serialization-config.json` live in `java/com/cowlark/fluxengine/` and are passed to the + `native_image` rule. They are platform-specific — regenerate with the tracing agent + (`-agentlib:native-image-agent=config-output-dir=...`) when adding JNI/reflection paths + or targeting a new platform. +- The native binary must remain a single standalone executable (no runtime files shipped). + +## Dependency injection (Dagger) + +- The Dagger annotation processor lives in the `wiring` package + (`java/com/cowlark/fluxengine/wiring/BUILD.bazel`), which defines `dagger_plugin` and + exports it via `exported_plugins` on the `wiring`/`dagger` targets. Any library that + depends on `//java/com/cowlark/fluxengine/wiring` gets Dagger annotation processing + automatically. +- `FluxEngineComponent` is the single `@Component`. It exposes accessors for the CLI + commands. Classes are injectable via `@Inject` constructors; there are no module + bindings for own classes unless needed. + +## CLI (picocli) + +- Commands live in `com.cowlark.fluxengine.cli`: `MainCommand` (root, `@Command(name = + "fluxengine")`), `TestCommand` (`test`), `TestDevicesCommand` (`test devices`). +- Subcommands are declared with `subcommands = {FooCommand.class}` on the parent and are + constructed by Dagger via a `CommandLine.IFactory` in `Main` that delegates to the + Dagger component. Keep this factory updated when adding subcommands. +- Picocli commands have package-private `@Inject` constructors. + +## USB + +- `UsbFinder` (`java/com/cowlark/fluxengine/usb/`) is the Java port of + `lib/usb/usbfinder.{cc,h}`. It uses usb4java-javax (javax.usb API). `UsbFinder` is + Dagger-injectable (`@Inject` constructor, instance methods) and `findUsbDevices()` + returns an `ImmutableList`. +- `DeviceType` is an enum carrying its display name as a property (`getDeviceName()`). +- jSerialComm is available for serial-port access (not yet used). + +## Code style + +- Allman brace style (opening brace on its own line), 4-space indent. +- Explicit types, no `var`. +- Prefer Guava utilities over hand-rolled checks: `Strings.nullToEmpty(...)` instead of + explicit null checks; use `ImmutableList` for returned collections. +- Tests use JUnit 4 (`@RunWith(JUnit4.class)`, `org.junit.Test`). +- Follow existing patterns in the package you are editing; keep new functionality + localized to the relevant package. + +## Process + +- Verify changes with `bazel build //java/...` and `bazel test //javatests/...` (and + `bazel run` for CLI-visible behaviour) before finishing. +- Do not commit unless asked. From 09f388546b2e91c4a427b16131c59f2ce901270e Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 22:35:50 +0200 Subject: [PATCH 038/192] UsbFinder can now get the serial port. --- java/com/cowlark/fluxengine/BUILD.bazel | 1 + java/com/cowlark/fluxengine/jni-config.json | 11 ++++++++ java/com/cowlark/fluxengine/usb/BUILD.bazel | 1 + .../com/cowlark/fluxengine/usb/UsbFinder.java | 25 ++++++++++++++++--- 4 files changed, 34 insertions(+), 4 deletions(-) diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 734b2df5..7860c572 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -26,6 +26,7 @@ native_image( extra_args = [ "--no-fallback", "-O2", + "-H:IncludeResources=(javax.usb.properties|org/usb4java/.*/libusb4java\\..*|.*/libjSerialComm.*|.*/jSerialComm.dll)", ], jni_config = ["jni-config.json"], reflection_config = ["reflect-config.json"], diff --git a/java/com/cowlark/fluxengine/jni-config.json b/java/com/cowlark/fluxengine/jni-config.json index e18b81fd..10a04b31 100644 --- a/java/com/cowlark/fluxengine/jni-config.json +++ b/java/com/cowlark/fluxengine/jni-config.json @@ -1,4 +1,7 @@ [ +{ + "name":"[Lcom.fazecast.jSerialComm.SerialPort;" +}, { "name":"[Lorg.usb4java.EndpointDescriptor;" }, @@ -8,6 +11,14 @@ { "name":"[Lorg.usb4java.InterfaceDescriptor;" }, +{ + "name":"com.fazecast.jSerialComm.SerialPort", + "fields":[{"name":"autoFlushIOBuffers"}, {"name":"baudRate"}, {"name":"comPort"}, {"name":"dataBits"}, {"name":"disableConfig"}, {"name":"disableExclusiveLock"}, {"name":"eventFlags"}, {"name":"eventListenerRunning"}, {"name":"flowControl"}, {"name":"friendlyName"}, {"name":"isDtrEnabled"}, {"name":"isRtsEnabled"}, {"name":"manufacturer"}, {"name":"parity"}, {"name":"portDescription"}, {"name":"portHandle"}, {"name":"portLocation"}, {"name":"productID"}, {"name":"readTimeout"}, {"name":"receiveDeviceQueueSize"}, {"name":"requestElevatedPermissions"}, {"name":"rs485ActiveHigh"}, {"name":"rs485DelayAfter"}, {"name":"rs485DelayBefore"}, {"name":"rs485EnableTermination"}, {"name":"rs485Mode"}, {"name":"rs485ModeControlEnabled"}, {"name":"rs485RxDuringTx"}, {"name":"sendDeviceQueueSize"}, {"name":"serialNumber"}, {"name":"stopBits"}, {"name":"timeoutMode"}, {"name":"vendorID"}, {"name":"writeTimeout"}, {"name":"xoffStopChar"}, {"name":"xonStartChar"}], + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"java.lang.Exception" +}, { "name":"org.usb4java.ConfigDescriptor", "fields":[{"name":"configDescriptorPointer"}] diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index 23020dba..b191ba45 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -7,6 +7,7 @@ java_library( srcs = glob(["*.java"]), resources = ["//java:javax.usb.properties"], deps = [ + "@maven//:com_fazecast_jSerialComm", "@maven//:com_google_guava_guava", "@maven//:javax_inject_javax_inject", "@maven//:javax_usb_usb_api", diff --git a/java/com/cowlark/fluxengine/usb/UsbFinder.java b/java/com/cowlark/fluxengine/usb/UsbFinder.java index de0d40e4..728e5101 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFinder.java +++ b/java/com/cowlark/fluxengine/usb/UsbFinder.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.usb; +import com.fazecast.jSerialComm.SerialPort; import com.google.common.collect.ImmutableList; import java.util.List; import java.util.Set; @@ -111,12 +112,28 @@ else if (id == APPLESAUCE_ID) candidate.type = DeviceType.FLUXENGINE; if (id == GREASEWEAZLE_ID || id == APPLESAUCE_ID) - { - // TODO: map the USB device to an OS serial port (CDC-ACM). - candidate.serialPort = null; - } + candidate.serialPort = findSerialPort(id, candidate.serial); candidates.add(candidate); } } + + private static String findSerialPort(int id, String serial) + { + int vendorId = id >>> 16; + int productId = id & 0xffff; + for (SerialPort port : SerialPort.getCommPorts()) + { + if (port.getVendorID() == vendorId && port.getProductID() == productId) + { + String portSerial = port.getSerialNumber(); + if (serial == null || serial.isEmpty() || portSerial == null || + serial.equals(portSerial)) + { + return port.getSystemPortName(); + } + } + } + return null; + } } From 31bee28395e8745da8920d669c99d92740bd977a Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 22:38:16 +0200 Subject: [PATCH 039/192] Delete translated files. --- lib/usb/usbfinder.cc | 85 ------------------------------------------- lib/usb/usbfinder.h | 27 -------------- src/fe-testdevices.cc | 41 --------------------- 3 files changed, 153 deletions(-) delete mode 100644 lib/usb/usbfinder.cc delete mode 100644 lib/usb/usbfinder.h delete mode 100644 src/fe-testdevices.cc diff --git a/lib/usb/usbfinder.cc b/lib/usb/usbfinder.cc deleted file mode 100644 index 4453ccb4..00000000 --- a/lib/usb/usbfinder.cc +++ /dev/null @@ -1,85 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/config/flags.h" -#include "lib/usb/usb.h" -#include "lib/core/bytes.h" -#include "lib/usb/usbfinder.h" -#include "lib/external/applesauce.h" -#include "lib/external/greaseweazle.h" -#include "protocol.h" -#include "libusbp.hpp" - -static const std::set VALID_DEVICES = { - GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID}; - -static const std::string get_serial_number(const libusbp::device& device) -{ - try - { - return device.get_serial_number(); - } - catch (const libusbp::error& e) - { - if (e.has_code(LIBUSBP_ERROR_NO_SERIAL_NUMBER)) - return "n/a"; - throw; - } -} - -std::vector> findUsbDevices() -{ - try - { - std::vector> candidates; - for (const auto& it : libusbp::list_connected_devices()) - { - auto candidate = std::make_unique(); - candidate->device = it; - - uint32_t id = (it.get_vendor_id() << 16) | it.get_product_id(); - if (VALID_DEVICES.find(id) != VALID_DEVICES.end()) - { - candidate->id = id; - candidate->serial = get_serial_number(it); - - if (id == GREASEWEAZLE_ID) - candidate->type = DEVICE_GREASEWEAZLE; - else if (id == APPLESAUCE_ID) - candidate->type = DEVICE_APPLESAUCE; - else if (id == FLUXENGINE_ID) - candidate->type = DEVICE_FLUXENGINE; - - if ((id == GREASEWEAZLE_ID) || (id == APPLESAUCE_ID)) - { - libusbp::serial_port port(candidate->device); - candidate->serialPort = port.get_name(); - } - - candidates.push_back(std::move(candidate)); - } - } - - return candidates; - } - catch (const libusbp::error& e) - { - error("USB error: {}", e.message()); - } -} - -std::string getDeviceName(DeviceType type) -{ - switch (type) - { - case DEVICE_GREASEWEAZLE: - return "Greaseweazle"; - - case DEVICE_FLUXENGINE: - return "FluxEngine"; - - case DEVICE_APPLESAUCE: - return "Applesauce"; - - default: - return "unknown"; - } -} diff --git a/lib/usb/usbfinder.h b/lib/usb/usbfinder.h deleted file mode 100644 index 435c7e3b..00000000 --- a/lib/usb/usbfinder.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef USBSERIAL_H -#define USBSERIAL_H - -#include "libusbp_config.h" -#include "libusbp.hpp" - -enum DeviceType -{ - DEVICE_FLUXENGINE, - DEVICE_GREASEWEAZLE, - DEVICE_APPLESAUCE, -}; - -extern std::string getDeviceName(DeviceType type); - -struct CandidateDevice -{ - DeviceType type; - libusbp::device device; - uint32_t id; - std::string serial; - std::string serialPort; -}; - -extern std::vector> findUsbDevices(); - -#endif diff --git a/src/fe-testdevices.cc b/src/fe-testdevices.cc deleted file mode 100644 index 25b60920..00000000 --- a/src/fe-testdevices.cc +++ /dev/null @@ -1,41 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/config/flags.h" -#include "lib/usb/usbfinder.h" -#include "fmt/format.h" - -static FlagGroup flags; - -int mainTestDevices(int argc, const char* argv[]) -{ - flags.parseFlagsWithConfigFiles(argc, argv, {}); - - auto candidates = findUsbDevices(); - switch (candidates.size()) - { - case 0: - fmt::print("Detected no devices.\n"); - break; - - case 1: - fmt::print("Detected one device:\n"); - break; - - default: - fmt::print("Detected {} devices:\n", candidates.size()); - } - - if (!candidates.empty()) - { - fmt::print( - "{:15} {:30} {}\n", "Type", "Serial number", "Port (if any)"); - for (auto& candidate : candidates) - { - fmt::print("{:15} {:30} {}\n", - getDeviceName(candidate->type), - candidate->serial, - candidate->serialPort); - } - } - - return 0; -} From 054d841913ffb9ec9fdcfa8e83a8bfd9015ab5ca Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 22:55:33 +0200 Subject: [PATCH 040/192] Start work on the USB device framework. --- .bazelrc | 1 + .../fluxengine/usb/AbstractUsbDevice.java | 38 +++++++++++++++++++ java/com/cowlark/fluxengine/usb/Voltages.java | 8 ++++ 3 files changed, 47 insertions(+) create mode 100644 .bazelrc create mode 100644 java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java create mode 100644 java/com/cowlark/fluxengine/usb/Voltages.java diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 00000000..e3a81e01 --- /dev/null +++ b/.bazelrc @@ -0,0 +1 @@ +common --java_language_version=21 diff --git a/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java b/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java new file mode 100644 index 00000000..52710bd8 --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java @@ -0,0 +1,38 @@ +package com.cowlark.fluxengine.usb; + +import javax.inject.Inject; + +/** + * Base class for USB floppy drive devices, ported from lib/usb/usb.h. + */ +public abstract class AbstractUsbDevice +{ + public void recalibrate() + { + seek(0); + } + + public abstract void seek(int track); + + public abstract long getRotationalPeriod(int hardSectorCount); + + public abstract void testBulkWrite(); + + public abstract void testBulkRead(); + + public abstract byte[] read(int side, boolean synced, long readTime, + long hardSectorThreshold); + + public abstract void write(int side, byte[] bytes, long hardSectorThreshold); + + public abstract void erase(int side, long hardSectorThreshold); + + public abstract void setDrive(int drive, boolean highDensity, int indexMode); + + public abstract void measureVoltages(Voltages[] voltages); + + protected String usbError(int error) + { + return String.format("USB error %d", error); + } +} diff --git a/java/com/cowlark/fluxengine/usb/Voltages.java b/java/com/cowlark/fluxengine/usb/Voltages.java new file mode 100644 index 00000000..2fffb9d5 --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/Voltages.java @@ -0,0 +1,8 @@ +package com.cowlark.fluxengine.usb; + +/** + * Voltage readings, ported from struct voltages in protocol.h. + */ +public record Voltages(int logic0Mv, int logic1Mv) +{ +} From eb5e2ba21d5e1cd6c4e86eb8e920baee7d7a0adc Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 23:14:23 +0200 Subject: [PATCH 041/192] Build the fl2 proto. --- .bazelrc | 6 ++++++ MODULE.bazel | 1 + java/com/cowlark/fluxengine/external/BUILD.bazel | 15 +++++++++++++++ .../com/cowlark/fluxengine}/external/fl2.proto | 0 4 files changed, 22 insertions(+) create mode 100644 java/com/cowlark/fluxengine/external/BUILD.bazel rename {lib => java/com/cowlark/fluxengine}/external/fl2.proto (100%) diff --git a/.bazelrc b/.bazelrc index e3a81e01..5e7aafde 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1,7 @@ common --java_language_version=21 + +# Dev machine toolchain workarounds: the system gcc is wrapped by ccache and +# the system linker (lld) lives in /opt/bin, outside Bazel's default action PATH. +build --repo_env=CC=/usr/bin/gcc +build --repo_env=CXX=/usr/bin/g++ +build --host_linkopt=-B/opt/bin diff --git a/MODULE.bazel b/MODULE.bazel index 2028a32a..08cad901 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,6 +1,7 @@ bazel_dep(name = "rules_java", version = "9.1.0") bazel_dep(name = "rules_jvm_external", version = "6.7") bazel_dep(name = "rules_proto", version = "7.1.0") +bazel_dep(name = "protobuf", version = "33.4", repo_name = "com_google_protobuf") http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") diff --git a/java/com/cowlark/fluxengine/external/BUILD.bazel b/java/com/cowlark/fluxengine/external/BUILD.bazel new file mode 100644 index 00000000..3558f582 --- /dev/null +++ b/java/com/cowlark/fluxengine/external/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "fl2_proto", + srcs = ["fl2.proto"], + deps = ["@com_google_protobuf//:descriptor_proto"], +) + +java_proto_library( + name = "fl2_java_proto", + deps = [":fl2_proto"], +) diff --git a/lib/external/fl2.proto b/java/com/cowlark/fluxengine/external/fl2.proto similarity index 100% rename from lib/external/fl2.proto rename to java/com/cowlark/fluxengine/external/fl2.proto From a06413d74259a94049536453b6d2a591d258906c Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 23:15:08 +0200 Subject: [PATCH 042/192] Build common.proto. --- java/com/cowlark/fluxengine/config/BUILD.bazel | 15 +++++++++++++++ .../com/cowlark/fluxengine}/config/common.proto | 0 2 files changed, 15 insertions(+) create mode 100644 java/com/cowlark/fluxengine/config/BUILD.bazel rename {lib => java/com/cowlark/fluxengine}/config/common.proto (100%) diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel new file mode 100644 index 00000000..a50aaae1 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "common_proto", + srcs = ["common.proto"], + deps = ["@com_google_protobuf//:descriptor_proto"], +) + +java_proto_library( + name = "common_java_proto", + deps = [":common_proto"], +) diff --git a/lib/config/common.proto b/java/com/cowlark/fluxengine/config/common.proto similarity index 100% rename from lib/config/common.proto rename to java/com/cowlark/fluxengine/config/common.proto From fcb23d30247783b39e3a4c98cea860cd58419fb3 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 23:17:14 +0200 Subject: [PATCH 043/192] Build layout.proto. --- java/com/cowlark/fluxengine/config/BUILD.bazel | 15 +++++++++++++++ .../com/cowlark/fluxengine}/config/layout.proto | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) rename {lib => java/com/cowlark/fluxengine}/config/layout.proto (97%) diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index a50aaae1..2023f369 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -6,6 +6,7 @@ package(default_visibility = ["//visibility:public"]) proto_library( name = "common_proto", srcs = ["common.proto"], + strip_import_prefix = "/java/com/cowlark/fluxengine/config/", deps = ["@com_google_protobuf//:descriptor_proto"], ) @@ -13,3 +14,17 @@ java_proto_library( name = "common_java_proto", deps = [":common_proto"], ) + +proto_library( + name = "layout_proto", + srcs = ["layout.proto"], + deps = [ + ":common_proto", + "//java/com/cowlark/fluxengine/external:fl2_proto", + ], +) + +java_proto_library( + name = "layout_java_proto", + deps = [":layout_proto"], +) diff --git a/lib/config/layout.proto b/java/com/cowlark/fluxengine/config/layout.proto similarity index 97% rename from lib/config/layout.proto rename to java/com/cowlark/fluxengine/config/layout.proto index dcb91f41..2337212c 100644 --- a/lib/config/layout.proto +++ b/java/com/cowlark/fluxengine/config/layout.proto @@ -1,7 +1,7 @@ syntax = "proto2"; -import "lib/config/common.proto"; -import "lib/external/fl2.proto"; +import "common.proto"; +import "fl2.proto"; message SectorListProto { From a2f2c770021fee8b9f2732c7c681726faab107df Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 23:22:04 +0200 Subject: [PATCH 044/192] Build drive.proto. --- java/com/cowlark/fluxengine/config/BUILD.bazel | 15 +++++++++++++++ .../com/cowlark/fluxengine}/config/drive.proto | 4 ++-- 2 files changed, 17 insertions(+), 2 deletions(-) rename {lib => java/com/cowlark/fluxengine}/config/drive.proto (96%) diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 2023f369..5334840f 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -28,3 +28,18 @@ java_proto_library( name = "layout_java_proto", deps = [":layout_proto"], ) + +proto_library( + name = "drive_proto", + srcs = ["drive.proto"], + strip_import_prefix = "/java/com/cowlark/fluxengine/config/", + deps = [ + ":common_proto", + "//java/com/cowlark/fluxengine/external:fl2_proto", + ], +) + +java_proto_library( + name = "drive_java_proto", + deps = [":drive_proto"], +) diff --git a/lib/config/drive.proto b/java/com/cowlark/fluxengine/config/drive.proto similarity index 96% rename from lib/config/drive.proto rename to java/com/cowlark/fluxengine/config/drive.proto index 705fb024..fe4ed40c 100644 --- a/lib/config/drive.proto +++ b/java/com/cowlark/fluxengine/config/drive.proto @@ -1,7 +1,7 @@ syntax = "proto2"; -import "lib/config/common.proto"; -import "lib/external/fl2.proto"; +import "common.proto"; +import "fl2.proto"; // Next: 14 message DriveProto From 195a131c6a5d6aa248d50b9e632e1d7a6b7c9b25 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 23:26:13 +0200 Subject: [PATCH 045/192] Build usb.proto. --- java/com/cowlark/fluxengine/usb/BUILD.bazel | 14 ++++++++++++++ .../cowlark/fluxengine/usb/UsbDeviceFactory.java | 2 ++ {lib => java/com/cowlark/fluxengine}/usb/usb.proto | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java rename {lib => java/com/cowlark/fluxengine}/usb/usb.proto (96%) diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index b191ba45..33d15ece 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -1,7 +1,21 @@ load("@rules_java//java:defs.bzl", "java_library") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") package(default_visibility = ["//visibility:public"]) +proto_library( + name = "usb_proto", + srcs = ["usb.proto"], + strip_import_prefix = "/java/com/cowlark/fluxengine/usb/", + deps = ["//java/com/cowlark/fluxengine/config:common_proto"], +) + +java_proto_library( + name = "usb_java_proto", + deps = [":usb_proto"], +) + java_library( name = "usb", srcs = glob(["*.java"]), diff --git a/java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java b/java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java new file mode 100644 index 00000000..261ea2bd --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java @@ -0,0 +1,2 @@ +package com.cowlark.fluxengine.usb; + diff --git a/lib/usb/usb.proto b/java/com/cowlark/fluxengine/usb/usb.proto similarity index 96% rename from lib/usb/usb.proto rename to java/com/cowlark/fluxengine/usb/usb.proto index 1826341b..a3c684b4 100644 --- a/lib/usb/usb.proto +++ b/java/com/cowlark/fluxengine/usb/usb.proto @@ -1,6 +1,6 @@ syntax = "proto2"; -import "lib/config/common.proto"; +import "common.proto"; message GreaseweazleProto { enum BusType { /* note that these must match CMD_SET_BUS codes */ From 6a855ba01a102e86e240acf99a7d1cc2266506df Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 4 Aug 2026 23:27:52 +0200 Subject: [PATCH 046/192] Port the greaseweazle tools. --- .gitignore | 1 + MODULE.bazel | 2 + fluxengine.iml | 4 +- .../cowlark/fluxengine/external/BUILD.bazel | 10 + .../fluxengine/external/FluxEngine.java | 189 ++++++++++++++++++ .../external/GreaseweazleUtils.java | 166 +++++++++++++++ javatests/com/cowlark/fluxengine/BUILD.bazel | 13 -- .../com/cowlark/fluxengine/EmptyTest.java | 11 - .../cowlark/fluxengine/external/BUILD.bazel | 15 ++ .../external/GreaseweazleUtilsTest.java | 78 ++++++++ .../cowlark/fluxengine/testing/BUILD.bazel | 11 + .../fluxengine/testing/TestHelpers.java | 15 ++ javatests/javatests.iml | 11 + 13 files changed, 501 insertions(+), 25 deletions(-) create mode 100644 java/com/cowlark/fluxengine/external/FluxEngine.java create mode 100644 java/com/cowlark/fluxengine/external/GreaseweazleUtils.java delete mode 100644 javatests/com/cowlark/fluxengine/BUILD.bazel delete mode 100644 javatests/com/cowlark/fluxengine/EmptyTest.java create mode 100644 javatests/com/cowlark/fluxengine/external/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java create mode 100644 javatests/com/cowlark/fluxengine/testing/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/testing/TestHelpers.java create mode 100644 javatests/javatests.iml diff --git a/.gitignore b/.gitignore index 8e734fc8..ddf22a9d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ bazel-bin .obj .project .idea/ +.ijwb/ /.ninja* /brother120tool /brother120tool-* diff --git a/MODULE.bazel b/MODULE.bazel index 08cad901..48cbb875 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,8 +16,10 @@ maven.install( "com.google.dagger:dagger:2.51.1", "com.google.dagger:dagger-compiler:2.51.1", "com.google.guava:guava:33.6.0-jre", + "com.google.truth:truth:1.4.5", "info.picocli:picocli:4.7.7", "info.picocli:picocli-codegen:4.7.7", + "io.netty:netty-buffer:4.2.16.Final", "javax.usb:usb-api:1.0.2", "junit:junit:4.13.2", "org.openjfx:javafx-controls:23.0.2", diff --git a/fluxengine.iml b/fluxengine.iml index 0e3e7da1..c01b9e60 100644 --- a/fluxengine.iml +++ b/fluxengine.iml @@ -5,7 +5,9 @@ - + + + diff --git a/java/com/cowlark/fluxengine/external/BUILD.bazel b/java/com/cowlark/fluxengine/external/BUILD.bazel index 3558f582..985434bc 100644 --- a/java/com/cowlark/fluxengine/external/BUILD.bazel +++ b/java/com/cowlark/fluxengine/external/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") @@ -6,6 +7,7 @@ package(default_visibility = ["//visibility:public"]) proto_library( name = "fl2_proto", srcs = ["fl2.proto"], + strip_import_prefix = "/java/com/cowlark/fluxengine/external/", deps = ["@com_google_protobuf//:descriptor_proto"], ) @@ -13,3 +15,11 @@ java_proto_library( name = "fl2_java_proto", deps = [":fl2_proto"], ) + +java_library( + name = "external", + srcs = glob(["*.java"]), + deps = [ + "@maven//:io_netty_netty_buffer", + ], +) diff --git a/java/com/cowlark/fluxengine/external/FluxEngine.java b/java/com/cowlark/fluxengine/external/FluxEngine.java new file mode 100644 index 00000000..2e510a38 --- /dev/null +++ b/java/com/cowlark/fluxengine/external/FluxEngine.java @@ -0,0 +1,189 @@ +package com.cowlark.fluxengine.external; + +/** + * Wire protocol definitions for the FluxEngine hardware. + */ +public final class FluxEngine +{ + public static final int FLUXENGINE_PROTOCOL_VERSION = 17; + + public static final int FLUXENGINE_VID = 0x1209; + public static final int FLUXENGINE_PID = 0x6e00; + public static final int FLUXENGINE_ID = (FLUXENGINE_VID << 16) | FLUXENGINE_PID; + + /* libusb uses these numbers */ + public static final int FLUXENGINE_DATA_OUT_EP = 0x01; + public static final int FLUXENGINE_DATA_IN_EP = 0x82; + public static final int FLUXENGINE_CMD_OUT_EP = 0x03; + public static final int FLUXENGINE_CMD_IN_EP = 0x84; + + /* the PSoC code uses these, sigh */ + public static final int FLUXENGINE_DATA_OUT_EP_NUM = FLUXENGINE_DATA_OUT_EP & 0x0f; + public static final int FLUXENGINE_DATA_IN_EP_NUM = FLUXENGINE_DATA_IN_EP & 0x0f; + public static final int FLUXENGINE_CMD_OUT_EP_NUM = FLUXENGINE_CMD_OUT_EP & 0x0f; + public static final int FLUXENGINE_CMD_IN_EP_NUM = FLUXENGINE_CMD_IN_EP & 0x0f; + + public static final int SIDE_SIDEA = 0 << 0; + public static final int SIDE_SIDEB = 1 << 0; + + public static final int DRIVE_0 = 0; + public static final int DRIVE_1 = 1; + public static final int DRIVE_DD = 0 << 1; + public static final int DRIVE_HD = 1 << 1; + + public static final int FRAME_SIZE = 64; + public static final int TICK_FREQUENCY = 12000000; + public static final int TICKS_PER_US = TICK_FREQUENCY / 1000000; + public static final int TICKS_PER_MS = TICK_FREQUENCY / 1000; + + public static final int PRECOMPENSATION_THRESHOLD_TICKS = + (int) (2.25 * TICKS_PER_US); + + public static final double NS_PER_TICK = 1000000000.0 / TICK_FREQUENCY; + public static final double US_PER_TICK = 1000000.0 / TICK_FREQUENCY; + public static final double MS_PER_TICK = 1000.0 / TICK_FREQUENCY; + + public static final int F_FRAME_ERROR = 0; + public static final int F_FRAME_DEBUG = 1; + public static final int F_FRAME_GET_VERSION_CMD = 2; + public static final int F_FRAME_GET_VERSION_REPLY = 3; + public static final int F_FRAME_SEEK_CMD = 4; + public static final int F_FRAME_SEEK_REPLY = 5; + public static final int F_FRAME_MEASURE_SPEED_CMD = 6; + public static final int F_FRAME_MEASURE_SPEED_REPLY = 7; + public static final int F_FRAME_BULK_WRITE_TEST_CMD = 8; + public static final int F_FRAME_BULK_WRITE_TEST_REPLY = 9; + public static final int F_FRAME_BULK_READ_TEST_CMD = 10; + public static final int F_FRAME_BULK_READ_TEST_REPLY = 11; + public static final int F_FRAME_READ_CMD = 12; + public static final int F_FRAME_READ_REPLY = 13; + public static final int F_FRAME_WRITE_CMD = 14; + public static final int F_FRAME_WRITE_REPLY = 15; + public static final int F_FRAME_ERASE_CMD = 16; + public static final int F_FRAME_ERASE_REPLY = 17; + public static final int F_FRAME_RECALIBRATE_CMD = 18; + public static final int F_FRAME_RECALIBRATE_REPLY = 19; + public static final int F_FRAME_SET_DRIVE_CMD = 20; + public static final int F_FRAME_SET_DRIVE_REPLY = 21; + public static final int F_FRAME_MEASURE_VOLTAGES_CMD = 22; + public static final int F_FRAME_MEASURE_VOLTAGES_REPLY = 23; + + public static final int F_ERROR_NONE = 0; + public static final int F_ERROR_BAD_COMMAND = 1; + public static final int F_ERROR_UNDERRUN = 2; + public static final int F_ERROR_INVALID_VALUE = 3; + public static final int F_ERROR_INTERNAL = 4; + + public static final int F_INDEX_REAL = 0; + public static final int F_INDEX_300 = 1; + public static final int F_INDEX_360 = 2; + + public static final int F_BIT_PULSE = 0x80; + public static final int F_BIT_INDEX = 0x40; + public static final int F_DESYNC = 0x00; /* obsolete */ + public static final int F_EOF = 0x100; /* synthetic, only produced by library */ + + public static class FrameHeader + { + public int type; + public int size; + } + + public static class AnyFrame + { + public FrameHeader f; + } + + public static class ErrorFrame + { + public FrameHeader f; + public int error; + } + + public static class DebugFrame + { + public FrameHeader f; + public byte[] payload = new byte[60]; + } + + public static class VersionFrame + { + public FrameHeader f; + public int version; + } + + public static class SeekFrame + { + public FrameHeader f; + public int track; + } + + public static class MeasureSpeedFrame + { + public FrameHeader f; + public int hardSectorCount; + } + + public static class SpeedFrame + { + public FrameHeader f; + public int periodMs; + } + + public static class ReadFrame + { + public FrameHeader f; + public int side; + public int synced; + public int milliseconds; + public int hardsecThresholdMs; + } + + public static class WriteFrame + { + public FrameHeader f; + public int side; + public long bytesToWrite; + public int hardsecThresholdMs; + } + + public static class EraseFrame + { + public FrameHeader f; + public int side; + public int hardsecThresholdMs; + } + + public static class SetDriveFrame + { + public FrameHeader f; + public int drive; + public int highDensity; + public int indexMode; + } + + public static class Voltages + { + public int logic0Mv; + public int logic1Mv; + } + + public static class VoltagesFrame + { + public FrameHeader f; + public Voltages outputBothOff = new Voltages(); + public Voltages outputDrive0Selected = new Voltages(); + public Voltages outputDrive1Selected = new Voltages(); + public Voltages outputDrive0Running = new Voltages(); + public Voltages outputDrive1Running = new Voltages(); + public Voltages inputBothOff = new Voltages(); + public Voltages inputDrive0Selected = new Voltages(); + public Voltages inputDrive1Selected = new Voltages(); + public Voltages inputDrive0Running = new Voltages(); + public Voltages inputDrive1Running = new Voltages(); + } + + private FluxEngine() + { + } +} diff --git a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java new file mode 100644 index 00000000..7ab2ae38 --- /dev/null +++ b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java @@ -0,0 +1,166 @@ +package com.cowlark.fluxengine.external; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * Flux stream conversion helpers, ported from lib/external/greaseweazle.cc. + */ +public final class GreaseweazleUtils +{ + private static final int FLUXOP_INDEX = 1; + private static final int FLUXOP_SPACE = 2; + + private GreaseweazleUtils() + { + } + + public static ByteBuf fluxEngineToGreaseweazle(ByteBuf fldata, double clock) + { + ByteBuf out = Unpooled.buffer(); + long ticksFl = 0; + long ticksGw = 0; + + while (fldata.isReadable()) + { + int b = fldata.readUnsignedByte(); + ticksFl += b & 0x3f; + if ((b & F_BIT_PULSE) != 0) + { + long newTicksGw = (long) (ticksFl * NS_PER_TICK / clock); + long delta = newTicksGw - ticksGw; + if (delta < 250) + out.writeByte((int) delta); + else + { + long high = (delta - 250) / 255; + if (high < 5) + { + out.writeByte((int) (250 + high)); + out.writeByte((int) (1 + (delta - 250) % 255)); + } + else + { + out.writeByte(255); + out.writeByte(FLUXOP_SPACE); + write28(out, delta - 249); + out.writeByte(249); + } + } + ticksGw = newTicksGw; + } + } + out.writeByte(0); /* end of stream */ + return out; + } + + public static ByteBuf greaseweazleToFluxEngine(ByteBuf gwdata, double clock) + { + ByteBuf out = Unpooled.buffer(); + long ticksGw = 0; + long lastEventFl = 0; + long indexGw = -1; + + while (gwdata.isReadable()) + { + int b = gwdata.readUnsignedByte(); + if (b == 0) + break; + + int event = 0; + if (b == 255) + { + switch (gwdata.readUnsignedByte()) + { + case FLUXOP_INDEX: + indexGw = ticksGw + read28(gwdata); + break; + + case FLUXOP_SPACE: + ticksGw += read28(gwdata); + break; + + default: + throw new RuntimeException("bad opcode in Greaseweazle stream"); + } + } + else + { + if (b < 250) + ticksGw += b; + else + { + long delta = 250 + (b - 250) * 255 + gwdata.readUnsignedByte() - 1; + ticksGw += delta; + } + event = F_BIT_PULSE; + } + + if (event != 0) + { + long indexFl = Math.round(indexGw * clock / NS_PER_TICK); + long ticksFl = Math.round(ticksGw * clock / NS_PER_TICK); + if (indexGw != -1) + { + if (indexFl < ticksFl) + { + long deltaFl = indexFl - lastEventFl; + while (deltaFl > 0x3f) + { + out.writeByte(0x3f); + deltaFl -= 0x3f; + } + out.writeByte((int) (deltaFl | F_BIT_INDEX)); + lastEventFl = indexFl; + indexGw = -1; + } + else if (indexFl == ticksFl) + event |= F_BIT_INDEX; + } + + long deltaFl = ticksFl - lastEventFl; + while (deltaFl > 0x3f) + { + out.writeByte(0x3f); + deltaFl -= 0x3f; + } + out.writeByte((int) (deltaFl | event)); + lastEventFl = ticksFl; + } + } + + return out; + } + + /* Left-truncates at the first index mark, so the resulting data is aligned + * at the index. */ + public static ByteBuf stripPartialRotation(ByteBuf fldata) + { + for (int i = fldata.readerIndex(); i < fldata.writerIndex(); i++) + { + if ((fldata.getByte(i) & F_BIT_INDEX) != 0) + return fldata.slice(i, fldata.writerIndex() - i); + } + return fldata; + } + + private static void write28(ByteBuf out, long val) + { + out.writeByte(1 | (int) (val << 1) & 0xff); + out.writeByte(1 | (int) (val >> 6) & 0xff); + out.writeByte(1 | (int) (val >> 13) & 0xff); + out.writeByte(1 | (int) (val >> 20) & 0xff); + } + + private static long read28(ByteBuf in) + { + return (long) ((in.readUnsignedByte() & 0xfe) >> 1) | + (long) (in.readUnsignedByte() & 0xfe) << 6 | + (long) (in.readUnsignedByte() & 0xfe) << 13 | + (long) (in.readUnsignedByte() & 0xfe) << 20; + } +} diff --git a/javatests/com/cowlark/fluxengine/BUILD.bazel b/javatests/com/cowlark/fluxengine/BUILD.bazel deleted file mode 100644 index 491ce55a..00000000 --- a/javatests/com/cowlark/fluxengine/BUILD.bazel +++ /dev/null @@ -1,13 +0,0 @@ -load("@rules_java//java:defs.bzl", "java_test") - -package(default_visibility = ["//visibility:public"]) - -java_test( - name = "EmptyTest", - srcs = ["EmptyTest.java"], - plugins = ["//java/com/cowlark/fluxengine/wiring:dagger_plugin"], - deps = [ - "//java/com/cowlark/fluxengine:fluxengine_lib", - "@maven//:junit_junit", - ], -) diff --git a/javatests/com/cowlark/fluxengine/EmptyTest.java b/javatests/com/cowlark/fluxengine/EmptyTest.java deleted file mode 100644 index 70f9e3c8..00000000 --- a/javatests/com/cowlark/fluxengine/EmptyTest.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.cowlark.fluxengine; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class EmptyTest{ - @Test - public void empty(){} -} diff --git a/javatests/com/cowlark/fluxengine/external/BUILD.bazel b/javatests/com/cowlark/fluxengine/external/BUILD.bazel new file mode 100644 index 00000000..0286706a --- /dev/null +++ b/javatests/com/cowlark/fluxengine/external/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "GreaseweazleUtilsTest", + srcs = ["GreaseweazleUtilsTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/external", + "//javatests/com/cowlark/fluxengine/testing", + "@maven//:com_google_truth_truth", + "@maven//:io_netty_netty_buffer", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java b/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java new file mode 100644 index 00000000..f948e6ab --- /dev/null +++ b/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java @@ -0,0 +1,78 @@ +package com.cowlark.fluxengine.external; + +import static com.cowlark.fluxengine.testing.TestHelpers.buf; +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.testing.TestHelpers; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GreaseweazleUtilsTest +{ + private static final double CLOCK = 2 * FluxEngine.NS_PER_TICK; + + private static void testConvert(ByteBuf gwBytes, ByteBuf flBytes) + { + byte[] expectedFl = ByteBufUtil.getBytes(flBytes); + byte[] expectedGw = ByteBufUtil.getBytes(gwBytes); + + ByteBuf gwToFl = GreaseweazleUtils.greaseweazleToFluxEngine( + Unpooled.copiedBuffer(gwBytes), CLOCK); + ByteBuf flToGw = GreaseweazleUtils.fluxEngineToGreaseweazle( + Unpooled.copiedBuffer(flBytes), CLOCK); + + assertThat(ByteBufUtil.getBytes(gwToFl)).isEqualTo(expectedFl); + assertThat(ByteBufUtil.getBytes(flToGw)).isEqualTo(expectedGw); + } + + private static ByteBuf encode28(int val) + { + return buf(1 | (val << 1) & 0xff, + 1 | (val >> 6) & 0xff, + 1 | (val >> 13) & 0xff, + 1 | (val >> 20) & 0xff); + } + + @Test + public void conversions() + { + /* Simple one-byte intervals. */ + testConvert( + buf(1, 1, 1, 1, 0), + buf(0x82, 0x82, 0x82, 0x82)); + + /* Larger one-byte intervals. */ + testConvert( + buf(32, 0), + buf(0x3f, 0x81)); + testConvert( + buf(64, 0), + buf(0x3f, 0x3f, 0x82)); + testConvert( + buf(128, 0), + buf(0x3f, 0x3f, 0x3f, 0x3f, 0x84)); + + /* Two-byte intervals. */ + testConvert( + buf(250, 1, 0), + buf(0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xbb)); + + /* Very long intervals. */ + ByteBuf gw = Unpooled.buffer(8); + gw.writeBytes(buf(255, 2)); /* FLUXOP_SPACE */ + gw.writeBytes(encode28(2048 - 249)); + gw.writeBytes(buf(249, 0)); + + ByteBuf fl = Unpooled.buffer(66); + for (int i = 0; i < 65; i++) + fl.writeByte(0x3f); + fl.writeByte(0x81); + + testConvert(gw, fl); + } +} diff --git a/javatests/com/cowlark/fluxengine/testing/BUILD.bazel b/javatests/com/cowlark/fluxengine/testing/BUILD.bazel new file mode 100644 index 00000000..ba155621 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/testing/BUILD.bazel @@ -0,0 +1,11 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "testing", + srcs = ["TestHelpers.java"], + deps = [ + "@maven//:io_netty_netty_buffer", + ], +) diff --git a/javatests/com/cowlark/fluxengine/testing/TestHelpers.java b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java new file mode 100644 index 00000000..b8b66d1a --- /dev/null +++ b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java @@ -0,0 +1,15 @@ +package com.cowlark.fluxengine.testing; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +public class TestHelpers +{ + public static ByteBuf buf(int... values) + { + byte[] bytes = new byte[values.length]; + for (int i = 0; i < values.length; i++) + bytes[i] = (byte) values[i]; + return Unpooled.wrappedBuffer(bytes); + } +} diff --git a/javatests/javatests.iml b/javatests/javatests.iml new file mode 100644 index 00000000..a6c28e92 --- /dev/null +++ b/javatests/javatests.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file From 9845e08e5d0869a06332606d9770076510e87751 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 00:22:34 +0200 Subject: [PATCH 047/192] Translate greaseweazleusb.cc, crudely. --- .../cowlark/fluxengine/config/common.proto | 2 + .../cowlark/fluxengine}/config/config.proto | 0 .../com/cowlark/fluxengine/config/drive.proto | 2 + .../cowlark/fluxengine/config/layout.proto | 2 + .../external/GreaseweazleUtils.java | 36 +- .../com/cowlark/fluxengine/external/fl2.proto | 2 + .../fluxengine/usb/AbstractUsbDevice.java | 5 +- java/com/cowlark/fluxengine/usb/BUILD.bazel | 3 + .../fluxengine/usb/GreaseweazleDevice.java | 519 ++++++++++++++++++ .../fluxengine/usb/UsbDeviceFactory.java | 2 - java/com/cowlark/fluxengine/usb/usb.proto | 2 + lib/usb/greaseweazleusb.cc | 438 --------------- 12 files changed, 569 insertions(+), 444 deletions(-) rename {lib => java/com/cowlark/fluxengine}/config/config.proto (100%) create mode 100644 java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java delete mode 100644 java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java delete mode 100644 lib/usb/greaseweazleusb.cc diff --git a/java/com/cowlark/fluxengine/config/common.proto b/java/com/cowlark/fluxengine/config/common.proto index 7b750e02..5b6c2474 100644 --- a/java/com/cowlark/fluxengine/config/common.proto +++ b/java/com/cowlark/fluxengine/config/common.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.config"; + import "google/protobuf/descriptor.proto"; extend google.protobuf.FieldOptions diff --git a/lib/config/config.proto b/java/com/cowlark/fluxengine/config/config.proto similarity index 100% rename from lib/config/config.proto rename to java/com/cowlark/fluxengine/config/config.proto diff --git a/java/com/cowlark/fluxengine/config/drive.proto b/java/com/cowlark/fluxengine/config/drive.proto index fe4ed40c..7bf4af78 100644 --- a/java/com/cowlark/fluxengine/config/drive.proto +++ b/java/com/cowlark/fluxengine/config/drive.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.config"; + import "common.proto"; import "fl2.proto"; diff --git a/java/com/cowlark/fluxengine/config/layout.proto b/java/com/cowlark/fluxengine/config/layout.proto index 2337212c..66692bd9 100644 --- a/java/com/cowlark/fluxengine/config/layout.proto +++ b/java/com/cowlark/fluxengine/config/layout.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.config"; + import "common.proto"; import "fl2.proto"; diff --git a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java index 7ab2ae38..1c095b17 100644 --- a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java +++ b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java @@ -12,8 +12,40 @@ */ public final class GreaseweazleUtils { - private static final int FLUXOP_INDEX = 1; - private static final int FLUXOP_SPACE = 2; + public static final int CMD_GET_INFO = 0; + public static final int CMD_SEEK = 2; + public static final int CMD_HEAD = 3; + public static final int CMD_MOTOR = 6; + public static final int CMD_READ_FLUX = 7; + public static final int CMD_WRITE_FLUX = 8; + public static final int CMD_GET_FLUX_STATUS = 9; + public static final int CMD_SELECT = 12; + public static final int CMD_SET_BUS_TYPE = 14; + public static final int CMD_SET_PIN = 15; + public static final int CMD_ERASE_FLUX = 17; + public static final int CMD_SOURCE_BYTES = 18; + public static final int CMD_SINK_BYTES = 19; + + public static final int ACK_OKAY = 0; + public static final int ACK_BAD_COMMAND = 1; + public static final int ACK_NO_INDEX = 2; + public static final int ACK_NO_TRK0 = 3; + public static final int ACK_FLUX_OVERFLOW = 4; + public static final int ACK_FLUX_UNDERFLOW = 5; + public static final int ACK_WRPROT = 6; + public static final int ACK_NO_UNIT = 7; + public static final int ACK_NO_BUS = 8; + public static final int ACK_BAD_UNIT = 9; + public static final int ACK_BAD_PIN = 10; + public static final int ACK_BAD_CYLINDER = 11; + + public static final int GETINFO_FIRMWARE = 0; + + public static final int FLUXOP_INDEX = 1; + public static final int FLUXOP_SPACE = 2; + + public static final int BAUD_NORMAL = 9600; + public static final int BAUD_CLEAR_COMMS = 10000; private GreaseweazleUtils() { diff --git a/java/com/cowlark/fluxengine/external/fl2.proto b/java/com/cowlark/fluxengine/external/fl2.proto index dadba35f..2ba53343 100644 --- a/java/com/cowlark/fluxengine/external/fl2.proto +++ b/java/com/cowlark/fluxengine/external/fl2.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.external"; + import "google/protobuf/descriptor.proto"; extend google.protobuf.FieldOptions diff --git a/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java b/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java index 52710bd8..296b992a 100644 --- a/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.usb; +import io.netty.buffer.ByteBuf; import javax.inject.Inject; /** @@ -20,10 +21,10 @@ public void recalibrate() public abstract void testBulkRead(); - public abstract byte[] read(int side, boolean synced, long readTime, + public abstract ByteBuf read(int side, boolean synced, long readTime, long hardSectorThreshold); - public abstract void write(int side, byte[] bytes, long hardSectorThreshold); + public abstract void write(int side, ByteBuf bytes, long hardSectorThreshold); public abstract void erase(int side, long hardSectorThreshold); diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index 33d15ece..f28b0ab2 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -21,8 +21,11 @@ java_library( srcs = glob(["*.java"]), resources = ["//java:javax.usb.properties"], deps = [ + "//java/com/cowlark/fluxengine/external", + ":usb_java_proto", "@maven//:com_fazecast_jSerialComm", "@maven//:com_google_guava_guava", + "@maven//:io_netty_netty_buffer", "@maven//:javax_inject_javax_inject", "@maven//:javax_usb_usb_api", "@maven//:org_usb4java_usb4java_javax", diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java new file mode 100644 index 00000000..3ee04b63 --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java @@ -0,0 +1,519 @@ +package com.cowlark.fluxengine.usb; + +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_BAD_COMMAND; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_BAD_CYLINDER; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_BAD_PIN; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_BAD_UNIT; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_FLUX_OVERFLOW; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_FLUX_UNDERFLOW; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_NO_BUS; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_NO_INDEX; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_NO_TRK0; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_NO_UNIT; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_OKAY; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.ACK_WRPROT; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.BAUD_CLEAR_COMMS; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.BAUD_NORMAL; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_ERASE_FLUX; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_GET_FLUX_STATUS; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_GET_INFO; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_HEAD; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_MOTOR; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_READ_FLUX; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_SEEK; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_SELECT; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_SET_BUS_TYPE; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_SET_PIN; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_SINK_BYTES; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_SOURCE_BYTES; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.CMD_WRITE_FLUX; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.FLUXOP_INDEX; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.FLUXOP_SPACE; +import static com.cowlark.fluxengine.external.GreaseweazleUtils.GETINFO_FIRMWARE; + +import com.cowlark.fluxengine.external.GreaseweazleUtils; +import com.cowlark.fluxengine.usb.Usb.GreaseweazleProto; +import com.fazecast.jSerialComm.SerialPort; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; + +/** + * Greaseweazle floppy drive device, ported from lib/usb/greaseweazleusb.cc. + */ +class GreaseweazleDevice extends AbstractUsbDevice +{ + private enum Version + {V22, V24, V29} + + private final SerialPort serial; + private final GreaseweazleProto config; + private Version version; + private long clock; + private long revolutions; + + GreaseweazleDevice(String port, GreaseweazleProto config) + { + this.config = config; + this.serial = SerialPort.getCommPort(port); + serial.setBaudRate(BAUD_NORMAL); + serial.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); + if (!serial.openPort()) + throw new RuntimeException("Unable to open serial port " + port); + + int version = getVersion(); + if (version >= 29) + this.version = Version.V29; + else if (version >= 24) + this.version = Version.V24; + else if (version == 22) + this.version = Version.V22; + else + throw new RuntimeException(String.format( + "only Greaseweazle firmware versions 22 and 24 or above are currently " + + "supported, but you have version %d. Please file a bug.", + version)); + + /* Twiddle the baud rate, which indicates to the Greaseweazle that the + * data stream has been reset. */ + serial.setBaudRate(BAUD_CLEAR_COMMS); + sleep(100); + serial.setBaudRate(BAUD_NORMAL); + + /* Configure the hardware. */ + doCommand(CMD_SET_BUS_TYPE, config.getBusType().getNumber()); + } + + private int getVersion() + { + doCommand(CMD_GET_INFO, GETINFO_FIRMWARE); + + ByteBuf response = Unpooled.wrappedBuffer(readBytes(32)); + response.readerIndex(4); + long freq = response.readUnsignedIntLE(); + clock = 1000000000L / freq; + + response.readerIndex(0); + return response.readUnsignedShort(); + } + + private long read28() + { + ByteBuf buffer = Unpooled.wrappedBuffer(readBytes(4)); + return (long) ((buffer.readUnsignedByte() & 0xfe) >> 1) | + (long) (buffer.readUnsignedByte() & 0xfe) << 6 | + (long) (buffer.readUnsignedByte() & 0xfe) << 13 | + (long) (buffer.readUnsignedByte() & 0xfe) << 20; + } + + private void doCommand(int cmd, int... payload) + { + byte[] command = new byte[2 + payload.length]; + command[0] = (byte) cmd; + command[1] = (byte) command.length; + for (int i = 0; i < payload.length; i++) + command[2 + i] = (byte) payload[i]; + doCommand(command); + } + + private void doCommand(ByteBuf command) + { + byte[] bytes = new byte[command.readableBytes()]; + command.getBytes(command.readerIndex(), bytes); + doCommand(bytes); + } + + private void doCommand(byte[] command) + { + writeBytes(command); + + byte[] buffer = readBytes(2); + + if ((buffer[0] & 0xff) != (command[0] & 0xff)) + throw new RuntimeException(String.format( + "command returned garbage (0x%x != 0x%x with status 0x%x)", + buffer[0], + command[0], + buffer[1])); + if (buffer[1] != 0) + throw new RuntimeException("Greaseweazle error: " + gwError(buffer[1] & 0xff)); + } + + @Override + public void seek(int track) + { + doCommand(CMD_SEEK, track); + } + + @Override + public long getRotationalPeriod(int hardSectorCount) + { + if (hardSectorCount != 0) + throw new RuntimeException("hard sectors are currently unsupported on the " + + "Greaseweazle"); + + /* The Greaseweazle doesn't have a command to fetch the period directly, + * so we have to do a flux read. */ + switch (version) + { + case V22: + doCommand(CMD_READ_FLUX); + break; + + case V24: + case V29: + { + ByteBuf cmd = Unpooled.buffer(8); + cmd.writeByte(CMD_READ_FLUX); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE(0); /* ticks default value (guessed) */ + cmd.writeShortLE(2); /* revolutions */ + doCommand(cmd); + } + } + + long ticksGw = 0; + long firstIndex = -1; + long secondIndex = -1; + for (; ; ) + { + int b = readByte(); + if (b == 0) + break; + + if (b == 255) + { + switch (readByte()) + { + case FLUXOP_INDEX: + { + long index = read28() + ticksGw; + if (firstIndex == -1) + firstIndex = index; + else if (secondIndex == -1) + secondIndex = index; + break; + } + + case FLUXOP_SPACE: + ticksGw += read28(); + break; + + default: + throw new RuntimeException("bad opcode in Greaseweazle stream"); + } + } else + { + if (b < 250) + ticksGw += b; + else + { + long delta = 250 + (b - 250) * 255 + readByte() - 1; + ticksGw += delta; + } + } + } + + if (secondIndex == -1) + throw new RuntimeException( + "unable to determine disk rotational period (is a disk in the drive?)"); + doCommand(CMD_GET_FLUX_STATUS); + + revolutions = (secondIndex - firstIndex) * clock; + return revolutions; + } + + @Override + public void testBulkWrite() + { + System.out.print("Writing data: "); + final int LEN = 10 * 1024 * 1024; + ByteBuf cmd; + switch (version) + { + case V22: + case V24: + { + cmd = Unpooled.buffer(6); + cmd.writeByte(CMD_SINK_BYTES); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE(LEN); + break; + } + + case V29: + { + cmd = Unpooled.buffer(10); + cmd.writeByte(CMD_SINK_BYTES); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE(LEN); + cmd.writeIntLE(0); /* seed */ + break; + } + + default: + throw new IllegalStateException(); + } + doCommand(cmd); + + byte[] junk = new byte[LEN]; + long seed = 0; + for (int i = 0; i < LEN; i++) + { + junk[i] = (byte) seed; + seed = ssRandNext(seed); + } + double startTime = getCurrentTime(); + writeBytes(junk); + readBytes(1); + double elapsedTime = getCurrentTime() - startTime; + + System.out.println(String.format( + "transferred %d bytes from PC -> device in %d ms (%d kb/s)", + LEN, + (int) (elapsedTime * 1000.0), + (int) ((LEN / 1024.0) / elapsedTime))); + } + + @Override + public void testBulkRead() + { + System.out.print("Reading data: "); + final int LEN = 10 * 1024 * 1024; + ByteBuf cmd; + switch (version) + { + case V22: + case V24: + { + cmd = Unpooled.buffer(6); + cmd.writeByte(CMD_SOURCE_BYTES); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE(LEN); + break; + } + + case V29: + { + cmd = Unpooled.buffer(10); + cmd.writeByte(CMD_SOURCE_BYTES); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE(LEN); + cmd.writeIntLE(0); /* seed */ + break; + } + + default: + throw new IllegalStateException(); + } + doCommand(cmd); + + double startTime = getCurrentTime(); + readBytes(LEN); + double elapsedTime = getCurrentTime() - startTime; + + System.out.println(String.format( + "transferred %d bytes from device -> PC in %d ms (%d kb/s)", + LEN, + (int) (elapsedTime * 1000.0), + (int) ((LEN / 1024.0) / elapsedTime))); + } + + @Override + public ByteBuf read(int side, boolean synced, long readTime, long hardSectorThreshold) + { + if (hardSectorThreshold != 0) + throw new RuntimeException("hard sectors are currently unsupported on the " + + "Greaseweazle"); + + doCommand(CMD_HEAD, side); + + switch (version) + { + case V22: + { + long revs = (readTime + revolutions - 1) / revolutions; + ByteBuf cmd = Unpooled.buffer(4); + cmd.writeByte(CMD_READ_FLUX); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE((int) (revs + (synced ? 1 : 0))); + doCommand(cmd); + break; + } + + case V24: + case V29: + { + ByteBuf cmd = Unpooled.buffer(8); + cmd.writeByte(CMD_READ_FLUX); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE((int) ((readTime + (synced ? revolutions : 0)) / clock)); + cmd.writeShortLE(0); + doCommand(cmd); + } + } + + ByteBuf buffer = Unpooled.buffer(); + for (; ; ) + { + int b = readByte(); + if (b == 0) + break; + buffer.writeByte(b); + } + + doCommand(CMD_GET_FLUX_STATUS); + + ByteBuf fldata = GreaseweazleUtils.greaseweazleToFluxEngine(buffer, clock); + if (synced) + fldata = GreaseweazleUtils.stripPartialRotation(fldata); + return fldata; + } + + @Override + public void write(int side, ByteBuf fldata, long hardSectorThreshold) + { + if (hardSectorThreshold != 0) + throw new RuntimeException("hard sectors are currently unsupported on the " + + "Greaseweazle"); + + doCommand(CMD_HEAD, side); + switch (version) + { + case V22: + doCommand(CMD_WRITE_FLUX, 1); + break; + + case V24: + case V29: + doCommand(CMD_WRITE_FLUX, 1, 1); + break; + } + ByteBuf gwdata = GreaseweazleUtils.fluxEngineToGreaseweazle(fldata, clock); + writeBytes(gwdata); + readByte(); /* synchronise */ + + doCommand(CMD_GET_FLUX_STATUS); + } + + @Override + public void erase(int side, long hardSectorThreshold) + { + if (hardSectorThreshold != 0) + throw new RuntimeException("hard sectors are currently unsupported on the " + + "Greaseweazle"); + + doCommand(CMD_HEAD, side); + + ByteBuf cmd = Unpooled.buffer(6); + cmd.writeByte(CMD_ERASE_FLUX); + cmd.writeByte(cmd.capacity()); + cmd.writeIntLE((int) (200e6 / clock)); + doCommand(cmd); + readByte(); /* synchronise */ + + doCommand(CMD_GET_FLUX_STATUS); + } + + @Override + public void setDrive(int drive, boolean highDensity, int indexMode) + { + doCommand(CMD_SELECT, drive); + doCommand(CMD_MOTOR, drive, 1); + doCommand(CMD_SET_PIN, 2, highDensity ? 1 : 0); + } + + @Override + public void measureVoltages(Voltages[] voltages) + { + throw new RuntimeException("unsupported operation on the Greaseweazle"); + } + + private static String gwError(int e) + { + switch (e) + { + case ACK_OKAY: + return "OK"; + case ACK_BAD_COMMAND: + return "Bad command"; + case ACK_NO_INDEX: + return "No index"; + case ACK_NO_TRK0: + return "No track 0"; + case ACK_FLUX_OVERFLOW: + return "Overflow"; + case ACK_FLUX_UNDERFLOW: + return "Underflow"; + case ACK_WRPROT: + return "Write protected"; + case ACK_NO_UNIT: + return "No unit"; + case ACK_NO_BUS: + return "No bus"; + case ACK_BAD_UNIT: + return "Invalid unit"; + case ACK_BAD_PIN: + return "Invalid pin"; + case ACK_BAD_CYLINDER: + return "Invalid track"; + default: + return "Unknown error"; + } + } + + private static long ssRandNext(long x) + { + return (x & 1) != 0 ? (x >> 1) ^ 0x80000062L : x >> 1; + } + + private int readByte() + { + return readBytes(1)[0] & 0xff; + } + + private byte[] readBytes(int count) + { + byte[] result = new byte[count]; + int offset = 0; + while (offset < count) + { + byte[] chunk = new byte[count - offset]; + int read = serial.readBytes(chunk, chunk.length); + if (read < 0) + throw new RuntimeException("serial read failed"); + System.arraycopy(chunk, 0, result, offset, read); + offset += read; + } + return result; + } + + private void writeBytes(byte[] data) + { + int written = serial.writeBytes(data, data.length); + if (written != data.length) + throw new RuntimeException("serial write failed"); + } + + private void writeBytes(ByteBuf data) + { + byte[] bytes = new byte[data.readableBytes()]; + data.getBytes(data.readerIndex(), bytes); + writeBytes(bytes); + } + + private static double getCurrentTime() + { + return System.nanoTime() / 1e9; + } + + private static void sleep(long ms) + { + try + { + Thread.sleep(ms); + } catch (InterruptedException e) + { + Thread.currentThread().interrupt(); + } + } +} diff --git a/java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java b/java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java deleted file mode 100644 index 261ea2bd..00000000 --- a/java/com/cowlark/fluxengine/usb/UsbDeviceFactory.java +++ /dev/null @@ -1,2 +0,0 @@ -package com.cowlark.fluxengine.usb; - diff --git a/java/com/cowlark/fluxengine/usb/usb.proto b/java/com/cowlark/fluxengine/usb/usb.proto index a3c684b4..22241ec5 100644 --- a/java/com/cowlark/fluxengine/usb/usb.proto +++ b/java/com/cowlark/fluxengine/usb/usb.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.usb"; + import "common.proto"; message GreaseweazleProto { diff --git a/lib/usb/greaseweazleusb.cc b/lib/usb/greaseweazleusb.cc deleted file mode 100644 index 2f6817b3..00000000 --- a/lib/usb/greaseweazleusb.cc +++ /dev/null @@ -1,438 +0,0 @@ -#include "lib/core/globals.h" -#include "protocol.h" -#include "lib/data/fluxmap.h" -#include "lib/core/bytes.h" -#include "lib/usb/usb.pb.h" -#include "lib/external/greaseweazle.h" -#include "lib/usb/serial.h" -#include "lib/usb/usb.h" -#include - -static const char* gw_error(int e) -{ - switch (e) - { - case ACK_OKAY: - return "OK"; - case ACK_BAD_COMMAND: - return "Bad command"; - case ACK_NO_INDEX: - return "No index"; - case ACK_NO_TRK0: - return "No track 0"; - case ACK_FLUX_OVERFLOW: - return "Overflow"; - case ACK_FLUX_UNDERFLOW: - return "Underflow"; - case ACK_WRPROT: - return "Write protected"; - case ACK_NO_UNIT: - return "No unit"; - case ACK_NO_BUS: - return "No bus"; - case ACK_BAD_UNIT: - return "Invalid unit"; - case ACK_BAD_PIN: - return "Invalid pin"; - case ACK_BAD_CYLINDER: - return "Invalid track"; - default: - return "Unknown error"; - } -} - -static uint32_t ss_rand_next(uint32_t x) -{ - return (x & 1) ? (x >> 1) ^ 0x80000062 : x >> 1; -} - -class GreaseweazleUsb : public USB -{ -private: - uint32_t read_28() - { - uint8_t buffer[4]; - _serial->read(buffer, sizeof(buffer)); - - return ((buffer[0] & 0xfe) >> 1) | ((buffer[1] & 0xfe) << 6) | - ((buffer[2] & 0xfe) << 13) | ((buffer[3] & 0xfe) << 20); - } - - void do_command(const Bytes& command) - { - _serial->write(command); - - uint8_t buffer[2]; - _serial->read(buffer, sizeof(buffer)); - - if (buffer[0] != command[0]) - error( - "command returned garbage (0x{:x} != 0x{:x} with status " - "0x{:x})", - buffer[0], - command[0], - buffer[1]); - if (buffer[1]) - error("Greaseweazle error: {}", gw_error(buffer[1])); - } - -public: - GreaseweazleUsb(const std::string& port, const GreaseweazleProto& config): - _serial(SerialPort::openSerialPort(port)), - _config(config) - { - int version = getVersion(); - if (version >= 29) - _version = V29; - else if (version >= 24) - _version = V24; - else if (version == 22) - _version = V22; - else - { - error( - "only Greaseweazle firmware versions 22 and 24 or above are " - "currently " - "supported, but you have version {}. Please file a bug.", - version); - } - - /* Twiddle the baud rate, which indicates to the Greaseweazle that the - * data stream has been reset. */ - - _serial->setBaudRate(10000); - usleep(100000); - _serial->setBaudRate(9600); - - /* Configure the hardware. */ - - do_command({CMD_SET_BUS_TYPE, 3, (uint8_t)config.bus_type()}); - } - -private: - int getVersion() - { - do_command({CMD_GET_INFO, 3, GETINFO_FIRMWARE}); - - Bytes response = _serial->readBytes(32); - ByteReader br(response); - - br.seek(4); - nanoseconds_t freq = br.read_le32(); - _clock = 1000000000 / freq; - - br.seek(0); - return br.read_be16(); - } - -public: - void seek(int track) override - { - do_command({CMD_SEEK, 3, (uint8_t)track}); - } - - nanoseconds_t getRotationalPeriod(int hardSectorCount) override - { - if (hardSectorCount != 0) - error("hard sectors are currently unsupported on the Greaseweazle"); - - /* The Greaseweazle doesn't have a command to fetch the period directly, - * so we have to do a flux read. */ - - switch (_version) - { - case V22: - do_command({CMD_READ_FLUX, 2}); - break; - - case V24: - case V29: - { - Bytes cmd(8); - cmd.writer() - .write_8(CMD_READ_FLUX) - .write_8(cmd.size()) - .write_le32(0) // ticks default value (guessed) - .write_le16(2); // revolutions - do_command(cmd); - } - } - - uint32_t ticks_gw = 0; - uint32_t firstindex = ~0; - uint32_t secondindex = ~0; - for (;;) - { - uint8_t b = _serial->readByte(); - if (!b) - break; - - if (b == 255) - { - switch (_serial->readByte()) - { - case FLUXOP_INDEX: - { - uint32_t index = read_28() + ticks_gw; - if (firstindex == ~0) - firstindex = index; - else if (secondindex == ~0) - secondindex = index; - break; - } - - case FLUXOP_SPACE: - ticks_gw += read_28(); - break; - - default: - error("bad opcode in Greaseweazle stream"); - } - } - else - { - if (b < 250) - ticks_gw += b; - else - { - int delta = 250 + (b - 250) * 255 + _serial->readByte() - 1; - ticks_gw += delta; - } - } - } - - if (secondindex == ~0) - error( - "unable to determine disk rotational period (is a disk in the " - "drive?)"); - do_command({CMD_GET_FLUX_STATUS, 2}); - - _revolutions = (nanoseconds_t)(secondindex - firstindex) * _clock; - return _revolutions; - } - - void testBulkWrite() override - { - std::cout << "Writing data: " << std::flush; - const int LEN = 10 * 1024 * 1024; - Bytes cmd; - switch (_version) - { - case V22: - case V24: - { - cmd.resize(6); - ByteWriter bw(cmd); - bw.write_8(CMD_SINK_BYTES); - bw.write_8(cmd.size()); - bw.write_le32(LEN); - break; - } - - case V29: - { - cmd.resize(10); - ByteWriter bw(cmd); - bw.write_8(CMD_SINK_BYTES); - bw.write_8(cmd.size()); - bw.write_le32(LEN); - bw.write_le32(0); // seed - break; - } - } - do_command(cmd); - - Bytes junk(LEN); - uint32_t seed = 0; - for (int i = 0; i < LEN; i++) - { - junk[i] = seed; - seed = ss_rand_next(seed); - } - double start_time = getCurrentTime(); - _serial->write(junk); - _serial->readBytes(1); - double elapsed_time = getCurrentTime() - start_time; - - std::cout << fmt::format( - "transferred {} bytes from PC -> device in {} ms ({} kb/s)\n", - LEN, - int(elapsed_time * 1000.0), - int((LEN / 1024.0) / elapsed_time)); - } - - void testBulkRead() override - { - std::cout << "Reading data: " << std::flush; - const int LEN = 10 * 1024 * 1024; - Bytes cmd; - switch (_version) - { - case V22: - case V24: - { - cmd.resize(6); - ByteWriter bw(cmd); - bw.write_8(CMD_SOURCE_BYTES); - bw.write_8(cmd.size()); - bw.write_le32(LEN); - break; - } - - case V29: - { - cmd.resize(10); - ByteWriter bw(cmd); - bw.write_8(CMD_SOURCE_BYTES); - bw.write_8(cmd.size()); - bw.write_le32(LEN); - bw.write_le32(0); // seed - break; - } - } - do_command(cmd); - - double start_time = getCurrentTime(); - _serial->readBytes(LEN); - double elapsed_time = getCurrentTime() - start_time; - - std::cout << fmt::format( - "transferred {} bytes from device -> PC in {} ms ({} kb/s)\n", - LEN, - int(elapsed_time * 1000.0), - int((LEN / 1024.0) / elapsed_time)); - } - - Bytes read(int side, - bool synced, - nanoseconds_t readTime, - nanoseconds_t hardSectorThreshold) override - { - if (hardSectorThreshold != 0) - error("hard sectors are currently unsupported on the Greaseweazle"); - - do_command({CMD_HEAD, 3, (uint8_t)side}); - - switch (_version) - { - case V22: - { - int revolutions = (readTime + _revolutions - 1) / _revolutions; - Bytes cmd(4); - cmd.writer() - .write_8(CMD_READ_FLUX) - .write_8(cmd.size()) - .write_le32(revolutions + (synced ? 1 : 0)); - do_command(cmd); - break; - } - - case V24: - case V29: - { - Bytes cmd(8); - cmd.writer() - .write_8(CMD_READ_FLUX) - .write_8(cmd.size()) - .write_le32( - (readTime + (synced ? _revolutions : 0)) / _clock) - .write_le16(0); - do_command(cmd); - } - } - - Bytes buffer; - ByteWriter bw(buffer); - for (;;) - { - uint8_t b = _serial->readByte(); - if (!b) - break; - bw.write_8(b); - } - - do_command({CMD_GET_FLUX_STATUS, 2}); - - Bytes fldata = greaseweazleToFluxEngine(buffer, _clock); - if (synced) - fldata = stripPartialRotation(fldata); - return fldata; - } - - void write(int side, - const Bytes& fldata, - nanoseconds_t hardSectorThreshold) override - { - if (hardSectorThreshold != 0) - error("hard sectors are currently unsupported on the Greaseweazle"); - - do_command({CMD_HEAD, 3, (uint8_t)side}); - switch (_version) - { - case V22: - do_command({CMD_WRITE_FLUX, 3, 1}); - break; - - case V24: - case V29: - do_command({CMD_WRITE_FLUX, 4, 1, 1}); - break; - } - _serial->write(fluxEngineToGreaseweazle(fldata, _clock)); - _serial->readByte(); /* synchronise */ - - do_command({CMD_GET_FLUX_STATUS, 2}); - } - - void erase(int side, nanoseconds_t hardSectorThreshold) override - { - if (hardSectorThreshold != 0) - error("hard sectors are currently unsupported on the Greaseweazle"); - - do_command({CMD_HEAD, 3, (uint8_t)side}); - - Bytes cmd(6); - ByteWriter bw(cmd); - bw.write_8(CMD_ERASE_FLUX); - bw.write_8(cmd.size()); - bw.write_le32(200e6 / _clock); - do_command(cmd); - _serial->readByte(); /* synchronise */ - - do_command({CMD_GET_FLUX_STATUS, 2}); - } - - void setDrive(int drive, bool high_density, int index_mode) override - { - do_command({CMD_SELECT, 3, (uint8_t)drive}); - do_command({CMD_MOTOR, 4, (uint8_t)drive, 1}); - do_command({CMD_SET_PIN, 4, 2, (uint8_t)(high_density ? 1 : 0)}); - } - - void measureVoltages(struct voltages_frame* voltages) override - { - error("unsupported operation on the Greaseweazle"); - } - -private: - enum - { - V22, - V24, - V29 - }; - - std::unique_ptr _serial; - const GreaseweazleProto& _config; - int _version; - nanoseconds_t _clock; - nanoseconds_t _revolutions; -}; - -USB* createGreaseweazleUsb( - const std::string& port, const GreaseweazleProto& config) -{ - return new GreaseweazleUsb(port, config); -} - -// vim: sw=4 ts=4 et From 18c96e477ad0bcd6620e801212ef2579ca94fa09 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 01:05:55 +0200 Subject: [PATCH 048/192] Add Bytes. --- java/com/cowlark/fluxengine/core/BUILD.bazel | 8 + java/com/cowlark/fluxengine/core/Bytes.java | 185 ++++++++++++++++++ .../com/cowlark/fluxengine/core/BUILD.bazel | 13 ++ .../cowlark/fluxengine/core/BytesTest.java | 68 +++++++ 4 files changed, 274 insertions(+) create mode 100644 java/com/cowlark/fluxengine/core/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/core/Bytes.java create mode 100644 javatests/com/cowlark/fluxengine/core/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/core/BytesTest.java diff --git a/java/com/cowlark/fluxengine/core/BUILD.bazel b/java/com/cowlark/fluxengine/core/BUILD.bazel new file mode 100644 index 00000000..205ba3dc --- /dev/null +++ b/java/com/cowlark/fluxengine/core/BUILD.bazel @@ -0,0 +1,8 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "core", + srcs = glob(["*.java"]), +) diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java new file mode 100644 index 00000000..77755551 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -0,0 +1,185 @@ +package com.cowlark.fluxengine.core; + +import java.nio.charset.StandardCharsets; + +/** + * A resizable byte container, ported from lib/core/bytes.h. Slices are + * read-only views over the shared backing storage. + */ +public final class Bytes +{ + private static final class Storage + { + byte[] data; + + Storage(int capacity) + { + data = new byte[capacity]; + } + } + + private Storage storage; + private int low; + private int high; + private boolean readOnly; + + public Bytes() + { + this(0); + } + + public Bytes(int size) + { + storage = new Storage(size); + low = 0; + high = size; + } + + public Bytes(byte[] data) + { + this(data.length); + System.arraycopy(data, 0, storage.data, 0, data.length); + } + + public Bytes(String data) + { + this(data.getBytes(StandardCharsets.UTF_8)); + } + + public static Bytes of(int... values) + { + byte[] data = new byte[values.length]; + for (int i = 0; i < values.length; i++) + data[i] = (byte) values[i]; + return new Bytes(data); + } + + private Bytes(Storage storage, int low, int high) + { + this.storage = storage; + this.low = low; + this.high = high; + readOnly = true; + } + + public int size() + { + return high - low; + } + + public boolean isEmpty() + { + return high == low; + } + + public byte get(int offset) + { + boundsCheck(offset); + return storage.data[low + offset]; + } + + public void set(int offset, byte value) + { + checkWritable(); + boundsCheck(offset); + storage.data[low + offset] = value; + } + + public byte[] toArray() + { + byte[] result = new byte[size()]; + System.arraycopy(storage.data, low, result, 0, result.length); + return result; + } + + public void resize(int newSize) + { + checkWritable(); + ensureCapacity(low + newSize); + high = low + newSize; + } + + public Bytes slice(int start, int len) + { + if (start < 0 || len < 0 || start + len > size()) + throw new IndexOutOfBoundsException(); + return new Bytes(storage, low + start, low + start + len); + } + + public Bytes concat(Bytes other) + { + Bytes result = new Bytes(size() + other.size()); + System.arraycopy(storage.data, low, result.storage.data, 0, size()); + System.arraycopy(other.storage.data, other.low, result.storage.data, + size(), other.size()); + return result; + } + + public Bytes repeat(int count) + { + Bytes result = new Bytes(size() * count); + for (int i = 0; i < count; i++) + System.arraycopy(storage.data, low, result.storage.data, i * size(), + size()); + return result; + } + + byte[] array() + { + return storage.data; + } + + @Override + public boolean equals(Object o) + { + if (!(o instanceof Bytes)) + return false; + Bytes other = (Bytes) o; + if (size() != other.size()) + return false; + for (int i = 0; i < size(); i++) + { + if (storage.data[low + i] != other.storage.data[other.low + i]) + return false; + } + return true; + } + + @Override + public int hashCode() + { + int hash = 1; + for (int i = 0; i < size(); i++) + hash = 31 * hash + storage.data[low + i]; + return hash; + } + + @Override + public String toString() + { + return String.format("Bytes(hash=%08x, readOnly=%s, size=%d)", + System.identityHashCode(this), readOnly, size()); + } + + private void checkWritable() + { + if (readOnly) + throw new UnsupportedOperationException("slice is read-only"); + } + + private void boundsCheck(int offset) + { + if (offset < 0 || offset >= size()) + throw new IndexOutOfBoundsException(String.valueOf(offset)); + } + + private void ensureCapacity(int capacity) + { + if (capacity <= storage.data.length) + return; + int newCapacity = Math.max(capacity, storage.data.length * 2); + byte[] newData = new byte[newCapacity]; + System.arraycopy(storage.data, 0, newData, 0, storage.data.length); + storage.data = newData; + } +} diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel new file mode 100644 index 00000000..0a1783bf --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "BytesTest", + srcs = ["BytesTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java new file mode 100644 index 00000000..ea25bb66 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -0,0 +1,68 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BytesTest +{ + @Test + public void boundsChecking() + { + Bytes bytes = Bytes.of(1, 2, 3); + + assertThrows(IndexOutOfBoundsException.class, () -> bytes.get(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> bytes.get(3)); + assertThrows(IndexOutOfBoundsException.class, () -> bytes.set(-1, (byte) 0)); + assertThrows(IndexOutOfBoundsException.class, () -> bytes.set(3, (byte) 0)); + assertThrows(IndexOutOfBoundsException.class, () -> bytes.slice(-1, 1)); + assertThrows(IndexOutOfBoundsException.class, () -> bytes.slice(0, -1)); + assertThrows(IndexOutOfBoundsException.class, () -> bytes.slice(1, 3)); + } + + @Test + public void resizing() + { + Bytes bytes = Bytes.of(1, 2, 3); + + bytes.resize(5); + assertThat(bytes.size()).isEqualTo(5); + assertThat(bytes.get(0) & 0xff).isEqualTo(1); + assertThat(bytes.get(2) & 0xff).isEqualTo(3); + assertThat(bytes.get(3) & 0xff).isEqualTo(0); + assertThat(bytes.get(4) & 0xff).isEqualTo(0); + + bytes.resize(1); + assertThat(bytes.size()).isEqualTo(1); + assertThat(bytes.get(0) & 0xff).isEqualTo(1); + + bytes.resize(0); + assertThat(bytes.size()).isEqualTo(0); + assertThat(bytes.isEmpty()).isTrue(); + } + + @Test + public void slicesAreReadOnlyViews() + { + Bytes parent = Bytes.of(10, 20, 30); + Bytes view = parent.slice(1, 2); + + /* A slice reads the shared data. */ + assertThat(view.size()).isEqualTo(2); + assertThat(view.get(0) & 0xff).isEqualTo(20); + assertThat(view.get(1) & 0xff).isEqualTo(30); + + /* ...and reflects later writes to the parent. */ + parent.set(1, (byte) 99); + assertThat(view.get(0) & 0xff).isEqualTo(99); + + /* But it cannot itself be mutated. */ + assertThrows(UnsupportedOperationException.class, () -> view.set(0, (byte) 1)); + assertThrows(UnsupportedOperationException.class, () -> view.resize(4)); + } +} From 3d68e6a334bd9cadd4c5c6cb153d8e89adbd90c6 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 01:27:07 +0200 Subject: [PATCH 049/192] Add ByteReader, ByteWriter. --- .../cowlark/fluxengine/core/ByteReader.java | 149 ++++++++++++++ .../cowlark/fluxengine/core/ByteWriter.java | 187 ++++++++++++++++++ .../com/cowlark/fluxengine/core/BUILD.bazel | 20 ++ .../fluxengine/core/ByteReaderTest.java | 106 ++++++++++ .../fluxengine/core/ByteWriterTest.java | 99 ++++++++++ 5 files changed, 561 insertions(+) create mode 100644 java/com/cowlark/fluxengine/core/ByteReader.java create mode 100644 java/com/cowlark/fluxengine/core/ByteWriter.java create mode 100644 javatests/com/cowlark/fluxengine/core/ByteReaderTest.java create mode 100644 javatests/com/cowlark/fluxengine/core/ByteWriterTest.java diff --git a/java/com/cowlark/fluxengine/core/ByteReader.java b/java/com/cowlark/fluxengine/core/ByteReader.java new file mode 100644 index 00000000..48a61d51 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/ByteReader.java @@ -0,0 +1,149 @@ +package com.cowlark.fluxengine.core; + +/** + * A cursor which reads values from a Bytes, ported from lib/core/bytes.h. + */ +public final class ByteReader +{ + private final Bytes bytes; + private int pos; + + public ByteReader(Bytes bytes) + { + this.bytes = bytes; + pos = 0; + } + + public int pos() + { + return pos; + } + + public ByteReader seek(int pos) + { + this.pos = pos; + return this; + } + + public ByteReader skip(int delta) + { + pos += delta; + return this; + } + + public boolean eof() + { + return pos >= bytes.size(); + } + + public int remaining() + { + return bytes.size() - pos; + } + + public Bytes read(int len) + { + checkReadable(len); + Bytes slice = bytes.slice(pos, len); + pos += len; + return slice; + } + + public int read8() + { + checkReadable(1); + return bytes.get(pos++) & 0xff; + } + + public int readBe16() + { + checkReadable(2); + int b1 = read8(); + int b2 = read8(); + return (b1 << 8) | b2; + } + + public int readLe16() + { + checkReadable(2); + int b1 = read8(); + int b2 = read8(); + return (b2 << 8) | b1; + } + + public int readBe24() + { + checkReadable(3); + int b1 = read8(); + int b2 = read8(); + int b3 = read8(); + return (b1 << 16) | (b2 << 8) | b3; + } + + public int readLe24() + { + checkReadable(3); + int b1 = read8(); + int b2 = read8(); + int b3 = read8(); + return (b3 << 16) | (b2 << 8) | b1; + } + + public int readBe32() + { + checkReadable(4); + int b1 = read8(); + int b2 = read8(); + int b3 = read8(); + int b4 = read8(); + return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4; + } + + public int readLe32() + { + checkReadable(4); + int b1 = read8(); + int b2 = read8(); + int b3 = read8(); + int b4 = read8(); + return (b4 << 24) | (b3 << 16) | (b2 << 8) | b1; + } + + public long readBe48() + { + checkReadable(6); + long hi = readBe16(); + long lo = readBe32() & 0xffffffffL; + return (hi << 32) | lo; + } + + public long readLe48() + { + checkReadable(6); + long lo = readLe32() & 0xffffffffL; + long hi = readLe16(); + return (hi << 32) | lo; + } + + public long readBe64() + { + checkReadable(8); + long hi = readBe32() & 0xffffffffL; + long lo = readBe32() & 0xffffffffL; + return (hi << 32) | lo; + } + + public long readLe64() + { + checkReadable(8); + long lo = readLe32() & 0xffffffffL; + long hi = readLe32() & 0xffffffffL; + return (hi << 32) | lo; + } + + private void checkReadable(int len) + { + if (len < 0 || pos + len > bytes.size()) + throw new IndexOutOfBoundsException(String.valueOf(pos)); + } +} diff --git a/java/com/cowlark/fluxengine/core/ByteWriter.java b/java/com/cowlark/fluxengine/core/ByteWriter.java new file mode 100644 index 00000000..622fcdfd --- /dev/null +++ b/java/com/cowlark/fluxengine/core/ByteWriter.java @@ -0,0 +1,187 @@ +package com.cowlark.fluxengine.core; + +/** + * A cursor which writes values into a Bytes, ported from lib/core/bytes.h. + */ +public final class ByteWriter +{ + private final Bytes bytes; + private int pos; + + public ByteWriter(Bytes bytes) + { + this.bytes = bytes; + pos = 0; + } + + public int pos() + { + return pos; + } + + public ByteWriter seek(int pos) + { + this.pos = pos; + return this; + } + + public ByteWriter seekToEnd() + { + pos = bytes.size(); + return this; + } + + public ByteWriter skip(int delta) + { + pos += delta; + return this; + } + + public ByteWriter write8(int value) + { + ensureWritable(1); + bytes.set(pos++, (byte) value); + return this; + } + + public ByteWriter writeBe16(int value) + { + ensureWritable(2); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) value); + return this; + } + + public ByteWriter writeLe16(int value) + { + ensureWritable(2); + bytes.set(pos++, (byte) value); + bytes.set(pos++, (byte) (value >> 8)); + return this; + } + + public ByteWriter writeBe24(int value) + { + ensureWritable(3); + bytes.set(pos++, (byte) (value >> 16)); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) value); + return this; + } + + public ByteWriter writeLe24(int value) + { + ensureWritable(3); + bytes.set(pos++, (byte) value); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) (value >> 16)); + return this; + } + + public ByteWriter writeBe32(int value) + { + ensureWritable(4); + bytes.set(pos++, (byte) (value >> 24)); + bytes.set(pos++, (byte) (value >> 16)); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) value); + return this; + } + + public ByteWriter writeLe32(int value) + { + ensureWritable(4); + bytes.set(pos++, (byte) value); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) (value >> 16)); + bytes.set(pos++, (byte) (value >> 24)); + return this; + } + + public ByteWriter writeBe48(long value) + { + ensureWritable(6); + bytes.set(pos++, (byte) (value >> 40)); + bytes.set(pos++, (byte) (value >> 32)); + bytes.set(pos++, (byte) (value >> 24)); + bytes.set(pos++, (byte) (value >> 16)); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) value); + return this; + } + + public ByteWriter writeLe48(long value) + { + ensureWritable(6); + bytes.set(pos++, (byte) value); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) (value >> 16)); + bytes.set(pos++, (byte) (value >> 24)); + bytes.set(pos++, (byte) (value >> 32)); + bytes.set(pos++, (byte) (value >> 40)); + return this; + } + + public ByteWriter writeBe64(long value) + { + ensureWritable(8); + bytes.set(pos++, (byte) (value >> 56)); + bytes.set(pos++, (byte) (value >> 48)); + bytes.set(pos++, (byte) (value >> 40)); + bytes.set(pos++, (byte) (value >> 32)); + bytes.set(pos++, (byte) (value >> 24)); + bytes.set(pos++, (byte) (value >> 16)); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) value); + return this; + } + + public ByteWriter writeLe64(long value) + { + ensureWritable(8); + bytes.set(pos++, (byte) value); + bytes.set(pos++, (byte) (value >> 8)); + bytes.set(pos++, (byte) (value >> 16)); + bytes.set(pos++, (byte) (value >> 24)); + bytes.set(pos++, (byte) (value >> 32)); + bytes.set(pos++, (byte) (value >> 40)); + bytes.set(pos++, (byte) (value >> 48)); + bytes.set(pos++, (byte) (value >> 56)); + return this; + } + + public ByteWriter write(Bytes data) + { + ensureWritable(data.size()); + for (int i = 0; i < data.size(); i++) + bytes.set(pos++, data.get(i)); + return this; + } + + public ByteWriter write(byte[] data) + { + ensureWritable(data.length); + for (byte b : data) + bytes.set(pos++, b); + return this; + } + + public ByteWriter pad(int count) + { + return pad(count, 0); + } + + public ByteWriter pad(int count, int value) + { + ensureWritable(count); + for (int i = 0; i < count; i++) + bytes.set(pos++, (byte) value); + return this; + } + + private void ensureWritable(int width) + { + if (pos + width > bytes.size()) + bytes.resize(pos + width); + } +} diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 0a1783bf..21228016 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -11,3 +11,23 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "ByteReaderTest", + srcs = ["ByteReaderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "ByteWriterTest", + srcs = ["ByteWriterTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/core/ByteReaderTest.java b/javatests/com/cowlark/fluxengine/core/ByteReaderTest.java new file mode 100644 index 00000000..c1131635 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/ByteReaderTest.java @@ -0,0 +1,106 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ByteReaderTest +{ + @Test + public void reads8And16() + { + ByteReader reader = new ByteReader(Bytes.of(0x01, 0x02, 0x03, 0x04, 0x05, 0x06)); + + assertThat(reader.read8()).isEqualTo(0x01); + assertThat(reader.readBe16()).isEqualTo(0x0203); + assertThat(reader.readLe16()).isEqualTo(0x0504); + assertThat(reader.read8()).isEqualTo(0x06); + assertThat(reader.eof()).isTrue(); + } + + @Test + public void reads24() + { + ByteReader reader = new ByteReader(Bytes.of(0x01, 0x02, 0x03, 0x04, 0x05, 0x06)); + + assertThat(reader.readBe24()).isEqualTo(0x010203); + assertThat(reader.readLe24()).isEqualTo(0x060504); + } + + @Test + public void reads32() + { + ByteReader reader = new ByteReader(Bytes.of( + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)); + + assertThat(reader.readBe32()).isEqualTo(0x01020304); + assertThat(reader.readLe32()).isEqualTo(0x08070605); + } + + @Test + public void reads48() + { + ByteReader reader = new ByteReader(Bytes.of( + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f)); + + assertThat(reader.readBe48()).isEqualTo(0x010203040506L); + assertThat(reader.readLe48()).isEqualTo(0x0f0e0d0c0b0aL); + } + + @Test + public void reads64() + { + ByteReader reader = new ByteReader(Bytes.of( + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10)); + + assertThat(reader.readBe64()).isEqualTo(0x0102030405060708L); + assertThat(reader.readLe64()).isEqualTo(0x100f0e0d0c0b0a09L); + } + + @Test + public void readSlice() + { + ByteReader reader = new ByteReader(Bytes.of(1, 2, 3, 4, 5)); + + Bytes slice = reader.read(2); + assertThat(slice.get(0) & 0xff).isEqualTo(1); + assertThat(slice.get(1) & 0xff).isEqualTo(2); + assertThat(reader.pos()).isEqualTo(2); + assertThat(reader.read8()).isEqualTo(3); + } + + @Test + public void seekSkipAndEof() + { + ByteReader reader = new ByteReader(Bytes.of(1, 2, 3)); + + assertThat(reader.pos()).isEqualTo(0); + assertThat(reader.remaining()).isEqualTo(3); + + assertThat(reader.skip(2).pos()).isEqualTo(2); + assertThat(reader.eof()).isFalse(); + assertThat(reader.remaining()).isEqualTo(1); + + assertThat(reader.skip(1).eof()).isTrue(); + assertThat(reader.seek(0).pos()).isEqualTo(0); + } + + @Test + public void boundsChecking() + { + ByteReader reader = new ByteReader(Bytes.of(1, 2, 3)); + reader.seek(3); + + assertThrows(IndexOutOfBoundsException.class, reader::read8); + assertThrows(IndexOutOfBoundsException.class, () -> reader.seek(2).readBe16()); + assertThrows(IndexOutOfBoundsException.class, () -> reader.seek(0).readBe32()); + assertThrows(IndexOutOfBoundsException.class, () -> reader.seek(0).read(4)); + } +} diff --git a/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java new file mode 100644 index 00000000..6cd6539f --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java @@ -0,0 +1,99 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ByteWriterTest +{ + @Test + public void writes8And16() + { + Bytes bytes = new Bytes(0); + new ByteWriter(bytes) + .write8(0x01) + .writeBe16(0x0203) + .writeLe16(0x0504) + .write8(0x06); + + assertThat(bytes.toArray()).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6}); + } + + @Test + public void writes24And32() + { + Bytes bytes = new Bytes(0); + new ByteWriter(bytes) + .writeBe24(0x010203) + .writeLe24(0x060504) + .writeBe32(0x0708090a) + .writeLe32(0x0e0d0c0b); + + assertThat(bytes.toArray()).isEqualTo(new byte[] { + 1, 2, 3, + 4, 5, 6, + 7, 8, 9, 10, + 11, 12, 13, 14}); + } + + @Test + public void writes48And64() + { + Bytes bytes = new Bytes(0); + new ByteWriter(bytes) + .writeBe48(0x010203040506L) + .writeLe48(0x0c0b0a090807L) + .writeBe64(0x0102030405060708L) + .writeLe64(0x100f0e0d0c0b0a09L); + + assertThat(bytes.toArray()).isEqualTo(new byte[] { + 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, + 1, 2, 3, 4, 5, 6, 7, 8, + 9, 10, 11, 12, 13, 14, 15, 16}); + } + + @Test + public void writesBytesAndPads() + { + Bytes bytes = new Bytes(0); + new ByteWriter(bytes) + .write(Bytes.of(1, 2)) + .write(new byte[] {3, 4}) + .pad(2, 0xff) + .pad(1); + + assertThat(bytes.toArray()).isEqualTo(new byte[] { + 1, 2, 3, 4, (byte) 0xff, (byte) 0xff, 0}); + } + + @Test + public void growsAndSeeks() + { + Bytes bytes = new Bytes(1); + bytes.set(0, (byte) 0xaa); + ByteWriter writer = new ByteWriter(bytes); + + writer.seekToEnd().write8(0x01); + assertThat(bytes.size()).isEqualTo(2); + assertThat(bytes.get(0) & 0xff).isEqualTo(0xaa); + assertThat(bytes.get(1) & 0xff).isEqualTo(0x01); + + writer.seek(0).write8(0x02); + assertThat(bytes.get(0) & 0xff).isEqualTo(0x02); + } + + @Test + public void writeToReadOnlySliceThrows() + { + Bytes slice = Bytes.of(1, 2, 3).slice(0, 3); + ByteWriter writer = new ByteWriter(slice); + + assertThrows(UnsupportedOperationException.class, () -> writer.write8(1)); + } +} From 22149411f02ec68a99c51ff3bb30b593850fbf4a Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 01:29:30 +0200 Subject: [PATCH 050/192] Use our own Bytes class with a nicer API. --- MODULE.bazel | 1 - java/com/cowlark/fluxengine/core/Bytes.java | 40 ++-- .../cowlark/fluxengine/external/BUILD.bazel | 2 +- .../external/GreaseweazleUtils.java | 85 ++++---- .../fluxengine/usb/AbstractUsbDevice.java | 7 +- java/com/cowlark/fluxengine/usb/BUILD.bazel | 2 +- .../fluxengine/usb/GreaseweazleDevice.java | 187 ++++++++---------- .../fluxengine/core/ByteWriterTest.java | 13 +- .../cowlark/fluxengine/core/BytesTest.java | 44 ++++- .../cowlark/fluxengine/external/BUILD.bazel | 2 +- .../external/GreaseweazleUtilsTest.java | 70 +++---- .../cowlark/fluxengine/testing/BUILD.bazel | 2 +- .../fluxengine/testing/TestHelpers.java | 10 +- 13 files changed, 241 insertions(+), 224 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 48cbb875..492160b3 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,7 +19,6 @@ maven.install( "com.google.truth:truth:1.4.5", "info.picocli:picocli:4.7.7", "info.picocli:picocli-codegen:4.7.7", - "io.netty:netty-buffer:4.2.16.Final", "javax.usb:usb-api:1.0.2", "junit:junit:4.13.2", "org.openjfx:javafx-controls:23.0.2", diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 77755551..6199f62d 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -3,25 +3,27 @@ import java.nio.charset.StandardCharsets; /** - * A resizable byte container, ported from lib/core/bytes.h. Slices are - * read-only views over the shared backing storage. + * A resizable byte container, ported from lib/core/bytes.h. Slices share the + * parent's storage; writes to a shared storage copy it first, so changes to + * one window are invisible to the others. */ public final class Bytes { private static final class Storage { byte[] data; + int refcount; Storage(int capacity) { data = new byte[capacity]; + refcount = 1; } } private Storage storage; private int low; private int high; - private boolean readOnly; public Bytes() { @@ -59,7 +61,7 @@ private Bytes(Storage storage, int low, int high) this.storage = storage; this.low = low; this.high = high; - readOnly = true; + storage.refcount++; } public int size() @@ -80,8 +82,8 @@ public byte get(int offset) public void set(int offset, byte value) { - checkWritable(); boundsCheck(offset); + detach(); storage.data[low + offset] = value; } @@ -94,7 +96,7 @@ public byte[] toArray() public void resize(int newSize) { - checkWritable(); + detach(); ensureCapacity(low + newSize); high = low + newSize; } @@ -129,6 +131,11 @@ byte[] array() return storage.data; } + int refcount() + { + return storage.refcount; + } + @Override public boolean equals(Object o) { @@ -157,14 +164,25 @@ public int hashCode() @Override public String toString() { - return String.format("Bytes(hash=%08x, readOnly=%s, size=%d)", - System.identityHashCode(this), readOnly, size()); + return String.format("Bytes(hash=%08x, refcount=%d, size=%d)", + System.identityHashCode(this), storage.refcount, size()); } - private void checkWritable() + /* Copy-on-write: if this window shares its storage with other windows, + * detach it into a private copy so mutations don't affect them. */ + private void detach() { - if (readOnly) - throw new UnsupportedOperationException("slice is read-only"); + if (storage.refcount > 1) + { + Storage old = storage; + int size = size(); + Storage fresh = new Storage(size); + System.arraycopy(old.data, low, fresh.data, 0, size); + storage = fresh; + low = 0; + high = size; + old.refcount--; + } } private void boundsCheck(int offset) diff --git a/java/com/cowlark/fluxengine/external/BUILD.bazel b/java/com/cowlark/fluxengine/external/BUILD.bazel index 985434bc..3e4f73ec 100644 --- a/java/com/cowlark/fluxengine/external/BUILD.bazel +++ b/java/com/cowlark/fluxengine/external/BUILD.bazel @@ -20,6 +20,6 @@ java_library( name = "external", srcs = glob(["*.java"]), deps = [ - "@maven//:io_netty_netty_buffer", + "//java/com/cowlark/fluxengine/core", ], ) diff --git a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java index 1c095b17..e97f76cd 100644 --- a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java +++ b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java @@ -4,8 +4,9 @@ import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; /** * Flux stream conversion helpers, ported from lib/external/greaseweazle.cc. @@ -51,69 +52,73 @@ private GreaseweazleUtils() { } - public static ByteBuf fluxEngineToGreaseweazle(ByteBuf fldata, double clock) + public static Bytes fluxEngineToGreaseweazle(Bytes fldata, double clock) { - ByteBuf out = Unpooled.buffer(); + Bytes out = new Bytes(0); + ByteWriter bw = new ByteWriter(out); + ByteReader br = new ByteReader(fldata); long ticksFl = 0; long ticksGw = 0; - while (fldata.isReadable()) + while (!br.eof()) { - int b = fldata.readUnsignedByte(); + int b = br.read8(); ticksFl += b & 0x3f; if ((b & F_BIT_PULSE) != 0) { long newTicksGw = (long) (ticksFl * NS_PER_TICK / clock); long delta = newTicksGw - ticksGw; if (delta < 250) - out.writeByte((int) delta); + bw.write8((int) delta); else { long high = (delta - 250) / 255; if (high < 5) { - out.writeByte((int) (250 + high)); - out.writeByte((int) (1 + (delta - 250) % 255)); + bw.write8((int) (250 + high)); + bw.write8((int) (1 + (delta - 250) % 255)); } else { - out.writeByte(255); - out.writeByte(FLUXOP_SPACE); - write28(out, delta - 249); - out.writeByte(249); + bw.write8(255); + bw.write8(FLUXOP_SPACE); + write28(bw, delta - 249); + bw.write8(249); } } ticksGw = newTicksGw; } } - out.writeByte(0); /* end of stream */ + bw.write8(0); /* end of stream */ return out; } - public static ByteBuf greaseweazleToFluxEngine(ByteBuf gwdata, double clock) + public static Bytes greaseweazleToFluxEngine(Bytes gwdata, double clock) { - ByteBuf out = Unpooled.buffer(); + Bytes out = new Bytes(0); + ByteWriter bw = new ByteWriter(out); + ByteReader br = new ByteReader(gwdata); long ticksGw = 0; long lastEventFl = 0; long indexGw = -1; - while (gwdata.isReadable()) + while (!br.eof()) { - int b = gwdata.readUnsignedByte(); + int b = br.read8(); if (b == 0) break; int event = 0; if (b == 255) { - switch (gwdata.readUnsignedByte()) + switch (br.read8()) { case FLUXOP_INDEX: - indexGw = ticksGw + read28(gwdata); + indexGw = ticksGw + read28(br); break; case FLUXOP_SPACE: - ticksGw += read28(gwdata); + ticksGw += read28(br); break; default: @@ -126,7 +131,7 @@ public static ByteBuf greaseweazleToFluxEngine(ByteBuf gwdata, double clock) ticksGw += b; else { - long delta = 250 + (b - 250) * 255 + gwdata.readUnsignedByte() - 1; + long delta = 250 + (b - 250) * 255 + br.read8() - 1; ticksGw += delta; } event = F_BIT_PULSE; @@ -143,10 +148,10 @@ public static ByteBuf greaseweazleToFluxEngine(ByteBuf gwdata, double clock) long deltaFl = indexFl - lastEventFl; while (deltaFl > 0x3f) { - out.writeByte(0x3f); + bw.write8(0x3f); deltaFl -= 0x3f; } - out.writeByte((int) (deltaFl | F_BIT_INDEX)); + bw.write8((int) (deltaFl | F_BIT_INDEX)); lastEventFl = indexFl; indexGw = -1; } @@ -157,10 +162,10 @@ else if (indexFl == ticksFl) long deltaFl = ticksFl - lastEventFl; while (deltaFl > 0x3f) { - out.writeByte(0x3f); + bw.write8(0x3f); deltaFl -= 0x3f; } - out.writeByte((int) (deltaFl | event)); + bw.write8((int) (deltaFl | event)); lastEventFl = ticksFl; } } @@ -170,29 +175,29 @@ else if (indexFl == ticksFl) /* Left-truncates at the first index mark, so the resulting data is aligned * at the index. */ - public static ByteBuf stripPartialRotation(ByteBuf fldata) + public static Bytes stripPartialRotation(Bytes fldata) { - for (int i = fldata.readerIndex(); i < fldata.writerIndex(); i++) + for (int i = 0; i < fldata.size(); i++) { - if ((fldata.getByte(i) & F_BIT_INDEX) != 0) - return fldata.slice(i, fldata.writerIndex() - i); + if ((fldata.get(i) & F_BIT_INDEX) != 0) + return fldata.slice(i, fldata.size() - i); } return fldata; } - private static void write28(ByteBuf out, long val) + private static void write28(ByteWriter out, long val) { - out.writeByte(1 | (int) (val << 1) & 0xff); - out.writeByte(1 | (int) (val >> 6) & 0xff); - out.writeByte(1 | (int) (val >> 13) & 0xff); - out.writeByte(1 | (int) (val >> 20) & 0xff); + out.write8(1 | (int) (val << 1) & 0xff); + out.write8(1 | (int) (val >> 6) & 0xff); + out.write8(1 | (int) (val >> 13) & 0xff); + out.write8(1 | (int) (val >> 20) & 0xff); } - private static long read28(ByteBuf in) + private static long read28(ByteReader in) { - return (long) ((in.readUnsignedByte() & 0xfe) >> 1) | - (long) (in.readUnsignedByte() & 0xfe) << 6 | - (long) (in.readUnsignedByte() & 0xfe) << 13 | - (long) (in.readUnsignedByte() & 0xfe) << 20; + return (long) ((in.read8() & 0xfe) >> 1) | + (long) (in.read8() & 0xfe) << 6 | + (long) (in.read8() & 0xfe) << 13 | + (long) (in.read8() & 0xfe) << 20; } } diff --git a/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java b/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java index 296b992a..effe045f 100644 --- a/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.usb; -import io.netty.buffer.ByteBuf; -import javax.inject.Inject; +import com.cowlark.fluxengine.core.Bytes; /** * Base class for USB floppy drive devices, ported from lib/usb/usb.h. @@ -21,10 +20,10 @@ public void recalibrate() public abstract void testBulkRead(); - public abstract ByteBuf read(int side, boolean synced, long readTime, + public abstract Bytes read(int side, boolean synced, long readTime, long hardSectorThreshold); - public abstract void write(int side, ByteBuf bytes, long hardSectorThreshold); + public abstract void write(int side, Bytes bytes, long hardSectorThreshold); public abstract void erase(int side, long hardSectorThreshold); diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index f28b0ab2..8651f06e 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -21,11 +21,11 @@ java_library( srcs = glob(["*.java"]), resources = ["//java:javax.usb.properties"], deps = [ + "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/external", ":usb_java_proto", "@maven//:com_fazecast_jSerialComm", "@maven//:com_google_guava_guava", - "@maven//:io_netty_netty_buffer", "@maven//:javax_inject_javax_inject", "@maven//:javax_usb_usb_api", "@maven//:org_usb4java_usb4java_javax", diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java index 3ee04b63..75835e1e 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java @@ -31,11 +31,14 @@ import static com.cowlark.fluxengine.external.GreaseweazleUtils.FLUXOP_SPACE; import static com.cowlark.fluxengine.external.GreaseweazleUtils.GETINFO_FIRMWARE; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.external.GreaseweazleUtils; import com.cowlark.fluxengine.usb.Usb.GreaseweazleProto; import com.fazecast.jSerialComm.SerialPort; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import com.google.common.util.concurrent.Uninterruptibles; +import java.time.Duration; /** * Greaseweazle floppy drive device, ported from lib/usb/greaseweazleusb.cc. @@ -76,7 +79,7 @@ else if (version == 22) /* Twiddle the baud rate, which indicates to the Greaseweazle that the * data stream has been reset. */ serial.setBaudRate(BAUD_CLEAR_COMMS); - sleep(100); + Uninterruptibles.sleepUninterruptibly(Duration.ofMillis(100)); serial.setBaudRate(BAUD_NORMAL); /* Configure the hardware. */ @@ -87,22 +90,22 @@ private int getVersion() { doCommand(CMD_GET_INFO, GETINFO_FIRMWARE); - ByteBuf response = Unpooled.wrappedBuffer(readBytes(32)); - response.readerIndex(4); - long freq = response.readUnsignedIntLE(); + ByteReader response = new ByteReader(readBytes(32)); + response.seek(4); + long freq = response.readLe32() & 0xffffffffL; clock = 1000000000L / freq; - response.readerIndex(0); - return response.readUnsignedShort(); + response.seek(0); + return response.readBe16(); } private long read28() { - ByteBuf buffer = Unpooled.wrappedBuffer(readBytes(4)); - return (long) ((buffer.readUnsignedByte() & 0xfe) >> 1) | - (long) (buffer.readUnsignedByte() & 0xfe) << 6 | - (long) (buffer.readUnsignedByte() & 0xfe) << 13 | - (long) (buffer.readUnsignedByte() & 0xfe) << 20; + ByteReader buffer = new ByteReader(readBytes(4)); + return (long) ((buffer.read8() & 0xfe) >> 1) | + (long) (buffer.read8() & 0xfe) << 6 | + (long) (buffer.read8() & 0xfe) << 13 | + (long) (buffer.read8() & 0xfe) << 20; } private void doCommand(int cmd, int... payload) @@ -115,27 +118,25 @@ private void doCommand(int cmd, int... payload) doCommand(command); } - private void doCommand(ByteBuf command) + private void doCommand(Bytes command) { - byte[] bytes = new byte[command.readableBytes()]; - command.getBytes(command.readerIndex(), bytes); - doCommand(bytes); + doCommand(command.toArray()); } private void doCommand(byte[] command) { writeBytes(command); - byte[] buffer = readBytes(2); + Bytes buffer = readBytes(2); - if ((buffer[0] & 0xff) != (command[0] & 0xff)) + if ((buffer.get(0) & 0xff) != (command[0] & 0xff)) throw new RuntimeException(String.format( "command returned garbage (0x%x != 0x%x with status 0x%x)", - buffer[0], + buffer.get(0), command[0], - buffer[1])); - if (buffer[1] != 0) - throw new RuntimeException("Greaseweazle error: " + gwError(buffer[1] & 0xff)); + buffer.get(1))); + if (buffer.get(1) != 0) + throw new RuntimeException("Greaseweazle error: " + gwError(buffer.get(1) & 0xff)); } @Override @@ -162,11 +163,12 @@ public long getRotationalPeriod(int hardSectorCount) case V24: case V29: { - ByteBuf cmd = Unpooled.buffer(8); - cmd.writeByte(CMD_READ_FLUX); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE(0); /* ticks default value (guessed) */ - cmd.writeShortLE(2); /* revolutions */ + Bytes cmd = new Bytes(0); + ByteWriter bw = new ByteWriter(cmd); + bw.write8(CMD_READ_FLUX); + bw.write8(8); + bw.writeLe32(0); /* ticks default value (guessed) */ + bw.writeLe16(2); /* revolutions */ doCommand(cmd); } } @@ -227,39 +229,35 @@ public void testBulkWrite() { System.out.print("Writing data: "); final int LEN = 10 * 1024 * 1024; - ByteBuf cmd; + Bytes cmd = new Bytes(0); + ByteWriter bw = new ByteWriter(cmd); switch (version) { case V22: case V24: - { - cmd = Unpooled.buffer(6); - cmd.writeByte(CMD_SINK_BYTES); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE(LEN); + bw.write8(CMD_SINK_BYTES); + bw.write8(6); + bw.writeLe32(LEN); break; - } case V29: - { - cmd = Unpooled.buffer(10); - cmd.writeByte(CMD_SINK_BYTES); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE(LEN); - cmd.writeIntLE(0); /* seed */ + bw.write8(CMD_SINK_BYTES); + bw.write8(10); + bw.writeLe32(LEN); + bw.writeLe32(0); /* seed */ break; - } default: throw new IllegalStateException(); } doCommand(cmd); - byte[] junk = new byte[LEN]; + Bytes junk = new Bytes(0); + ByteWriter jw = new ByteWriter(junk); long seed = 0; for (int i = 0; i < LEN; i++) { - junk[i] = (byte) seed; + jw.write8((int) seed); seed = ssRandNext(seed); } double startTime = getCurrentTime(); @@ -279,28 +277,23 @@ public void testBulkRead() { System.out.print("Reading data: "); final int LEN = 10 * 1024 * 1024; - ByteBuf cmd; + Bytes cmd = new Bytes(0); + ByteWriter bw = new ByteWriter(cmd); switch (version) { case V22: case V24: - { - cmd = Unpooled.buffer(6); - cmd.writeByte(CMD_SOURCE_BYTES); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE(LEN); + bw.write8(CMD_SOURCE_BYTES); + bw.write8(6); + bw.writeLe32(LEN); break; - } case V29: - { - cmd = Unpooled.buffer(10); - cmd.writeByte(CMD_SOURCE_BYTES); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE(LEN); - cmd.writeIntLE(0); /* seed */ + bw.write8(CMD_SOURCE_BYTES); + bw.write8(10); + bw.writeLe32(LEN); + bw.writeLe32(0); /* seed */ break; - } default: throw new IllegalStateException(); @@ -319,7 +312,7 @@ public void testBulkRead() } @Override - public ByteBuf read(int side, boolean synced, long readTime, long hardSectorThreshold) + public Bytes read(int side, boolean synced, long readTime, long hardSectorThreshold) { if (hardSectorThreshold != 0) throw new RuntimeException("hard sectors are currently unsupported on the " + @@ -332,10 +325,11 @@ public ByteBuf read(int side, boolean synced, long readTime, long hardSectorThre case V22: { long revs = (readTime + revolutions - 1) / revolutions; - ByteBuf cmd = Unpooled.buffer(4); - cmd.writeByte(CMD_READ_FLUX); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE((int) (revs + (synced ? 1 : 0))); + Bytes cmd = new Bytes(0); + ByteWriter bw = new ByteWriter(cmd); + bw.write8(CMD_READ_FLUX); + bw.write8(4); + bw.writeLe32((int) (revs + (synced ? 1 : 0))); doCommand(cmd); break; } @@ -343,34 +337,36 @@ public ByteBuf read(int side, boolean synced, long readTime, long hardSectorThre case V24: case V29: { - ByteBuf cmd = Unpooled.buffer(8); - cmd.writeByte(CMD_READ_FLUX); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE((int) ((readTime + (synced ? revolutions : 0)) / clock)); - cmd.writeShortLE(0); + Bytes cmd = new Bytes(0); + ByteWriter bw = new ByteWriter(cmd); + bw.write8(CMD_READ_FLUX); + bw.write8(8); + bw.writeLe32((int) ((readTime + (synced ? revolutions : 0)) / clock)); + bw.writeLe16(0); doCommand(cmd); } } - ByteBuf buffer = Unpooled.buffer(); + Bytes buffer = new Bytes(0); + ByteWriter bw = new ByteWriter(buffer); for (; ; ) { int b = readByte(); if (b == 0) break; - buffer.writeByte(b); + bw.write8(b); } doCommand(CMD_GET_FLUX_STATUS); - ByteBuf fldata = GreaseweazleUtils.greaseweazleToFluxEngine(buffer, clock); + Bytes fldata = GreaseweazleUtils.greaseweazleToFluxEngine(buffer, clock); if (synced) fldata = GreaseweazleUtils.stripPartialRotation(fldata); return fldata; } @Override - public void write(int side, ByteBuf fldata, long hardSectorThreshold) + public void write(int side, Bytes fldata, long hardSectorThreshold) { if (hardSectorThreshold != 0) throw new RuntimeException("hard sectors are currently unsupported on the " + @@ -388,7 +384,7 @@ public void write(int side, ByteBuf fldata, long hardSectorThreshold) doCommand(CMD_WRITE_FLUX, 1, 1); break; } - ByteBuf gwdata = GreaseweazleUtils.fluxEngineToGreaseweazle(fldata, clock); + Bytes gwdata = GreaseweazleUtils.fluxEngineToGreaseweazle(fldata, clock); writeBytes(gwdata); readByte(); /* synchronise */ @@ -404,10 +400,11 @@ public void erase(int side, long hardSectorThreshold) doCommand(CMD_HEAD, side); - ByteBuf cmd = Unpooled.buffer(6); - cmd.writeByte(CMD_ERASE_FLUX); - cmd.writeByte(cmd.capacity()); - cmd.writeIntLE((int) (200e6 / clock)); + Bytes cmd = new Bytes(0); + ByteWriter bw = new ByteWriter(cmd); + bw.write8(CMD_ERASE_FLUX); + bw.write8(6); + bw.writeLe32((int) (200e6 / clock)); doCommand(cmd); readByte(); /* synchronise */ @@ -468,21 +465,22 @@ private static long ssRandNext(long x) private int readByte() { - return readBytes(1)[0] & 0xff; + return readBytes(1).get(0) & 0xff; } - private byte[] readBytes(int count) + private Bytes readBytes(int count) { - byte[] result = new byte[count]; - int offset = 0; - while (offset < count) + Bytes result = new Bytes(0); + ByteWriter bw = new ByteWriter(result); + byte[] chunk = new byte[4096]; + while (bw.pos() < count) { - byte[] chunk = new byte[count - offset]; - int read = serial.readBytes(chunk, chunk.length); + int read = serial.readBytes(chunk, + Math.min(chunk.length, count - bw.pos())); if (read < 0) throw new RuntimeException("serial read failed"); - System.arraycopy(chunk, 0, result, offset, read); - offset += read; + for (int i = 0; i < read; i++) + bw.write8(chunk[i] & 0xff); } return result; } @@ -494,26 +492,13 @@ private void writeBytes(byte[] data) throw new RuntimeException("serial write failed"); } - private void writeBytes(ByteBuf data) + private void writeBytes(Bytes data) { - byte[] bytes = new byte[data.readableBytes()]; - data.getBytes(data.readerIndex(), bytes); - writeBytes(bytes); + writeBytes(data.toArray()); } private static double getCurrentTime() { return System.nanoTime() / 1e9; } - - private static void sleep(long ms) - { - try - { - Thread.sleep(ms); - } catch (InterruptedException e) - { - Thread.currentThread().interrupt(); - } - } } diff --git a/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java index 6cd6539f..5dc75e40 100644 --- a/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java +++ b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java @@ -2,8 +2,6 @@ import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -89,11 +87,14 @@ public void growsAndSeeks() } @Test - public void writeToReadOnlySliceThrows() + public void writingToASliceDetachesIt() { - Bytes slice = Bytes.of(1, 2, 3).slice(0, 3); - ByteWriter writer = new ByteWriter(slice); + Bytes parent = Bytes.of(1, 2, 3); + Bytes slice = parent.slice(0, 3); + + new ByteWriter(slice).write8(0xaa); - assertThrows(UnsupportedOperationException.class, () -> writer.write8(1)); + assertThat(slice.get(0) & 0xff).isEqualTo(0xaa); + assertThat(parent.get(0) & 0xff).isEqualTo(1); } } diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java index ea25bb66..b25e9801 100644 --- a/javatests/com/cowlark/fluxengine/core/BytesTest.java +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -47,22 +47,50 @@ public void resizing() } @Test - public void slicesAreReadOnlyViews() + public void slicesShareStorage() { Bytes parent = Bytes.of(10, 20, 30); Bytes view = parent.slice(1, 2); - /* A slice reads the shared data. */ assertThat(view.size()).isEqualTo(2); assertThat(view.get(0) & 0xff).isEqualTo(20); assertThat(view.get(1) & 0xff).isEqualTo(30); + assertThat(parent.refcount()).isEqualTo(2); + } + + @Test + public void copyOnWriteOnlyWhenShared() + { + /* Lone bytes: writes don't detach, so the refcount stays 1. */ + Bytes lone = Bytes.of(1, 2, 3); + lone.set(0, (byte) 9); + lone.resize(4); + assertThat(lone.refcount()).isEqualTo(1); + assertThat(lone.get(0) & 0xff).isEqualTo(9); + + /* Shared bytes: a write on the parent detaches it, leaving the view + * unchanged. */ + Bytes parent = Bytes.of(1, 2, 3); + Bytes view = parent.slice(0, 3); + parent.set(0, (byte) 9); + assertThat(parent.get(0) & 0xff).isEqualTo(9); + assertThat(view.get(0) & 0xff).isEqualTo(1); + assertThat(parent.refcount()).isEqualTo(1); - /* ...and reflects later writes to the parent. */ - parent.set(1, (byte) 99); - assertThat(view.get(0) & 0xff).isEqualTo(99); + /* And a write on the view detaches it, leaving the parent unchanged. */ + Bytes parent2 = Bytes.of(1, 2, 3); + Bytes view2 = parent2.slice(0, 3); + view2.set(2, (byte) 7); + assertThat(view2.get(2) & 0xff).isEqualTo(7); + assertThat(parent2.get(2) & 0xff).isEqualTo(3); + assertThat(view2.refcount()).isEqualTo(1); - /* But it cannot itself be mutated. */ - assertThrows(UnsupportedOperationException.class, () -> view.set(0, (byte) 1)); - assertThrows(UnsupportedOperationException.class, () -> view.resize(4)); + /* Resizing a shared window detaches it too. */ + Bytes parent3 = Bytes.of(1, 2, 3); + Bytes view3 = parent3.slice(0, 3); + parent3.resize(5); + assertThat(parent3.size()).isEqualTo(5); + assertThat(view3.size()).isEqualTo(3); + assertThat(view3.get(0) & 0xff).isEqualTo(1); } } diff --git a/javatests/com/cowlark/fluxengine/external/BUILD.bazel b/javatests/com/cowlark/fluxengine/external/BUILD.bazel index 0286706a..17250464 100644 --- a/javatests/com/cowlark/fluxengine/external/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/external/BUILD.bazel @@ -6,10 +6,10 @@ java_test( name = "GreaseweazleUtilsTest", srcs = ["GreaseweazleUtilsTest.java"], deps = [ + "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/external", "//javatests/com/cowlark/fluxengine/testing", "@maven//:com_google_truth_truth", - "@maven//:io_netty_netty_buffer", "@maven//:junit_junit", ], ) diff --git a/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java b/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java index f948e6ab..ca2e5d95 100644 --- a/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java +++ b/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java @@ -1,12 +1,9 @@ package com.cowlark.fluxengine.external; -import static com.cowlark.fluxengine.testing.TestHelpers.buf; import static com.google.common.truth.Truth.assertThat; -import com.cowlark.fluxengine.testing.TestHelpers; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufUtil; -import io.netty.buffer.Unpooled; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -16,23 +13,17 @@ public class GreaseweazleUtilsTest { private static final double CLOCK = 2 * FluxEngine.NS_PER_TICK; - private static void testConvert(ByteBuf gwBytes, ByteBuf flBytes) + private static void testConvert(Bytes gwBytes, Bytes flBytes) { - byte[] expectedFl = ByteBufUtil.getBytes(flBytes); - byte[] expectedGw = ByteBufUtil.getBytes(gwBytes); - - ByteBuf gwToFl = GreaseweazleUtils.greaseweazleToFluxEngine( - Unpooled.copiedBuffer(gwBytes), CLOCK); - ByteBuf flToGw = GreaseweazleUtils.fluxEngineToGreaseweazle( - Unpooled.copiedBuffer(flBytes), CLOCK); - - assertThat(ByteBufUtil.getBytes(gwToFl)).isEqualTo(expectedFl); - assertThat(ByteBufUtil.getBytes(flToGw)).isEqualTo(expectedGw); + assertThat(GreaseweazleUtils.greaseweazleToFluxEngine(gwBytes, CLOCK)) + .isEqualTo(flBytes); + assertThat(GreaseweazleUtils.fluxEngineToGreaseweazle(flBytes, CLOCK)) + .isEqualTo(gwBytes); } - private static ByteBuf encode28(int val) + private static Bytes encode28(int val) { - return buf(1 | (val << 1) & 0xff, + return Bytes.of(1 | (val << 1) & 0xff, 1 | (val >> 6) & 0xff, 1 | (val >> 13) & 0xff, 1 | (val >> 20) & 0xff); @@ -42,36 +33,35 @@ private static ByteBuf encode28(int val) public void conversions() { /* Simple one-byte intervals. */ - testConvert( - buf(1, 1, 1, 1, 0), - buf(0x82, 0x82, 0x82, 0x82)); + testConvert(Bytes.of(1, 1, 1, 1, 0), + Bytes.of(0x82, 0x82, 0x82, 0x82)); /* Larger one-byte intervals. */ - testConvert( - buf(32, 0), - buf(0x3f, 0x81)); - testConvert( - buf(64, 0), - buf(0x3f, 0x3f, 0x82)); - testConvert( - buf(128, 0), - buf(0x3f, 0x3f, 0x3f, 0x3f, 0x84)); + testConvert(Bytes.of(32, 0), + Bytes.of(0x3f, 0x81)); + testConvert(Bytes.of(64, 0), + Bytes.of(0x3f, 0x3f, 0x82)); + testConvert(Bytes.of(128, 0), + Bytes.of(0x3f, 0x3f, 0x3f, 0x3f, 0x84)); /* Two-byte intervals. */ - testConvert( - buf(250, 1, 0), - buf(0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xbb)); + testConvert(Bytes.of(250, 1, 0), + Bytes.of(0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xbb)); /* Very long intervals. */ - ByteBuf gw = Unpooled.buffer(8); - gw.writeBytes(buf(255, 2)); /* FLUXOP_SPACE */ - gw.writeBytes(encode28(2048 - 249)); - gw.writeBytes(buf(249, 0)); + Bytes gw = new Bytes(0); + new ByteWriter(gw) + .write8(255) + .write8(2) /* FLUXOP_SPACE */ + .write(encode28(2048 - 249)) + .write8(249) + .write8(0); - ByteBuf fl = Unpooled.buffer(66); + Bytes fl = new Bytes(0); + ByteWriter bw = new ByteWriter(fl); for (int i = 0; i < 65; i++) - fl.writeByte(0x3f); - fl.writeByte(0x81); + bw.write8(0x3f); + bw.write8(0x81); testConvert(gw, fl); } diff --git a/javatests/com/cowlark/fluxengine/testing/BUILD.bazel b/javatests/com/cowlark/fluxengine/testing/BUILD.bazel index ba155621..67449081 100644 --- a/javatests/com/cowlark/fluxengine/testing/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/testing/BUILD.bazel @@ -6,6 +6,6 @@ java_library( name = "testing", srcs = ["TestHelpers.java"], deps = [ - "@maven//:io_netty_netty_buffer", + "//java/com/cowlark/fluxengine/core", ], ) diff --git a/javatests/com/cowlark/fluxengine/testing/TestHelpers.java b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java index b8b66d1a..ace75878 100644 --- a/javatests/com/cowlark/fluxengine/testing/TestHelpers.java +++ b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java @@ -1,15 +1,7 @@ package com.cowlark.fluxengine.testing; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import com.cowlark.fluxengine.core.Bytes; public class TestHelpers { - public static ByteBuf buf(int... values) - { - byte[] bytes = new byte[values.length]; - for (int i = 0; i < values.length; i++) - bytes[i] = (byte) values[i]; - return Unpooled.wrappedBuffer(bytes); - } } From e8001c3d6f8ad210034a9d036d1a7936d04f12ef Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 16:38:21 +0200 Subject: [PATCH 051/192] Add BitReader/BitWriter. --- .../cowlark/fluxengine/core/BitReader.java | 32 +++++++++++ .../cowlark/fluxengine/core/BitWriter.java | 49 ++++++++++++++++ .../com/cowlark/fluxengine/core/BUILD.bazel | 20 +++++++ .../fluxengine/core/BitReaderTest.java | 56 +++++++++++++++++++ .../fluxengine/core/BitWriterTest.java | 41 ++++++++++++++ 5 files changed, 198 insertions(+) create mode 100644 java/com/cowlark/fluxengine/core/BitReader.java create mode 100644 java/com/cowlark/fluxengine/core/BitWriter.java create mode 100644 javatests/com/cowlark/fluxengine/core/BitReaderTest.java create mode 100644 javatests/com/cowlark/fluxengine/core/BitWriterTest.java diff --git a/java/com/cowlark/fluxengine/core/BitReader.java b/java/com/cowlark/fluxengine/core/BitReader.java new file mode 100644 index 00000000..4f21ad84 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/BitReader.java @@ -0,0 +1,32 @@ +package com.cowlark.fluxengine.core; + +/** + * A cursor which reads bits from a ByteReader. + */ +public final class BitReader +{ + private final ByteReader reader; + private int fifo; + private int bitcount; + + public BitReader(ByteReader reader) + { + this.reader = reader; + } + + public boolean get() + { + if (bitcount == 0) + fifo = reader.read8(); + + boolean bit = (fifo & 0x80) != 0; + fifo <<= 1; + bitcount = (bitcount + 1) & 7; + return bit; + } + + public boolean eof() + { + return bitcount == 0 && reader.eof(); + } +} diff --git a/java/com/cowlark/fluxengine/core/BitWriter.java b/java/com/cowlark/fluxengine/core/BitWriter.java new file mode 100644 index 00000000..fe0a1dd1 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/BitWriter.java @@ -0,0 +1,49 @@ +package com.cowlark.fluxengine.core; + +/** + * A cursor which packs bits into a ByteWriter. + */ +public final class BitWriter +{ + private final ByteWriter writer; + private int fifo; + private int bitcount; + + public BitWriter(ByteWriter writer) + { + this.writer = writer; + } + + public BitWriter push(int bits, int size) + { + bits <<= 32 - size; + + while (size-- != 0) + { + fifo = (fifo << 1) | (bits >>> 31); + bitcount++; + bits <<= 1; + if (bitcount == 8) + { + writer.write8(fifo); + bitcount = 0; + fifo = 0; + } + } + return this; + } + + public BitWriter push(boolean bit) + { + return push(bit ? 1 : 0, 1); + } + + public void flush() + { + if (bitcount != 0) + { + writer.write8(fifo); + bitcount = 0; + } + } +} diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 21228016..312c6c3d 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -31,3 +31,23 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "BitWriterTest", + srcs = ["BitWriterTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "BitReaderTest", + srcs = ["BitReaderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/core/BitReaderTest.java b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java new file mode 100644 index 00000000..9e369bf8 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java @@ -0,0 +1,56 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BitReaderTest +{ + @Test + public void readsBits() + { + Bytes bytes = Bytes.of(0xd6, 0xa0); /* 11010110 10100000 */ + BitReader reader = new BitReader(new ByteReader(bytes)); + + boolean[] expected = { + true, true, false, true, false, true, true, false, + true, false, true, false, false, false, false, false}; + for (boolean bit : expected) + assertThat(reader.get()).isEqualTo(bit); + assertThat(reader.eof()).isTrue(); + } + + @Test + public void roundTrip() + { + Bytes bytes = new Bytes(0); + new BitWriter(new ByteWriter(bytes)) + .push(0b11010110, 8) + .push(0b10101100, 8) + .flush(); + + BitReader reader = new BitReader(new ByteReader(bytes)); + boolean[] expected = { + true, true, false, true, false, true, true, false, + true, false, true, false, true, true, false, false}; + for (boolean bit : expected) + assertThat(reader.get()).isEqualTo(bit); + assertThat(reader.eof()).isTrue(); + } + + @Test + public void readingPastEndThrows() + { + Bytes bytes = Bytes.of(0x80); + BitReader reader = new BitReader(new ByteReader(bytes)); + for (int i = 0; i < 8; i++) + reader.get(); + + assertThrows(IndexOutOfBoundsException.class, reader::get); + } +} diff --git a/javatests/com/cowlark/fluxengine/core/BitWriterTest.java b/javatests/com/cowlark/fluxengine/core/BitWriterTest.java new file mode 100644 index 00000000..8148ba35 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/BitWriterTest.java @@ -0,0 +1,41 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BitWriterTest +{ + @Test + public void writesWholeByte() + { + Bytes bytes = new Bytes(0); + ByteWriter bw = new ByteWriter(bytes); + new BitWriter(bw).push(0b11010110, 8).flush(); + + assertThat(bytes.toArray()).isEqualTo(new byte[] {(byte) 0xd6}); + } + + @Test + public void packsAcrossBytes() + { + Bytes bytes = new Bytes(0); + ByteWriter bw = new ByteWriter(bytes); + new BitWriter(bw).push(0b11010110, 8).push(0b101, 3).flush(); + + assertThat(bytes.toArray()).isEqualTo(new byte[] {(byte) 0xd6, 0x05}); + } + + @Test + public void flushesPartialByte() + { + Bytes bytes = new Bytes(0); + ByteWriter bw = new ByteWriter(bytes); + new BitWriter(bw).push(0b101, 3).flush(); + + assertThat(bytes.toArray()).isEqualTo(new byte[] {0x05}); + } +} From fd66de2de7297e60791155158a6a6e6bd91d0311 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 16:41:15 +0200 Subject: [PATCH 052/192] Make various things Iterable/Iterators. --- .../cowlark/fluxengine/core/BitReader.java | 19 ++++++++++++++++++- .../cowlark/fluxengine/core/ByteReader.java | 19 ++++++++++++++++++- java/com/cowlark/fluxengine/core/Bytes.java | 8 +++++++- .../fluxengine/core/BitReaderTest.java | 16 ++++++++++++++++ .../cowlark/fluxengine/core/BytesTest.java | 13 ++++++++++++- 5 files changed, 71 insertions(+), 4 deletions(-) diff --git a/java/com/cowlark/fluxengine/core/BitReader.java b/java/com/cowlark/fluxengine/core/BitReader.java index 4f21ad84..8eb24bd6 100644 --- a/java/com/cowlark/fluxengine/core/BitReader.java +++ b/java/com/cowlark/fluxengine/core/BitReader.java @@ -1,9 +1,12 @@ package com.cowlark.fluxengine.core; +import java.util.Iterator; +import java.util.NoSuchElementException; + /** * A cursor which reads bits from a ByteReader. */ -public final class BitReader +public final class BitReader implements Iterator { private final ByteReader reader; private int fifo; @@ -29,4 +32,18 @@ public boolean eof() { return bitcount == 0 && reader.eof(); } + + @Override + public boolean hasNext() + { + return !eof(); + } + + @Override + public Boolean next() + { + if (!hasNext()) + throw new NoSuchElementException(); + return get(); + } } diff --git a/java/com/cowlark/fluxengine/core/ByteReader.java b/java/com/cowlark/fluxengine/core/ByteReader.java index 48a61d51..f66de432 100644 --- a/java/com/cowlark/fluxengine/core/ByteReader.java +++ b/java/com/cowlark/fluxengine/core/ByteReader.java @@ -1,9 +1,12 @@ package com.cowlark.fluxengine.core; +import java.util.Iterator; +import java.util.NoSuchElementException; + /** * A cursor which reads values from a Bytes, ported from lib/core/bytes.h. */ -public final class ByteReader +public final class ByteReader implements Iterator { private final Bytes bytes; private int pos; @@ -41,6 +44,20 @@ public int remaining() return bytes.size() - pos; } + @Override + public boolean hasNext() + { + return !eof(); + } + + @Override + public Byte next() + { + if (!hasNext()) + throw new NoSuchElementException(); + return (byte) read8(); + } + public Bytes read(int len) { checkReadable(len); diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 6199f62d..44f12e0b 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -7,7 +7,7 @@ * parent's storage; writes to a shared storage copy it first, so changes to * one window are invisible to the others. */ -public final class Bytes +public final class Bytes implements Iterable { private static final class Storage { @@ -136,6 +136,12 @@ int refcount() return storage.refcount; } + @Override + public ByteReader iterator() + { + return new ByteReader(this); + } + @Override public boolean equals(Object o) { diff --git a/javatests/com/cowlark/fluxengine/core/BitReaderTest.java b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java index 9e369bf8..2014d6dc 100644 --- a/javatests/com/cowlark/fluxengine/core/BitReaderTest.java +++ b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertThrows; +import java.util.Iterator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -53,4 +54,19 @@ public void readingPastEndThrows() assertThrows(IndexOutOfBoundsException.class, reader::get); } + + @Test + public void iteration() + { + Iterator iterator = new BitReader(new ByteReader(Bytes.of(0xd6))); + boolean[] expected = { + true, true, false, true, false, true, true, false}; + for (boolean bit : expected) + { + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isEqualTo(bit); + } + assertThat(iterator.hasNext()).isFalse(); + assertThrows(java.util.NoSuchElementException.class, iterator::next); + } } diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java index b25e9801..6d7d6ced 100644 --- a/javatests/com/cowlark/fluxengine/core/BytesTest.java +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -61,7 +61,6 @@ public void slicesShareStorage() @Test public void copyOnWriteOnlyWhenShared() { - /* Lone bytes: writes don't detach, so the refcount stays 1. */ Bytes lone = Bytes.of(1, 2, 3); lone.set(0, (byte) 9); lone.resize(4); @@ -93,4 +92,16 @@ public void copyOnWriteOnlyWhenShared() assertThat(view3.size()).isEqualTo(3); assertThat(view3.get(0) & 0xff).isEqualTo(1); } + + @Test + public void iteration() + { + Bytes bytes = Bytes.of(1, 2, 3); + int expected = 1; + for (Byte b : bytes) + { + assertThat(b.intValue()).isEqualTo(expected++); + } + assertThat(expected).isEqualTo(4); + } } From 1e96c789525ffaeb8ee4a9a4f41960a1c1a6a44f Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 16:53:09 +0200 Subject: [PATCH 053/192] Add some more Bytes methods. --- java/com/cowlark/fluxengine/core/BUILD.bazel | 3 + java/com/cowlark/fluxengine/core/Bytes.java | 57 ++++++++++++++++++- .../com/cowlark/fluxengine/core/BUILD.bazel | 1 + .../cowlark/fluxengine/core/BytesTest.java | 54 +++++++++++++++++- 4 files changed, 113 insertions(+), 2 deletions(-) diff --git a/java/com/cowlark/fluxengine/core/BUILD.bazel b/java/com/cowlark/fluxengine/core/BUILD.bazel index 205ba3dc..61f95064 100644 --- a/java/com/cowlark/fluxengine/core/BUILD.bazel +++ b/java/com/cowlark/fluxengine/core/BUILD.bazel @@ -5,4 +5,7 @@ package(default_visibility = ["//visibility:public"]) java_library( name = "core", srcs = glob(["*.java"]), + deps = [ + "@maven//:com_google_guava_guava", + ], ) diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 44f12e0b..9e0a5208 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.core; +import com.google.common.collect.ImmutableList; import java.nio.charset.StandardCharsets; /** @@ -103,11 +104,65 @@ public void resize(int newSize) public Bytes slice(int start, int len) { - if (start < 0 || len < 0 || start + len > size()) + if (start < 0 || len < 0) throw new IndexOutOfBoundsException(); + if (start >= size()) + return new Bytes(len); + int available = Math.min(len, size() - start); + if (available < len) + { + Bytes result = new Bytes(len); + System.arraycopy(storage.data, low + start, result.storage.data, 0, + available); + return result; + } return new Bytes(storage, low + start, low + start + len); } + public Bytes slice(int start) + { + int len = 0; + if (start < size()) + len = size() - start; + return slice(start, len); + } + + public void clear() + { + resize(0); + } + + public ImmutableList split(int separator) + { + ImmutableList.Builder pieces = ImmutableList.builder(); + int lastEnd = 0; + for (int i = 0; i < size(); i++) + { + if ((get(i) & 0xff) == separator) + { + pieces.add(slice(lastEnd, i - lastEnd)); + lastEnd = i + 1; + } + } + pieces.add(slice(lastEnd)); + return pieces.build(); + } + + public Bytes swab() + { + Bytes output = new Bytes(0); + ByteWriter bw = new ByteWriter(output); + ByteReader br = new ByteReader(this); + while (!br.eof()) + { + int a = br.read8(); + int b = br.eof() ? 0 : br.read8(); + bw.write8(b); + bw.write8(a); + } + return output; + } + public Bytes concat(Bytes other) { Bytes result = new Bytes(size() + other.size()); diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 312c6c3d..d7367e8b 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -7,6 +7,7 @@ java_test( srcs = ["BytesTest.java"], deps = [ "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_guava_guava", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java index 6d7d6ced..3eb39e6e 100644 --- a/javatests/com/cowlark/fluxengine/core/BytesTest.java +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertThrows; +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -22,7 +23,58 @@ public void boundsChecking() assertThrows(IndexOutOfBoundsException.class, () -> bytes.set(3, (byte) 0)); assertThrows(IndexOutOfBoundsException.class, () -> bytes.slice(-1, 1)); assertThrows(IndexOutOfBoundsException.class, () -> bytes.slice(0, -1)); - assertThrows(IndexOutOfBoundsException.class, () -> bytes.slice(1, 3)); + } + + @Test + public void sliceZeroPads() + { + Bytes bytes = Bytes.of(1, 2, 3); + + assertThat(bytes.slice(1, 3).toArray()).isEqualTo(new byte[] {2, 3, 0}); + assertThat(bytes.slice(5, 2).toArray()).isEqualTo(new byte[] {0, 0}); + assertThat(bytes.slice(3, 2).toArray()).isEqualTo(new byte[] {0, 0}); + assertThat(bytes.slice(2).toArray()).isEqualTo(new byte[] {3}); + assertThat(bytes.slice(5).isEmpty()).isTrue(); + } + + @Test + public void clear() + { + Bytes bytes = Bytes.of(1, 2, 3); + bytes.clear(); + assertThat(bytes.size()).isEqualTo(0); + assertThat(bytes.isEmpty()).isTrue(); + } + + @Test + public void split() + { + Bytes bytes = Bytes.of(1, 2, 0, 3, 4, 0, 5); + ImmutableList pieces = bytes.split(0); + + assertThat(pieces).hasSize(3); + assertThat(pieces.get(0).toArray()).isEqualTo(new byte[] {1, 2}); + assertThat(pieces.get(1).toArray()).isEqualTo(new byte[] {3, 4}); + assertThat(pieces.get(2).toArray()).isEqualTo(new byte[] {5}); + + /* Consecutive separators and a trailing separator yield empty pieces. */ + ImmutableList empties = Bytes.of(0, 1, 0, 0).split(0); + assertThat(empties).hasSize(4); + assertThat(empties.get(0).isEmpty()).isTrue(); + assertThat(empties.get(1).toArray()).isEqualTo(new byte[] {1}); + assertThat(empties.get(2).isEmpty()).isTrue(); + assertThat(empties.get(3).isEmpty()).isTrue(); + } + + @Test + public void swab() + { + assertThat(Bytes.of(1, 2, 3, 4).swab().toArray()) + .isEqualTo(new byte[] {2, 1, 4, 3}); + + /* Odd length pads the trailing byte with a zero. */ + assertThat(Bytes.of(1, 2, 3).swab().toArray()) + .isEqualTo(new byte[] {2, 1, 0, 3}); } @Test From bfa000f2e3588325e6188eb461d9c3608f722013 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 17:00:08 +0200 Subject: [PATCH 054/192] Add compress/decompress. --- java/com/cowlark/fluxengine/core/Bytes.java | 55 +++++++++++++++++++ .../cowlark/fluxengine/core/BytesTest.java | 24 ++++++++ 2 files changed, 79 insertions(+) diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 9e0a5208..73fcec15 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -1,7 +1,11 @@ package com.cowlark.fluxengine.core; import com.google.common.collect.ImmutableList; +import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; +import java.util.zip.DataFormatException; +import java.util.zip.Deflater; +import java.util.zip.Inflater; /** * A resizable byte container, ported from lib/core/bytes.h. Slices share the @@ -163,6 +167,57 @@ public Bytes swab() return output; } + /* Produces zlib-format (RFC 1950) data, compatible with the C++ zlib + * compress(). */ + public Bytes compress() + { + Deflater deflater = new Deflater(); + deflater.setInput(storage.data, low, size()); + deflater.finish(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + while (!deflater.finished()) + { + int n = deflater.deflate(buffer); + out.write(buffer, 0, n); + } + deflater.end(); + return new Bytes(out.toByteArray()); + } + + /* Consumes zlib-format (RFC 1950) data, compatible with the C++ zlib + * uncompress(). */ + public Bytes decompress() + { + Inflater inflater = new Inflater(); + inflater.setInput(storage.data, low, size()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[4096]; + try + { + while (true) + { + int n = inflater.inflate(buffer); + if (n > 0) + out.write(buffer, 0, n); + if (inflater.finished()) + break; + if (n == 0) + throw new RuntimeException("failed to decompress data"); + } + } + catch (DataFormatException e) + { + throw new RuntimeException( + "failed to decompress data: " + e.getMessage()); + } + finally + { + inflater.end(); + } + return new Bytes(out.toByteArray()); + } + public Bytes concat(Bytes other) { Bytes result = new Bytes(size() + other.size()); diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java index 3eb39e6e..7e5f0fa2 100644 --- a/javatests/com/cowlark/fluxengine/core/BytesTest.java +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -77,6 +77,30 @@ public void swab() .isEqualTo(new byte[] {2, 1, 0, 3}); } + @Test + public void compressAndDecompress() + { + Bytes data = new Bytes(0); + ByteWriter bw = new ByteWriter(data); + for (int i = 0; i < 10000; i++) + bw.write8(i & 0xff); + + Bytes compressed = data.compress(); + + /* zlib format: first byte is the CMF header (0x78 for deflate). */ + assertThat(compressed.get(0) & 0xff).isEqualTo(0x78); + assertThat(compressed.size()).isLessThan(data.size()); + + assertThat(compressed.decompress()).isEqualTo(data); + } + + @Test + public void compressAndDecompressEmpty() + { + Bytes data = new Bytes(0); + assertThat(data.compress().decompress()).isEqualTo(data); + } + @Test public void resizing() { From a925657afad33066437f18de864818b20d7d092c Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 17:26:49 +0200 Subject: [PATCH 055/192] Make Bytes now a List. --- .../cowlark/fluxengine/core/ByteReader.java | 2 +- .../cowlark/fluxengine/core/ByteWriter.java | 100 +++--- java/com/cowlark/fluxengine/core/Bytes.java | 292 +++++++++++++++++- .../external/GreaseweazleUtils.java | 2 +- .../fluxengine/usb/GreaseweazleDevice.java | 14 +- .../fluxengine/core/BitWriterTest.java | 6 +- .../fluxengine/core/ByteWriterTest.java | 8 +- .../cowlark/fluxengine/core/BytesTest.java | 61 +++- 8 files changed, 397 insertions(+), 88 deletions(-) diff --git a/java/com/cowlark/fluxengine/core/ByteReader.java b/java/com/cowlark/fluxengine/core/ByteReader.java index f66de432..96874c7b 100644 --- a/java/com/cowlark/fluxengine/core/ByteReader.java +++ b/java/com/cowlark/fluxengine/core/ByteReader.java @@ -69,7 +69,7 @@ public Bytes read(int len) public int read8() { checkReadable(1); - return bytes.get(pos++) & 0xff; + return bytes.getByte(pos++) & 0xff; } public int readBe16() diff --git a/java/com/cowlark/fluxengine/core/ByteWriter.java b/java/com/cowlark/fluxengine/core/ByteWriter.java index 622fcdfd..f5936633 100644 --- a/java/com/cowlark/fluxengine/core/ByteWriter.java +++ b/java/com/cowlark/fluxengine/core/ByteWriter.java @@ -40,113 +40,113 @@ public ByteWriter skip(int delta) public ByteWriter write8(int value) { ensureWritable(1); - bytes.set(pos++, (byte) value); + bytes.setByte(pos++, (byte) value); return this; } public ByteWriter writeBe16(int value) { ensureWritable(2); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) value); return this; } public ByteWriter writeLe16(int value) { ensureWritable(2); - bytes.set(pos++, (byte) value); - bytes.set(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 8)); return this; } public ByteWriter writeBe24(int value) { ensureWritable(3); - bytes.set(pos++, (byte) (value >> 16)); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) value); return this; } public ByteWriter writeLe24(int value) { ensureWritable(3); - bytes.set(pos++, (byte) value); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) (value >> 16)); return this; } public ByteWriter writeBe32(int value) { ensureWritable(4); - bytes.set(pos++, (byte) (value >> 24)); - bytes.set(pos++, (byte) (value >> 16)); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 24)); + bytes.setByte(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) value); return this; } public ByteWriter writeLe32(int value) { ensureWritable(4); - bytes.set(pos++, (byte) value); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) (value >> 16)); - bytes.set(pos++, (byte) (value >> 24)); + bytes.setByte(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) (value >> 24)); return this; } public ByteWriter writeBe48(long value) { ensureWritable(6); - bytes.set(pos++, (byte) (value >> 40)); - bytes.set(pos++, (byte) (value >> 32)); - bytes.set(pos++, (byte) (value >> 24)); - bytes.set(pos++, (byte) (value >> 16)); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 40)); + bytes.setByte(pos++, (byte) (value >> 32)); + bytes.setByte(pos++, (byte) (value >> 24)); + bytes.setByte(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) value); return this; } public ByteWriter writeLe48(long value) { ensureWritable(6); - bytes.set(pos++, (byte) value); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) (value >> 16)); - bytes.set(pos++, (byte) (value >> 24)); - bytes.set(pos++, (byte) (value >> 32)); - bytes.set(pos++, (byte) (value >> 40)); + bytes.setByte(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) (value >> 24)); + bytes.setByte(pos++, (byte) (value >> 32)); + bytes.setByte(pos++, (byte) (value >> 40)); return this; } public ByteWriter writeBe64(long value) { ensureWritable(8); - bytes.set(pos++, (byte) (value >> 56)); - bytes.set(pos++, (byte) (value >> 48)); - bytes.set(pos++, (byte) (value >> 40)); - bytes.set(pos++, (byte) (value >> 32)); - bytes.set(pos++, (byte) (value >> 24)); - bytes.set(pos++, (byte) (value >> 16)); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 56)); + bytes.setByte(pos++, (byte) (value >> 48)); + bytes.setByte(pos++, (byte) (value >> 40)); + bytes.setByte(pos++, (byte) (value >> 32)); + bytes.setByte(pos++, (byte) (value >> 24)); + bytes.setByte(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) value); return this; } public ByteWriter writeLe64(long value) { ensureWritable(8); - bytes.set(pos++, (byte) value); - bytes.set(pos++, (byte) (value >> 8)); - bytes.set(pos++, (byte) (value >> 16)); - bytes.set(pos++, (byte) (value >> 24)); - bytes.set(pos++, (byte) (value >> 32)); - bytes.set(pos++, (byte) (value >> 40)); - bytes.set(pos++, (byte) (value >> 48)); - bytes.set(pos++, (byte) (value >> 56)); + bytes.setByte(pos++, (byte) value); + bytes.setByte(pos++, (byte) (value >> 8)); + bytes.setByte(pos++, (byte) (value >> 16)); + bytes.setByte(pos++, (byte) (value >> 24)); + bytes.setByte(pos++, (byte) (value >> 32)); + bytes.setByte(pos++, (byte) (value >> 40)); + bytes.setByte(pos++, (byte) (value >> 48)); + bytes.setByte(pos++, (byte) (value >> 56)); return this; } @@ -154,7 +154,7 @@ public ByteWriter write(Bytes data) { ensureWritable(data.size()); for (int i = 0; i < data.size(); i++) - bytes.set(pos++, data.get(i)); + bytes.setByte(pos++, data.get(i)); return this; } @@ -162,7 +162,7 @@ public ByteWriter write(byte[] data) { ensureWritable(data.length); for (byte b : data) - bytes.set(pos++, b); + bytes.setByte(pos++, b); return this; } @@ -175,7 +175,7 @@ public ByteWriter pad(int count, int value) { ensureWritable(count); for (int i = 0; i < count; i++) - bytes.set(pos++, (byte) value); + bytes.setByte(pos++, (byte) value); return this; } diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 73fcec15..9958da87 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -3,6 +3,11 @@ import com.google.common.collect.ImmutableList; import java.io.ByteArrayOutputStream; import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.ListIterator; +import java.util.NoSuchElementException; import java.util.zip.DataFormatException; import java.util.zip.Deflater; import java.util.zip.Inflater; @@ -12,7 +17,7 @@ * parent's storage; writes to a shared storage copy it first, so changes to * one window are invisible to the others. */ -public final class Bytes implements Iterable +public final class Bytes implements List { private static final class Storage { @@ -79,26 +84,67 @@ public boolean isEmpty() return high == low; } - public byte get(int offset) + @Override + public Byte get(int offset) + { + return getByte(offset); + } + + /* Fast, allocation-free byte access for hot paths (avoids Byte boxing). */ + public byte getByte(int offset) { boundsCheck(offset); return storage.data[low + offset]; } - public void set(int offset, byte value) + @Override + public Byte set(int offset, Byte value) { boundsCheck(offset); detach(); + byte old = storage.data[low + offset]; storage.data[low + offset] = value; + return old; } - public byte[] toArray() + /* Fast, allocation-free byte write for hot paths (avoids Byte boxing). */ + public void setByte(int offset, byte value) + { + boundsCheck(offset); + detach(); + storage.data[low + offset] = value; + } + + public byte[] toByteArray() { byte[] result = new byte[size()]; System.arraycopy(storage.data, low, result, 0, result.length); return result; } + @Override + public Object[] toArray() + { + Object[] result = new Object[size()]; + for (int i = 0; i < size(); i++) + result[i] = getByte(i); + return result; + } + + @Override + @SuppressWarnings("unchecked") + public T[] toArray(T[] a) + { + int n = size(); + if (a.length < n) + a = (T[]) Arrays.copyOf(a, n, a.getClass()); + for (int i = 0; i < n; i++) + a[i] = (T) Byte.valueOf(getByte(i)); + if (a.length > n) + a[n] = null; + return a; + } + public void resize(int newSize) { detach(); @@ -142,7 +188,7 @@ public ImmutableList split(int separator) int lastEnd = 0; for (int i = 0; i < size(); i++) { - if ((get(i) & 0xff) == separator) + if ((getByte(i) & 0xff) == separator) { pieces.add(slice(lastEnd, i - lastEnd)); lastEnd = i + 1; @@ -253,21 +299,245 @@ public ByteReader iterator() } @Override - public boolean equals(Object o) + public boolean add(Byte value) { - if (!(o instanceof Bytes)) - return false; - Bytes other = (Bytes) o; - if (size() != other.size()) + detach(); + ensureCapacity(high + 1); + storage.data[high] = value; + high++; + return true; + } + + @Override + public void add(int index, Byte value) + { + if (index < 0 || index > size()) + throw new IndexOutOfBoundsException(String.valueOf(index)); + detach(); + ensureCapacity(high + 1); + System.arraycopy(storage.data, low + index, storage.data, low + index + 1, + size() - index); + storage.data[low + index] = value; + high++; + } + + @Override + public Byte remove(int index) + { + if (index < 0 || index >= size()) + throw new IndexOutOfBoundsException(String.valueOf(index)); + detach(); + byte old = storage.data[low + index]; + System.arraycopy(storage.data, low + index + 1, storage.data, + low + index, size() - index - 1); + high--; + return old; + } + + @Override + public boolean remove(Object o) + { + int index = indexOf(o); + if (index < 0) return false; + remove(index); + return true; + } + + @Override + public int indexOf(Object o) + { + if (!(o instanceof Byte)) + return -1; + byte target = (Byte) o; for (int i = 0; i < size(); i++) { - if (storage.data[low + i] != other.storage.data[other.low + i]) + if (storage.data[low + i] == target) + return i; + } + return -1; + } + + @Override + public int lastIndexOf(Object o) + { + if (!(o instanceof Byte)) + return -1; + byte target = (Byte) o; + for (int i = size() - 1; i >= 0; i--) + { + if (storage.data[low + i] == target) + return i; + } + return -1; + } + + @Override + public ListIterator listIterator() + { + return listIterator(0); + } + + @Override + public ListIterator listIterator(final int index) + { + if (index < 0 || index > size()) + throw new IndexOutOfBoundsException(String.valueOf(index)); + return new ListIterator() + { + private int cursor = index; + + @Override + public boolean hasNext() + { + return cursor < size(); + } + + @Override + public Byte next() + { + if (!hasNext()) + throw new NoSuchElementException(); + return get(cursor++); + } + + @Override + public boolean hasPrevious() + { + return cursor > 0; + } + + @Override + public Byte previous() + { + if (!hasPrevious()) + throw new NoSuchElementException(); + return get(--cursor); + } + + @Override + public int nextIndex() + { + return cursor; + } + + @Override + public int previousIndex() + { + return cursor - 1; + } + + @Override + public void remove() + { + throw new UnsupportedOperationException(); + } + + @Override + public void set(Byte value) + { + throw new UnsupportedOperationException(); + } + + @Override + public void add(Byte value) + { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public List subList(int fromIndex, int toIndex) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean contains(Object o) + { + return indexOf(o) >= 0; + } + + @Override + public boolean containsAll(Collection c) + { + for (Object o : c) + { + if (!contains(o)) return false; } return true; } + @Override + public boolean addAll(Collection c) + { + for (Byte b : c) + add(b); + return !c.isEmpty(); + } + + @Override + public boolean addAll(int index, Collection c) + { + if (c.isEmpty()) + return false; + for (Byte b : c) + add(index++, b); + return true; + } + + @Override + public boolean removeAll(Collection c) + { + boolean changed = false; + for (int i = size() - 1; i >= 0; i--) + { + if (c.contains(getByte(i))) + { + remove(i); + changed = true; + } + } + return changed; + } + + @Override + public boolean retainAll(Collection c) + { + boolean changed = false; + for (int i = size() - 1; i >= 0; i--) + { + if (!c.contains(getByte(i))) + { + remove(i); + changed = true; + } + } + return changed; + } + + @Override + public boolean equals(Object o) + { + if (o instanceof Bytes) + { + Bytes other = (Bytes) o; + if (size() != other.size()) + return false; + for (int i = 0; i < size(); i++) + { + if (storage.data[low + i] != other.storage.data[other.low + i]) + return false; + } + return true; + } + if (o instanceof List) + return o.equals(this); + return false; + } + @Override public int hashCode() { diff --git a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java index e97f76cd..bfec4b88 100644 --- a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java +++ b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java @@ -179,7 +179,7 @@ public static Bytes stripPartialRotation(Bytes fldata) { for (int i = 0; i < fldata.size(); i++) { - if ((fldata.get(i) & F_BIT_INDEX) != 0) + if ((fldata.getByte(i) & F_BIT_INDEX) != 0) return fldata.slice(i, fldata.size() - i); } return fldata; diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java index 75835e1e..f76302d4 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java @@ -120,7 +120,7 @@ private void doCommand(int cmd, int... payload) private void doCommand(Bytes command) { - doCommand(command.toArray()); + doCommand(command.toByteArray()); } private void doCommand(byte[] command) @@ -129,14 +129,14 @@ private void doCommand(byte[] command) Bytes buffer = readBytes(2); - if ((buffer.get(0) & 0xff) != (command[0] & 0xff)) + if ((buffer.getByte(0) & 0xff) != (command[0] & 0xff)) throw new RuntimeException(String.format( "command returned garbage (0x%x != 0x%x with status 0x%x)", - buffer.get(0), + buffer.getByte(0), command[0], - buffer.get(1))); - if (buffer.get(1) != 0) - throw new RuntimeException("Greaseweazle error: " + gwError(buffer.get(1) & 0xff)); + buffer.getByte(1))); + if (buffer.getByte(1) != 0) + throw new RuntimeException("Greaseweazle error: " + gwError(buffer.getByte(1) & 0xff)); } @Override @@ -494,7 +494,7 @@ private void writeBytes(byte[] data) private void writeBytes(Bytes data) { - writeBytes(data.toArray()); + writeBytes(data.toByteArray()); } private static double getCurrentTime() diff --git a/javatests/com/cowlark/fluxengine/core/BitWriterTest.java b/javatests/com/cowlark/fluxengine/core/BitWriterTest.java index 8148ba35..27321f6c 100644 --- a/javatests/com/cowlark/fluxengine/core/BitWriterTest.java +++ b/javatests/com/cowlark/fluxengine/core/BitWriterTest.java @@ -16,7 +16,7 @@ public void writesWholeByte() ByteWriter bw = new ByteWriter(bytes); new BitWriter(bw).push(0b11010110, 8).flush(); - assertThat(bytes.toArray()).isEqualTo(new byte[] {(byte) 0xd6}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {(byte) 0xd6}); } @Test @@ -26,7 +26,7 @@ public void packsAcrossBytes() ByteWriter bw = new ByteWriter(bytes); new BitWriter(bw).push(0b11010110, 8).push(0b101, 3).flush(); - assertThat(bytes.toArray()).isEqualTo(new byte[] {(byte) 0xd6, 0x05}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {(byte) 0xd6, 0x05}); } @Test @@ -36,6 +36,6 @@ public void flushesPartialByte() ByteWriter bw = new ByteWriter(bytes); new BitWriter(bw).push(0b101, 3).flush(); - assertThat(bytes.toArray()).isEqualTo(new byte[] {0x05}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {0x05}); } } diff --git a/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java index 5dc75e40..b251a681 100644 --- a/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java +++ b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java @@ -19,7 +19,7 @@ public void writes8And16() .writeLe16(0x0504) .write8(0x06); - assertThat(bytes.toArray()).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6}); } @Test @@ -32,7 +32,7 @@ public void writes24And32() .writeBe32(0x0708090a) .writeLe32(0x0e0d0c0b); - assertThat(bytes.toArray()).isEqualTo(new byte[] { + assertThat(bytes.toByteArray()).isEqualTo(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, @@ -49,7 +49,7 @@ public void writes48And64() .writeBe64(0x0102030405060708L) .writeLe64(0x100f0e0d0c0b0a09L); - assertThat(bytes.toArray()).isEqualTo(new byte[] { + assertThat(bytes.toByteArray()).isEqualTo(new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6, 7, 8, @@ -66,7 +66,7 @@ public void writesBytesAndPads() .pad(2, 0xff) .pad(1); - assertThat(bytes.toArray()).isEqualTo(new byte[] { + assertThat(bytes.toByteArray()).isEqualTo(new byte[] { 1, 2, 3, 4, (byte) 0xff, (byte) 0xff, 0}); } diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java index 7e5f0fa2..1ff186c0 100644 --- a/javatests/com/cowlark/fluxengine/core/BytesTest.java +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -5,6 +5,7 @@ import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; +import java.util.ListIterator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -30,10 +31,10 @@ public void sliceZeroPads() { Bytes bytes = Bytes.of(1, 2, 3); - assertThat(bytes.slice(1, 3).toArray()).isEqualTo(new byte[] {2, 3, 0}); - assertThat(bytes.slice(5, 2).toArray()).isEqualTo(new byte[] {0, 0}); - assertThat(bytes.slice(3, 2).toArray()).isEqualTo(new byte[] {0, 0}); - assertThat(bytes.slice(2).toArray()).isEqualTo(new byte[] {3}); + assertThat(bytes.slice(1, 3).toByteArray()).isEqualTo(new byte[] {2, 3, 0}); + assertThat(bytes.slice(5, 2).toByteArray()).isEqualTo(new byte[] {0, 0}); + assertThat(bytes.slice(3, 2).toByteArray()).isEqualTo(new byte[] {0, 0}); + assertThat(bytes.slice(2).toByteArray()).isEqualTo(new byte[] {3}); assertThat(bytes.slice(5).isEmpty()).isTrue(); } @@ -53,15 +54,15 @@ public void split() ImmutableList pieces = bytes.split(0); assertThat(pieces).hasSize(3); - assertThat(pieces.get(0).toArray()).isEqualTo(new byte[] {1, 2}); - assertThat(pieces.get(1).toArray()).isEqualTo(new byte[] {3, 4}); - assertThat(pieces.get(2).toArray()).isEqualTo(new byte[] {5}); + assertThat(pieces.get(0).toByteArray()).isEqualTo(new byte[] {1, 2}); + assertThat(pieces.get(1).toByteArray()).isEqualTo(new byte[] {3, 4}); + assertThat(pieces.get(2).toByteArray()).isEqualTo(new byte[] {5}); /* Consecutive separators and a trailing separator yield empty pieces. */ ImmutableList empties = Bytes.of(0, 1, 0, 0).split(0); assertThat(empties).hasSize(4); assertThat(empties.get(0).isEmpty()).isTrue(); - assertThat(empties.get(1).toArray()).isEqualTo(new byte[] {1}); + assertThat(empties.get(1).toByteArray()).isEqualTo(new byte[] {1}); assertThat(empties.get(2).isEmpty()).isTrue(); assertThat(empties.get(3).isEmpty()).isTrue(); } @@ -69,11 +70,11 @@ public void split() @Test public void swab() { - assertThat(Bytes.of(1, 2, 3, 4).swab().toArray()) + assertThat(Bytes.of(1, 2, 3, 4).swab().toByteArray()) .isEqualTo(new byte[] {2, 1, 4, 3}); /* Odd length pads the trailing byte with a zero. */ - assertThat(Bytes.of(1, 2, 3).swab().toArray()) + assertThat(Bytes.of(1, 2, 3).swab().toByteArray()) .isEqualTo(new byte[] {2, 1, 0, 3}); } @@ -95,12 +96,50 @@ public void compressAndDecompress() } @Test - public void compressAndDecompressEmpty() + public void compressAndUncompressEmpty() { Bytes data = new Bytes(0); assertThat(data.compress().decompress()).isEqualTo(data); } + @Test + public void listOperations() + { + Bytes bytes = Bytes.of(1, 2, 3); + + assertThat(bytes.contains(Byte.valueOf((byte) 2))).isTrue(); + assertThat(bytes.indexOf(Byte.valueOf((byte) 2))).isEqualTo(1); + assertThat(bytes.lastIndexOf(Byte.valueOf((byte) 2))).isEqualTo(1); + assertThat(bytes.indexOf(Byte.valueOf((byte) 9))).isEqualTo(-1); + + bytes.add(Byte.valueOf((byte) 4)); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {1, 2, 3, 4}); + + bytes.add(1, Byte.valueOf((byte) 9)); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {1, 9, 2, 3, 4}); + + assertThat(bytes.remove(0)).isEqualTo((byte) 1); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {9, 2, 3, 4}); + + assertThat(bytes.remove(Byte.valueOf((byte) 3))).isTrue(); + assertThat(bytes.toByteArray()).isEqualTo(new byte[] {9, 2, 4}); + + ListIterator it = bytes.listIterator(); + assertThat(it.next()).isEqualTo((byte) 9); + assertThat(it.next()).isEqualTo((byte) 2); + assertThat(it.previous()).isEqualTo((byte) 2); + } + + @Test + public void listEquality() + { + Bytes bytes = Bytes.of(1, 2, 3); + java.util.List other = java.util.Arrays.asList((byte) 1, (byte) 2, (byte) 3); + + assertThat(bytes.equals(other)).isTrue(); + assertThat(other.equals(bytes)).isTrue(); + } + @Test public void resizing() { From 5a57702856d758b0c3cc20b2260ce3ef82915f98 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 20:00:25 +0200 Subject: [PATCH 056/192] Do Bits. --- .../cowlark/fluxengine/core/BitReader.java | 9 ++ java/com/cowlark/fluxengine/core/Bits.java | 126 ++++++++++++++++ java/com/cowlark/fluxengine/core/Bytes.java | 20 +++ .../com/cowlark/fluxengine/core/BUILD.bazel | 10 ++ .../fluxengine/core/BitReaderTest.java | 14 ++ .../com/cowlark/fluxengine/core/BitsTest.java | 141 ++++++++++++++++++ .../cowlark/fluxengine/core/BytesTest.java | 16 ++ 7 files changed, 336 insertions(+) create mode 100644 java/com/cowlark/fluxengine/core/Bits.java create mode 100644 javatests/com/cowlark/fluxengine/core/BitsTest.java diff --git a/java/com/cowlark/fluxengine/core/BitReader.java b/java/com/cowlark/fluxengine/core/BitReader.java index 8eb24bd6..fe3bae50 100644 --- a/java/com/cowlark/fluxengine/core/BitReader.java +++ b/java/com/cowlark/fluxengine/core/BitReader.java @@ -33,6 +33,15 @@ public boolean eof() return bitcount == 0 && reader.eof(); } + /* Reads `count` bits into a fresh Bits. */ + public Bits get(int count) + { + Bits bits = new Bits(count); + for (int i = 0; i < count; i++) + bits.setBit(i, get()); + return bits; + } + @Override public boolean hasNext() { diff --git a/java/com/cowlark/fluxengine/core/Bits.java b/java/com/cowlark/fluxengine/core/Bits.java new file mode 100644 index 00000000..acd66d61 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/Bits.java @@ -0,0 +1,126 @@ +package com.cowlark.fluxengine.core; + +import java.util.AbstractList; +import java.util.BitSet; + +/** + * A packed list of booleans backed by a java.util.BitSet, the Java equivalent + * of std::vector. The logical size is tracked separately, so trailing + * falses are part of the list. + */ +public final class Bits extends AbstractList +{ + private final BitSet bits = new BitSet(); + private int size; + + public Bits() + { + } + + public Bits(int size) + { + this.size = size; + } + + @Override + public int size() + { + return size; + } + + @Override + public Boolean get(int index) + { + return getBit(index); + } + + /* Fast, allocation-free bit access for hot paths. */ + public boolean getBit(int index) + { + checkIndex(index); + return bits.get(index); + } + + @Override + public Boolean set(int index, Boolean value) + { + checkIndex(index); + boolean old = bits.get(index); + bits.set(index, value); + return old; + } + + /* Fast, allocation-free bit write for hot paths. */ + public void setBit(int index, boolean value) + { + checkIndex(index); + bits.set(index, value); + } + + @Override + public boolean add(Boolean value) + { + bits.set(size, value); + size++; + modCount++; + return true; + } + + @Override + public void add(int index, Boolean value) + { + if (index < 0 || index > size) + throw new IndexOutOfBoundsException(String.valueOf(index)); + for (int i = size; i > index; i--) + bits.set(i, bits.get(i - 1)); + bits.set(index, value); + size++; + modCount++; + } + + @Override + public Boolean remove(int index) + { + throw new UnsupportedOperationException(); + } + + @Override + public boolean remove(Object o) + { + throw new UnsupportedOperationException(); + } + + @Override + public void clear() + { + bits.clear(); + size = 0; + modCount++; + } + + /* Returns a new Bits with the bits in reverse order. */ + public Bits reverseBits() + { + Bits result = new Bits(size); + for (int i = 0; i < size; i++) + result.setBit(size - 1 - i, getBit(i)); + return result; + } + + /* Packs the bits MSB-first into a Bytes (the inverse of Bytes.toBits). */ + public Bytes toBytes() + { + Bytes bytes = new Bytes(0); + BitWriter bitw = new BitWriter(new ByteWriter(bytes)); + for (int i = 0; i < size; i++) + bitw.push(getBit(i)); + bitw.flush(); + return bytes; + } + + private void checkIndex(int index) + { + if (index < 0 || index >= size) + throw new IndexOutOfBoundsException(String.valueOf(index)); + } +} diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 9958da87..2b64ac96 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -213,6 +213,26 @@ public Bytes swab() return output; } + /* Extracts the bytes as bits, MSB-first within each byte. */ + public Bits toBits() + { + Bits bits = new Bits(size() * 8); + int bit = 0; + for (int i = 0; i < size(); i++) + { + int b = getByte(i) & 0xff; + bits.setBit(bit++, (b & 0x80) != 0); + bits.setBit(bit++, (b & 0x40) != 0); + bits.setBit(bit++, (b & 0x20) != 0); + bits.setBit(bit++, (b & 0x10) != 0); + bits.setBit(bit++, (b & 0x08) != 0); + bits.setBit(bit++, (b & 0x04) != 0); + bits.setBit(bit++, (b & 0x02) != 0); + bits.setBit(bit++, (b & 0x01) != 0); + } + return bits; + } + /* Produces zlib-format (RFC 1950) data, compatible with the C++ zlib * compress(). */ public Bytes compress() diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index d7367e8b..198af61c 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -52,3 +52,13 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "BitsTest", + srcs = ["BitsTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/core/BitReaderTest.java b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java index 2014d6dc..db77980b 100644 --- a/javatests/com/cowlark/fluxengine/core/BitReaderTest.java +++ b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java @@ -69,4 +69,18 @@ public void iteration() assertThat(iterator.hasNext()).isFalse(); assertThrows(java.util.NoSuchElementException.class, iterator::next); } + + @Test + public void get() + { + BitReader reader = new BitReader(new ByteReader(Bytes.of(0xd6))); + + Bits bits = reader.get(5); + assertThat(bits.size()).isEqualTo(5); + assertThat(bits.get(0)).isTrue(); + assertThat(bits.get(1)).isTrue(); + assertThat(bits.get(2)).isFalse(); + assertThat(bits.get(3)).isTrue(); + assertThat(bits.get(4)).isFalse(); + } } diff --git a/javatests/com/cowlark/fluxengine/core/BitsTest.java b/javatests/com/cowlark/fluxengine/core/BitsTest.java new file mode 100644 index 00000000..491c23ef --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/BitsTest.java @@ -0,0 +1,141 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class BitsTest +{ + @Test + public void basicGetSet() + { + Bits bits = new Bits(5); + assertThat(bits.size()).isEqualTo(5); + assertThat(bits.get(0)).isFalse(); + assertThat(bits.get(4)).isFalse(); + + assertThat(bits.set(2, true)).isFalse(); + bits.setBit(4, true); + + assertThat(bits.get(2)).isTrue(); + assertThat(bits.getBit(4)).isTrue(); + assertThat(bits.get(0)).isFalse(); + assertThat(bits.set(2, false)).isTrue(); + } + + @Test + public void add() + { + Bits bits = new Bits(0); + bits.add(true); + bits.add(false); + bits.add(true); + + assertThat(bits.size()).isEqualTo(3); + assertThat(bits.get(0)).isTrue(); + assertThat(bits.get(1)).isFalse(); + assertThat(bits.get(2)).isTrue(); + } + + @Test + public void insert() + { + Bits bits = new Bits(3); + bits.add(1, true); + + assertThat(bits.size()).isEqualTo(4); + assertThat(bits.get(0)).isFalse(); + assertThat(bits.get(1)).isTrue(); + assertThat(bits.get(2)).isFalse(); + assertThat(bits.get(3)).isFalse(); + } + + @Test + public void removeThrows() + { + Bits bits = new Bits(2); + + assertThrows(UnsupportedOperationException.class, () -> bits.remove(0)); + assertThrows(UnsupportedOperationException.class, () -> bits.remove(Boolean.TRUE)); + } + + @Test + public void clear() + { + Bits bits = new Bits(4); + bits.set(1, true); + bits.clear(); + assertThat(bits.size()).isEqualTo(0); + } + + @Test + public void iteration() + { + Bits bits = new Bits(0); + bits.add(true); + bits.add(false); + bits.add(true); + + java.util.Iterator it = bits.iterator(); + assertThat(it.next()).isTrue(); + assertThat(it.next()).isFalse(); + assertThat(it.next()).isTrue(); + assertThat(it.hasNext()).isFalse(); + } + + @Test + public void listEquality() + { + Bits bits = new Bits(0); + bits.add(true); + bits.add(false); + + List other = java.util.Arrays.asList(true, false); + assertThat(bits.equals(other)).isTrue(); + assertThat(other.equals(bits)).isTrue(); + } + + @Test + public void boundsChecking() + { + Bits bits = new Bits(2); + + assertThrows(IndexOutOfBoundsException.class, () -> bits.get(-1)); + assertThrows(IndexOutOfBoundsException.class, () -> bits.get(2)); + assertThrows(IndexOutOfBoundsException.class, () -> bits.set(2, true)); + } + + @Test + public void reverseBits() + { + Bits bits = new Bits(0); + bits.add(true); + bits.add(false); + bits.add(true); + bits.add(false); + + Bits reversed = bits.reverseBits(); + assertThat(reversed.size()).isEqualTo(4); + assertThat(reversed.get(0)).isFalse(); + assertThat(reversed.get(1)).isTrue(); + assertThat(reversed.get(2)).isFalse(); + assertThat(reversed.get(3)).isTrue(); + + /* The original is unchanged. */ + assertThat(bits.get(0)).isTrue(); + assertThat(bits.get(2)).isTrue(); + } + + @Test + public void toBytesRoundTrip() + { + Bytes bytes = Bytes.of(0xd6, 0xa5); + assertThat(bytes.toBits().toBytes()).isEqualTo(bytes); + } +} diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java index 1ff186c0..75172c43 100644 --- a/javatests/com/cowlark/fluxengine/core/BytesTest.java +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -78,6 +78,22 @@ public void swab() .isEqualTo(new byte[] {2, 1, 0, 3}); } + @Test + public void toBits() + { + Bits bits = Bytes.of(0xd6).toBits(); + + assertThat(bits.size()).isEqualTo(8); + assertThat(bits.get(0)).isTrue(); + assertThat(bits.get(1)).isTrue(); + assertThat(bits.get(2)).isFalse(); + assertThat(bits.get(3)).isTrue(); + assertThat(bits.get(4)).isFalse(); + assertThat(bits.get(5)).isTrue(); + assertThat(bits.get(6)).isTrue(); + assertThat(bits.get(7)).isFalse(); + } + @Test public void compressAndDecompress() { From a2ecffb970a714471197c0da918d10043c177eab Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 20:15:07 +0200 Subject: [PATCH 057/192] Change proto include paths. --- java/com/cowlark/fluxengine/config/BUILD.bazel | 5 +++-- java/com/cowlark/fluxengine/config/config.proto | 8 ++++---- java/com/cowlark/fluxengine/config/drive.proto | 4 ++-- java/com/cowlark/fluxengine/config/layout.proto | 4 ++-- java/com/cowlark/fluxengine/external/BUILD.bazel | 2 +- java/com/cowlark/fluxengine/usb/BUILD.bazel | 2 +- java/com/cowlark/fluxengine/usb/usb.proto | 2 +- 7 files changed, 14 insertions(+), 13 deletions(-) diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 5334840f..1284c51c 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -6,7 +6,7 @@ package(default_visibility = ["//visibility:public"]) proto_library( name = "common_proto", srcs = ["common.proto"], - strip_import_prefix = "/java/com/cowlark/fluxengine/config/", + strip_import_prefix = "/java/", deps = ["@com_google_protobuf//:descriptor_proto"], ) @@ -18,6 +18,7 @@ java_proto_library( proto_library( name = "layout_proto", srcs = ["layout.proto"], + strip_import_prefix = "/java/", deps = [ ":common_proto", "//java/com/cowlark/fluxengine/external:fl2_proto", @@ -32,7 +33,7 @@ java_proto_library( proto_library( name = "drive_proto", srcs = ["drive.proto"], - strip_import_prefix = "/java/com/cowlark/fluxengine/config/", + strip_import_prefix = "/java/", deps = [ ":common_proto", "//java/com/cowlark/fluxengine/external:fl2_proto", diff --git a/java/com/cowlark/fluxengine/config/config.proto b/java/com/cowlark/fluxengine/config/config.proto index 3635f275..60351f35 100644 --- a/java/com/cowlark/fluxengine/config/config.proto +++ b/java/com/cowlark/fluxengine/config/config.proto @@ -6,11 +6,11 @@ import "lib/imagereader/imagereader.proto"; import "lib/imagewriter/imagewriter.proto"; import "lib/fluxsource/fluxsource.proto"; import "lib/fluxsink/fluxsink.proto"; -import "lib/usb/usb.proto"; +import "com/cowlark/fluxengine/usb/usb.proto"; import "lib/vfs/vfs.proto"; -import "lib/config/drive.proto"; -import "lib/config/common.proto"; -import "lib/config/layout.proto"; +import "com/cowlark/fluxengine/config/drive.proto"; +import "com/cowlark/fluxengine/config/common.proto"; +import "com/cowlark/fluxengine/config/layout.proto"; enum SupportStatus { diff --git a/java/com/cowlark/fluxengine/config/drive.proto b/java/com/cowlark/fluxengine/config/drive.proto index 7bf4af78..72b71dd0 100644 --- a/java/com/cowlark/fluxengine/config/drive.proto +++ b/java/com/cowlark/fluxengine/config/drive.proto @@ -2,8 +2,8 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.config"; -import "common.proto"; -import "fl2.proto"; +import "com/cowlark/fluxengine/config/common.proto"; +import "com/cowlark/fluxengine/external/fl2.proto"; // Next: 14 message DriveProto diff --git a/java/com/cowlark/fluxengine/config/layout.proto b/java/com/cowlark/fluxengine/config/layout.proto index 66692bd9..ea798fbb 100644 --- a/java/com/cowlark/fluxengine/config/layout.proto +++ b/java/com/cowlark/fluxengine/config/layout.proto @@ -2,8 +2,8 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.config"; -import "common.proto"; -import "fl2.proto"; +import "com/cowlark/fluxengine/config/common.proto"; +import "com/cowlark/fluxengine/external/fl2.proto"; message SectorListProto { diff --git a/java/com/cowlark/fluxengine/external/BUILD.bazel b/java/com/cowlark/fluxengine/external/BUILD.bazel index 3e4f73ec..d0ae6237 100644 --- a/java/com/cowlark/fluxengine/external/BUILD.bazel +++ b/java/com/cowlark/fluxengine/external/BUILD.bazel @@ -7,7 +7,7 @@ package(default_visibility = ["//visibility:public"]) proto_library( name = "fl2_proto", srcs = ["fl2.proto"], - strip_import_prefix = "/java/com/cowlark/fluxengine/external/", + strip_import_prefix = "/java/", deps = ["@com_google_protobuf//:descriptor_proto"], ) diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index 8651f06e..724c9d87 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -7,7 +7,7 @@ package(default_visibility = ["//visibility:public"]) proto_library( name = "usb_proto", srcs = ["usb.proto"], - strip_import_prefix = "/java/com/cowlark/fluxengine/usb/", + strip_import_prefix = "/java/", deps = ["//java/com/cowlark/fluxengine/config:common_proto"], ) diff --git a/java/com/cowlark/fluxengine/usb/usb.proto b/java/com/cowlark/fluxengine/usb/usb.proto index 22241ec5..0461eecd 100644 --- a/java/com/cowlark/fluxengine/usb/usb.proto +++ b/java/com/cowlark/fluxengine/usb/usb.proto @@ -2,7 +2,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.usb"; -import "common.proto"; +import "com/cowlark/fluxengine/config/common.proto"; message GreaseweazleProto { enum BusType { /* note that these must match CMD_SET_BUS codes */ From ca90fda83645d67d5bc1291eec96350833b56d30 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 20:42:28 +0200 Subject: [PATCH 058/192] Convert a lot of proto files. --- .../cowlark/fluxengine/config/config.proto | 16 +++++++++------- .../fluxengine}/decoders/decoders.proto | 6 ++++-- .../fluxengine}/encoders/encoders.proto | 2 ++ .../cowlark/fluxengine/fluxsink/BUILD.bazel | 16 ++++++++++++++++ .../fluxengine}/fluxsink/fluxsink.proto | 4 +++- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 16 ++++++++++++++++ .../fluxengine}/fluxsource/fluxsource.proto | 4 +++- .../fluxengine/imagereader/BUILD.bazel | 16 ++++++++++++++++ .../fluxengine}/imagereader/imagereader.proto | 4 +++- .../fluxengine/imagewriter/BUILD.bazel | 19 +++++++++++++++++++ .../fluxengine}/imagewriter/imagewriter.proto | 6 ++++-- java/com/cowlark/fluxengine/vfs/BUILD.bazel | 16 ++++++++++++++++ .../com/cowlark/fluxengine}/vfs/vfs.proto | 4 +++- 13 files changed, 114 insertions(+), 15 deletions(-) rename {lib => java/com/cowlark/fluxengine}/decoders/decoders.proto (94%) rename {lib => java/com/cowlark/fluxengine}/encoders/encoders.proto (94%) create mode 100644 java/com/cowlark/fluxengine/fluxsink/BUILD.bazel rename {lib => java/com/cowlark/fluxengine}/fluxsink/fluxsink.proto (92%) create mode 100644 java/com/cowlark/fluxengine/fluxsource/BUILD.bazel rename {lib => java/com/cowlark/fluxengine}/fluxsource/fluxsource.proto (93%) create mode 100644 java/com/cowlark/fluxengine/imagereader/BUILD.bazel rename {lib => java/com/cowlark/fluxengine}/imagereader/imagereader.proto (90%) create mode 100644 java/com/cowlark/fluxengine/imagewriter/BUILD.bazel rename {lib => java/com/cowlark/fluxengine}/imagewriter/imagewriter.proto (91%) create mode 100644 java/com/cowlark/fluxengine/vfs/BUILD.bazel rename {lib => java/com/cowlark/fluxengine}/vfs/vfs.proto (97%) diff --git a/java/com/cowlark/fluxengine/config/config.proto b/java/com/cowlark/fluxengine/config/config.proto index 60351f35..186dda4a 100644 --- a/java/com/cowlark/fluxengine/config/config.proto +++ b/java/com/cowlark/fluxengine/config/config.proto @@ -1,13 +1,15 @@ syntax = "proto2"; -import "lib/decoders/decoders.proto"; -import "lib/encoders/encoders.proto"; -import "lib/imagereader/imagereader.proto"; -import "lib/imagewriter/imagewriter.proto"; -import "lib/fluxsource/fluxsource.proto"; -import "lib/fluxsink/fluxsink.proto"; +option java_package = "com.cowlark.fluxengine.config"; + +import "com/cowlark/fluxengine/decoders/decoders.proto"; +import "com/cowlark/fluxengine/encoders/encoders.proto"; +import "com/cowlark/fluxengine/imagereader/imagereader.proto"; +import "com/cowlark/fluxengine/imagewriter/imagewriter.proto"; +import "com/cowlark/fluxengine/fluxsource/fluxsource.proto"; +import "com/cowlark/fluxengine/fluxsink/fluxsink.proto"; import "com/cowlark/fluxengine/usb/usb.proto"; -import "lib/vfs/vfs.proto"; +import "com/cowlark/fluxengine/vfs/vfs.proto"; import "com/cowlark/fluxengine/config/drive.proto"; import "com/cowlark/fluxengine/config/common.proto"; import "com/cowlark/fluxengine/config/layout.proto"; diff --git a/lib/decoders/decoders.proto b/java/com/cowlark/fluxengine/decoders/decoders.proto similarity index 94% rename from lib/decoders/decoders.proto rename to java/com/cowlark/fluxengine/decoders/decoders.proto index df448ec5..b6b7dc9b 100644 --- a/lib/decoders/decoders.proto +++ b/java/com/cowlark/fluxengine/decoders/decoders.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.decoders"; + import "arch/agat/agat.proto"; import "arch/aeslanier/aeslanier.proto"; import "arch/amiga/amiga.proto"; @@ -19,8 +21,8 @@ import "arch/tartu/tartu.proto"; import "arch/tids990/tids990.proto"; import "arch/victor9k/victor9k.proto"; import "arch/zilogmcz/zilogmcz.proto"; -import "lib/fluxsink/fluxsink.proto"; -import "lib/config/common.proto"; +import "com/cowlark/fluxengine/fluxsink/fluxsink.proto"; +import "com/cowlark/fluxengine/config/common.proto"; //NEXT: 33 message DecoderProto { diff --git a/lib/encoders/encoders.proto b/java/com/cowlark/fluxengine/encoders/encoders.proto similarity index 94% rename from lib/encoders/encoders.proto rename to java/com/cowlark/fluxengine/encoders/encoders.proto index 6dfd6cbe..29d67099 100644 --- a/lib/encoders/encoders.proto +++ b/java/com/cowlark/fluxengine/encoders/encoders.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.encoders"; + import "arch/agat/agat.proto"; import "arch/amiga/amiga.proto"; import "arch/apple2/apple2.proto"; diff --git a/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel new file mode 100644 index 00000000..66a83b10 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "fluxsink_proto", + srcs = ["fluxsink.proto"], + strip_import_prefix = "/java/", + deps = ["//java/com/cowlark/fluxengine/config:common_proto"], +) + +java_proto_library( + name = "fluxsink_java_proto", + deps = [":fluxsink_proto"], +) diff --git a/lib/fluxsink/fluxsink.proto b/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto similarity index 92% rename from lib/fluxsink/fluxsink.proto rename to java/com/cowlark/fluxengine/fluxsink/fluxsink.proto index dd08f658..8632e756 100644 --- a/lib/fluxsink/fluxsink.proto +++ b/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.fluxsink"; + +import "com/cowlark/fluxengine/config/common.proto"; message HardwareFluxSinkProto {} diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel new file mode 100644 index 00000000..86d9f3dc --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "fluxsource_proto", + srcs = ["fluxsource.proto"], + strip_import_prefix = "/java/", + deps = ["//java/com/cowlark/fluxengine/config:common_proto"], +) + +java_proto_library( + name = "fluxsource_java_proto", + deps = [":fluxsource_proto"], +) diff --git a/lib/fluxsource/fluxsource.proto b/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto similarity index 93% rename from lib/fluxsource/fluxsource.proto rename to java/com/cowlark/fluxengine/fluxsource/fluxsource.proto index fa377049..b70a0b75 100644 --- a/lib/fluxsource/fluxsource.proto +++ b/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.fluxsource"; + +import "com/cowlark/fluxengine/config/common.proto"; message HardwareFluxSourceProto {} diff --git a/java/com/cowlark/fluxengine/imagereader/BUILD.bazel b/java/com/cowlark/fluxengine/imagereader/BUILD.bazel new file mode 100644 index 00000000..b4ac9a05 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "imagereader_proto", + srcs = ["imagereader.proto"], + strip_import_prefix = "/java/", + deps = ["//java/com/cowlark/fluxengine/config:common_proto"], +) + +java_proto_library( + name = "imagereader_java_proto", + deps = [":imagereader_proto"], +) diff --git a/lib/imagereader/imagereader.proto b/java/com/cowlark/fluxengine/imagereader/imagereader.proto similarity index 90% rename from lib/imagereader/imagereader.proto rename to java/com/cowlark/fluxengine/imagereader/imagereader.proto index 6622d8c7..8aa5a420 100644 --- a/lib/imagereader/imagereader.proto +++ b/java/com/cowlark/fluxengine/imagereader/imagereader.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.imagereader"; + +import "com/cowlark/fluxengine/config/common.proto"; message ImgInputOutputProto { optional bool filesystem_sector_order = 1 [ diff --git a/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel new file mode 100644 index 00000000..51fefc4b --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "imagewriter_proto", + srcs = ["imagewriter.proto"], + strip_import_prefix = "/java/", + deps = [ + "//java/com/cowlark/fluxengine/config:common_proto", + "//java/com/cowlark/fluxengine/imagereader:imagereader_proto", + ], +) + +java_proto_library( + name = "imagewriter_java_proto", + deps = [":imagewriter_proto"], +) diff --git a/lib/imagewriter/imagewriter.proto b/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto similarity index 91% rename from lib/imagewriter/imagewriter.proto rename to java/com/cowlark/fluxengine/imagewriter/imagewriter.proto index 8b8d05a1..d5769a5d 100644 --- a/lib/imagewriter/imagewriter.proto +++ b/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto @@ -1,7 +1,9 @@ syntax = "proto2"; -import "lib/imagereader/imagereader.proto"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.imagewriter"; + +import "com/cowlark/fluxengine/imagereader/imagereader.proto"; +import "com/cowlark/fluxengine/config/common.proto"; message D64OutputProto {} diff --git a/java/com/cowlark/fluxengine/vfs/BUILD.bazel b/java/com/cowlark/fluxengine/vfs/BUILD.bazel new file mode 100644 index 00000000..807279d8 --- /dev/null +++ b/java/com/cowlark/fluxengine/vfs/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "vfs_proto", + srcs = ["vfs.proto"], + strip_import_prefix = "/java/", + deps = ["//java/com/cowlark/fluxengine/config:common_proto"], +) + +java_proto_library( + name = "vfs_java_proto", + deps = [":vfs_proto"], +) diff --git a/lib/vfs/vfs.proto b/java/com/cowlark/fluxengine/vfs/vfs.proto similarity index 97% rename from lib/vfs/vfs.proto rename to java/com/cowlark/fluxengine/vfs/vfs.proto index 6a0c8d82..98f0db77 100644 --- a/lib/vfs/vfs.proto +++ b/java/com/cowlark/fluxengine/vfs/vfs.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.vfs"; + +import "com/cowlark/fluxengine/config/common.proto"; message AcornDfsProto { From c02127b7ed2fc2bf8bef87f6444205927c5caac2 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 20:51:58 +0200 Subject: [PATCH 059/192] Move all the arch stuff, and convert the protos. --- arch/aeslanier/aeslanier.proto | 4 -- arch/f85/f85.proto | 4 -- arch/fb100/fb100.proto | 4 -- arch/mx/mx.proto | 4 -- arch/rolandd20/rolandd20.proto | 5 --- arch/smaky6/smaky6.proto | 4 -- arch/zilogmcz/zilogmcz.proto | 4 -- java/com/cowlark/fluxengine/arch/BUILD.bazel | 16 ++++++++ .../fluxengine/arch}/aeslanier/aeslanier.h | 0 .../fluxengine/arch/aeslanier/aeslanier.proto | 6 +++ .../fluxengine/arch}/aeslanier/decoder.cc | 0 .../com/cowlark/fluxengine/arch}/agat/agat.cc | 0 .../com/cowlark/fluxengine/arch}/agat/agat.h | 0 .../cowlark/fluxengine/arch}/agat/agat.proto | 4 +- .../cowlark/fluxengine/arch}/agat/decoder.cc | 0 .../cowlark/fluxengine/arch}/agat/encoder.cc | 0 .../cowlark/fluxengine/arch}/amiga/amiga.cc | 0 .../cowlark/fluxengine/arch}/amiga/amiga.h | 0 .../fluxengine/arch}/amiga/amiga.proto | 4 +- .../cowlark/fluxengine/arch}/amiga/decoder.cc | 0 .../cowlark/fluxengine/arch}/amiga/encoder.cc | 0 .../cowlark/fluxengine/arch}/apple2/apple2.h | 0 .../fluxengine/arch}/apple2/apple2.proto | 4 +- .../fluxengine/arch}/apple2/data_gcr.h | 0 .../fluxengine/arch}/apple2/decoder.cc | 0 .../fluxengine/arch}/apple2/encoder.cc | 0 .../com/cowlark/fluxengine/arch}/arch.cc | 0 .../com/cowlark/fluxengine/arch}/arch.h | 0 .../fluxengine/arch}/brother/brother.h | 0 .../fluxengine/arch}/brother/brother.proto | 2 + .../fluxengine/arch}/brother/data_gcr.h | 0 .../fluxengine/arch}/brother/decoder.cc | 0 .../fluxengine/arch}/brother/encoder.cc | 0 .../fluxengine/arch}/brother/header_gcr.h | 0 .../com/cowlark/fluxengine/arch}/build.py | 0 .../com/cowlark/fluxengine/arch}/c64/c64.cc | 0 .../com/cowlark/fluxengine/arch}/c64/c64.h | 0 .../cowlark/fluxengine/arch}/c64/c64.proto | 4 +- .../cowlark/fluxengine/arch}/c64/data_gcr.h | 0 .../cowlark/fluxengine/arch}/c64/decoder.cc | 0 .../cowlark/fluxengine/arch}/c64/encoder.cc | 0 .../cowlark/fluxengine/arch}/f85/data_gcr.h | 0 .../cowlark/fluxengine/arch}/f85/decoder.cc | 0 .../com/cowlark/fluxengine/arch}/f85/f85.h | 0 .../com/cowlark/fluxengine/arch/f85/f85.proto | 6 +++ .../cowlark/fluxengine/arch}/fb100/decoder.cc | 0 .../cowlark/fluxengine/arch}/fb100/fb100.h | 0 .../cowlark/fluxengine/arch/fb100/fb100.proto | 6 +++ .../cowlark/fluxengine/arch}/ibm/decoder.cc | 0 .../cowlark/fluxengine/arch}/ibm/encoder.cc | 0 .../com/cowlark/fluxengine/arch}/ibm/ibm.h | 0 .../cowlark/fluxengine/arch}/ibm/ibm.proto | 4 +- .../fluxengine/arch}/macintosh/data_gcr.h | 0 .../fluxengine/arch}/macintosh/decoder.cc | 0 .../fluxengine/arch}/macintosh/encoder.cc | 0 .../fluxengine/arch}/macintosh/macintosh.h | 0 .../arch}/macintosh/macintosh.proto | 4 +- .../fluxengine/arch}/micropolis/decoder.cc | 0 .../fluxengine/arch}/micropolis/encoder.cc | 0 .../fluxengine/arch}/micropolis/micropolis.h | 0 .../arch}/micropolis/micropolis.proto | 4 +- .../cowlark/fluxengine/arch}/mx/decoder.cc | 0 .../com/cowlark/fluxengine/arch}/mx/mx.h | 0 java/com/cowlark/fluxengine/arch/mx/mx.proto | 6 +++ .../fluxengine/arch}/northstar/decoder.cc | 0 .../fluxengine/arch}/northstar/encoder.cc | 0 .../fluxengine/arch}/northstar/northstar.h | 0 .../arch}/northstar/northstar.proto | 4 +- .../fluxengine/arch}/rolandd20/decoder.cc | 0 .../fluxengine/arch}/rolandd20/rolandd20.h | 0 .../fluxengine/arch/rolandd20/rolandd20.proto | 7 ++++ .../fluxengine/arch}/smaky6/decoder.cc | 0 .../cowlark/fluxengine/arch}/smaky6/smaky6.h | 0 .../fluxengine/arch/smaky6/smaky6.proto | 6 +++ .../cowlark/fluxengine/arch}/tartu/decoder.cc | 0 .../cowlark/fluxengine/arch}/tartu/encoder.cc | 0 .../cowlark/fluxengine/arch}/tartu/tartu.h | 0 .../fluxengine/arch}/tartu/tartu.proto | 4 +- .../fluxengine/arch}/tids990/decoder.cc | 0 .../fluxengine/arch}/tids990/encoder.cc | 0 .../fluxengine/arch}/tids990/tids990.h | 0 .../fluxengine/arch}/tids990/tids990.proto | 4 +- .../fluxengine/arch}/victor9k/data_gcr.h | 0 .../fluxengine/arch}/victor9k/decoder.cc | 0 .../fluxengine/arch}/victor9k/encoder.cc | 0 .../fluxengine/arch}/victor9k/victor9k.h | 0 .../fluxengine/arch}/victor9k/victor9k.proto | 4 +- .../fluxengine/arch}/zilogmcz/decoder.cc | 0 .../fluxengine/arch}/zilogmcz/zilogmcz.h | 0 .../fluxengine/arch/zilogmcz/zilogmcz.proto | 6 +++ .../com/cowlark/fluxengine/config/BUILD.bazel | 24 ++++++++++++ .../cowlark/fluxengine/decoders/BUILD.bazel | 20 ++++++++++ .../fluxengine/decoders/decoders.proto | 38 +++++++++---------- .../cowlark/fluxengine/encoders/BUILD.bazel | 16 ++++++++ .../fluxengine/encoders/encoders.proto | 24 ++++++------ 95 files changed, 185 insertions(+), 71 deletions(-) delete mode 100644 arch/aeslanier/aeslanier.proto delete mode 100644 arch/f85/f85.proto delete mode 100644 arch/fb100/fb100.proto delete mode 100644 arch/mx/mx.proto delete mode 100644 arch/rolandd20/rolandd20.proto delete mode 100644 arch/smaky6/smaky6.proto delete mode 100644 arch/zilogmcz/zilogmcz.proto create mode 100644 java/com/cowlark/fluxengine/arch/BUILD.bazel rename {arch => java/com/cowlark/fluxengine/arch}/aeslanier/aeslanier.h (100%) create mode 100644 java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto rename {arch => java/com/cowlark/fluxengine/arch}/aeslanier/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/agat/agat.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/agat/agat.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/agat/agat.proto (85%) rename {arch => java/com/cowlark/fluxengine/arch}/agat/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/agat/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/amiga/amiga.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/amiga/amiga.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/amiga/amiga.proto (72%) rename {arch => java/com/cowlark/fluxengine/arch}/amiga/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/amiga/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/apple2/apple2.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/apple2/apple2.proto (85%) rename {arch => java/com/cowlark/fluxengine/arch}/apple2/data_gcr.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/apple2/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/apple2/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/arch.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/arch.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/brother/brother.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/brother/brother.proto (88%) rename {arch => java/com/cowlark/fluxengine/arch}/brother/data_gcr.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/brother/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/brother/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/brother/header_gcr.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/build.py (100%) rename {arch => java/com/cowlark/fluxengine/arch}/c64/c64.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/c64/c64.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/c64/c64.proto (65%) rename {arch => java/com/cowlark/fluxengine/arch}/c64/data_gcr.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/c64/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/c64/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/f85/data_gcr.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/f85/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/f85/f85.h (100%) create mode 100644 java/com/cowlark/fluxengine/arch/f85/f85.proto rename {arch => java/com/cowlark/fluxengine/arch}/fb100/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/fb100/fb100.h (100%) create mode 100644 java/com/cowlark/fluxengine/arch/fb100/fb100.proto rename {arch => java/com/cowlark/fluxengine/arch}/ibm/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/ibm/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/ibm/ibm.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/ibm/ibm.proto (95%) rename {arch => java/com/cowlark/fluxengine/arch}/macintosh/data_gcr.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/macintosh/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/macintosh/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/macintosh/macintosh.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/macintosh/macintosh.proto (66%) rename {arch => java/com/cowlark/fluxengine/arch}/micropolis/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/micropolis/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/micropolis/micropolis.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/micropolis/micropolis.proto (89%) rename {arch => java/com/cowlark/fluxengine/arch}/mx/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/mx/mx.h (100%) create mode 100644 java/com/cowlark/fluxengine/arch/mx/mx.proto rename {arch => java/com/cowlark/fluxengine/arch}/northstar/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/northstar/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/northstar/northstar.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/northstar/northstar.proto (74%) rename {arch => java/com/cowlark/fluxengine/arch}/rolandd20/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/rolandd20/rolandd20.h (100%) create mode 100644 java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto rename {arch => java/com/cowlark/fluxengine/arch}/smaky6/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/smaky6/smaky6.h (100%) create mode 100644 java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto rename {arch => java/com/cowlark/fluxengine/arch}/tartu/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/tartu/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/tartu/tartu.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/tartu/tartu.proto (90%) rename {arch => java/com/cowlark/fluxengine/arch}/tids990/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/tids990/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/tids990/tids990.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/tids990/tids990.proto (89%) rename {arch => java/com/cowlark/fluxengine/arch}/victor9k/data_gcr.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/victor9k/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/victor9k/encoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/victor9k/victor9k.h (100%) rename {arch => java/com/cowlark/fluxengine/arch}/victor9k/victor9k.proto (91%) rename {arch => java/com/cowlark/fluxengine/arch}/zilogmcz/decoder.cc (100%) rename {arch => java/com/cowlark/fluxengine/arch}/zilogmcz/zilogmcz.h (100%) create mode 100644 java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto create mode 100644 java/com/cowlark/fluxengine/decoders/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/encoders/BUILD.bazel diff --git a/arch/aeslanier/aeslanier.proto b/arch/aeslanier/aeslanier.proto deleted file mode 100644 index fe2df689..00000000 --- a/arch/aeslanier/aeslanier.proto +++ /dev/null @@ -1,4 +0,0 @@ -syntax = "proto2"; - -message AesLanierDecoderProto {} - diff --git a/arch/f85/f85.proto b/arch/f85/f85.proto deleted file mode 100644 index 5fac2a91..00000000 --- a/arch/f85/f85.proto +++ /dev/null @@ -1,4 +0,0 @@ -syntax = "proto2"; - -message F85DecoderProto {} - diff --git a/arch/fb100/fb100.proto b/arch/fb100/fb100.proto deleted file mode 100644 index fb60a49e..00000000 --- a/arch/fb100/fb100.proto +++ /dev/null @@ -1,4 +0,0 @@ -syntax = "proto2"; - -message Fb100DecoderProto {} - diff --git a/arch/mx/mx.proto b/arch/mx/mx.proto deleted file mode 100644 index 72c86c24..00000000 --- a/arch/mx/mx.proto +++ /dev/null @@ -1,4 +0,0 @@ -syntax = "proto2"; - -message MxDecoderProto {} - diff --git a/arch/rolandd20/rolandd20.proto b/arch/rolandd20/rolandd20.proto deleted file mode 100644 index 6ff0ef83..00000000 --- a/arch/rolandd20/rolandd20.proto +++ /dev/null @@ -1,5 +0,0 @@ -syntax = "proto2"; - -message RolandD20DecoderProto {} - - diff --git a/arch/smaky6/smaky6.proto b/arch/smaky6/smaky6.proto deleted file mode 100644 index 6a0bfed1..00000000 --- a/arch/smaky6/smaky6.proto +++ /dev/null @@ -1,4 +0,0 @@ -syntax = "proto2"; - -message Smaky6DecoderProto {} - diff --git a/arch/zilogmcz/zilogmcz.proto b/arch/zilogmcz/zilogmcz.proto deleted file mode 100644 index 0458a792..00000000 --- a/arch/zilogmcz/zilogmcz.proto +++ /dev/null @@ -1,4 +0,0 @@ -syntax = "proto2"; - -message ZilogMczDecoderProto {} - diff --git a/java/com/cowlark/fluxengine/arch/BUILD.bazel b/java/com/cowlark/fluxengine/arch/BUILD.bazel new file mode 100644 index 00000000..a3a6db07 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "arch_proto", + srcs = glob(["*/*.proto"]), + strip_import_prefix = "/java/", + deps = ["//java/com/cowlark/fluxengine/config:common_proto"], +) + +java_proto_library( + name = "arch_java_proto", + deps = [":arch_proto"], +) diff --git a/arch/aeslanier/aeslanier.h b/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.h similarity index 100% rename from arch/aeslanier/aeslanier.h rename to java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.h diff --git a/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto b/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto new file mode 100644 index 00000000..ae971cd8 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; + +option java_package = "com.cowlark.fluxengine.aeslanier"; + +message AesLanierDecoderProto {} + diff --git a/arch/aeslanier/decoder.cc b/java/com/cowlark/fluxengine/arch/aeslanier/decoder.cc similarity index 100% rename from arch/aeslanier/decoder.cc rename to java/com/cowlark/fluxengine/arch/aeslanier/decoder.cc diff --git a/arch/agat/agat.cc b/java/com/cowlark/fluxengine/arch/agat/agat.cc similarity index 100% rename from arch/agat/agat.cc rename to java/com/cowlark/fluxengine/arch/agat/agat.cc diff --git a/arch/agat/agat.h b/java/com/cowlark/fluxengine/arch/agat/agat.h similarity index 100% rename from arch/agat/agat.h rename to java/com/cowlark/fluxengine/arch/agat/agat.h diff --git a/arch/agat/agat.proto b/java/com/cowlark/fluxengine/arch/agat/agat.proto similarity index 85% rename from arch/agat/agat.proto rename to java/com/cowlark/fluxengine/arch/agat/agat.proto index 58377b12..1ee851cb 100644 --- a/arch/agat/agat.proto +++ b/java/com/cowlark/fluxengine/arch/agat/agat.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.agat"; + +import "com/cowlark/fluxengine/config/common.proto"; message AgatDecoderProto {} diff --git a/arch/agat/decoder.cc b/java/com/cowlark/fluxengine/arch/agat/decoder.cc similarity index 100% rename from arch/agat/decoder.cc rename to java/com/cowlark/fluxengine/arch/agat/decoder.cc diff --git a/arch/agat/encoder.cc b/java/com/cowlark/fluxengine/arch/agat/encoder.cc similarity index 100% rename from arch/agat/encoder.cc rename to java/com/cowlark/fluxengine/arch/agat/encoder.cc diff --git a/arch/amiga/amiga.cc b/java/com/cowlark/fluxengine/arch/amiga/amiga.cc similarity index 100% rename from arch/amiga/amiga.cc rename to java/com/cowlark/fluxengine/arch/amiga/amiga.cc diff --git a/arch/amiga/amiga.h b/java/com/cowlark/fluxengine/arch/amiga/amiga.h similarity index 100% rename from arch/amiga/amiga.h rename to java/com/cowlark/fluxengine/arch/amiga/amiga.h diff --git a/arch/amiga/amiga.proto b/java/com/cowlark/fluxengine/arch/amiga/amiga.proto similarity index 72% rename from arch/amiga/amiga.proto rename to java/com/cowlark/fluxengine/arch/amiga/amiga.proto index ee3474dc..2c76629d 100644 --- a/arch/amiga/amiga.proto +++ b/java/com/cowlark/fluxengine/arch/amiga/amiga.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.amiga"; + +import "com/cowlark/fluxengine/config/common.proto"; message AmigaDecoderProto {} diff --git a/arch/amiga/decoder.cc b/java/com/cowlark/fluxengine/arch/amiga/decoder.cc similarity index 100% rename from arch/amiga/decoder.cc rename to java/com/cowlark/fluxengine/arch/amiga/decoder.cc diff --git a/arch/amiga/encoder.cc b/java/com/cowlark/fluxengine/arch/amiga/encoder.cc similarity index 100% rename from arch/amiga/encoder.cc rename to java/com/cowlark/fluxengine/arch/amiga/encoder.cc diff --git a/arch/apple2/apple2.h b/java/com/cowlark/fluxengine/arch/apple2/apple2.h similarity index 100% rename from arch/apple2/apple2.h rename to java/com/cowlark/fluxengine/arch/apple2/apple2.h diff --git a/arch/apple2/apple2.proto b/java/com/cowlark/fluxengine/arch/apple2/apple2.proto similarity index 85% rename from arch/apple2/apple2.proto rename to java/com/cowlark/fluxengine/arch/apple2/apple2.proto index 5a18f837..8720ba4e 100644 --- a/arch/apple2/apple2.proto +++ b/java/com/cowlark/fluxengine/arch/apple2/apple2.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.apple2"; + +import "com/cowlark/fluxengine/config/common.proto"; message Apple2DecoderProto { optional uint32 side_one_track_offset = 1 diff --git a/arch/apple2/data_gcr.h b/java/com/cowlark/fluxengine/arch/apple2/data_gcr.h similarity index 100% rename from arch/apple2/data_gcr.h rename to java/com/cowlark/fluxengine/arch/apple2/data_gcr.h diff --git a/arch/apple2/decoder.cc b/java/com/cowlark/fluxengine/arch/apple2/decoder.cc similarity index 100% rename from arch/apple2/decoder.cc rename to java/com/cowlark/fluxengine/arch/apple2/decoder.cc diff --git a/arch/apple2/encoder.cc b/java/com/cowlark/fluxengine/arch/apple2/encoder.cc similarity index 100% rename from arch/apple2/encoder.cc rename to java/com/cowlark/fluxengine/arch/apple2/encoder.cc diff --git a/arch/arch.cc b/java/com/cowlark/fluxengine/arch/arch.cc similarity index 100% rename from arch/arch.cc rename to java/com/cowlark/fluxengine/arch/arch.cc diff --git a/arch/arch.h b/java/com/cowlark/fluxengine/arch/arch.h similarity index 100% rename from arch/arch.h rename to java/com/cowlark/fluxengine/arch/arch.h diff --git a/arch/brother/brother.h b/java/com/cowlark/fluxengine/arch/brother/brother.h similarity index 100% rename from arch/brother/brother.h rename to java/com/cowlark/fluxengine/arch/brother/brother.h diff --git a/arch/brother/brother.proto b/java/com/cowlark/fluxengine/arch/brother/brother.proto similarity index 88% rename from arch/brother/brother.proto rename to java/com/cowlark/fluxengine/arch/brother/brother.proto index 7171e85b..3acd9219 100644 --- a/arch/brother/brother.proto +++ b/java/com/cowlark/fluxengine/arch/brother/brother.proto @@ -1,5 +1,7 @@ syntax = "proto2"; +option java_package = "com.cowlark.fluxengine.brother"; + message BrotherDecoderProto {} enum BrotherFormat { diff --git a/arch/brother/data_gcr.h b/java/com/cowlark/fluxengine/arch/brother/data_gcr.h similarity index 100% rename from arch/brother/data_gcr.h rename to java/com/cowlark/fluxengine/arch/brother/data_gcr.h diff --git a/arch/brother/decoder.cc b/java/com/cowlark/fluxengine/arch/brother/decoder.cc similarity index 100% rename from arch/brother/decoder.cc rename to java/com/cowlark/fluxengine/arch/brother/decoder.cc diff --git a/arch/brother/encoder.cc b/java/com/cowlark/fluxengine/arch/brother/encoder.cc similarity index 100% rename from arch/brother/encoder.cc rename to java/com/cowlark/fluxengine/arch/brother/encoder.cc diff --git a/arch/brother/header_gcr.h b/java/com/cowlark/fluxengine/arch/brother/header_gcr.h similarity index 100% rename from arch/brother/header_gcr.h rename to java/com/cowlark/fluxengine/arch/brother/header_gcr.h diff --git a/arch/build.py b/java/com/cowlark/fluxengine/arch/build.py similarity index 100% rename from arch/build.py rename to java/com/cowlark/fluxengine/arch/build.py diff --git a/arch/c64/c64.cc b/java/com/cowlark/fluxengine/arch/c64/c64.cc similarity index 100% rename from arch/c64/c64.cc rename to java/com/cowlark/fluxengine/arch/c64/c64.cc diff --git a/arch/c64/c64.h b/java/com/cowlark/fluxengine/arch/c64/c64.h similarity index 100% rename from arch/c64/c64.h rename to java/com/cowlark/fluxengine/arch/c64/c64.h diff --git a/arch/c64/c64.proto b/java/com/cowlark/fluxengine/arch/c64/c64.proto similarity index 65% rename from arch/c64/c64.proto rename to java/com/cowlark/fluxengine/arch/c64/c64.proto index 641bc182..9624f6b8 100644 --- a/arch/c64/c64.proto +++ b/java/com/cowlark/fluxengine/arch/c64/c64.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.c64"; + +import "com/cowlark/fluxengine/config/common.proto"; message Commodore64DecoderProto {} diff --git a/arch/c64/data_gcr.h b/java/com/cowlark/fluxengine/arch/c64/data_gcr.h similarity index 100% rename from arch/c64/data_gcr.h rename to java/com/cowlark/fluxengine/arch/c64/data_gcr.h diff --git a/arch/c64/decoder.cc b/java/com/cowlark/fluxengine/arch/c64/decoder.cc similarity index 100% rename from arch/c64/decoder.cc rename to java/com/cowlark/fluxengine/arch/c64/decoder.cc diff --git a/arch/c64/encoder.cc b/java/com/cowlark/fluxengine/arch/c64/encoder.cc similarity index 100% rename from arch/c64/encoder.cc rename to java/com/cowlark/fluxengine/arch/c64/encoder.cc diff --git a/arch/f85/data_gcr.h b/java/com/cowlark/fluxengine/arch/f85/data_gcr.h similarity index 100% rename from arch/f85/data_gcr.h rename to java/com/cowlark/fluxengine/arch/f85/data_gcr.h diff --git a/arch/f85/decoder.cc b/java/com/cowlark/fluxengine/arch/f85/decoder.cc similarity index 100% rename from arch/f85/decoder.cc rename to java/com/cowlark/fluxengine/arch/f85/decoder.cc diff --git a/arch/f85/f85.h b/java/com/cowlark/fluxengine/arch/f85/f85.h similarity index 100% rename from arch/f85/f85.h rename to java/com/cowlark/fluxengine/arch/f85/f85.h diff --git a/java/com/cowlark/fluxengine/arch/f85/f85.proto b/java/com/cowlark/fluxengine/arch/f85/f85.proto new file mode 100644 index 00000000..7daa6d5c --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/f85/f85.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; + +option java_package = "com.cowlark.fluxengine.f85"; + +message F85DecoderProto {} + diff --git a/arch/fb100/decoder.cc b/java/com/cowlark/fluxengine/arch/fb100/decoder.cc similarity index 100% rename from arch/fb100/decoder.cc rename to java/com/cowlark/fluxengine/arch/fb100/decoder.cc diff --git a/arch/fb100/fb100.h b/java/com/cowlark/fluxengine/arch/fb100/fb100.h similarity index 100% rename from arch/fb100/fb100.h rename to java/com/cowlark/fluxengine/arch/fb100/fb100.h diff --git a/java/com/cowlark/fluxengine/arch/fb100/fb100.proto b/java/com/cowlark/fluxengine/arch/fb100/fb100.proto new file mode 100644 index 00000000..b165ed42 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/fb100/fb100.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; + +option java_package = "com.cowlark.fluxengine.fb100"; + +message Fb100DecoderProto {} + diff --git a/arch/ibm/decoder.cc b/java/com/cowlark/fluxengine/arch/ibm/decoder.cc similarity index 100% rename from arch/ibm/decoder.cc rename to java/com/cowlark/fluxengine/arch/ibm/decoder.cc diff --git a/arch/ibm/encoder.cc b/java/com/cowlark/fluxengine/arch/ibm/encoder.cc similarity index 100% rename from arch/ibm/encoder.cc rename to java/com/cowlark/fluxengine/arch/ibm/encoder.cc diff --git a/arch/ibm/ibm.h b/java/com/cowlark/fluxengine/arch/ibm/ibm.h similarity index 100% rename from arch/ibm/ibm.h rename to java/com/cowlark/fluxengine/arch/ibm/ibm.h diff --git a/arch/ibm/ibm.proto b/java/com/cowlark/fluxengine/arch/ibm/ibm.proto similarity index 95% rename from arch/ibm/ibm.proto rename to java/com/cowlark/fluxengine/arch/ibm/ibm.proto index ee289b34..063ebcf6 100644 --- a/arch/ibm/ibm.proto +++ b/java/com/cowlark/fluxengine/arch/ibm/ibm.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.ibm"; + +import "com/cowlark/fluxengine/config/common.proto"; message IbmDecoderProto { // Next: 11 diff --git a/arch/macintosh/data_gcr.h b/java/com/cowlark/fluxengine/arch/macintosh/data_gcr.h similarity index 100% rename from arch/macintosh/data_gcr.h rename to java/com/cowlark/fluxengine/arch/macintosh/data_gcr.h diff --git a/arch/macintosh/decoder.cc b/java/com/cowlark/fluxengine/arch/macintosh/decoder.cc similarity index 100% rename from arch/macintosh/decoder.cc rename to java/com/cowlark/fluxengine/arch/macintosh/decoder.cc diff --git a/arch/macintosh/encoder.cc b/java/com/cowlark/fluxengine/arch/macintosh/encoder.cc similarity index 100% rename from arch/macintosh/encoder.cc rename to java/com/cowlark/fluxengine/arch/macintosh/encoder.cc diff --git a/arch/macintosh/macintosh.h b/java/com/cowlark/fluxengine/arch/macintosh/macintosh.h similarity index 100% rename from arch/macintosh/macintosh.h rename to java/com/cowlark/fluxengine/arch/macintosh/macintosh.h diff --git a/arch/macintosh/macintosh.proto b/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto similarity index 66% rename from arch/macintosh/macintosh.proto rename to java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto index 5ff666a3..57c97771 100644 --- a/arch/macintosh/macintosh.proto +++ b/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.macintosh"; + +import "com/cowlark/fluxengine/config/common.proto"; message MacintoshDecoderProto {} diff --git a/arch/micropolis/decoder.cc b/java/com/cowlark/fluxengine/arch/micropolis/decoder.cc similarity index 100% rename from arch/micropolis/decoder.cc rename to java/com/cowlark/fluxengine/arch/micropolis/decoder.cc diff --git a/arch/micropolis/encoder.cc b/java/com/cowlark/fluxengine/arch/micropolis/encoder.cc similarity index 100% rename from arch/micropolis/encoder.cc rename to java/com/cowlark/fluxengine/arch/micropolis/encoder.cc diff --git a/arch/micropolis/micropolis.h b/java/com/cowlark/fluxengine/arch/micropolis/micropolis.h similarity index 100% rename from arch/micropolis/micropolis.h rename to java/com/cowlark/fluxengine/arch/micropolis/micropolis.h diff --git a/arch/micropolis/micropolis.proto b/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto similarity index 89% rename from arch/micropolis/micropolis.proto rename to java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto index 4c4f3438..53b79d75 100644 --- a/arch/micropolis/micropolis.proto +++ b/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.micropolis"; + +import "com/cowlark/fluxengine/config/common.proto"; message MicropolisDecoderProto { enum ChecksumType { diff --git a/arch/mx/decoder.cc b/java/com/cowlark/fluxengine/arch/mx/decoder.cc similarity index 100% rename from arch/mx/decoder.cc rename to java/com/cowlark/fluxengine/arch/mx/decoder.cc diff --git a/arch/mx/mx.h b/java/com/cowlark/fluxengine/arch/mx/mx.h similarity index 100% rename from arch/mx/mx.h rename to java/com/cowlark/fluxengine/arch/mx/mx.h diff --git a/java/com/cowlark/fluxengine/arch/mx/mx.proto b/java/com/cowlark/fluxengine/arch/mx/mx.proto new file mode 100644 index 00000000..0a98d724 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/mx/mx.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; + +option java_package = "com.cowlark.fluxengine.mx"; + +message MxDecoderProto {} + diff --git a/arch/northstar/decoder.cc b/java/com/cowlark/fluxengine/arch/northstar/decoder.cc similarity index 100% rename from arch/northstar/decoder.cc rename to java/com/cowlark/fluxengine/arch/northstar/decoder.cc diff --git a/arch/northstar/encoder.cc b/java/com/cowlark/fluxengine/arch/northstar/encoder.cc similarity index 100% rename from arch/northstar/encoder.cc rename to java/com/cowlark/fluxengine/arch/northstar/encoder.cc diff --git a/arch/northstar/northstar.h b/java/com/cowlark/fluxengine/arch/northstar/northstar.h similarity index 100% rename from arch/northstar/northstar.h rename to java/com/cowlark/fluxengine/arch/northstar/northstar.h diff --git a/arch/northstar/northstar.proto b/java/com/cowlark/fluxengine/arch/northstar/northstar.proto similarity index 74% rename from arch/northstar/northstar.proto rename to java/com/cowlark/fluxengine/arch/northstar/northstar.proto index 0693e77d..35e1f115 100644 --- a/arch/northstar/northstar.proto +++ b/java/com/cowlark/fluxengine/arch/northstar/northstar.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.northstar"; + +import "com/cowlark/fluxengine/config/common.proto"; message NorthstarDecoderProto {} diff --git a/arch/rolandd20/decoder.cc b/java/com/cowlark/fluxengine/arch/rolandd20/decoder.cc similarity index 100% rename from arch/rolandd20/decoder.cc rename to java/com/cowlark/fluxengine/arch/rolandd20/decoder.cc diff --git a/arch/rolandd20/rolandd20.h b/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.h similarity index 100% rename from arch/rolandd20/rolandd20.h rename to java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.h diff --git a/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto b/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto new file mode 100644 index 00000000..8af930a3 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto @@ -0,0 +1,7 @@ +syntax = "proto2"; + +option java_package = "com.cowlark.fluxengine.rolandd20"; + +message RolandD20DecoderProto {} + + diff --git a/arch/smaky6/decoder.cc b/java/com/cowlark/fluxengine/arch/smaky6/decoder.cc similarity index 100% rename from arch/smaky6/decoder.cc rename to java/com/cowlark/fluxengine/arch/smaky6/decoder.cc diff --git a/arch/smaky6/smaky6.h b/java/com/cowlark/fluxengine/arch/smaky6/smaky6.h similarity index 100% rename from arch/smaky6/smaky6.h rename to java/com/cowlark/fluxengine/arch/smaky6/smaky6.h diff --git a/java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto b/java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto new file mode 100644 index 00000000..9a45309a --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; + +option java_package = "com.cowlark.fluxengine.smaky6"; + +message Smaky6DecoderProto {} + diff --git a/arch/tartu/decoder.cc b/java/com/cowlark/fluxengine/arch/tartu/decoder.cc similarity index 100% rename from arch/tartu/decoder.cc rename to java/com/cowlark/fluxengine/arch/tartu/decoder.cc diff --git a/arch/tartu/encoder.cc b/java/com/cowlark/fluxengine/arch/tartu/encoder.cc similarity index 100% rename from arch/tartu/encoder.cc rename to java/com/cowlark/fluxengine/arch/tartu/encoder.cc diff --git a/arch/tartu/tartu.h b/java/com/cowlark/fluxengine/arch/tartu/tartu.h similarity index 100% rename from arch/tartu/tartu.h rename to java/com/cowlark/fluxengine/arch/tartu/tartu.h diff --git a/arch/tartu/tartu.proto b/java/com/cowlark/fluxengine/arch/tartu/tartu.proto similarity index 90% rename from arch/tartu/tartu.proto rename to java/com/cowlark/fluxengine/arch/tartu/tartu.proto index f66b2f27..a2712260 100644 --- a/arch/tartu/tartu.proto +++ b/java/com/cowlark/fluxengine/arch/tartu/tartu.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.tartu"; + +import "com/cowlark/fluxengine/config/common.proto"; message TartuDecoderProto {} diff --git a/arch/tids990/decoder.cc b/java/com/cowlark/fluxengine/arch/tids990/decoder.cc similarity index 100% rename from arch/tids990/decoder.cc rename to java/com/cowlark/fluxengine/arch/tids990/decoder.cc diff --git a/arch/tids990/encoder.cc b/java/com/cowlark/fluxengine/arch/tids990/encoder.cc similarity index 100% rename from arch/tids990/encoder.cc rename to java/com/cowlark/fluxengine/arch/tids990/encoder.cc diff --git a/arch/tids990/tids990.h b/java/com/cowlark/fluxengine/arch/tids990/tids990.h similarity index 100% rename from arch/tids990/tids990.h rename to java/com/cowlark/fluxengine/arch/tids990/tids990.h diff --git a/arch/tids990/tids990.proto b/java/com/cowlark/fluxengine/arch/tids990/tids990.proto similarity index 89% rename from arch/tids990/tids990.proto rename to java/com/cowlark/fluxengine/arch/tids990/tids990.proto index 8091e5d7..1edcc33e 100644 --- a/arch/tids990/tids990.proto +++ b/java/com/cowlark/fluxengine/arch/tids990/tids990.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.tids990"; + +import "com/cowlark/fluxengine/config/common.proto"; message Tids990DecoderProto {} diff --git a/arch/victor9k/data_gcr.h b/java/com/cowlark/fluxengine/arch/victor9k/data_gcr.h similarity index 100% rename from arch/victor9k/data_gcr.h rename to java/com/cowlark/fluxengine/arch/victor9k/data_gcr.h diff --git a/arch/victor9k/decoder.cc b/java/com/cowlark/fluxengine/arch/victor9k/decoder.cc similarity index 100% rename from arch/victor9k/decoder.cc rename to java/com/cowlark/fluxengine/arch/victor9k/decoder.cc diff --git a/arch/victor9k/encoder.cc b/java/com/cowlark/fluxengine/arch/victor9k/encoder.cc similarity index 100% rename from arch/victor9k/encoder.cc rename to java/com/cowlark/fluxengine/arch/victor9k/encoder.cc diff --git a/arch/victor9k/victor9k.h b/java/com/cowlark/fluxengine/arch/victor9k/victor9k.h similarity index 100% rename from arch/victor9k/victor9k.h rename to java/com/cowlark/fluxengine/arch/victor9k/victor9k.h diff --git a/arch/victor9k/victor9k.proto b/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto similarity index 91% rename from arch/victor9k/victor9k.proto rename to java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto index 8d0ea666..2465062f 100644 --- a/arch/victor9k/victor9k.proto +++ b/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto @@ -1,6 +1,8 @@ syntax = "proto2"; -import "lib/config/common.proto"; +option java_package = "com.cowlark.fluxengine.victor9k"; + +import "com/cowlark/fluxengine/config/common.proto"; message Victor9kDecoderProto {} diff --git a/arch/zilogmcz/decoder.cc b/java/com/cowlark/fluxengine/arch/zilogmcz/decoder.cc similarity index 100% rename from arch/zilogmcz/decoder.cc rename to java/com/cowlark/fluxengine/arch/zilogmcz/decoder.cc diff --git a/arch/zilogmcz/zilogmcz.h b/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.h similarity index 100% rename from arch/zilogmcz/zilogmcz.h rename to java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.h diff --git a/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto b/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto new file mode 100644 index 00000000..55b15099 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto @@ -0,0 +1,6 @@ +syntax = "proto2"; + +option java_package = "com.cowlark.fluxengine.zilogmcz"; + +message ZilogMczDecoderProto {} + diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 1284c51c..535d8439 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -44,3 +44,27 @@ java_proto_library( name = "drive_java_proto", deps = [":drive_proto"], ) + +proto_library( + name = "config_proto", + srcs = ["config.proto"], + strip_import_prefix = "/java/", + deps = [ + ":common_proto", + ":drive_proto", + ":layout_proto", + "//java/com/cowlark/fluxengine/decoders:decoders_proto", + "//java/com/cowlark/fluxengine/encoders:encoders_proto", + "//java/com/cowlark/fluxengine/fluxsink:fluxsink_proto", + "//java/com/cowlark/fluxengine/fluxsource:fluxsource_proto", + "//java/com/cowlark/fluxengine/imagereader:imagereader_proto", + "//java/com/cowlark/fluxengine/imagewriter:imagewriter_proto", + "//java/com/cowlark/fluxengine/usb:usb_proto", + "//java/com/cowlark/fluxengine/vfs:vfs_proto", + ], +) + +java_proto_library( + name = "config_java_proto", + deps = [":config_proto"], +) diff --git a/java/com/cowlark/fluxengine/decoders/BUILD.bazel b/java/com/cowlark/fluxengine/decoders/BUILD.bazel new file mode 100644 index 00000000..d905a5d3 --- /dev/null +++ b/java/com/cowlark/fluxengine/decoders/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "decoders_proto", + srcs = ["decoders.proto"], + strip_import_prefix = "/java/", + deps = [ + "//java/com/cowlark/fluxengine/arch:arch_proto", + "//java/com/cowlark/fluxengine/config:common_proto", + "//java/com/cowlark/fluxengine/fluxsink:fluxsink_proto", + ], +) + +java_proto_library( + name = "decoders_java_proto", + deps = [":decoders_proto"], +) diff --git a/java/com/cowlark/fluxengine/decoders/decoders.proto b/java/com/cowlark/fluxengine/decoders/decoders.proto index b6b7dc9b..14867521 100644 --- a/java/com/cowlark/fluxengine/decoders/decoders.proto +++ b/java/com/cowlark/fluxengine/decoders/decoders.proto @@ -2,25 +2,25 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.decoders"; -import "arch/agat/agat.proto"; -import "arch/aeslanier/aeslanier.proto"; -import "arch/amiga/amiga.proto"; -import "arch/apple2/apple2.proto"; -import "arch/brother/brother.proto"; -import "arch/c64/c64.proto"; -import "arch/f85/f85.proto"; -import "arch/fb100/fb100.proto"; -import "arch/ibm/ibm.proto"; -import "arch/macintosh/macintosh.proto"; -import "arch/micropolis/micropolis.proto"; -import "arch/mx/mx.proto"; -import "arch/northstar/northstar.proto"; -import "arch/rolandd20/rolandd20.proto"; -import "arch/smaky6/smaky6.proto"; -import "arch/tartu/tartu.proto"; -import "arch/tids990/tids990.proto"; -import "arch/victor9k/victor9k.proto"; -import "arch/zilogmcz/zilogmcz.proto"; +import "com/cowlark/fluxengine/arch/agat/agat.proto"; +import "com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto"; +import "com/cowlark/fluxengine/arch/amiga/amiga.proto"; +import "com/cowlark/fluxengine/arch/apple2/apple2.proto"; +import "com/cowlark/fluxengine/arch/brother/brother.proto"; +import "com/cowlark/fluxengine/arch/c64/c64.proto"; +import "com/cowlark/fluxengine/arch/f85/f85.proto"; +import "com/cowlark/fluxengine/arch/fb100/fb100.proto"; +import "com/cowlark/fluxengine/arch/ibm/ibm.proto"; +import "com/cowlark/fluxengine/arch/macintosh/macintosh.proto"; +import "com/cowlark/fluxengine/arch/micropolis/micropolis.proto"; +import "com/cowlark/fluxengine/arch/mx/mx.proto"; +import "com/cowlark/fluxengine/arch/northstar/northstar.proto"; +import "com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto"; +import "com/cowlark/fluxengine/arch/smaky6/smaky6.proto"; +import "com/cowlark/fluxengine/arch/tartu/tartu.proto"; +import "com/cowlark/fluxengine/arch/tids990/tids990.proto"; +import "com/cowlark/fluxengine/arch/victor9k/victor9k.proto"; +import "com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto"; import "com/cowlark/fluxengine/fluxsink/fluxsink.proto"; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/encoders/BUILD.bazel b/java/com/cowlark/fluxengine/encoders/BUILD.bazel new file mode 100644 index 00000000..fd71a7f5 --- /dev/null +++ b/java/com/cowlark/fluxengine/encoders/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") + +package(default_visibility = ["//visibility:public"]) + +proto_library( + name = "encoders_proto", + srcs = ["encoders.proto"], + strip_import_prefix = "/java/", + deps = ["//java/com/cowlark/fluxengine/arch:arch_proto"], +) + +java_proto_library( + name = "encoders_java_proto", + deps = [":encoders_proto"], +) diff --git a/java/com/cowlark/fluxengine/encoders/encoders.proto b/java/com/cowlark/fluxengine/encoders/encoders.proto index 29d67099..6af8be39 100644 --- a/java/com/cowlark/fluxengine/encoders/encoders.proto +++ b/java/com/cowlark/fluxengine/encoders/encoders.proto @@ -2,18 +2,18 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.encoders"; -import "arch/agat/agat.proto"; -import "arch/amiga/amiga.proto"; -import "arch/apple2/apple2.proto"; -import "arch/brother/brother.proto"; -import "arch/c64/c64.proto"; -import "arch/ibm/ibm.proto"; -import "arch/macintosh/macintosh.proto"; -import "arch/micropolis/micropolis.proto"; -import "arch/northstar/northstar.proto"; -import "arch/tartu/tartu.proto"; -import "arch/tids990/tids990.proto"; -import "arch/victor9k/victor9k.proto"; +import "com/cowlark/fluxengine/arch/agat/agat.proto"; +import "com/cowlark/fluxengine/arch/amiga/amiga.proto"; +import "com/cowlark/fluxengine/arch/apple2/apple2.proto"; +import "com/cowlark/fluxengine/arch/brother/brother.proto"; +import "com/cowlark/fluxengine/arch/c64/c64.proto"; +import "com/cowlark/fluxengine/arch/ibm/ibm.proto"; +import "com/cowlark/fluxengine/arch/macintosh/macintosh.proto"; +import "com/cowlark/fluxengine/arch/micropolis/micropolis.proto"; +import "com/cowlark/fluxengine/arch/northstar/northstar.proto"; +import "com/cowlark/fluxengine/arch/tartu/tartu.proto"; +import "com/cowlark/fluxengine/arch/tids990/tids990.proto"; +import "com/cowlark/fluxengine/arch/victor9k/victor9k.proto"; message EncoderProto { From 404044e0f94c24043696676bd7deddaa0548a498 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 21:49:24 +0200 Subject: [PATCH 060/192] Do a lot of DI rewiring. Not sure I like Dagger. --- java/com/cowlark/fluxengine/BUILD.bazel | 2 + .../fluxengine/FluxEngineComponent.java | 12 ++--- java/com/cowlark/fluxengine/Main.java | 27 ++--------- .../cowlark/fluxengine/cli/CliComponent.java | 41 ++++++++++++++++ .../fluxengine/cli/TestBandwidthCommand.java | 31 ++++++++++++ .../cowlark/fluxengine/cli/TestCommand.java | 3 +- .../fluxengine/cli/TestDevicesCommand.java | 15 +++--- .../com/cowlark/fluxengine/config/BUILD.bazel | 12 +++++ .../fluxengine/config/ConfigComponent.java | 20 ++++++++ .../fluxengine/config/ConfigFactory.java | 11 +++++ java/com/cowlark/fluxengine/usb/BUILD.bazel | 7 ++- ...Device.java => GreaseweazleUsbDevice.java} | 4 +- .../cowlark/fluxengine/usb/UsbComponent.java | 20 ++++++++ ...{AbstractUsbDevice.java => UsbDevice.java} | 2 +- .../usb/{UsbFinder.java => UsbFactory.java} | 47 +++++++++++-------- .../com/cowlark/fluxengine/wiring/BUILD.bazel | 5 +- .../com/cowlark/fluxengine/wiring/Scoped.java | 14 ++++++ 17 files changed, 208 insertions(+), 65 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/CliComponent.java create mode 100644 java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java create mode 100644 java/com/cowlark/fluxengine/config/ConfigComponent.java create mode 100644 java/com/cowlark/fluxengine/config/ConfigFactory.java rename java/com/cowlark/fluxengine/usb/{GreaseweazleDevice.java => GreaseweazleUsbDevice.java} (99%) create mode 100644 java/com/cowlark/fluxengine/usb/UsbComponent.java rename java/com/cowlark/fluxengine/usb/{AbstractUsbDevice.java => UsbDevice.java} (95%) rename java/com/cowlark/fluxengine/usb/{UsbFinder.java => UsbFactory.java} (77%) create mode 100644 java/com/cowlark/fluxengine/wiring/Scoped.java diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 7860c572..0e93249d 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -8,6 +8,8 @@ java_library( srcs = glob(["*.java"]), deps = [ "//java/com/cowlark/fluxengine/cli", + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/usb", "//java/com/cowlark/fluxengine/wiring", "@maven//:com_google_dagger_dagger", "@maven//:info_picocli_picocli", diff --git a/java/com/cowlark/fluxengine/FluxEngineComponent.java b/java/com/cowlark/fluxengine/FluxEngineComponent.java index 9eac474b..2e99e0d6 100644 --- a/java/com/cowlark/fluxengine/FluxEngineComponent.java +++ b/java/com/cowlark/fluxengine/FluxEngineComponent.java @@ -1,8 +1,8 @@ package com.cowlark.fluxengine; -import com.cowlark.fluxengine.cli.MainCommand; -import com.cowlark.fluxengine.cli.TestCommand; -import com.cowlark.fluxengine.cli.TestDevicesCommand; +import com.cowlark.fluxengine.cli.CliComponent; +import com.cowlark.fluxengine.config.ConfigComponent; +import com.cowlark.fluxengine.usb.UsbComponent; import dagger.Component; import javax.inject.Singleton; @@ -15,9 +15,9 @@ static FluxEngineComponent create() return DaggerFluxEngineComponent.create(); } - MainCommand mainCommand(); + ConfigComponent.Factory configComponentFactory(); - TestCommand testCommand(); + UsbComponent.Factory usbComponentFactory(); - TestDevicesCommand testDevicesCommand(); + CliComponent.Factory cliComponentFactory(); } diff --git a/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java index a0e4a627..508f16d9 100644 --- a/java/com/cowlark/fluxengine/Main.java +++ b/java/com/cowlark/fluxengine/Main.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine; -import com.cowlark.fluxengine.cli.TestCommand; -import com.cowlark.fluxengine.cli.TestDevicesCommand; +import com.cowlark.fluxengine.cli.CliComponent; import picocli.CommandLine; public class Main @@ -9,29 +8,9 @@ public class Main public static void main(String[] args) { FluxEngineComponent component = FluxEngineComponent.create(); + CliComponent cliComponent = component.cliComponentFactory().create(); CommandLine commandLine = - new CommandLine(component.mainCommand(), new CommandFactory(component)); + new CommandLine(cliComponent.mainCommand(), cliComponent); commandLine.execute(args); } - - private static final class CommandFactory implements CommandLine.IFactory - { - private final FluxEngineComponent component; - - CommandFactory(FluxEngineComponent component) - { - this.component = component; - } - - @Override - @SuppressWarnings("unchecked") - public K create(Class cls) throws Exception - { - if (cls == TestCommand.class) - return (K) component.testCommand(); - if (cls == TestDevicesCommand.class) - return (K) component.testDevicesCommand(); - return CommandLine.defaultFactory().create(cls); - } - } } diff --git a/java/com/cowlark/fluxengine/cli/CliComponent.java b/java/com/cowlark/fluxengine/cli/CliComponent.java new file mode 100644 index 00000000..bac4f149 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/CliComponent.java @@ -0,0 +1,41 @@ +package com.cowlark.fluxengine.cli; + +import com.cowlark.fluxengine.wiring.Scoped; +import dagger.Subcomponent; +import picocli.CommandLine; +import picocli.CommandLine.IFactory; + +/** + * Dagger subcomponent for CLI-related accessors. + */ +@Scoped +@Subcomponent +public interface CliComponent extends IFactory +{ + MainCommand mainCommand(); + + TestCommand testCommand(); + + TestDevicesCommand testDevicesCommand(); + + TestBandwidthCommand testBandwidthCommand(); + + @Subcomponent.Factory + interface Factory + { + CliComponent create(); + } + + @Override + @SuppressWarnings("unchecked") + default K create(Class cls) throws Exception + { + if (cls == TestCommand.class) + return (K) testCommand(); + if (cls == TestDevicesCommand.class) + return (K) testDevicesCommand(); + if (cls == TestBandwidthCommand.class) + return (K) testBandwidthCommand(); + return CommandLine.defaultFactory().create(cls); + } +} diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java new file mode 100644 index 00000000..ebc43e14 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -0,0 +1,31 @@ +package com.cowlark.fluxengine.cli; + +import com.cowlark.fluxengine.usb.UsbFactory; +import picocli.CommandLine.Command; +import javax.inject.Inject; + +/** + * Test USB bulk transfer bandwidth, modelled after src/fe-testbandwidth.cc. + */ +@Command(name = "bandwidth", description = "Test USB bulk transfer bandwidth") +public class TestBandwidthCommand implements Runnable +{ + private final UsbFactory usbFactory; + + @Inject + TestBandwidthCommand(UsbFactory usbFactory) + { + this.usbFactory = usbFactory; + } + + @Override + public void run() + { + /* The C++ acquires the device via getUsb(), which isn't wired up in + * the Java port yet, so the bulk tests are commented out until device + * selection is available. */ + // AbstractUsbDevice device = getUsb(); + // device.testBulkWrite(); + // device.testBulkRead(); + } +} diff --git a/java/com/cowlark/fluxengine/cli/TestCommand.java b/java/com/cowlark/fluxengine/cli/TestCommand.java index bd4d4869..6ceb1baf 100644 --- a/java/com/cowlark/fluxengine/cli/TestCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestCommand.java @@ -3,7 +3,8 @@ import picocli.CommandLine.Command; import javax.inject.Inject; -@Command(name = "test", description = "Test commands", subcommands = {TestDevicesCommand.class}) +@Command(name = "test", description = "Test commands", + subcommands = {TestDevicesCommand.class, TestBandwidthCommand.class}) public class TestCommand { @Inject diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java index 62f7526e..ae853654 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -2,27 +2,26 @@ import static com.google.common.base.Strings.nullToEmpty; -import com.cowlark.fluxengine.usb.UsbFinder; -import com.google.common.base.Strings; -import java.util.List; +import com.cowlark.fluxengine.usb.UsbFactory; import picocli.CommandLine.Command; import javax.inject.Inject; +import java.util.List; @Command(name = "devices", description = "List attached USB devices") public class TestDevicesCommand implements Runnable { - private final UsbFinder usbFinder; + private final UsbFactory usbFactory; @Inject - TestDevicesCommand(UsbFinder usbFinder) + TestDevicesCommand(UsbFactory usbFactory) { - this.usbFinder = usbFinder; + this.usbFactory = usbFactory; } @Override public void run() { - List candidates = usbFinder.findUsbDevices(); + List candidates = usbFactory.findUsbDevices(); switch (candidates.size()) { case 0: @@ -41,7 +40,7 @@ public void run() { System.out.println(String.format("%-15s %-30s %s", "Type", "Serial number", "Port (if any)")); - for (UsbFinder.CandidateDevice candidate : candidates) + for (UsbFactory.CandidateDevice candidate : candidates) { System.out.println(String.format("%-15s %-30s %s", candidate.type.getDeviceName(), diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 535d8439..7c05bcb7 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -1,3 +1,4 @@ +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") @@ -68,3 +69,14 @@ java_proto_library( name = "config_java_proto", deps = [":config_proto"], ) + +java_library( + name = "config", + srcs = glob(["*.java"]), + deps = [ + ":config_java_proto", + "//java/com/cowlark/fluxengine/wiring", + "@maven//:com_google_dagger_dagger", + "@maven//:javax_inject_javax_inject", + ], +) diff --git a/java/com/cowlark/fluxengine/config/ConfigComponent.java b/java/com/cowlark/fluxengine/config/ConfigComponent.java new file mode 100644 index 00000000..7cf965d0 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ConfigComponent.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.config; + +import dagger.Subcomponent; +import com.cowlark.fluxengine.wiring.Scoped; + +/** + * Dagger subcomponent for configuration-related accessors. + */ +@Scoped +@Subcomponent +public interface ConfigComponent +{ + ConfigFactory configFactory(); + + @Subcomponent.Factory + interface Factory + { + ConfigComponent create(); + } +} diff --git a/java/com/cowlark/fluxengine/config/ConfigFactory.java b/java/com/cowlark/fluxengine/config/ConfigFactory.java new file mode 100644 index 00000000..017d9c6d --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ConfigFactory.java @@ -0,0 +1,11 @@ +package com.cowlark.fluxengine.config; + +import javax.inject.Inject; + +public class ConfigFactory +{ + @Inject + public ConfigFactory() + { + } +} diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index 724c9d87..7eaf9f8e 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -1,6 +1,6 @@ +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") -load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") package(default_visibility = ["//visibility:public"]) @@ -21,10 +21,13 @@ java_library( srcs = glob(["*.java"]), resources = ["//java:javax.usb.properties"], deps = [ + ":usb_java_proto", + "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/external", - ":usb_java_proto", + "//java/com/cowlark/fluxengine/wiring", "@maven//:com_fazecast_jSerialComm", + "@maven//:com_google_dagger_dagger", "@maven//:com_google_guava_guava", "@maven//:javax_inject_javax_inject", "@maven//:javax_usb_usb_api", diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java similarity index 99% rename from java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java rename to java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index f76302d4..c1fb1408 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -43,7 +43,7 @@ /** * Greaseweazle floppy drive device, ported from lib/usb/greaseweazleusb.cc. */ -class GreaseweazleDevice extends AbstractUsbDevice +class GreaseweazleUsbDevice extends UsbDevice { private enum Version {V22, V24, V29} @@ -54,7 +54,7 @@ private enum Version private long clock; private long revolutions; - GreaseweazleDevice(String port, GreaseweazleProto config) + GreaseweazleUsbDevice(String port, GreaseweazleProto config) { this.config = config; this.serial = SerialPort.getCommPort(port); diff --git a/java/com/cowlark/fluxengine/usb/UsbComponent.java b/java/com/cowlark/fluxengine/usb/UsbComponent.java new file mode 100644 index 00000000..152e0c2b --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/UsbComponent.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.usb; + +import dagger.Subcomponent; +import com.cowlark.fluxengine.wiring.Scoped; + +/** + * Dagger subcomponent for USB-related accessors. + */ +@Scoped +@Subcomponent +public interface UsbComponent +{ + UsbFactory usbFactory(); + + @Subcomponent.Factory + interface Factory + { + UsbComponent create(); + } +} diff --git a/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java b/java/com/cowlark/fluxengine/usb/UsbDevice.java similarity index 95% rename from java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java rename to java/com/cowlark/fluxengine/usb/UsbDevice.java index effe045f..c469ede7 100644 --- a/java/com/cowlark/fluxengine/usb/AbstractUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/UsbDevice.java @@ -5,7 +5,7 @@ /** * Base class for USB floppy drive devices, ported from lib/usb/usb.h. */ -public abstract class AbstractUsbDevice +public abstract class UsbDevice { public void recalibrate() { diff --git a/java/com/cowlark/fluxengine/usb/UsbFinder.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java similarity index 77% rename from java/com/cowlark/fluxengine/usb/UsbFinder.java rename to java/com/cowlark/fluxengine/usb/UsbFactory.java index 728e5101..7835cb72 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFinder.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -1,27 +1,24 @@ package com.cowlark.fluxengine.usb; +import com.cowlark.fluxengine.config.ConfigFactory; import com.fazecast.jSerialComm.SerialPort; import com.google.common.collect.ImmutableList; -import java.util.List; -import java.util.Set; +import org.usb4java.javax.Services; import javax.inject.Inject; -import javax.usb.UsbDevice; import javax.usb.UsbDeviceDescriptor; import javax.usb.UsbException; import javax.usb.UsbHub; import javax.usb.UsbServices; -import org.usb4java.javax.Services; +import java.util.Set; /** * USB device finder, ported from lib/usb/usbfinder.cc. */ -public final class UsbFinder +public final class UsbFactory { public enum DeviceType { - FLUXENGINE("FluxEngine"), - GREASEWEAZLE("Greaseweazle"), - APPLESAUCE("Applesauce"); + FLUXENGINE("FluxEngine"), GREASEWEAZLE("Greaseweazle"), APPLESAUCE("Applesauce"); private final String deviceName; @@ -39,7 +36,7 @@ public String getDeviceName() public static final class CandidateDevice { public DeviceType type; - public UsbDevice device; + public javax.usb.UsbDevice device; public int id; public String serial; public String serialPort; @@ -50,20 +47,22 @@ public static final class CandidateDevice private static final int APPLESAUCE_ID = 0x16c00483; private static final Set VALID_DEVICES = - Set.of(GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID); + Set.of(GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID); + + private final ConfigFactory configFactory; @Inject - public UsbFinder() + public UsbFactory(ConfigFactory configFactory) { + this.configFactory = configFactory; } - private static String getSerialNumber(UsbDevice device) + private static String getSerialNumber(javax.usb.UsbDevice device) { try { return device.getSerialNumberString(); - } - catch (UsbException | java.io.UnsupportedEncodingException e) + } catch (UsbException | java.io.UnsupportedEncodingException e) { return "n/a"; } @@ -77,25 +76,33 @@ public ImmutableList findUsbDevices() UsbServices services = new Services(); UsbHub rootHub = services.getRootUsbHub(); walkHub(rootHub, candidates); - } - catch (UsbException e) + } catch (UsbException e) { System.err.println("USB error: " + e.getMessage()); } return candidates.build(); } + public UsbDevice connect(CandidateDevice device) + { + return null; + } + + public UsbDevice connect() + { + return null; + } + private static void walkHub(UsbHub hub, ImmutableList.Builder candidates) { for (Object o : hub.getAttachedUsbDevices()) { - UsbDevice usbDevice = (UsbDevice) o; + javax.usb.UsbDevice usbDevice = (javax.usb.UsbDevice) o; if (usbDevice.isUsbHub()) walkHub((UsbHub) usbDevice, candidates); UsbDeviceDescriptor descriptor = usbDevice.getUsbDeviceDescriptor(); - int id = ((descriptor.idVendor() & 0xffff) << 16) | - (descriptor.idProduct() & 0xffff); + int id = ((descriptor.idVendor() & 0xffff) << 16) | (descriptor.idProduct() & 0xffff); if (!VALID_DEVICES.contains(id)) continue; @@ -128,7 +135,7 @@ private static String findSerialPort(int id, String serial) { String portSerial = port.getSerialNumber(); if (serial == null || serial.isEmpty() || portSerial == null || - serial.equals(portSerial)) + serial.equals(portSerial)) { return port.getSystemPortName(); } diff --git a/java/com/cowlark/fluxengine/wiring/BUILD.bazel b/java/com/cowlark/fluxengine/wiring/BUILD.bazel index 646f1e31..fe4c1a67 100644 --- a/java/com/cowlark/fluxengine/wiring/BUILD.bazel +++ b/java/com/cowlark/fluxengine/wiring/BUILD.bazel @@ -17,6 +17,9 @@ java_library( java_library( name = "wiring", - srcs = [], + srcs = glob(["*.java"]), exported_plugins = [":dagger_plugin"], + deps = [ + "@maven//:javax_inject_javax_inject", + ], ) diff --git a/java/com/cowlark/fluxengine/wiring/Scoped.java b/java/com/cowlark/fluxengine/wiring/Scoped.java new file mode 100644 index 00000000..c3f04070 --- /dev/null +++ b/java/com/cowlark/fluxengine/wiring/Scoped.java @@ -0,0 +1,14 @@ +package com.cowlark.fluxengine.wiring; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.inject.Scope; + +/** + * Dagger scope for subcomponents. + */ +@Scope +@Retention(RetentionPolicy.RUNTIME) +public @interface Scoped +{ +} From cdb5061f353762785055804ac6d8cec298375852 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 22:05:19 +0200 Subject: [PATCH 061/192] Rearrange all the DI stuff, again. --- java/com/cowlark/fluxengine/BUILD.bazel | 20 ++------- .../fluxengine/FluxEngineComponent.java | 23 ----------- java/com/cowlark/fluxengine/Main.java | 16 -------- java/com/cowlark/fluxengine/cli/BUILD.bazel | 6 +-- .../cowlark/fluxengine/cli/CliComponent.java | 41 ------------------- .../fluxengine/cli/CommandWithConfig.java | 26 ++++++++++++ java/com/cowlark/fluxengine/cli/Main.java | 22 ++++++++++ .../cowlark/fluxengine/cli/MainCommand.java | 6 --- .../fluxengine/cli/TestBandwidthCommand.java | 17 +++----- .../cowlark/fluxengine/cli/TestCommand.java | 5 --- .../fluxengine/cli/TestDevicesCommand.java | 17 +++----- .../com/cowlark/fluxengine/config/BUILD.bazel | 1 + .../com/cowlark/fluxengine/config/Config.java | 21 ++++++++++ .../fluxengine/config/ConfigComponent.java | 2 + .../fluxengine/config/ConfigFactory.java | 11 ----- .../cowlark/fluxengine/usb/UsbFactory.java | 8 ++-- .../com/cowlark/fluxengine/wiring/BUILD.bazel | 19 ++++++++- .../wiring/FluxEngineComponent.java | 34 +++++++++++++++ .../fluxengine/wiring/UnmatchArgs.java | 14 +++++++ 19 files changed, 158 insertions(+), 151 deletions(-) delete mode 100644 java/com/cowlark/fluxengine/FluxEngineComponent.java delete mode 100644 java/com/cowlark/fluxengine/Main.java delete mode 100644 java/com/cowlark/fluxengine/cli/CliComponent.java create mode 100644 java/com/cowlark/fluxengine/cli/CommandWithConfig.java create mode 100644 java/com/cowlark/fluxengine/cli/Main.java create mode 100644 java/com/cowlark/fluxengine/config/Config.java delete mode 100644 java/com/cowlark/fluxengine/config/ConfigFactory.java create mode 100644 java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java create mode 100644 java/com/cowlark/fluxengine/wiring/UnmatchArgs.java diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 0e93249d..a1360d43 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,26 +1,12 @@ -load("@rules_java//java:defs.bzl", "java_binary", "java_library") +load("@rules_java//java:defs.bzl", "java_binary") load("//:native_image.bzl", "native_image") package(default_visibility = ["//visibility:public"]) -java_library( - name = "fluxengine_lib", - srcs = glob(["*.java"]), - deps = [ - "//java/com/cowlark/fluxengine/cli", - "//java/com/cowlark/fluxengine/config", - "//java/com/cowlark/fluxengine/usb", - "//java/com/cowlark/fluxengine/wiring", - "@maven//:com_google_dagger_dagger", - "@maven//:info_picocli_picocli", - "@maven//:javax_inject_javax_inject", - ], -) - java_binary( name = "fluxengine", - main_class = "com.cowlark.fluxengine.Main", - runtime_deps = [":fluxengine_lib"], + main_class = "com.cowlark.fluxengine.cli.Main", + runtime_deps = ["//java/com/cowlark/fluxengine/cli"], ) native_image( diff --git a/java/com/cowlark/fluxengine/FluxEngineComponent.java b/java/com/cowlark/fluxengine/FluxEngineComponent.java deleted file mode 100644 index 2e99e0d6..00000000 --- a/java/com/cowlark/fluxengine/FluxEngineComponent.java +++ /dev/null @@ -1,23 +0,0 @@ -package com.cowlark.fluxengine; - -import com.cowlark.fluxengine.cli.CliComponent; -import com.cowlark.fluxengine.config.ConfigComponent; -import com.cowlark.fluxengine.usb.UsbComponent; -import dagger.Component; -import javax.inject.Singleton; - -@Singleton -@Component -interface FluxEngineComponent -{ - static FluxEngineComponent create() - { - return DaggerFluxEngineComponent.create(); - } - - ConfigComponent.Factory configComponentFactory(); - - UsbComponent.Factory usbComponentFactory(); - - CliComponent.Factory cliComponentFactory(); -} diff --git a/java/com/cowlark/fluxengine/Main.java b/java/com/cowlark/fluxengine/Main.java deleted file mode 100644 index 508f16d9..00000000 --- a/java/com/cowlark/fluxengine/Main.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.cowlark.fluxengine; - -import com.cowlark.fluxengine.cli.CliComponent; -import picocli.CommandLine; - -public class Main -{ - public static void main(String[] args) - { - FluxEngineComponent component = FluxEngineComponent.create(); - CliComponent cliComponent = component.cliComponentFactory().create(); - CommandLine commandLine = - new CommandLine(cliComponent.mainCommand(), cliComponent); - commandLine.execute(args); - } -} diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 3d175692..895f3c73 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -1,4 +1,4 @@ -load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin") +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") package(default_visibility = ["//visibility:public"]) @@ -14,10 +14,8 @@ java_library( plugins = [":picocli"], deps = [ "//java/com/cowlark/fluxengine/usb", - "//java/com/cowlark/fluxengine/wiring", - "@maven//:com_google_dagger_dagger", + "//java/com/cowlark/fluxengine/wiring:component", "@maven//:com_google_guava_guava", "@maven//:info_picocli_picocli", - "@maven//:javax_inject_javax_inject", ], ) diff --git a/java/com/cowlark/fluxengine/cli/CliComponent.java b/java/com/cowlark/fluxengine/cli/CliComponent.java deleted file mode 100644 index bac4f149..00000000 --- a/java/com/cowlark/fluxengine/cli/CliComponent.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.cowlark.fluxengine.cli; - -import com.cowlark.fluxengine.wiring.Scoped; -import dagger.Subcomponent; -import picocli.CommandLine; -import picocli.CommandLine.IFactory; - -/** - * Dagger subcomponent for CLI-related accessors. - */ -@Scoped -@Subcomponent -public interface CliComponent extends IFactory -{ - MainCommand mainCommand(); - - TestCommand testCommand(); - - TestDevicesCommand testDevicesCommand(); - - TestBandwidthCommand testBandwidthCommand(); - - @Subcomponent.Factory - interface Factory - { - CliComponent create(); - } - - @Override - @SuppressWarnings("unchecked") - default K create(Class cls) throws Exception - { - if (cls == TestCommand.class) - return (K) testCommand(); - if (cls == TestDevicesCommand.class) - return (K) testDevicesCommand(); - if (cls == TestBandwidthCommand.class) - return (K) testBandwidthCommand(); - return CommandLine.defaultFactory().create(cls); - } -} diff --git a/java/com/cowlark/fluxengine/cli/CommandWithConfig.java b/java/com/cowlark/fluxengine/cli/CommandWithConfig.java new file mode 100644 index 00000000..f9e3a0f2 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/CommandWithConfig.java @@ -0,0 +1,26 @@ +package com.cowlark.fluxengine.cli; + +import java.util.List; +import picocli.CommandLine.Command; +import picocli.CommandLine.Unmatched; + +/** + * Base class for commands which accept dotted `--config.flag=value` + * arguments, which picocli collects as unmatched arguments. + */ +@Command +public abstract class CommandWithConfig +{ + @Unmatched + private List unmatched; + + protected List unmatchedArguments() + { + return unmatched == null ? List.of() : unmatched; + } + + /* TODO: process the dotted --arguments. */ + protected void processConfigArguments() + { + } +} diff --git a/java/com/cowlark/fluxengine/cli/Main.java b/java/com/cowlark/fluxengine/cli/Main.java new file mode 100644 index 00000000..e87e6e60 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/Main.java @@ -0,0 +1,22 @@ +package com.cowlark.fluxengine.cli; + +import picocli.CommandLine; + +public class Main +{ + public static void main(String[] args) + { + CommandLine commandLine = new CommandLine(new MainCommand()); + allowUnmatchedArguments(commandLine); + commandLine.execute(args); + } + + /* The dotted --config.flag=value arguments don't match any declared + * option, so allow them to be collected as unmatched arguments. */ + private static void allowUnmatchedArguments(CommandLine commandLine) + { + commandLine.setUnmatchedArgumentsAllowed(true); + for (CommandLine sub : commandLine.getSubcommands().values()) + allowUnmatchedArguments(sub); + } +} diff --git a/java/com/cowlark/fluxengine/cli/MainCommand.java b/java/com/cowlark/fluxengine/cli/MainCommand.java index 92b678c4..a91ffef0 100644 --- a/java/com/cowlark/fluxengine/cli/MainCommand.java +++ b/java/com/cowlark/fluxengine/cli/MainCommand.java @@ -1,17 +1,11 @@ package com.cowlark.fluxengine.cli; import picocli.CommandLine.Command; -import javax.inject.Inject; @Command(name = "fluxengine", mixinStandardHelpOptions = true, subcommands = {TestCommand.class}, description = "FluxEngine CLI") public class MainCommand implements Runnable { - @Inject - MainCommand() - { - } - @Override public void run() { diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index ebc43e14..4c724dba 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,30 +1,23 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.usb.UsbFactory; +import com.cowlark.fluxengine.wiring.FluxEngineComponent; import picocli.CommandLine.Command; -import javax.inject.Inject; /** * Test USB bulk transfer bandwidth, modelled after src/fe-testbandwidth.cc. */ @Command(name = "bandwidth", description = "Test USB bulk transfer bandwidth") -public class TestBandwidthCommand implements Runnable +public class TestBandwidthCommand extends CommandWithConfig implements Runnable { - private final UsbFactory usbFactory; - - @Inject - TestBandwidthCommand(UsbFactory usbFactory) - { - this.usbFactory = usbFactory; - } - @Override public void run() { + FluxEngineComponent component = FluxEngineComponent.create(unmatchedArguments()); + /* The C++ acquires the device via getUsb(), which isn't wired up in * the Java port yet, so the bulk tests are commented out until device * selection is available. */ - // AbstractUsbDevice device = getUsb(); + // UsbDevice device = component.usbComponentFactory().create().usbFactory().connect(); // device.testBulkWrite(); // device.testBulkRead(); } diff --git a/java/com/cowlark/fluxengine/cli/TestCommand.java b/java/com/cowlark/fluxengine/cli/TestCommand.java index 6ceb1baf..af51f0f7 100644 --- a/java/com/cowlark/fluxengine/cli/TestCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestCommand.java @@ -1,14 +1,9 @@ package com.cowlark.fluxengine.cli; import picocli.CommandLine.Command; -import javax.inject.Inject; @Command(name = "test", description = "Test commands", subcommands = {TestDevicesCommand.class, TestBandwidthCommand.class}) public class TestCommand { - @Inject - TestCommand() - { - } } diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java index ae853654..c7a64d75 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -3,24 +3,19 @@ import static com.google.common.base.Strings.nullToEmpty; import com.cowlark.fluxengine.usb.UsbFactory; -import picocli.CommandLine.Command; -import javax.inject.Inject; +import com.cowlark.fluxengine.wiring.FluxEngineComponent; import java.util.List; +import picocli.CommandLine.Command; @Command(name = "devices", description = "List attached USB devices") -public class TestDevicesCommand implements Runnable +public class TestDevicesCommand extends CommandWithConfig implements Runnable { - private final UsbFactory usbFactory; - - @Inject - TestDevicesCommand(UsbFactory usbFactory) - { - this.usbFactory = usbFactory; - } - @Override public void run() { + FluxEngineComponent component = FluxEngineComponent.create(unmatchedArguments()); + UsbFactory usbFactory = component.usbComponentFactory().create().usbFactory(); + List candidates = usbFactory.findUsbDevices(); switch (candidates.size()) { diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 7c05bcb7..aabffebb 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -77,6 +77,7 @@ java_library( ":config_java_proto", "//java/com/cowlark/fluxengine/wiring", "@maven//:com_google_dagger_dagger", + "@maven//:com_google_guava_guava", "@maven//:javax_inject_javax_inject", ], ) diff --git a/java/com/cowlark/fluxengine/config/Config.java b/java/com/cowlark/fluxengine/config/Config.java new file mode 100644 index 00000000..362715a1 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/Config.java @@ -0,0 +1,21 @@ +package com.cowlark.fluxengine.config; + +import com.cowlark.fluxengine.wiring.UnmatchArgs; +import com.google.common.collect.ImmutableList; +import javax.inject.Inject; + +/** + * The assembled configuration, built from the unmatched command-line + * arguments. + */ +public class Config +{ + private final ImmutableList args; + + @Inject + public Config(@UnmatchArgs ImmutableList args) + { + this.args = args; + /* TODO: process the unmatched arguments. */ + } +} diff --git a/java/com/cowlark/fluxengine/config/ConfigComponent.java b/java/com/cowlark/fluxengine/config/ConfigComponent.java index 7cf965d0..8387763b 100644 --- a/java/com/cowlark/fluxengine/config/ConfigComponent.java +++ b/java/com/cowlark/fluxengine/config/ConfigComponent.java @@ -10,6 +10,8 @@ @Subcomponent public interface ConfigComponent { + Config config(); + ConfigFactory configFactory(); @Subcomponent.Factory diff --git a/java/com/cowlark/fluxengine/config/ConfigFactory.java b/java/com/cowlark/fluxengine/config/ConfigFactory.java deleted file mode 100644 index 017d9c6d..00000000 --- a/java/com/cowlark/fluxengine/config/ConfigFactory.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.cowlark.fluxengine.config; - -import javax.inject.Inject; - -public class ConfigFactory -{ - @Inject - public ConfigFactory() - { - } -} diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 7835cb72..39de2653 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -1,6 +1,6 @@ package com.cowlark.fluxengine.usb; -import com.cowlark.fluxengine.config.ConfigFactory; +import com.cowlark.fluxengine.config.Config; import com.fazecast.jSerialComm.SerialPort; import com.google.common.collect.ImmutableList; import org.usb4java.javax.Services; @@ -49,12 +49,12 @@ public static final class CandidateDevice private static final Set VALID_DEVICES = Set.of(GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID); - private final ConfigFactory configFactory; + private final Config config; @Inject - public UsbFactory(ConfigFactory configFactory) + public UsbFactory(Config config) { - this.configFactory = configFactory; + this.config = config; } private static String getSerialNumber(javax.usb.UsbDevice device) diff --git a/java/com/cowlark/fluxengine/wiring/BUILD.bazel b/java/com/cowlark/fluxengine/wiring/BUILD.bazel index fe4c1a67..6af12166 100644 --- a/java/com/cowlark/fluxengine/wiring/BUILD.bazel +++ b/java/com/cowlark/fluxengine/wiring/BUILD.bazel @@ -17,9 +17,26 @@ java_library( java_library( name = "wiring", - srcs = glob(["*.java"]), + srcs = [ + "Scoped.java", + "UnmatchArgs.java", + ], exported_plugins = [":dagger_plugin"], deps = [ + ":dagger", + "@maven//:javax_inject_javax_inject", + ], +) + +java_library( + name = "component", + srcs = ["FluxEngineComponent.java"], + deps = [ + ":wiring", + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/usb", + "@maven//:com_google_dagger_dagger", + "@maven//:com_google_guava_guava", "@maven//:javax_inject_javax_inject", ], ) diff --git a/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java b/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java new file mode 100644 index 00000000..2a8985b6 --- /dev/null +++ b/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java @@ -0,0 +1,34 @@ +package com.cowlark.fluxengine.wiring; + +import com.cowlark.fluxengine.config.ConfigComponent; +import com.cowlark.fluxengine.usb.UsbComponent; +import com.google.common.collect.ImmutableList; +import dagger.BindsInstance; +import dagger.Component; +import java.util.List; +import javax.inject.Singleton; + +@Singleton +@Component +public interface FluxEngineComponent +{ + static FluxEngineComponent create(List args) + { + return DaggerFluxEngineComponent.builder() + .unmatchArgs(ImmutableList.copyOf(args)) + .build(); + } + + @Component.Builder + interface Builder + { + @BindsInstance + Builder unmatchArgs(@UnmatchArgs ImmutableList args); + + FluxEngineComponent build(); + } + + ConfigComponent.Factory configComponentFactory(); + + UsbComponent.Factory usbComponentFactory(); +} diff --git a/java/com/cowlark/fluxengine/wiring/UnmatchArgs.java b/java/com/cowlark/fluxengine/wiring/UnmatchArgs.java new file mode 100644 index 00000000..56b0cac0 --- /dev/null +++ b/java/com/cowlark/fluxengine/wiring/UnmatchArgs.java @@ -0,0 +1,14 @@ +package com.cowlark.fluxengine.wiring; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import javax.inject.Qualifier; + +/** + * Qualifier for the unmatched command-line arguments. + */ +@Qualifier +@Retention(RetentionPolicy.RUNTIME) +public @interface UnmatchArgs +{ +} From 2a0f04dfec88a413bd9bf21a3cec03b92404dd49 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 22:38:23 +0200 Subject: [PATCH 062/192] Some renaming. --- java/com/cowlark/fluxengine/config/Config.java | 4 ++-- java/com/cowlark/fluxengine/config/ConfigComponent.java | 2 -- java/com/cowlark/fluxengine/wiring/BUILD.bazel | 2 +- java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java | 4 ++-- .../wiring/{UnmatchArgs.java => UnmatchedArgs.java} | 2 +- 5 files changed, 6 insertions(+), 8 deletions(-) rename java/com/cowlark/fluxengine/wiring/{UnmatchArgs.java => UnmatchedArgs.java} (89%) diff --git a/java/com/cowlark/fluxengine/config/Config.java b/java/com/cowlark/fluxengine/config/Config.java index 362715a1..9e37fb72 100644 --- a/java/com/cowlark/fluxengine/config/Config.java +++ b/java/com/cowlark/fluxengine/config/Config.java @@ -1,6 +1,6 @@ package com.cowlark.fluxengine.config; -import com.cowlark.fluxengine.wiring.UnmatchArgs; +import com.cowlark.fluxengine.wiring.UnmatchedArgs; import com.google.common.collect.ImmutableList; import javax.inject.Inject; @@ -13,7 +13,7 @@ public class Config private final ImmutableList args; @Inject - public Config(@UnmatchArgs ImmutableList args) + public Config(@UnmatchedArgs ImmutableList args) { this.args = args; /* TODO: process the unmatched arguments. */ diff --git a/java/com/cowlark/fluxengine/config/ConfigComponent.java b/java/com/cowlark/fluxengine/config/ConfigComponent.java index 8387763b..5469868c 100644 --- a/java/com/cowlark/fluxengine/config/ConfigComponent.java +++ b/java/com/cowlark/fluxengine/config/ConfigComponent.java @@ -12,8 +12,6 @@ public interface ConfigComponent { Config config(); - ConfigFactory configFactory(); - @Subcomponent.Factory interface Factory { diff --git a/java/com/cowlark/fluxengine/wiring/BUILD.bazel b/java/com/cowlark/fluxengine/wiring/BUILD.bazel index 6af12166..e439fc4a 100644 --- a/java/com/cowlark/fluxengine/wiring/BUILD.bazel +++ b/java/com/cowlark/fluxengine/wiring/BUILD.bazel @@ -19,7 +19,7 @@ java_library( name = "wiring", srcs = [ "Scoped.java", - "UnmatchArgs.java", + "UnmatchedArgs.java", ], exported_plugins = [":dagger_plugin"], deps = [ diff --git a/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java b/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java index 2a8985b6..0391354b 100644 --- a/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java +++ b/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java @@ -15,7 +15,7 @@ public interface FluxEngineComponent static FluxEngineComponent create(List args) { return DaggerFluxEngineComponent.builder() - .unmatchArgs(ImmutableList.copyOf(args)) + .unmatchedArgs(ImmutableList.copyOf(args)) .build(); } @@ -23,7 +23,7 @@ static FluxEngineComponent create(List args) interface Builder { @BindsInstance - Builder unmatchArgs(@UnmatchArgs ImmutableList args); + Builder unmatchedArgs(@UnmatchedArgs ImmutableList args); FluxEngineComponent build(); } diff --git a/java/com/cowlark/fluxengine/wiring/UnmatchArgs.java b/java/com/cowlark/fluxengine/wiring/UnmatchedArgs.java similarity index 89% rename from java/com/cowlark/fluxengine/wiring/UnmatchArgs.java rename to java/com/cowlark/fluxengine/wiring/UnmatchedArgs.java index 56b0cac0..961df322 100644 --- a/java/com/cowlark/fluxengine/wiring/UnmatchArgs.java +++ b/java/com/cowlark/fluxengine/wiring/UnmatchedArgs.java @@ -9,6 +9,6 @@ */ @Qualifier @Retention(RetentionPolicy.RUNTIME) -public @interface UnmatchArgs +public @interface UnmatchedArgs { } From fb1301f1b4797463895b6b98ccb29dce70586e12 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 22:51:42 +0200 Subject: [PATCH 063/192] Remove all the DI, as I don't think it's useful. --- MODULE.bazel | 2 - java/com/cowlark/fluxengine/cli/BUILD.bazel | 2 +- .../fluxengine/cli/TestBandwidthCommand.java | 10 +++-- .../fluxengine/cli/TestDevicesCommand.java | 6 +-- .../com/cowlark/fluxengine/config/BUILD.bazel | 3 -- .../com/cowlark/fluxengine/config/Config.java | 5 +-- .../fluxengine/config/ConfigComponent.java | 20 --------- java/com/cowlark/fluxengine/usb/BUILD.bazel | 3 -- .../cowlark/fluxengine/usb/UsbComponent.java | 20 --------- .../cowlark/fluxengine/usb/UsbFactory.java | 2 - .../com/cowlark/fluxengine/wiring/BUILD.bazel | 42 ------------------- .../wiring/FluxEngineComponent.java | 34 --------------- .../com/cowlark/fluxengine/wiring/Scoped.java | 14 ------- .../fluxengine/wiring/UnmatchedArgs.java | 14 ------- 14 files changed, 12 insertions(+), 165 deletions(-) delete mode 100644 java/com/cowlark/fluxengine/config/ConfigComponent.java delete mode 100644 java/com/cowlark/fluxengine/usb/UsbComponent.java delete mode 100644 java/com/cowlark/fluxengine/wiring/BUILD.bazel delete mode 100644 java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java delete mode 100644 java/com/cowlark/fluxengine/wiring/Scoped.java delete mode 100644 java/com/cowlark/fluxengine/wiring/UnmatchedArgs.java diff --git a/MODULE.bazel b/MODULE.bazel index 492160b3..41c79da0 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -13,8 +13,6 @@ maven.install( artifacts = [ "org.projectlombok:lombok:1.18.30", "com.fazecast:jSerialComm:2.11.4", - "com.google.dagger:dagger:2.51.1", - "com.google.dagger:dagger-compiler:2.51.1", "com.google.guava:guava:33.6.0-jre", "com.google.truth:truth:1.4.5", "info.picocli:picocli:4.7.7", diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 895f3c73..d4faea3c 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -13,8 +13,8 @@ java_library( srcs = glob(["*.java"]), plugins = [":picocli"], deps = [ + "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/usb", - "//java/com/cowlark/fluxengine/wiring:component", "@maven//:com_google_guava_guava", "@maven//:info_picocli_picocli", ], diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 4c724dba..90ead7c2 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,6 +1,9 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.wiring.FluxEngineComponent; +import com.cowlark.fluxengine.config.Config; +import com.cowlark.fluxengine.usb.UsbFactory; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.google.common.collect.ImmutableList; import picocli.CommandLine.Command; /** @@ -12,12 +15,13 @@ public class TestBandwidthCommand extends CommandWithConfig implements Runnable @Override public void run() { - FluxEngineComponent component = FluxEngineComponent.create(unmatchedArguments()); + UsbFactory usbFactory = + new UsbFactory(new Config(ImmutableList.copyOf(unmatchedArguments()))); + UsbDevice device = usbFactory.connect(); /* The C++ acquires the device via getUsb(), which isn't wired up in * the Java port yet, so the bulk tests are commented out until device * selection is available. */ - // UsbDevice device = component.usbComponentFactory().create().usbFactory().connect(); // device.testBulkWrite(); // device.testBulkRead(); } diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java index c7a64d75..81a7b772 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -2,8 +2,9 @@ import static com.google.common.base.Strings.nullToEmpty; +import com.cowlark.fluxengine.config.Config; import com.cowlark.fluxengine.usb.UsbFactory; -import com.cowlark.fluxengine.wiring.FluxEngineComponent; +import com.google.common.collect.ImmutableList; import java.util.List; import picocli.CommandLine.Command; @@ -13,8 +14,7 @@ public class TestDevicesCommand extends CommandWithConfig implements Runnable @Override public void run() { - FluxEngineComponent component = FluxEngineComponent.create(unmatchedArguments()); - UsbFactory usbFactory = component.usbComponentFactory().create().usbFactory(); + UsbFactory usbFactory = new UsbFactory(new Config(ImmutableList.copyOf(unmatchedArguments()))); List candidates = usbFactory.findUsbDevices(); switch (candidates.size()) diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index aabffebb..6aa5e5f9 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -75,9 +75,6 @@ java_library( srcs = glob(["*.java"]), deps = [ ":config_java_proto", - "//java/com/cowlark/fluxengine/wiring", - "@maven//:com_google_dagger_dagger", "@maven//:com_google_guava_guava", - "@maven//:javax_inject_javax_inject", ], ) diff --git a/java/com/cowlark/fluxengine/config/Config.java b/java/com/cowlark/fluxengine/config/Config.java index 9e37fb72..3c254f20 100644 --- a/java/com/cowlark/fluxengine/config/Config.java +++ b/java/com/cowlark/fluxengine/config/Config.java @@ -1,8 +1,6 @@ package com.cowlark.fluxengine.config; -import com.cowlark.fluxengine.wiring.UnmatchedArgs; import com.google.common.collect.ImmutableList; -import javax.inject.Inject; /** * The assembled configuration, built from the unmatched command-line @@ -12,8 +10,7 @@ public class Config { private final ImmutableList args; - @Inject - public Config(@UnmatchedArgs ImmutableList args) + public Config(ImmutableList args) { this.args = args; /* TODO: process the unmatched arguments. */ diff --git a/java/com/cowlark/fluxengine/config/ConfigComponent.java b/java/com/cowlark/fluxengine/config/ConfigComponent.java deleted file mode 100644 index 5469868c..00000000 --- a/java/com/cowlark/fluxengine/config/ConfigComponent.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.cowlark.fluxengine.config; - -import dagger.Subcomponent; -import com.cowlark.fluxengine.wiring.Scoped; - -/** - * Dagger subcomponent for configuration-related accessors. - */ -@Scoped -@Subcomponent -public interface ConfigComponent -{ - Config config(); - - @Subcomponent.Factory - interface Factory - { - ConfigComponent create(); - } -} diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index 7eaf9f8e..d5c392b6 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -25,11 +25,8 @@ java_library( "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/external", - "//java/com/cowlark/fluxengine/wiring", "@maven//:com_fazecast_jSerialComm", - "@maven//:com_google_dagger_dagger", "@maven//:com_google_guava_guava", - "@maven//:javax_inject_javax_inject", "@maven//:javax_usb_usb_api", "@maven//:org_usb4java_usb4java_javax", ], diff --git a/java/com/cowlark/fluxengine/usb/UsbComponent.java b/java/com/cowlark/fluxengine/usb/UsbComponent.java deleted file mode 100644 index 152e0c2b..00000000 --- a/java/com/cowlark/fluxengine/usb/UsbComponent.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.cowlark.fluxengine.usb; - -import dagger.Subcomponent; -import com.cowlark.fluxengine.wiring.Scoped; - -/** - * Dagger subcomponent for USB-related accessors. - */ -@Scoped -@Subcomponent -public interface UsbComponent -{ - UsbFactory usbFactory(); - - @Subcomponent.Factory - interface Factory - { - UsbComponent create(); - } -} diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 39de2653..e8085223 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -4,7 +4,6 @@ import com.fazecast.jSerialComm.SerialPort; import com.google.common.collect.ImmutableList; import org.usb4java.javax.Services; -import javax.inject.Inject; import javax.usb.UsbDeviceDescriptor; import javax.usb.UsbException; import javax.usb.UsbHub; @@ -51,7 +50,6 @@ public static final class CandidateDevice private final Config config; - @Inject public UsbFactory(Config config) { this.config = config; diff --git a/java/com/cowlark/fluxengine/wiring/BUILD.bazel b/java/com/cowlark/fluxengine/wiring/BUILD.bazel deleted file mode 100644 index e439fc4a..00000000 --- a/java/com/cowlark/fluxengine/wiring/BUILD.bazel +++ /dev/null @@ -1,42 +0,0 @@ -load("@rules_java//java:defs.bzl", "java_library", "java_plugin") - -package(default_visibility = ["//visibility:public"]) - -java_plugin( - name = "dagger_plugin", - processor_class = "dagger.internal.codegen.ComponentProcessor", - deps = [ - "@maven//:com_google_dagger_dagger_compiler", - ], -) - -java_library( - name = "dagger", - exported_plugins = [":dagger_plugin"], -) - -java_library( - name = "wiring", - srcs = [ - "Scoped.java", - "UnmatchedArgs.java", - ], - exported_plugins = [":dagger_plugin"], - deps = [ - ":dagger", - "@maven//:javax_inject_javax_inject", - ], -) - -java_library( - name = "component", - srcs = ["FluxEngineComponent.java"], - deps = [ - ":wiring", - "//java/com/cowlark/fluxengine/config", - "//java/com/cowlark/fluxengine/usb", - "@maven//:com_google_dagger_dagger", - "@maven//:com_google_guava_guava", - "@maven//:javax_inject_javax_inject", - ], -) diff --git a/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java b/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java deleted file mode 100644 index 0391354b..00000000 --- a/java/com/cowlark/fluxengine/wiring/FluxEngineComponent.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cowlark.fluxengine.wiring; - -import com.cowlark.fluxengine.config.ConfigComponent; -import com.cowlark.fluxengine.usb.UsbComponent; -import com.google.common.collect.ImmutableList; -import dagger.BindsInstance; -import dagger.Component; -import java.util.List; -import javax.inject.Singleton; - -@Singleton -@Component -public interface FluxEngineComponent -{ - static FluxEngineComponent create(List args) - { - return DaggerFluxEngineComponent.builder() - .unmatchedArgs(ImmutableList.copyOf(args)) - .build(); - } - - @Component.Builder - interface Builder - { - @BindsInstance - Builder unmatchedArgs(@UnmatchedArgs ImmutableList args); - - FluxEngineComponent build(); - } - - ConfigComponent.Factory configComponentFactory(); - - UsbComponent.Factory usbComponentFactory(); -} diff --git a/java/com/cowlark/fluxengine/wiring/Scoped.java b/java/com/cowlark/fluxengine/wiring/Scoped.java deleted file mode 100644 index c3f04070..00000000 --- a/java/com/cowlark/fluxengine/wiring/Scoped.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cowlark.fluxengine.wiring; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import javax.inject.Scope; - -/** - * Dagger scope for subcomponents. - */ -@Scope -@Retention(RetentionPolicy.RUNTIME) -public @interface Scoped -{ -} diff --git a/java/com/cowlark/fluxengine/wiring/UnmatchedArgs.java b/java/com/cowlark/fluxengine/wiring/UnmatchedArgs.java deleted file mode 100644 index 961df322..00000000 --- a/java/com/cowlark/fluxengine/wiring/UnmatchedArgs.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cowlark.fluxengine.wiring; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import javax.inject.Qualifier; - -/** - * Qualifier for the unmatched command-line arguments. - */ -@Qualifier -@Retention(RetentionPolicy.RUNTIME) -public @interface UnmatchedArgs -{ -} From 548f5c74a847bc2946ac7ddaa1eac9cb45ae0d15 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 22:58:11 +0200 Subject: [PATCH 064/192] Some refactoring. --- AGENTS.md | 1 + .../fluxengine/cli/CommandWithConfig.java | 10 ++----- .../fluxengine/cli/TestBandwidthCommand.java | 16 ++++------ .../fluxengine/cli/TestDevicesCommand.java | 29 ++++++++++--------- .../fluxengine/usb/GreaseweazleUsbDevice.java | 12 ++++---- .../cowlark/fluxengine/usb/UsbFactory.java | 11 +++---- 6 files changed, 34 insertions(+), 45 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 407bb52e..5f7cd8f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -79,6 +79,7 @@ Useful commands: - Explicit types, no `var`. - Prefer Guava utilities over hand-rolled checks: `Strings.nullToEmpty(...)` instead of explicit null checks; use `ImmutableList` for returned collections. +- Prefer `System.out.printf(...)` over `System.out.println(String.format(...))`. - Tests use JUnit 4 (`@RunWith(JUnit4.class)`, `org.junit.Test`). - Follow existing patterns in the package you are editing; keep new functionality localized to the relevant package. diff --git a/java/com/cowlark/fluxengine/cli/CommandWithConfig.java b/java/com/cowlark/fluxengine/cli/CommandWithConfig.java index f9e3a0f2..f3fce94c 100644 --- a/java/com/cowlark/fluxengine/cli/CommandWithConfig.java +++ b/java/com/cowlark/fluxengine/cli/CommandWithConfig.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.cli; import java.util.List; +import com.google.common.collect.ImmutableList; import picocli.CommandLine.Command; import picocli.CommandLine.Unmatched; @@ -14,13 +15,8 @@ public abstract class CommandWithConfig @Unmatched private List unmatched; - protected List unmatchedArguments() - { - return unmatched == null ? List.of() : unmatched; - } - - /* TODO: process the dotted --arguments. */ - protected void processConfigArguments() + protected ImmutableList unmatchedArguments() { + return unmatched == null ? ImmutableList.of() : ImmutableList.copyOf(unmatched); } } diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 90ead7c2..5e47cf30 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,9 +1,8 @@ package com.cowlark.fluxengine.cli; import com.cowlark.fluxengine.config.Config; -import com.cowlark.fluxengine.usb.UsbFactory; import com.cowlark.fluxengine.usb.UsbDevice; -import com.google.common.collect.ImmutableList; +import com.cowlark.fluxengine.usb.UsbFactory; import picocli.CommandLine.Command; /** @@ -15,14 +14,9 @@ public class TestBandwidthCommand extends CommandWithConfig implements Runnable @Override public void run() { - UsbFactory usbFactory = - new UsbFactory(new Config(ImmutableList.copyOf(unmatchedArguments()))); - UsbDevice device = usbFactory.connect(); - - /* The C++ acquires the device via getUsb(), which isn't wired up in - * the Java port yet, so the bulk tests are commented out until device - * selection is available. */ - // device.testBulkWrite(); - // device.testBulkRead(); + Config config = new Config(unmatchedArguments()); + UsbDevice device = UsbFactory.connect(config); + device.testBulkWrite(); + device.testBulkRead(); } } diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java index 81a7b772..2487b30f 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -2,11 +2,10 @@ import static com.google.common.base.Strings.nullToEmpty; -import com.cowlark.fluxengine.config.Config; import com.cowlark.fluxengine.usb.UsbFactory; -import com.google.common.collect.ImmutableList; -import java.util.List; +import com.cowlark.fluxengine.usb.UsbFactory.CandidateDevice; import picocli.CommandLine.Command; +import java.util.List; @Command(name = "devices", description = "List attached USB devices") public class TestDevicesCommand extends CommandWithConfig implements Runnable @@ -14,9 +13,7 @@ public class TestDevicesCommand extends CommandWithConfig implements Runnable @Override public void run() { - UsbFactory usbFactory = new UsbFactory(new Config(ImmutableList.copyOf(unmatchedArguments()))); - - List candidates = usbFactory.findUsbDevices(); + List candidates = UsbFactory.findUsbDevices(); switch (candidates.size()) { case 0: @@ -28,19 +25,23 @@ public void run() break; default: - System.out.println(String.format("Detected %d devices:", candidates.size())); + System.out.printf("Detected %d devices:\n", candidates.size()); } if (!candidates.isEmpty()) { - System.out.println(String.format("%-15s %-30s %s", - "Type", "Serial number", "Port (if any)")); - for (UsbFactory.CandidateDevice candidate : candidates) + System.out.printf( + "%-15s %-30s %s\n", + "Type", + "Serial number", + "Port (if any)"); + for (CandidateDevice candidate : candidates) { - System.out.println(String.format("%-15s %-30s %s", - candidate.type.getDeviceName(), - candidate.serial, - nullToEmpty(candidate.serialPort))); + System.out.printf( + "%-15s %-30s %s\n", + candidate.type.getDeviceName(), + candidate.serial, + nullToEmpty(candidate.serialPort)); } } } diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index c1fb1408..952b4c16 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -265,11 +265,11 @@ public void testBulkWrite() readBytes(1); double elapsedTime = getCurrentTime() - startTime; - System.out.println(String.format( - "transferred %d bytes from PC -> device in %d ms (%d kb/s)", + System.out.printf( + "transferred %d bytes from PC -> device in %d ms (%d kb/s)\n", LEN, (int) (elapsedTime * 1000.0), - (int) ((LEN / 1024.0) / elapsedTime))); + (int) ((LEN / 1024.0) / elapsedTime)); } @Override @@ -304,11 +304,11 @@ public void testBulkRead() readBytes(LEN); double elapsedTime = getCurrentTime() - startTime; - System.out.println(String.format( - "transferred %d bytes from device -> PC in %d ms (%d kb/s)", + System.out.printf( + "transferred %d bytes from device -> PC in %d ms (%d kb/s)\n", LEN, (int) (elapsedTime * 1000.0), - (int) ((LEN / 1024.0) / elapsedTime))); + (int) ((LEN / 1024.0) / elapsedTime)); } @Override diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index e8085223..40f57874 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -48,11 +48,8 @@ public static final class CandidateDevice private static final Set VALID_DEVICES = Set.of(GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID); - private final Config config; - - public UsbFactory(Config config) + private UsbFactory() { - this.config = config; } private static String getSerialNumber(javax.usb.UsbDevice device) @@ -66,7 +63,7 @@ private static String getSerialNumber(javax.usb.UsbDevice device) } } - public ImmutableList findUsbDevices() + public static ImmutableList findUsbDevices() { ImmutableList.Builder candidates = ImmutableList.builder(); try @@ -81,12 +78,12 @@ public ImmutableList findUsbDevices() return candidates.build(); } - public UsbDevice connect(CandidateDevice device) + public static UsbDevice connect(CandidateDevice device) { return null; } - public UsbDevice connect() + public static UsbDevice connect(Config config) { return null; } From 4c697d286ef84353386c4c01c65ac7b402092787 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 23:10:17 +0200 Subject: [PATCH 065/192] Start doing some of the USB device wireup. --- .../com/cowlark/fluxengine/config/Config.java | 12 ++++ java/com/cowlark/fluxengine/core/Bytes.java | 4 +- .../fluxengine/core/FluxEngineException.java | 17 ++++++ .../external/GreaseweazleUtils.java | 3 +- .../fluxengine/usb/GreaseweazleUsbDevice.java | 27 +++++---- .../cowlark/fluxengine/usb/UsbFactory.java | 60 ++++++++++++++++++- 6 files changed, 106 insertions(+), 17 deletions(-) create mode 100644 java/com/cowlark/fluxengine/core/FluxEngineException.java diff --git a/java/com/cowlark/fluxengine/config/Config.java b/java/com/cowlark/fluxengine/config/Config.java index 3c254f20..4c1d39cd 100644 --- a/java/com/cowlark/fluxengine/config/Config.java +++ b/java/com/cowlark/fluxengine/config/Config.java @@ -15,4 +15,16 @@ public Config(ImmutableList args) this.args = args; /* TODO: process the unmatched arguments. */ } + + /* Looks up a --name=value argument; returns null if not present. */ + public String get(String name) + { + String prefix = "--" + name + "="; + for (String arg : args) + { + if (arg.startsWith(prefix)) + return arg.substring(prefix.length()); + } + return null; + } } diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 2b64ac96..b51aea2b 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -269,12 +269,12 @@ public Bytes decompress() if (inflater.finished()) break; if (n == 0) - throw new RuntimeException("failed to decompress data"); + throw new FluxEngineException("failed to decompress data"); } } catch (DataFormatException e) { - throw new RuntimeException( + throw new FluxEngineException( "failed to decompress data: " + e.getMessage()); } finally diff --git a/java/com/cowlark/fluxengine/core/FluxEngineException.java b/java/com/cowlark/fluxengine/core/FluxEngineException.java new file mode 100644 index 00000000..b8bd6289 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/FluxEngineException.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.core; + +/** + * The base exception for FluxEngine errors. + */ +public class FluxEngineException extends RuntimeException +{ + public FluxEngineException(String message) + { + super(message); + } + + public FluxEngineException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java index bfec4b88..d4ab1da0 100644 --- a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java +++ b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java @@ -7,6 +7,7 @@ import com.cowlark.fluxengine.core.ByteReader; import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; /** * Flux stream conversion helpers, ported from lib/external/greaseweazle.cc. @@ -122,7 +123,7 @@ public static Bytes greaseweazleToFluxEngine(Bytes gwdata, double clock) break; default: - throw new RuntimeException("bad opcode in Greaseweazle stream"); + throw new FluxEngineException("bad opcode in Greaseweazle stream"); } } else diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index 952b4c16..e2a55ada 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -34,6 +34,7 @@ import com.cowlark.fluxengine.core.ByteReader; import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.external.GreaseweazleUtils; import com.cowlark.fluxengine.usb.Usb.GreaseweazleProto; import com.fazecast.jSerialComm.SerialPort; @@ -61,7 +62,7 @@ private enum Version serial.setBaudRate(BAUD_NORMAL); serial.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); if (!serial.openPort()) - throw new RuntimeException("Unable to open serial port " + port); + throw new FluxEngineException("Unable to open serial port " + port); int version = getVersion(); if (version >= 29) @@ -71,7 +72,7 @@ else if (version >= 24) else if (version == 22) this.version = Version.V22; else - throw new RuntimeException(String.format( + throw new FluxEngineException(String.format( "only Greaseweazle firmware versions 22 and 24 or above are currently " + "supported, but you have version %d. Please file a bug.", version)); @@ -130,13 +131,13 @@ private void doCommand(byte[] command) Bytes buffer = readBytes(2); if ((buffer.getByte(0) & 0xff) != (command[0] & 0xff)) - throw new RuntimeException(String.format( + throw new FluxEngineException(String.format( "command returned garbage (0x%x != 0x%x with status 0x%x)", buffer.getByte(0), command[0], buffer.getByte(1))); if (buffer.getByte(1) != 0) - throw new RuntimeException("Greaseweazle error: " + gwError(buffer.getByte(1) & 0xff)); + throw new FluxEngineException("Greaseweazle error: " + gwError(buffer.getByte(1) & 0xff)); } @Override @@ -149,7 +150,7 @@ public void seek(int track) public long getRotationalPeriod(int hardSectorCount) { if (hardSectorCount != 0) - throw new RuntimeException("hard sectors are currently unsupported on the " + + throw new FluxEngineException("hard sectors are currently unsupported on the " + "Greaseweazle"); /* The Greaseweazle doesn't have a command to fetch the period directly, @@ -201,7 +202,7 @@ else if (secondIndex == -1) break; default: - throw new RuntimeException("bad opcode in Greaseweazle stream"); + throw new FluxEngineException("bad opcode in Greaseweazle stream"); } } else { @@ -216,7 +217,7 @@ else if (secondIndex == -1) } if (secondIndex == -1) - throw new RuntimeException( + throw new FluxEngineException( "unable to determine disk rotational period (is a disk in the drive?)"); doCommand(CMD_GET_FLUX_STATUS); @@ -315,7 +316,7 @@ public void testBulkRead() public Bytes read(int side, boolean synced, long readTime, long hardSectorThreshold) { if (hardSectorThreshold != 0) - throw new RuntimeException("hard sectors are currently unsupported on the " + + throw new FluxEngineException("hard sectors are currently unsupported on the " + "Greaseweazle"); doCommand(CMD_HEAD, side); @@ -369,7 +370,7 @@ public Bytes read(int side, boolean synced, long readTime, long hardSectorThresh public void write(int side, Bytes fldata, long hardSectorThreshold) { if (hardSectorThreshold != 0) - throw new RuntimeException("hard sectors are currently unsupported on the " + + throw new FluxEngineException("hard sectors are currently unsupported on the " + "Greaseweazle"); doCommand(CMD_HEAD, side); @@ -395,7 +396,7 @@ public void write(int side, Bytes fldata, long hardSectorThreshold) public void erase(int side, long hardSectorThreshold) { if (hardSectorThreshold != 0) - throw new RuntimeException("hard sectors are currently unsupported on the " + + throw new FluxEngineException("hard sectors are currently unsupported on the " + "Greaseweazle"); doCommand(CMD_HEAD, side); @@ -422,7 +423,7 @@ public void setDrive(int drive, boolean highDensity, int indexMode) @Override public void measureVoltages(Voltages[] voltages) { - throw new RuntimeException("unsupported operation on the Greaseweazle"); + throw new FluxEngineException("unsupported operation on the Greaseweazle"); } private static String gwError(int e) @@ -478,7 +479,7 @@ private Bytes readBytes(int count) int read = serial.readBytes(chunk, Math.min(chunk.length, count - bw.pos())); if (read < 0) - throw new RuntimeException("serial read failed"); + throw new FluxEngineException("serial read failed"); for (int i = 0; i < read; i++) bw.write8(chunk[i] & 0xff); } @@ -489,7 +490,7 @@ private void writeBytes(byte[] data) { int written = serial.writeBytes(data, data.length); if (written != data.length) - throw new RuntimeException("serial write failed"); + throw new FluxEngineException("serial write failed"); } private void writeBytes(Bytes data) diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 40f57874..c52130e0 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -1,6 +1,9 @@ package com.cowlark.fluxengine.usb; +import static com.google.common.base.Strings.nullToEmpty; + import com.cowlark.fluxengine.config.Config; +import com.cowlark.fluxengine.core.FluxEngineException; import com.fazecast.jSerialComm.SerialPort; import com.google.common.collect.ImmutableList; import org.usb4java.javax.Services; @@ -85,7 +88,62 @@ public static UsbDevice connect(CandidateDevice device) public static UsbDevice connect(Config config) { - return null; + return connect(selectDevice(config)); + } + + /* Selects a device to use, based on the configuration, ported from + * lib/usb/usb.cc. */ + public static CandidateDevice selectDevice(Config config) + { + ImmutableList candidates = findUsbDevices(); + if (candidates.isEmpty()) + throw new FluxEngineException( + "no devices found (is one plugged in? Do you have the " + + "appropriate permissions?"); + + String wantedSerial = config.get("usb.serial"); + if (wantedSerial != null) + { + for (CandidateDevice candidate : candidates) + { + if (candidate.serial.equals(wantedSerial)) + return candidate; + } + throw new FluxEngineException( + "serial number not found (try without one to list or " + + "autodetect devices)"); + } + + if (candidates.size() == 1) + return candidates.get(0); + + System.err.println( + "More than one device detected; use --usb.serial= to " + + "select one:"); + for (CandidateDevice candidate : candidates) + { + System.err.print(" "); + switch (candidate.type) + { + case FLUXENGINE: + System.err.printf("FluxEngine: %s\n", candidate.serial); + break; + + case GREASEWEAZLE: + System.err.printf("Greaseweazle: %s on %s\n", + candidate.serial, + nullToEmpty(candidate.serialPort)); + break; + + case APPLESAUCE: + System.err.printf("Applesauce: %s on %s\n", + candidate.serial, + nullToEmpty(candidate.serialPort)); + break; + } + } + System.exit(1); + return null; /* unreachable */ } private static void walkHub(UsbHub hub, ImmutableList.Builder candidates) From bf67f10e60a3c33fd57f7b448580dc75e1e79a73 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 5 Aug 2026 23:43:28 +0200 Subject: [PATCH 066/192] I think some of this design may actually work. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + .../fluxengine/cli/TestBandwidthCommand.java | 5 +-- .../com/cowlark/fluxengine/config/Config.java | 30 ------------------ .../fluxengine/config/ConfigParser.java | 20 ++++++++++++ .../cowlark/fluxengine/config/config.proto | 1 + java/com/cowlark/fluxengine/usb/BUILD.bazel | 1 + .../cowlark/fluxengine/usb/UsbFactory.java | 31 ++++++++++--------- 7 files changed, 42 insertions(+), 47 deletions(-) delete mode 100644 java/com/cowlark/fluxengine/config/Config.java create mode 100644 java/com/cowlark/fluxengine/config/ConfigParser.java diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index d4faea3c..92abc48c 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -14,6 +14,7 @@ java_library( plugins = [":picocli"], deps = [ "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", "@maven//:info_picocli_picocli", diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 5e47cf30..ed04e1c9 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.config.Config; +import com.cowlark.fluxengine.config.ConfigFile.ConfigProto; +import com.cowlark.fluxengine.config.ConfigParser; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; import picocli.CommandLine.Command; @@ -14,7 +15,7 @@ public class TestBandwidthCommand extends CommandWithConfig implements Runnable @Override public void run() { - Config config = new Config(unmatchedArguments()); + ConfigProto config = ConfigParser.parse(unmatchedArguments()); UsbDevice device = UsbFactory.connect(config); device.testBulkWrite(); device.testBulkRead(); diff --git a/java/com/cowlark/fluxengine/config/Config.java b/java/com/cowlark/fluxengine/config/Config.java deleted file mode 100644 index 4c1d39cd..00000000 --- a/java/com/cowlark/fluxengine/config/Config.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.cowlark.fluxengine.config; - -import com.google.common.collect.ImmutableList; - -/** - * The assembled configuration, built from the unmatched command-line - * arguments. - */ -public class Config -{ - private final ImmutableList args; - - public Config(ImmutableList args) - { - this.args = args; - /* TODO: process the unmatched arguments. */ - } - - /* Looks up a --name=value argument; returns null if not present. */ - public String get(String name) - { - String prefix = "--" + name + "="; - for (String arg : args) - { - if (arg.startsWith(prefix)) - return arg.substring(prefix.length()); - } - return null; - } -} diff --git a/java/com/cowlark/fluxengine/config/ConfigParser.java b/java/com/cowlark/fluxengine/config/ConfigParser.java new file mode 100644 index 00000000..0e2a7509 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ConfigParser.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.config; + +import com.cowlark.fluxengine.config.ConfigFile.ConfigProto; +import com.google.common.collect.ImmutableList; + +/** + * The assembled configuration, built from the unmatched command-line + * arguments. + */ +public class ConfigParser +{ + private ConfigParser() + { + } + + public static ConfigProto parse(ImmutableList args) + { + return ConfigProto.getDefaultInstance(); + } +} diff --git a/java/com/cowlark/fluxengine/config/config.proto b/java/com/cowlark/fluxengine/config/config.proto index 186dda4a..a7ef85d2 100644 --- a/java/com/cowlark/fluxengine/config/config.proto +++ b/java/com/cowlark/fluxengine/config/config.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.config"; +option java_outer_classname = "ConfigFile"; import "com/cowlark/fluxengine/decoders/decoders.proto"; import "com/cowlark/fluxengine/encoders/encoders.proto"; diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index d5c392b6..a89d43cf 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -23,6 +23,7 @@ java_library( deps = [ ":usb_java_proto", "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/external", "@maven//:com_fazecast_jSerialComm", diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index c52130e0..0dc3ae42 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -2,10 +2,12 @@ import static com.google.common.base.Strings.nullToEmpty; -import com.cowlark.fluxengine.config.Config; +import com.cowlark.fluxengine.config.ConfigFile.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; import com.fazecast.jSerialComm.SerialPort; +import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; import org.usb4java.javax.Services; import javax.usb.UsbDeviceDescriptor; import javax.usb.UsbException; @@ -86,23 +88,22 @@ public static UsbDevice connect(CandidateDevice device) return null; } - public static UsbDevice connect(Config config) + public static UsbDevice connect(ConfigProto config) { return connect(selectDevice(config)); } /* Selects a device to use, based on the configuration, ported from * lib/usb/usb.cc. */ - public static CandidateDevice selectDevice(Config config) + public static CandidateDevice selectDevice(ConfigProto config) { ImmutableList candidates = findUsbDevices(); if (candidates.isEmpty()) - throw new FluxEngineException( - "no devices found (is one plugged in? Do you have the " + - "appropriate permissions?"); + throw new FluxEngineException("no devices found (is one plugged in? Do you have the " + + "appropriate permissions?"); - String wantedSerial = config.get("usb.serial"); - if (wantedSerial != null) + String wantedSerial = config.getUsb().getSerial(); + if (Strings.isNullOrEmpty(wantedSerial)) { for (CandidateDevice candidate : candidates) { @@ -110,16 +111,14 @@ public static CandidateDevice selectDevice(Config config) return candidate; } throw new FluxEngineException( - "serial number not found (try without one to list or " + - "autodetect devices)"); + "serial number not found (try without one to list or autodetect devices)"); } if (candidates.size() == 1) - return candidates.get(0); + return Iterables.getOnlyElement(candidates); System.err.println( - "More than one device detected; use --usb.serial= to " + - "select one:"); + "More than one device detected; use --usb.serial= to " + "select one:"); for (CandidateDevice candidate : candidates) { System.err.print(" "); @@ -130,13 +129,15 @@ public static CandidateDevice selectDevice(Config config) break; case GREASEWEAZLE: - System.err.printf("Greaseweazle: %s on %s\n", + System.err.printf( + "Greaseweazle: %s on %s\n", candidate.serial, nullToEmpty(candidate.serialPort)); break; case APPLESAUCE: - System.err.printf("Applesauce: %s on %s\n", + System.err.printf( + "Applesauce: %s on %s\n", candidate.serial, nullToEmpty(candidate.serialPort)); break; From 4ed06104dd3b1744106620288985978aafb8133b Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 6 Aug 2026 00:07:50 +0200 Subject: [PATCH 067/192] More config stuff. --- MODULE.bazel | 1 + .../fluxengine/cli/TestBandwidthCommand.java | 6 +++--- java/com/cowlark/fluxengine/config/BUILD.bazel | 3 ++- .../fluxengine/config/ConfigParser.java | 18 +++++++++++++++--- 4 files changed, 21 insertions(+), 7 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 41c79da0..10e1582b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -15,6 +15,7 @@ maven.install( "com.fazecast:jSerialComm:2.11.4", "com.google.guava:guava:33.6.0-jre", "com.google.truth:truth:1.4.5", + "com.jayway.jsonpath:json-path:3.0.0", "info.picocli:picocli:4.7.7", "info.picocli:picocli-codegen:4.7.7", "javax.usb:usb-api:1.0.2", diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index ed04e1c9..03cbeed5 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,6 +1,5 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.config.ConfigFile.ConfigProto; import com.cowlark.fluxengine.config.ConfigParser; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; @@ -15,8 +14,9 @@ public class TestBandwidthCommand extends CommandWithConfig implements Runnable @Override public void run() { - ConfigProto config = ConfigParser.parse(unmatchedArguments()); - UsbDevice device = UsbFactory.connect(config); + ConfigParser config = new ConfigParser(); + config.parse(unmatchedArguments()); + UsbDevice device = UsbFactory.connect(config.build()); device.testBulkWrite(); device.testBulkRead(); } diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 6aa5e5f9..598ca219 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -1,6 +1,6 @@ +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") -load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") package(default_visibility = ["//visibility:public"]) @@ -76,5 +76,6 @@ java_library( deps = [ ":config_java_proto", "@maven//:com_google_guava_guava", + "@maven//:com_jayway_jsonpath_json_path", ], ) diff --git a/java/com/cowlark/fluxengine/config/ConfigParser.java b/java/com/cowlark/fluxengine/config/ConfigParser.java index 0e2a7509..79231035 100644 --- a/java/com/cowlark/fluxengine/config/ConfigParser.java +++ b/java/com/cowlark/fluxengine/config/ConfigParser.java @@ -9,12 +9,24 @@ */ public class ConfigParser { - private ConfigParser() + private ConfigProto proto = ConfigProto.getDefaultInstance(); + + public ConfigParser() { } - public static ConfigProto parse(ImmutableList args) + public ConfigParser parse(ImmutableList args) { - return ConfigProto.getDefaultInstance(); + return this; + } + + public ConfigParser set(String key, String value){ + return this; } + + public ConfigProto build() + { + return proto; + } + } From 7f28f8d77d2e272ab0ee066a117edc048981f3c5 Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 6 Aug 2026 01:23:36 +0200 Subject: [PATCH 068/192] Add the Flags class, because I think we're going to need it. --- .bazelrc | 5 + AGENTS.md | 20 + MODULE.bazel | 2 +- .../fluxengine/config/ConfigParser.java | 36 +- java/com/cowlark/fluxengine/core/BUILD.bazel | 11 +- java/com/cowlark/fluxengine/core/Flags.java | 465 ++++++++++++++++++ .../com/cowlark/fluxengine/core/BUILD.bazel | 10 + .../cowlark/fluxengine/core/FlagsTest.java | 107 ++++ 8 files changed, 653 insertions(+), 3 deletions(-) create mode 100644 java/com/cowlark/fluxengine/core/Flags.java create mode 100644 javatests/com/cowlark/fluxengine/core/FlagsTest.java diff --git a/.bazelrc b/.bazelrc index 5e7aafde..b1f54ff4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,5 +1,10 @@ common --java_language_version=21 +# Lombok generates builder classes that are part of the public API; the +# annotation-processor output doesn't reach the interface jar used by header +# compilation (and Turbine can't run Lombok), so disable header compilation. +build --experimental_java_header_compilation=false + # Dev machine toolchain workarounds: the system gcc is wrapped by ccache and # the system linker (lld) lives in /opt/bin, outside Bazel's default action PATH. build --repo_env=CC=/usr/bin/gcc diff --git a/AGENTS.md b/AGENTS.md index 5f7cd8f7..b7569def 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -44,6 +44,26 @@ Useful commands: or targeting a new platform. - The native binary must remain a single standalone executable (no runtime files shipped). +## Lombok builders + +- The `Flags` classes (`java/com/cowlark/fluxengine/core/Flags.java`) use Lombok builders. + Construct flag instances with + `XxxFlag.builder().setGroup(g).setNames(names).setHelpText(h).build()` + rather than constructors. `@Builder(setterPrefix = "set")` on the private all-args + constructor generates the `setX` methods (the ctor param is named `helpText` for + `setHelpText`). The `core` BUILD defines a `lombok_plugin` (`generates_api = True`, + wired via `plugins`); lombok is also a compile-time `dep` so the `import lombok.Builder;` + resolves. +- Lombok doesn't run under Turbine, and generated classes don't reach the header jar, so + `.bazelrc` sets `--experimental_java_header_compilation=false`. +- Pattern: put `@Builder` on a private all-args constructor. `@Builder.Default` can't supply + custom defaults on parameters (illegal `= value` syntax, and defaults to 0/null/false), + so normalize defaults in the constructor body (e.g. `defaultValue != null ? defaultValue : + ""`). `FlagGroup.addFlag(this)` happens in the base `Flag` constructor, so `build()` + registers the flag. +- `HexIntFlag` extends `ValueFlag` directly (not `IntFlag`): two `@Builder`s would + both generate a static `builder()` and clash via hiding. + ## Dependency injection (Dagger) - The Dagger annotation processor lives in the `wiring` package diff --git a/MODULE.bazel b/MODULE.bazel index 10e1582b..598e477f 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -11,7 +11,7 @@ use_repo(graalvm_ext, "graalvm") maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( artifacts = [ - "org.projectlombok:lombok:1.18.30", + "org.projectlombok:lombok:1.18.46", "com.fazecast:jSerialComm:2.11.4", "com.google.guava:guava:33.6.0-jre", "com.google.truth:truth:1.4.5", diff --git a/java/com/cowlark/fluxengine/config/ConfigParser.java b/java/com/cowlark/fluxengine/config/ConfigParser.java index 79231035..a47fdf1f 100644 --- a/java/com/cowlark/fluxengine/config/ConfigParser.java +++ b/java/com/cowlark/fluxengine/config/ConfigParser.java @@ -17,10 +17,44 @@ public ConfigParser() public ConfigParser parse(ImmutableList args) { + int i = 0; + while (i < args.size()) + { + String arg = args.get(i); + if (arg.startsWith("--")) + { + int eq = arg.indexOf('='); + if (eq >= 0) + { + set(arg.substring(2, eq), arg.substring(eq + 1)); + } + else if (i + 1 < args.size()) + { + set(arg.substring(2), args.get(i + 1)); + i++; + } + } + else if (arg.startsWith("-") && arg.length() > 1) + { + int eq = arg.indexOf('='); + if (eq >= 0) + { + set(arg.substring(1, eq), arg.substring(eq + 1)); + } + else if (i + 1 < args.size()) + { + set(arg.substring(1), args.get(i + 1)); + i++; + } + } + /* bare arguments are ignored */ + i++; + } return this; } - public ConfigParser set(String key, String value){ + public ConfigParser set(String key, String value) + { return this; } diff --git a/java/com/cowlark/fluxengine/core/BUILD.bazel b/java/com/cowlark/fluxengine/core/BUILD.bazel index 61f95064..e0129258 100644 --- a/java/com/cowlark/fluxengine/core/BUILD.bazel +++ b/java/com/cowlark/fluxengine/core/BUILD.bazel @@ -1,11 +1,20 @@ -load("@rules_java//java:defs.bzl", "java_library") +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") package(default_visibility = ["//visibility:public"]) +java_plugin( + name = "lombok_plugin", + processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + generates_api = True, + deps = ["@maven//:org_projectlombok_lombok"], +) + java_library( name = "core", srcs = glob(["*.java"]), + plugins = [":lombok_plugin"], deps = [ "@maven//:com_google_guava_guava", + "@maven//:org_projectlombok_lombok", ], ) diff --git a/java/com/cowlark/fluxengine/core/Flags.java b/java/com/cowlark/fluxengine/core/Flags.java new file mode 100644 index 00000000..57a15bc8 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/Flags.java @@ -0,0 +1,465 @@ +package com.cowlark.fluxengine.core; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Predicate; + +import lombok.Builder; + +/** + * Command-line flags system, ported from lib/config/flags.h. + */ +public class Flags +{ + public static class FlagGroup + { + private final List parents; + private final List flags = new ArrayList<>(); + private boolean initialised; + + public FlagGroup() + { + parents = List.of(); + } + + public FlagGroup(FlagGroup... parents) + { + this.parents = List.of(parents); + } + + public void addFlag(Flag flag) + { + flags.add(flag); + } + + public void parse(String[] argv) + { + List filenames = parseWithFilenames(argv, unused -> false); + if (!filenames.isEmpty()) + throw new FluxEngineException( + "non-option parameter '" + filenames.get(0) + "' seen (try --help)"); + } + + public List parseWithFilenames(String[] argv, Predicate callback) + { + if (initialised) + throw new IllegalStateException("called parse() twice"); + + /* Recursively accumulate a list of all flags. */ + Map flagsByName = new HashMap<>(); + recurse(this, flagsByName); + + List filenames = new ArrayList<>(); + int index = 0; + while (index < argv.length) + { + String thisArg = argv[index]; + String thatArg = (index < argv.length - 1) ? argv[index + 1] : ""; + + String key; + String value; + boolean useThat = false; + + if (thisArg.isEmpty()) + { + /* Ignore this argument. */ + } + else if (thisArg.charAt(0) != '-') + { + /* This is a filename. */ + if (!callback.test(thisArg)) + filenames.add(thisArg); + } + else + { + if (thisArg.length() > 1 && thisArg.charAt(1) == '-') + { + /* Long option. */ + int equals = thisArg.lastIndexOf('='); + if (equals >= 0) + { + key = thisArg.substring(0, equals); + value = thisArg.substring(equals + 1); + } + else + { + key = thisArg; + value = thatArg; + useThat = true; + } + } + else + { + /* Short option. */ + if (thisArg.length() > 2) + { + key = thisArg.substring(0, 2); + value = thisArg.substring(2); + } + else + { + key = thisArg; + value = thatArg; + useThat = true; + } + } + + Flag flag = flagsByName.get(key); + if (flag == null) + throw new FluxEngineException( + "unrecognised flag '" + key + "'; try --help"); + flag.set(value); + if (useThat && flag.hasArgument()) + index++; + } + + index++; + } + + return filenames; + } + + public void checkInitialised() + { + if (!initialised) + throw new IllegalStateException("Attempt to access uninitialised flag"); + } + + private void recurse(FlagGroup group, Map flagsByName) + { + if (group.initialised) + return; + + for (FlagGroup parent : group.parents) + recurse(parent, flagsByName); + + for (Flag flag : group.flags) + { + for (String name : flag.names()) + { + if (flagsByName.containsKey(name)) + throw new IllegalStateException( + "two flags use the name '" + name + "'"); + flagsByName.put(name, flag); + } + } + + group.initialised = true; + } + } + + public abstract static class Flag + { + private final FlagGroup group; + private final List names; + private final String helptext; + + protected Flag(FlagGroup group, List names, String helptext) + { + this.group = group; + this.names = List.copyOf(names); + this.helptext = helptext; + group.addFlag(this); + } + + public String name() + { + return names.get(0); + } + + public List names() + { + return names; + } + + public String helptext() + { + return helptext; + } + + public abstract boolean hasArgument(); + + public abstract String defaultValueAsString(); + + public abstract void set(String value); + + protected void checkInitialised() + { + group.checkInitialised(); + } + } + + public static class ActionFlag extends Flag + { + private final Runnable voidCallback; + private final Consumer valueCallback; + private final boolean hasArgument; + + @Builder(setterPrefix = "set") + private ActionFlag(FlagGroup group, List names, String helpText, + Runnable voidCallback, Consumer valueCallback) + { + super(group, names, helpText); + this.voidCallback = voidCallback; + this.valueCallback = valueCallback; + hasArgument = valueCallback != null; + } + + @Override + public boolean hasArgument() + { + return hasArgument; + } + + @Override + public String defaultValueAsString() + { + return ""; + } + + @Override + public void set(String value) + { + if (hasArgument) + valueCallback.accept(value); + else + voidCallback.run(); + } + } + + public static class SettableFlag extends Flag + { + private boolean value; + + @Builder(setterPrefix = "set") + private SettableFlag(FlagGroup group, List names, String helpText) + { + super(group, names, helpText); + } + + public boolean get() + { + checkInitialised(); + return value; + } + + @Override + public boolean hasArgument() + { + return false; + } + + @Override + public String defaultValueAsString() + { + return "false"; + } + + @Override + public void set(String value) + { + this.value = true; + } + } + + public abstract static class ValueFlag extends Flag + { + private T defaultValue; + private final Consumer callback; + protected T value; + private boolean isSet; + + protected ValueFlag(FlagGroup group, List names, String helptext, + T defaultValue, Consumer callback) + { + super(group, names, helptext); + this.defaultValue = defaultValue; + this.value = defaultValue; + this.callback = callback; + } + + public T get() + { + checkInitialised(); + return value; + } + + public boolean isSet() + { + return isSet; + } + + public void setDefaultValue(T value) + { + defaultValue = value; + this.value = value; + } + + protected void setValue(T value) + { + this.value = value; + callback.accept(value); + isSet = true; + } + } + + public static class StringFlag extends ValueFlag + { + @Builder(setterPrefix = "set") + private StringFlag(FlagGroup group, List names, String helpText, + String defaultValue, Consumer callback) + { + super(group, names, helpText, + defaultValue != null ? defaultValue : "", + callback != null ? callback : unused -> {}); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return value; + } + + @Override + public void set(String value) + { + setValue(value); + } + } + + public static class IntFlag extends ValueFlag + { + @Builder(setterPrefix = "set") + private IntFlag(FlagGroup group, List names, String helpText, + int defaultValue, Consumer callback) + { + super(group, names, helpText, defaultValue, + callback != null ? callback : unused -> {}); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return Integer.toString(value); + } + + @Override + public void set(String value) + { + setValue(Integer.parseInt(value)); + } + } + + public static class HexIntFlag extends ValueFlag + { + @Builder(setterPrefix = "set") + private HexIntFlag(FlagGroup group, List names, String helpText, + Integer defaultValue) + { + super(group, names, helpText, + defaultValue != null ? defaultValue : 0, unused -> {}); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return String.format("0x%x", value); + } + + @Override + public void set(String value) + { + setValue(Integer.parseInt(value)); + } + } + + public static class DoubleFlag extends ValueFlag + { + @Builder(setterPrefix = "set") + private DoubleFlag(FlagGroup group, List names, String helpText, + Double defaultValue, Consumer callback) + { + super(group, names, helpText, + defaultValue != null ? defaultValue : 1.0, + callback != null ? callback : unused -> {}); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return Double.toString(value); + } + + @Override + public void set(String value) + { + setValue(Double.parseDouble(value)); + } + } + + public static class BoolFlag extends ValueFlag + { + @Builder(setterPrefix = "set") + private BoolFlag(FlagGroup group, List names, String helpText, + boolean defaultValue, Consumer callback) + { + super(group, names, helpText, defaultValue, + callback != null ? callback : unused -> {}); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return value ? "true" : "false"; + } + + @Override + public void set(String value) + { + if (value.equals("true") || value.equals("y")) + setValue(true); + else if (value.equals("false") || value.equals("n")) + setValue(false); + else + throw new FluxEngineException( + "can't parse '" + value + "'; try 'true' or 'false'"); + } + } + + private Flags() + { + } +} diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 198af61c..fc8744f9 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -13,6 +13,16 @@ java_test( ], ) +java_test( + name = "FlagsTest", + srcs = ["FlagsTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "ByteReaderTest", srcs = ["ByteReaderTest.java"], diff --git a/javatests/com/cowlark/fluxengine/core/FlagsTest.java b/javatests/com/cowlark/fluxengine/core/FlagsTest.java new file mode 100644 index 00000000..fef2576b --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/FlagsTest.java @@ -0,0 +1,107 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.core.Flags.BoolFlag; +import com.cowlark.fluxengine.core.Flags.FlagGroup; +import com.cowlark.fluxengine.core.Flags.IntFlag; +import com.cowlark.fluxengine.core.Flags.StringFlag; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import java.util.List; + +@RunWith(JUnit4.class) +public class FlagsTest +{ + @Test + public void parsesFlags() + { + FlagGroup group = new FlagGroup(); + StringFlag config = StringFlag.builder() + .setGroup(group) + .setNames(List.of("--config", "-c")) + .setHelpText("config file") + .build(); + IntFlag count = IntFlag.builder() + .setGroup(group) + .setNames(List.of("--count")) + .setHelpText("count") + .build(); + BoolFlag verbose = BoolFlag.builder() + .setGroup(group) + .setNames(List.of("--verbose")) + .setHelpText("verbose") + .build(); + + group.parse(new String[]{"--config=foo", "-c", "bar", "--count", "7", "--verbose=true"}); + + assertThat(config.get()).isEqualTo("bar"); + assertThat(count.get()).isEqualTo(7); + assertThat(verbose.get()).isTrue(); + } + + @Test + public void parsesParentGroups() + { + FlagGroup common = new FlagGroup(); + StringFlag serial = StringFlag.builder() + .setGroup(common) + .setNames(List.of("--serial")) + .setHelpText("serial") + .build(); + FlagGroup group = new FlagGroup(common); + StringFlag thing = StringFlag.builder() + .setGroup(group) + .setNames(List.of("--thing")) + .setHelpText("thing") + .build(); + + group.parse(new String[]{"--serial=abc", "--thing=xyz"}); + + assertThat(serial.get()).isEqualTo("abc"); + assertThat(thing.get()).isEqualTo("xyz"); + } + + @Test + public void duplicateNamesThrow() + { + FlagGroup group = new FlagGroup(); + StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("one").build(); + StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("two").build(); + + assertThrows(IllegalStateException.class, () -> group.parse(new String[]{"--foo=x"})); + } + + @Test + public void unknownFlagThrows() + { + FlagGroup group = new FlagGroup(); + assertThrows(FluxEngineException.class, () -> group.parse(new String[]{"--nope=x"})); + } + + @Test + public void filenames() + { + FlagGroup group = new FlagGroup(); + List filenames = group.parseWithFilenames( + new String[]{"one.dsk", "two.dsk"}, + name -> name.equals("one.dsk")); + + assertThat(filenames).containsExactly("two.dsk"); + } + + @Test + public void uninitialisedFlagThrows() + { + FlagGroup group = new FlagGroup(); + StringFlag flag = StringFlag.builder() + .setGroup(group) + .setNames(List.of("--foo")) + .setHelpText("foo") + .build(); + + assertThrows(IllegalStateException.class, flag::get); + } +} From f17f740424bfcaa5238275889e356b2f058fc47a Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 6 Aug 2026 21:53:08 +0200 Subject: [PATCH 069/192] Refactor. --- .bazelrc | 2 +- .../fluxengine/cli/TestBandwidthCommand.java | 10 +- .../{ConfigParser.java => ConfigBuilder.java} | 10 +- .../cowlark/fluxengine/config/config.proto | 2 +- .../fluxengine/core/flags/ActionFlag.java | 46 ++++++ .../fluxengine/core/flags/BoolFlag.java | 44 ++++++ .../fluxengine/core/flags/DoubleFlag.java | 42 ++++++ .../cowlark/fluxengine/core/flags/Flag.java | 44 ++++++ .../fluxengine/core/flags/FlagGroup.java | 138 ++++++++++++++++++ .../fluxengine/core/flags/HexIntFlag.java | 36 +++++ .../fluxengine/core/flags/IntFlag.java | 38 +++++ .../fluxengine/core/flags/SettableFlag.java | 39 +++++ .../fluxengine/core/flags/StringFlag.java | 42 ++++++ .../fluxengine/core/flags/ValueFlag.java | 48 ++++++ .../cowlark/fluxengine/usb/UsbFactory.java | 2 +- 15 files changed, 531 insertions(+), 12 deletions(-) rename java/com/cowlark/fluxengine/config/{ConfigParser.java => ConfigBuilder.java} (86%) create mode 100644 java/com/cowlark/fluxengine/core/flags/ActionFlag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/BoolFlag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/DoubleFlag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/Flag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/FlagGroup.java create mode 100644 java/com/cowlark/fluxengine/core/flags/HexIntFlag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/IntFlag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/SettableFlag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/StringFlag.java create mode 100644 java/com/cowlark/fluxengine/core/flags/ValueFlag.java diff --git a/.bazelrc b/.bazelrc index b1f54ff4..23ef3cf7 100644 --- a/.bazelrc +++ b/.bazelrc @@ -3,7 +3,7 @@ common --java_language_version=21 # Lombok generates builder classes that are part of the public API; the # annotation-processor output doesn't reach the interface jar used by header # compilation (and Turbine can't run Lombok), so disable header compilation. -build --experimental_java_header_compilation=false +build --java_header_compilation=false # Dev machine toolchain workarounds: the system gcc is wrapped by ccache and # the system linker (lld) lives in /opt/bin, outside Bazel's default action PATH. diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 03cbeed5..3edc3e24 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.config.ConfigParser; +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; import picocli.CommandLine.Command; @@ -14,9 +15,10 @@ public class TestBandwidthCommand extends CommandWithConfig implements Runnable @Override public void run() { - ConfigParser config = new ConfigParser(); - config.parse(unmatchedArguments()); - UsbDevice device = UsbFactory.connect(config.build()); + ConfigProto config = new ConfigBuilder() + .build(); + + UsbDevice device = UsbFactory.connect(config); device.testBulkWrite(); device.testBulkRead(); } diff --git a/java/com/cowlark/fluxengine/config/ConfigParser.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java similarity index 86% rename from java/com/cowlark/fluxengine/config/ConfigParser.java rename to java/com/cowlark/fluxengine/config/ConfigBuilder.java index a47fdf1f..d0a4616a 100644 --- a/java/com/cowlark/fluxengine/config/ConfigParser.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -1,21 +1,21 @@ package com.cowlark.fluxengine.config; -import com.cowlark.fluxengine.config.ConfigFile.ConfigProto; +import com.cowlark.fluxengine.config.ConfigProto; import com.google.common.collect.ImmutableList; /** * The assembled configuration, built from the unmatched command-line * arguments. */ -public class ConfigParser +public class ConfigBuilder { private ConfigProto proto = ConfigProto.getDefaultInstance(); - public ConfigParser() + public ConfigBuilder() { } - public ConfigParser parse(ImmutableList args) + public ConfigBuilder parse(ImmutableList args) { int i = 0; while (i < args.size()) @@ -53,7 +53,7 @@ else if (i + 1 < args.size()) return this; } - public ConfigParser set(String key, String value) + public ConfigBuilder set(String key, String value) { return this; } diff --git a/java/com/cowlark/fluxengine/config/config.proto b/java/com/cowlark/fluxengine/config/config.proto index a7ef85d2..968d2d4e 100644 --- a/java/com/cowlark/fluxengine/config/config.proto +++ b/java/com/cowlark/fluxengine/config/config.proto @@ -1,7 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.config"; -option java_outer_classname = "ConfigFile"; +option java_multiple_files = true; import "com/cowlark/fluxengine/decoders/decoders.proto"; import "com/cowlark/fluxengine/encoders/encoders.proto"; diff --git a/java/com/cowlark/fluxengine/core/flags/ActionFlag.java b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java new file mode 100644 index 00000000..374375e9 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java @@ -0,0 +1,46 @@ +package com.cowlark.fluxengine.core.flags; + +import lombok.Builder; +import java.util.List; +import java.util.function.Consumer; + +public class ActionFlag extends Flag +{ + private final Runnable voidCallback; + private final Consumer valueCallback; + private final boolean hasArgument; + + @Builder(setterPrefix = "set") + private ActionFlag(FlagGroup group, + List names, + String helpText, + Runnable voidCallback, + Consumer valueCallback) + { + super(group, names, helpText); + this.voidCallback = voidCallback; + this.valueCallback = valueCallback; + hasArgument = valueCallback != null; + } + + @Override + public boolean hasArgument() + { + return hasArgument; + } + + @Override + public String defaultValueAsString() + { + return ""; + } + + @Override + public void set(String value) + { + if (hasArgument) + valueCallback.accept(value); + else + voidCallback.run(); + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/BoolFlag.java b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java new file mode 100644 index 00000000..feb6e921 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java @@ -0,0 +1,44 @@ +package com.cowlark.fluxengine.core.flags; + +import com.cowlark.fluxengine.core.FluxEngineException; +import lombok.Builder; +import java.util.List; +import java.util.function.Consumer; + +public class BoolFlag extends ValueFlag +{ + @Builder(setterPrefix = "set") + private BoolFlag(FlagGroup group, + List names, + String helpText, + boolean defaultValue, + Consumer callback) + { + super( + group, names, helpText, defaultValue, callback != null ? callback : unused -> { + }); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return value ? "true" : "false"; + } + + @Override + public void set(String value) + { + if (value.equals("true") || value.equals("y")) + setValue(true); + else if (value.equals("false") || value.equals("n")) + setValue(false); + else + throw new FluxEngineException("can't parse '" + value + "'; try 'true' or 'false'"); + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java new file mode 100644 index 00000000..2512f71c --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java @@ -0,0 +1,42 @@ +package com.cowlark.fluxengine.core.flags; + +import lombok.Builder; +import java.util.List; +import java.util.function.Consumer; + +public class DoubleFlag extends ValueFlag +{ + @Builder(setterPrefix = "set") + private DoubleFlag(FlagGroup group, + List names, + String helpText, + Double defaultValue, + Consumer callback) + { + super( + group, + names, + helpText, + defaultValue != null ? defaultValue : 1.0, + callback != null ? callback : unused -> { + }); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return Double.toString(value); + } + + @Override + public void set(String value) + { + setValue(Double.parseDouble(value)); + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/Flag.java b/java/com/cowlark/fluxengine/core/flags/Flag.java new file mode 100644 index 00000000..61e72a6e --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/Flag.java @@ -0,0 +1,44 @@ +package com.cowlark.fluxengine.core.flags; + +import java.util.List; + +public abstract class Flag +{ + private final FlagGroup group; + private final List names; + private final String helptext; + + protected Flag(FlagGroup group, List names, String helptext) + { + this.group = group; + this.names = List.copyOf(names); + this.helptext = helptext; + group.addFlag(this); + } + + public String name() + { + return names.get(0); + } + + public List names() + { + return names; + } + + public String helptext() + { + return helptext; + } + + public abstract boolean hasArgument(); + + public abstract String defaultValueAsString(); + + public abstract void set(String value); + + protected void checkInitialised() + { + group.checkInitialised(); + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/FlagGroup.java b/java/com/cowlark/fluxengine/core/flags/FlagGroup.java new file mode 100644 index 00000000..f31339c6 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/FlagGroup.java @@ -0,0 +1,138 @@ +package com.cowlark.fluxengine.core.flags; + +import com.cowlark.fluxengine.core.FluxEngineException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Predicate; + +public class FlagGroup +{ + private final List parents; + private final List flags = new ArrayList<>(); + private boolean initialised; + + public FlagGroup() + { + parents = List.of(); + } + + public FlagGroup(FlagGroup... parents) + { + this.parents = List.of(parents); + } + + public void addFlag(Flag flag) + { + flags.add(flag); + } + + public void parse(String[] argv) + { + List filenames = parseWithFilenames(argv, unused -> false); + if (!filenames.isEmpty()) + throw new FluxEngineException( + "non-option parameter '" + filenames.get(0) + "' seen (try --help)"); + } + + public List parseWithFilenames(String[] argv, Predicate callback) + { + if (initialised) + throw new IllegalStateException("called parse() twice"); + + /* Recursively accumulate a list of all flags. */ + Map flagsByName = new HashMap<>(); + recurse(this, flagsByName); + + List filenames = new ArrayList<>(); + int index = 0; + while (index < argv.length) + { + String thisArg = argv[index]; + String thatArg = (index < argv.length - 1) ? argv[index + 1] : ""; + + String key; + String value; + boolean useThat = false; + + if (thisArg.isEmpty()) + { + /* Ignore this argument. */ + } else if (thisArg.charAt(0) != '-') + { + /* This is a filename. */ + if (!callback.test(thisArg)) + filenames.add(thisArg); + } else + { + if (thisArg.length() > 1 && thisArg.charAt(1) == '-') + { + /* Long option. */ + int equals = thisArg.lastIndexOf('='); + if (equals >= 0) + { + key = thisArg.substring(0, equals); + value = thisArg.substring(equals + 1); + } else + { + key = thisArg; + value = thatArg; + useThat = true; + } + } else + { + /* Short option. */ + if (thisArg.length() > 2) + { + key = thisArg.substring(0, 2); + value = thisArg.substring(2); + } else + { + key = thisArg; + value = thatArg; + useThat = true; + } + } + + Flag flag = flagsByName.get(key); + if (flag == null) + throw new FluxEngineException("unrecognised flag '" + key + "'; try --help"); + flag.set(value); + if (useThat && flag.hasArgument()) + index++; + } + + index++; + } + + return filenames; + } + + public void checkInitialised() + { + if (!initialised) + throw new IllegalStateException("Attempt to access uninitialised flag"); + } + + private void recurse(FlagGroup group, Map flagsByName) + { + if (group.initialised) + return; + + for (FlagGroup parent : group.parents) + recurse(parent, flagsByName); + + for (Flag flag : group.flags) + { + for (String name : flag.names()) + { + if (flagsByName.containsKey(name)) + throw new IllegalStateException("two flags use the name '" + name + "'"); + flagsByName.put(name, flag); + } + } + + group.initialised = true; + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java new file mode 100644 index 00000000..55750e90 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java @@ -0,0 +1,36 @@ +package com.cowlark.fluxengine.core.flags; + +import lombok.Builder; +import java.util.List; + +public class HexIntFlag extends ValueFlag +{ + @Builder(setterPrefix = "set") + private HexIntFlag(FlagGroup group, + List names, + String helpText, + Integer defaultValue) + { + super( + group, names, helpText, defaultValue != null ? defaultValue : 0, unused -> { + }); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return String.format("0x%x", value); + } + + @Override + public void set(String value) + { + setValue(Integer.parseInt(value)); + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/IntFlag.java b/java/com/cowlark/fluxengine/core/flags/IntFlag.java new file mode 100644 index 00000000..2323c6cf --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/IntFlag.java @@ -0,0 +1,38 @@ +package com.cowlark.fluxengine.core.flags; + +import lombok.Builder; +import java.util.List; +import java.util.function.Consumer; + +public class IntFlag extends ValueFlag +{ + @Builder(setterPrefix = "set") + private IntFlag(FlagGroup group, + List names, + String helpText, + int defaultValue, + Consumer callback) + { + super( + group, names, helpText, defaultValue, callback != null ? callback : unused -> { + }); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return Integer.toString(value); + } + + @Override + public void set(String value) + { + setValue(Integer.parseInt(value)); + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/SettableFlag.java b/java/com/cowlark/fluxengine/core/flags/SettableFlag.java new file mode 100644 index 00000000..1971ea5f --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/SettableFlag.java @@ -0,0 +1,39 @@ +package com.cowlark.fluxengine.core.flags; + +import lombok.Builder; +import java.util.List; + +public class SettableFlag extends Flag +{ + private boolean value; + + @Builder(setterPrefix = "set") + private SettableFlag(FlagGroup group, List names, String helpText) + { + super(group, names, helpText); + } + + public boolean get() + { + checkInitialised(); + return value; + } + + @Override + public boolean hasArgument() + { + return false; + } + + @Override + public String defaultValueAsString() + { + return "false"; + } + + @Override + public void set(String value) + { + this.value = true; + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/StringFlag.java b/java/com/cowlark/fluxengine/core/flags/StringFlag.java new file mode 100644 index 00000000..a919640b --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/StringFlag.java @@ -0,0 +1,42 @@ +package com.cowlark.fluxengine.core.flags; + +import lombok.Builder; +import java.util.List; +import java.util.function.Consumer; + +public class StringFlag extends ValueFlag +{ + @Builder(setterPrefix = "set") + private StringFlag(FlagGroup group, + List names, + String helpText, + String defaultValue, + Consumer callback) + { + super( + group, + names, + helpText, + defaultValue != null ? defaultValue : "", + callback != null ? callback : unused -> { + }); + } + + @Override + public boolean hasArgument() + { + return true; + } + + @Override + public String defaultValueAsString() + { + return value; + } + + @Override + public void set(String value) + { + setValue(value); + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/ValueFlag.java b/java/com/cowlark/fluxengine/core/flags/ValueFlag.java new file mode 100644 index 00000000..484985d7 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/ValueFlag.java @@ -0,0 +1,48 @@ +package com.cowlark.fluxengine.core.flags; + +import java.util.List; +import java.util.function.Consumer; + +public abstract class ValueFlag extends Flag +{ + private T defaultValue; + private final Consumer callback; + protected T value; + private boolean isSet; + + protected ValueFlag(FlagGroup group, + List names, + String helptext, + T defaultValue, + Consumer callback) + { + super(group, names, helptext); + this.defaultValue = defaultValue; + this.value = defaultValue; + this.callback = callback; + } + + public T get() + { + checkInitialised(); + return value; + } + + public boolean isSet() + { + return isSet; + } + + public void setDefaultValue(T value) + { + defaultValue = value; + this.value = value; + } + + protected void setValue(T value) + { + this.value = value; + callback.accept(value); + isSet = true; + } +} diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 0dc3ae42..88b3eccd 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -2,7 +2,7 @@ import static com.google.common.base.Strings.nullToEmpty; -import com.cowlark.fluxengine.config.ConfigFile.ConfigProto; +import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; import com.fazecast.jSerialComm.SerialPort; import com.google.common.base.Strings; From 8bf917936c4310ecee1a1f91c3a79dbf7c89cc17 Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 6 Aug 2026 23:02:00 +0200 Subject: [PATCH 070/192] Rework flags. Again. --- AGENTS.md | 32 +- java/com/cowlark/fluxengine/core/BUILD.bazel | 11 +- java/com/cowlark/fluxengine/core/Flags.java | 465 ------------------ .../fluxengine/core/flags/ActionFlag.java | 9 +- .../cowlark/fluxengine/core/flags/BUILD.bazel | 21 + .../fluxengine/core/flags/BoolFlag.java | 17 +- .../fluxengine/core/flags/DoubleFlag.java | 19 +- .../fluxengine/core/flags/FlagGroup.java | 106 +--- .../cowlark/fluxengine/core/flags/Flags.java | 115 +++++ .../fluxengine/core/flags/HexIntFlag.java | 13 +- .../fluxengine/core/flags/IntFlag.java | 14 +- .../fluxengine/core/flags/StringFlag.java | 19 +- .../fluxengine/core/flags/ValueFlag.java | 9 +- .../com/cowlark/fluxengine/core/BUILD.bazel | 10 - .../cowlark/fluxengine/core/FlagsTest.java | 107 ---- .../cowlark/fluxengine/core/flags/BUILD.bazel | 14 + .../fluxengine/core/flags/FlagsTest.java | 139 ++++++ 17 files changed, 371 insertions(+), 749 deletions(-) delete mode 100644 java/com/cowlark/fluxengine/core/Flags.java create mode 100644 java/com/cowlark/fluxengine/core/flags/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/core/flags/Flags.java delete mode 100644 javatests/com/cowlark/fluxengine/core/FlagsTest.java create mode 100644 javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java diff --git a/AGENTS.md b/AGENTS.md index b7569def..37a2896c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,8 @@ GraalVM extension/rule). - Java sources: `java/` (standard Bazel layout, `com` is a direct child of `java`) - Java tests: `javatests/` - Packages (Java): `com.cowlark.fluxengine` (Main, FluxEngineComponent), - `com.cowlark.fluxengine.cli`, `com.cowlark.fluxengine.usb`, `com.cowlark.fluxengine.wiring` + `com.cowlark.fluxengine.cli`, `com.cowlark.fluxengine.core`, `com.cowlark.fluxengine.core.flags`, + `com.cowlark.fluxengine.usb`, `com.cowlark.fluxengine.wiring` - Each package directory has its own `BUILD.bazel`. Useful commands: @@ -46,14 +47,14 @@ Useful commands: ## Lombok builders -- The `Flags` classes (`java/com/cowlark/fluxengine/core/Flags.java`) use Lombok builders. - Construct flag instances with - `XxxFlag.builder().setGroup(g).setNames(names).setHelpText(h).build()` - rather than constructors. `@Builder(setterPrefix = "set")` on the private all-args - constructor generates the `setX` methods (the ctor param is named `helpText` for - `setHelpText`). The `core` BUILD defines a `lombok_plugin` (`generates_api = True`, - wired via `plugins`); lombok is also a compile-time `dep` so the `import lombok.Builder;` - resolves. +- The flag classes live in `com.cowlark.fluxengine.core.flags` (one class per file: `Flag`, + `FlagGroup`, `Flags`, `ActionFlag`, `SettableFlag`, `ValueFlag`, `StringFlag`, `IntFlag`, + `HexIntFlag`, `DoubleFlag`, `BoolFlag`). Construct flag instances with + `XxxFlag.builder().setGroup(g).setNames(names).setHelpText(h).build()` rather than + constructors. `@Builder(setterPrefix = "set")` on the private all-args constructor + generates the `setX` methods (the ctor param is named `helpText` for `setHelpText`). The + `core/flags` BUILD defines a `lombok_plugin` (`generates_api = True`, wired via + `plugins`); lombok is also a compile-time `dep` so the `import lombok.Builder;` resolves. - Lombok doesn't run under Turbine, and generated classes don't reach the header jar, so `.bazelrc` sets `--experimental_java_header_compilation=false`. - Pattern: put `@Builder` on a private all-args constructor. `@Builder.Default` can't supply @@ -64,6 +65,19 @@ Useful commands: - `HexIntFlag` extends `ValueFlag` directly (not `IntFlag`): two `@Builder`s would both generate a static `builder()` and clash via hiding. +## Flags parsing + +- Parsing is done by the static `Flags.parse(String[] argv, FlagGroup... groups)` / + `Flags.parseWithFilenames(String[] argv, Predicate callback, FlagGroup... groups)`. + It first runs `FlagGroup.initialise` over every root group (recursive duplicate-name check + into a shared `Set`, marking groups initialised), then walks argv and resolves each flag via + `FlagGroup.findFlag(key)`, which scans the group's own flags then recurses into its parents. + `Flags.parse` calls `flag.set(value)` and only consumes a space-separated value when + `useThat && flag.hasArgument()`. `findFlag` is public and overridable so a group can + intercept/absorb flags (e.g. a config group) before they fall through to its parents. +- `parseWithFilenames` returns `ImmutableList` (Guava). Duplicate flag names throw + `IllegalStateException`; unknown flags throw `FluxEngineException`. + ## Dependency injection (Dagger) - The Dagger annotation processor lives in the `wiring` package diff --git a/java/com/cowlark/fluxengine/core/BUILD.bazel b/java/com/cowlark/fluxengine/core/BUILD.bazel index e0129258..61f95064 100644 --- a/java/com/cowlark/fluxengine/core/BUILD.bazel +++ b/java/com/cowlark/fluxengine/core/BUILD.bazel @@ -1,20 +1,11 @@ -load("@rules_java//java:defs.bzl", "java_library", "java_plugin") +load("@rules_java//java:defs.bzl", "java_library") package(default_visibility = ["//visibility:public"]) -java_plugin( - name = "lombok_plugin", - processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", - generates_api = True, - deps = ["@maven//:org_projectlombok_lombok"], -) - java_library( name = "core", srcs = glob(["*.java"]), - plugins = [":lombok_plugin"], deps = [ "@maven//:com_google_guava_guava", - "@maven//:org_projectlombok_lombok", ], ) diff --git a/java/com/cowlark/fluxengine/core/Flags.java b/java/com/cowlark/fluxengine/core/Flags.java deleted file mode 100644 index 57a15bc8..00000000 --- a/java/com/cowlark/fluxengine/core/Flags.java +++ /dev/null @@ -1,465 +0,0 @@ -package com.cowlark.fluxengine.core; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.function.Predicate; - -import lombok.Builder; - -/** - * Command-line flags system, ported from lib/config/flags.h. - */ -public class Flags -{ - public static class FlagGroup - { - private final List parents; - private final List flags = new ArrayList<>(); - private boolean initialised; - - public FlagGroup() - { - parents = List.of(); - } - - public FlagGroup(FlagGroup... parents) - { - this.parents = List.of(parents); - } - - public void addFlag(Flag flag) - { - flags.add(flag); - } - - public void parse(String[] argv) - { - List filenames = parseWithFilenames(argv, unused -> false); - if (!filenames.isEmpty()) - throw new FluxEngineException( - "non-option parameter '" + filenames.get(0) + "' seen (try --help)"); - } - - public List parseWithFilenames(String[] argv, Predicate callback) - { - if (initialised) - throw new IllegalStateException("called parse() twice"); - - /* Recursively accumulate a list of all flags. */ - Map flagsByName = new HashMap<>(); - recurse(this, flagsByName); - - List filenames = new ArrayList<>(); - int index = 0; - while (index < argv.length) - { - String thisArg = argv[index]; - String thatArg = (index < argv.length - 1) ? argv[index + 1] : ""; - - String key; - String value; - boolean useThat = false; - - if (thisArg.isEmpty()) - { - /* Ignore this argument. */ - } - else if (thisArg.charAt(0) != '-') - { - /* This is a filename. */ - if (!callback.test(thisArg)) - filenames.add(thisArg); - } - else - { - if (thisArg.length() > 1 && thisArg.charAt(1) == '-') - { - /* Long option. */ - int equals = thisArg.lastIndexOf('='); - if (equals >= 0) - { - key = thisArg.substring(0, equals); - value = thisArg.substring(equals + 1); - } - else - { - key = thisArg; - value = thatArg; - useThat = true; - } - } - else - { - /* Short option. */ - if (thisArg.length() > 2) - { - key = thisArg.substring(0, 2); - value = thisArg.substring(2); - } - else - { - key = thisArg; - value = thatArg; - useThat = true; - } - } - - Flag flag = flagsByName.get(key); - if (flag == null) - throw new FluxEngineException( - "unrecognised flag '" + key + "'; try --help"); - flag.set(value); - if (useThat && flag.hasArgument()) - index++; - } - - index++; - } - - return filenames; - } - - public void checkInitialised() - { - if (!initialised) - throw new IllegalStateException("Attempt to access uninitialised flag"); - } - - private void recurse(FlagGroup group, Map flagsByName) - { - if (group.initialised) - return; - - for (FlagGroup parent : group.parents) - recurse(parent, flagsByName); - - for (Flag flag : group.flags) - { - for (String name : flag.names()) - { - if (flagsByName.containsKey(name)) - throw new IllegalStateException( - "two flags use the name '" + name + "'"); - flagsByName.put(name, flag); - } - } - - group.initialised = true; - } - } - - public abstract static class Flag - { - private final FlagGroup group; - private final List names; - private final String helptext; - - protected Flag(FlagGroup group, List names, String helptext) - { - this.group = group; - this.names = List.copyOf(names); - this.helptext = helptext; - group.addFlag(this); - } - - public String name() - { - return names.get(0); - } - - public List names() - { - return names; - } - - public String helptext() - { - return helptext; - } - - public abstract boolean hasArgument(); - - public abstract String defaultValueAsString(); - - public abstract void set(String value); - - protected void checkInitialised() - { - group.checkInitialised(); - } - } - - public static class ActionFlag extends Flag - { - private final Runnable voidCallback; - private final Consumer valueCallback; - private final boolean hasArgument; - - @Builder(setterPrefix = "set") - private ActionFlag(FlagGroup group, List names, String helpText, - Runnable voidCallback, Consumer valueCallback) - { - super(group, names, helpText); - this.voidCallback = voidCallback; - this.valueCallback = valueCallback; - hasArgument = valueCallback != null; - } - - @Override - public boolean hasArgument() - { - return hasArgument; - } - - @Override - public String defaultValueAsString() - { - return ""; - } - - @Override - public void set(String value) - { - if (hasArgument) - valueCallback.accept(value); - else - voidCallback.run(); - } - } - - public static class SettableFlag extends Flag - { - private boolean value; - - @Builder(setterPrefix = "set") - private SettableFlag(FlagGroup group, List names, String helpText) - { - super(group, names, helpText); - } - - public boolean get() - { - checkInitialised(); - return value; - } - - @Override - public boolean hasArgument() - { - return false; - } - - @Override - public String defaultValueAsString() - { - return "false"; - } - - @Override - public void set(String value) - { - this.value = true; - } - } - - public abstract static class ValueFlag extends Flag - { - private T defaultValue; - private final Consumer callback; - protected T value; - private boolean isSet; - - protected ValueFlag(FlagGroup group, List names, String helptext, - T defaultValue, Consumer callback) - { - super(group, names, helptext); - this.defaultValue = defaultValue; - this.value = defaultValue; - this.callback = callback; - } - - public T get() - { - checkInitialised(); - return value; - } - - public boolean isSet() - { - return isSet; - } - - public void setDefaultValue(T value) - { - defaultValue = value; - this.value = value; - } - - protected void setValue(T value) - { - this.value = value; - callback.accept(value); - isSet = true; - } - } - - public static class StringFlag extends ValueFlag - { - @Builder(setterPrefix = "set") - private StringFlag(FlagGroup group, List names, String helpText, - String defaultValue, Consumer callback) - { - super(group, names, helpText, - defaultValue != null ? defaultValue : "", - callback != null ? callback : unused -> {}); - } - - @Override - public boolean hasArgument() - { - return true; - } - - @Override - public String defaultValueAsString() - { - return value; - } - - @Override - public void set(String value) - { - setValue(value); - } - } - - public static class IntFlag extends ValueFlag - { - @Builder(setterPrefix = "set") - private IntFlag(FlagGroup group, List names, String helpText, - int defaultValue, Consumer callback) - { - super(group, names, helpText, defaultValue, - callback != null ? callback : unused -> {}); - } - - @Override - public boolean hasArgument() - { - return true; - } - - @Override - public String defaultValueAsString() - { - return Integer.toString(value); - } - - @Override - public void set(String value) - { - setValue(Integer.parseInt(value)); - } - } - - public static class HexIntFlag extends ValueFlag - { - @Builder(setterPrefix = "set") - private HexIntFlag(FlagGroup group, List names, String helpText, - Integer defaultValue) - { - super(group, names, helpText, - defaultValue != null ? defaultValue : 0, unused -> {}); - } - - @Override - public boolean hasArgument() - { - return true; - } - - @Override - public String defaultValueAsString() - { - return String.format("0x%x", value); - } - - @Override - public void set(String value) - { - setValue(Integer.parseInt(value)); - } - } - - public static class DoubleFlag extends ValueFlag - { - @Builder(setterPrefix = "set") - private DoubleFlag(FlagGroup group, List names, String helpText, - Double defaultValue, Consumer callback) - { - super(group, names, helpText, - defaultValue != null ? defaultValue : 1.0, - callback != null ? callback : unused -> {}); - } - - @Override - public boolean hasArgument() - { - return true; - } - - @Override - public String defaultValueAsString() - { - return Double.toString(value); - } - - @Override - public void set(String value) - { - setValue(Double.parseDouble(value)); - } - } - - public static class BoolFlag extends ValueFlag - { - @Builder(setterPrefix = "set") - private BoolFlag(FlagGroup group, List names, String helpText, - boolean defaultValue, Consumer callback) - { - super(group, names, helpText, defaultValue, - callback != null ? callback : unused -> {}); - } - - @Override - public boolean hasArgument() - { - return true; - } - - @Override - public String defaultValueAsString() - { - return value ? "true" : "false"; - } - - @Override - public void set(String value) - { - if (value.equals("true") || value.equals("y")) - setValue(true); - else if (value.equals("false") || value.equals("n")) - setValue(false); - else - throw new FluxEngineException( - "can't parse '" + value + "'; try 'true' or 'false'"); - } - } - - private Flags() - { - } -} diff --git a/java/com/cowlark/fluxengine/core/flags/ActionFlag.java b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java index 374375e9..39acd634 100644 --- a/java/com/cowlark/fluxengine/core/flags/ActionFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java @@ -1,8 +1,8 @@ package com.cowlark.fluxengine.core.flags; -import lombok.Builder; import java.util.List; import java.util.function.Consumer; +import lombok.Builder; public class ActionFlag extends Flag { @@ -11,11 +11,8 @@ public class ActionFlag extends Flag private final boolean hasArgument; @Builder(setterPrefix = "set") - private ActionFlag(FlagGroup group, - List names, - String helpText, - Runnable voidCallback, - Consumer valueCallback) + private ActionFlag(FlagGroup group, List names, String helpText, + Runnable voidCallback, Consumer valueCallback) { super(group, names, helpText); this.voidCallback = voidCallback; diff --git a/java/com/cowlark/fluxengine/core/flags/BUILD.bazel b/java/com/cowlark/fluxengine/core/flags/BUILD.bazel new file mode 100644 index 00000000..4679ecef --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") + +package(default_visibility = ["//visibility:public"]) + +java_plugin( + name = "lombok_plugin", + processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + generates_api = True, + deps = ["@maven//:org_projectlombok_lombok"], +) + +java_library( + name = "flags", + srcs = glob(["*.java"]), + plugins = [":lombok_plugin"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_guava_guava", + "@maven//:org_projectlombok_lombok", + ], +) diff --git a/java/com/cowlark/fluxengine/core/flags/BoolFlag.java b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java index feb6e921..3d0059ae 100644 --- a/java/com/cowlark/fluxengine/core/flags/BoolFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java @@ -1,22 +1,18 @@ package com.cowlark.fluxengine.core.flags; import com.cowlark.fluxengine.core.FluxEngineException; -import lombok.Builder; import java.util.List; import java.util.function.Consumer; +import lombok.Builder; public class BoolFlag extends ValueFlag { @Builder(setterPrefix = "set") - private BoolFlag(FlagGroup group, - List names, - String helpText, - boolean defaultValue, - Consumer callback) + private BoolFlag(FlagGroup group, List names, String helpText, + boolean defaultValue, Consumer callback) { - super( - group, names, helpText, defaultValue, callback != null ? callback : unused -> { - }); + super(group, names, helpText, defaultValue, + callback != null ? callback : unused -> {}); } @Override @@ -39,6 +35,7 @@ public void set(String value) else if (value.equals("false") || value.equals("n")) setValue(false); else - throw new FluxEngineException("can't parse '" + value + "'; try 'true' or 'false'"); + throw new FluxEngineException( + "can't parse '" + value + "'; try 'true' or 'false'"); } } diff --git a/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java index 2512f71c..18b1f2c6 100644 --- a/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java @@ -1,25 +1,18 @@ package com.cowlark.fluxengine.core.flags; -import lombok.Builder; import java.util.List; import java.util.function.Consumer; +import lombok.Builder; public class DoubleFlag extends ValueFlag { @Builder(setterPrefix = "set") - private DoubleFlag(FlagGroup group, - List names, - String helpText, - Double defaultValue, - Consumer callback) + private DoubleFlag(FlagGroup group, List names, String helpText, + Double defaultValue, Consumer callback) { - super( - group, - names, - helpText, - defaultValue != null ? defaultValue : 1.0, - callback != null ? callback : unused -> { - }); + super(group, names, helpText, + defaultValue != null ? defaultValue : 1.0, + callback != null ? callback : unused -> {}); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/FlagGroup.java b/java/com/cowlark/fluxengine/core/flags/FlagGroup.java index f31339c6..e2b2ade4 100644 --- a/java/com/cowlark/fluxengine/core/flags/FlagGroup.java +++ b/java/com/cowlark/fluxengine/core/flags/FlagGroup.java @@ -1,26 +1,27 @@ package com.cowlark.fluxengine.core.flags; -import com.cowlark.fluxengine.core.FluxEngineException; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.function.Predicate; +import java.util.Set; +import lombok.AccessLevel; +import lombok.Getter; public class FlagGroup { - private final List parents; + private final ImmutableList parents; private final List flags = new ArrayList<>(); + @Getter(AccessLevel.PACKAGE) private boolean initialised; public FlagGroup() { - parents = List.of(); + parents = ImmutableList.of(); } public FlagGroup(FlagGroup... parents) { - this.parents = List.of(parents); + this.parents = ImmutableList.copyOf(parents); } public void addFlag(Flag flag) @@ -28,85 +29,25 @@ public void addFlag(Flag flag) flags.add(flag); } - public void parse(String[] argv) + public Flag findFlag(String key) { - List filenames = parseWithFilenames(argv, unused -> false); - if (!filenames.isEmpty()) - throw new FluxEngineException( - "non-option parameter '" + filenames.get(0) + "' seen (try --help)"); - } - - public List parseWithFilenames(String[] argv, Predicate callback) - { - if (initialised) - throw new IllegalStateException("called parse() twice"); - - /* Recursively accumulate a list of all flags. */ - Map flagsByName = new HashMap<>(); - recurse(this, flagsByName); - - List filenames = new ArrayList<>(); - int index = 0; - while (index < argv.length) + for (Flag flag : flags) { - String thisArg = argv[index]; - String thatArg = (index < argv.length - 1) ? argv[index + 1] : ""; - - String key; - String value; - boolean useThat = false; - - if (thisArg.isEmpty()) - { - /* Ignore this argument. */ - } else if (thisArg.charAt(0) != '-') - { - /* This is a filename. */ - if (!callback.test(thisArg)) - filenames.add(thisArg); - } else + for (String name : flag.names()) { - if (thisArg.length() > 1 && thisArg.charAt(1) == '-') - { - /* Long option. */ - int equals = thisArg.lastIndexOf('='); - if (equals >= 0) - { - key = thisArg.substring(0, equals); - value = thisArg.substring(equals + 1); - } else - { - key = thisArg; - value = thatArg; - useThat = true; - } - } else - { - /* Short option. */ - if (thisArg.length() > 2) - { - key = thisArg.substring(0, 2); - value = thisArg.substring(2); - } else - { - key = thisArg; - value = thatArg; - useThat = true; - } - } - - Flag flag = flagsByName.get(key); - if (flag == null) - throw new FluxEngineException("unrecognised flag '" + key + "'; try --help"); - flag.set(value); - if (useThat && flag.hasArgument()) - index++; + if (name.equals(key)) + return flag; } + } - index++; + for (FlagGroup parent : parents) + { + Flag flag = parent.findFlag(key); + if (flag != null) + return flag; } - return filenames; + return null; } public void checkInitialised() @@ -115,21 +56,20 @@ public void checkInitialised() throw new IllegalStateException("Attempt to access uninitialised flag"); } - private void recurse(FlagGroup group, Map flagsByName) + static void initialise(FlagGroup group, Set names) { if (group.initialised) return; for (FlagGroup parent : group.parents) - recurse(parent, flagsByName); + initialise(parent, names); for (Flag flag : group.flags) { for (String name : flag.names()) { - if (flagsByName.containsKey(name)) + if (!names.add(name)) throw new IllegalStateException("two flags use the name '" + name + "'"); - flagsByName.put(name, flag); } } diff --git a/java/com/cowlark/fluxengine/core/flags/Flags.java b/java/com/cowlark/fluxengine/core/flags/Flags.java new file mode 100644 index 00000000..be030c70 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/flags/Flags.java @@ -0,0 +1,115 @@ +package com.cowlark.fluxengine.core.flags; + +import com.cowlark.fluxengine.core.FluxEngineException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; +import java.util.Set; +import java.util.function.Predicate; + +/** + * Command-line flags system, ported from lib/config/flags.{h,cc}. + */ +public class Flags +{ + public static void parse(String[] argv, FlagGroup... groups) + { + ImmutableList filenames = parseWithFilenames(argv, unused -> false, groups); + if (!filenames.isEmpty()) + throw new FluxEngineException( + "non-option parameter '" + filenames.get(0) + "' seen (try --help)"); + } + + public static ImmutableList parseWithFilenames( + String[] argv, Predicate callback, FlagGroup... groups) + { + if (groups.length == 0) + throw new IllegalArgumentException("no flag groups"); + if (groups[0].isInitialised()) + throw new IllegalStateException("called parse() twice"); + + /* Recursively accumulate a list of all flag names, checking for duplicates. */ + Set names = Sets.newHashSet(); + for (FlagGroup group : groups) + FlagGroup.initialise(group, names); + + ImmutableList.Builder filenames = ImmutableList.builder(); + int index = 0; + while (index < argv.length) + { + String thisArg = argv[index]; + String thatArg = (index < argv.length - 1) ? argv[index + 1] : ""; + + String key; + String value; + boolean useThat = false; + + if (thisArg.isEmpty()) + { + /* Ignore this argument. */ + } + else if (thisArg.charAt(0) != '-') + { + /* This is a filename. */ + if (!callback.test(thisArg)) + filenames.add(thisArg); + } + else + { + if (thisArg.length() > 1 && thisArg.charAt(1) == '-') + { + /* Long option. */ + int equals = thisArg.lastIndexOf('='); + if (equals >= 0) + { + key = thisArg.substring(0, equals); + value = thisArg.substring(equals + 1); + } + else + { + key = thisArg; + value = thatArg; + useThat = true; + } + } + else + { + /* Short option. */ + if (thisArg.length() > 2) + { + key = thisArg.substring(0, 2); + value = thisArg.substring(2); + } + else + { + key = thisArg; + value = thatArg; + useThat = true; + } + } + + Flag flag = null; + for (FlagGroup group : groups) + { + flag = group.findFlag(key); + if (flag != null) + break; + } + + if (flag == null) + throw new FluxEngineException( + "unrecognised flag '" + key + "'; try --help"); + flag.set(value); + if (useThat && flag.hasArgument()) + index++; + } + + index++; + } + + return filenames.build(); + } + + private Flags() + { + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java index 55750e90..af3fef18 100644 --- a/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java @@ -1,19 +1,16 @@ package com.cowlark.fluxengine.core.flags; -import lombok.Builder; import java.util.List; +import lombok.Builder; public class HexIntFlag extends ValueFlag { @Builder(setterPrefix = "set") - private HexIntFlag(FlagGroup group, - List names, - String helpText, - Integer defaultValue) + private HexIntFlag(FlagGroup group, List names, String helpText, + Integer defaultValue) { - super( - group, names, helpText, defaultValue != null ? defaultValue : 0, unused -> { - }); + super(group, names, helpText, + defaultValue != null ? defaultValue : 0, unused -> {}); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/IntFlag.java b/java/com/cowlark/fluxengine/core/flags/IntFlag.java index 2323c6cf..83f0f704 100644 --- a/java/com/cowlark/fluxengine/core/flags/IntFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/IntFlag.java @@ -1,21 +1,17 @@ package com.cowlark.fluxengine.core.flags; -import lombok.Builder; import java.util.List; import java.util.function.Consumer; +import lombok.Builder; public class IntFlag extends ValueFlag { @Builder(setterPrefix = "set") - private IntFlag(FlagGroup group, - List names, - String helpText, - int defaultValue, - Consumer callback) + private IntFlag(FlagGroup group, List names, String helpText, + int defaultValue, Consumer callback) { - super( - group, names, helpText, defaultValue, callback != null ? callback : unused -> { - }); + super(group, names, helpText, defaultValue, + callback != null ? callback : unused -> {}); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/StringFlag.java b/java/com/cowlark/fluxengine/core/flags/StringFlag.java index a919640b..9d2c3f58 100644 --- a/java/com/cowlark/fluxengine/core/flags/StringFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/StringFlag.java @@ -1,25 +1,18 @@ package com.cowlark.fluxengine.core.flags; -import lombok.Builder; import java.util.List; import java.util.function.Consumer; +import lombok.Builder; public class StringFlag extends ValueFlag { @Builder(setterPrefix = "set") - private StringFlag(FlagGroup group, - List names, - String helpText, - String defaultValue, - Consumer callback) + private StringFlag(FlagGroup group, List names, String helpText, + String defaultValue, Consumer callback) { - super( - group, - names, - helpText, - defaultValue != null ? defaultValue : "", - callback != null ? callback : unused -> { - }); + super(group, names, helpText, + defaultValue != null ? defaultValue : "", + callback != null ? callback : unused -> {}); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/ValueFlag.java b/java/com/cowlark/fluxengine/core/flags/ValueFlag.java index 484985d7..53a22588 100644 --- a/java/com/cowlark/fluxengine/core/flags/ValueFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/ValueFlag.java @@ -10,13 +10,10 @@ public abstract class ValueFlag extends Flag protected T value; private boolean isSet; - protected ValueFlag(FlagGroup group, - List names, - String helptext, - T defaultValue, - Consumer callback) + protected ValueFlag(FlagGroup group, List names, String helpText, + T defaultValue, Consumer callback) { - super(group, names, helptext); + super(group, names, helpText); this.defaultValue = defaultValue; this.value = defaultValue; this.callback = callback; diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index fc8744f9..198af61c 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -13,16 +13,6 @@ java_test( ], ) -java_test( - name = "FlagsTest", - srcs = ["FlagsTest.java"], - deps = [ - "//java/com/cowlark/fluxengine/core", - "@maven//:com_google_truth_truth", - "@maven//:junit_junit", - ], -) - java_test( name = "ByteReaderTest", srcs = ["ByteReaderTest.java"], diff --git a/javatests/com/cowlark/fluxengine/core/FlagsTest.java b/javatests/com/cowlark/fluxengine/core/FlagsTest.java deleted file mode 100644 index fef2576b..00000000 --- a/javatests/com/cowlark/fluxengine/core/FlagsTest.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.cowlark.fluxengine.core; - -import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; - -import com.cowlark.fluxengine.core.Flags.BoolFlag; -import com.cowlark.fluxengine.core.Flags.FlagGroup; -import com.cowlark.fluxengine.core.Flags.IntFlag; -import com.cowlark.fluxengine.core.Flags.StringFlag; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import java.util.List; - -@RunWith(JUnit4.class) -public class FlagsTest -{ - @Test - public void parsesFlags() - { - FlagGroup group = new FlagGroup(); - StringFlag config = StringFlag.builder() - .setGroup(group) - .setNames(List.of("--config", "-c")) - .setHelpText("config file") - .build(); - IntFlag count = IntFlag.builder() - .setGroup(group) - .setNames(List.of("--count")) - .setHelpText("count") - .build(); - BoolFlag verbose = BoolFlag.builder() - .setGroup(group) - .setNames(List.of("--verbose")) - .setHelpText("verbose") - .build(); - - group.parse(new String[]{"--config=foo", "-c", "bar", "--count", "7", "--verbose=true"}); - - assertThat(config.get()).isEqualTo("bar"); - assertThat(count.get()).isEqualTo(7); - assertThat(verbose.get()).isTrue(); - } - - @Test - public void parsesParentGroups() - { - FlagGroup common = new FlagGroup(); - StringFlag serial = StringFlag.builder() - .setGroup(common) - .setNames(List.of("--serial")) - .setHelpText("serial") - .build(); - FlagGroup group = new FlagGroup(common); - StringFlag thing = StringFlag.builder() - .setGroup(group) - .setNames(List.of("--thing")) - .setHelpText("thing") - .build(); - - group.parse(new String[]{"--serial=abc", "--thing=xyz"}); - - assertThat(serial.get()).isEqualTo("abc"); - assertThat(thing.get()).isEqualTo("xyz"); - } - - @Test - public void duplicateNamesThrow() - { - FlagGroup group = new FlagGroup(); - StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("one").build(); - StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("two").build(); - - assertThrows(IllegalStateException.class, () -> group.parse(new String[]{"--foo=x"})); - } - - @Test - public void unknownFlagThrows() - { - FlagGroup group = new FlagGroup(); - assertThrows(FluxEngineException.class, () -> group.parse(new String[]{"--nope=x"})); - } - - @Test - public void filenames() - { - FlagGroup group = new FlagGroup(); - List filenames = group.parseWithFilenames( - new String[]{"one.dsk", "two.dsk"}, - name -> name.equals("one.dsk")); - - assertThat(filenames).containsExactly("two.dsk"); - } - - @Test - public void uninitialisedFlagThrows() - { - FlagGroup group = new FlagGroup(); - StringFlag flag = StringFlag.builder() - .setGroup(group) - .setNames(List.of("--foo")) - .setHelpText("foo") - .build(); - - assertThrows(IllegalStateException.class, flag::get); - } -} diff --git a/javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel new file mode 100644 index 00000000..b16819f7 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "FlagsTest", + srcs = ["FlagsTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/core/flags", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java new file mode 100644 index 00000000..e14f7bf6 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java @@ -0,0 +1,139 @@ +package com.cowlark.fluxengine.core.flags; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.core.FluxEngineException; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FlagsTest +{ + @Test + public void parsesFlags() + { + FlagGroup group = new FlagGroup(); + StringFlag config = StringFlag.builder() + .setGroup(group).setNames(List.of("--config", "-c")).setHelpText("config file").build(); + IntFlag count = IntFlag.builder() + .setGroup(group).setNames(List.of("--count")).setHelpText("count").build(); + BoolFlag verbose = BoolFlag.builder() + .setGroup(group).setNames(List.of("--verbose")).setHelpText("verbose").build(); + + Flags.parse(new String[] { + "--config=foo", "-c", "bar", "--count", "7", "--verbose=true"}, group); + + assertThat(config.get()).isEqualTo("bar"); + assertThat(count.get()).isEqualTo(7); + assertThat(verbose.get()).isTrue(); + } + + @Test + public void parsesParentGroups() + { + FlagGroup common = new FlagGroup(); + StringFlag serial = StringFlag.builder() + .setGroup(common).setNames(List.of("--serial")).setHelpText("serial").build(); + FlagGroup group = new FlagGroup(common); + StringFlag thing = StringFlag.builder() + .setGroup(group).setNames(List.of("--thing")).setHelpText("thing").build(); + + Flags.parse(new String[] {"--serial=abc", "--thing=xyz"}, group); + + assertThat(serial.get()).isEqualTo("abc"); + assertThat(thing.get()).isEqualTo("xyz"); + } + + @Test + public void searchesAcrossMultipleRootGroups() + { + FlagGroup first = new FlagGroup(); + FlagGroup second = new FlagGroup(); + StringFlag thing = StringFlag.builder() + .setGroup(second).setNames(List.of("--thing")).setHelpText("thing").build(); + + Flags.parse(new String[] {"--thing=xyz"}, first, second); + + assertThat(thing.get()).isEqualTo("xyz"); + } + + @Test + public void duplicateNamesThrow() + { + FlagGroup group = new FlagGroup(); + StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("one").build(); + StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("two").build(); + + assertThrows(IllegalStateException.class, + () -> Flags.parse(new String[] {"--foo=x"}, group)); + } + + @Test + public void unknownFlagThrows() + { + FlagGroup group = new FlagGroup(); + assertThrows(FluxEngineException.class, + () -> Flags.parse(new String[] {"--nope=x"}, group)); + } + + @Test + public void filenames() + { + FlagGroup group = new FlagGroup(); + List filenames = Flags.parseWithFilenames( + new String[] {"one.dsk", "two.dsk"}, name -> name.equals("one.dsk"), group); + + assertThat(filenames).containsExactly("two.dsk"); + } + + @Test + public void uninitialisedFlagThrows() + { + FlagGroup group = new FlagGroup(); + StringFlag flag = StringFlag.builder() + .setGroup(group).setNames(List.of("--foo")).setHelpText("foo").build(); + + assertThrows(IllegalStateException.class, flag::get); + } + + @Test + public void findFlagReturnsTheFlag() + { + FlagGroup group = new FlagGroup(); + StringFlag foo = StringFlag.builder() + .setGroup(group).setNames(List.of("--foo", "-f")).setHelpText("foo").build(); + + assertThat(group.findFlag("--foo")).isSameInstanceAs(foo); + assertThat(group.findFlag("-f")).isSameInstanceAs(foo); + assertThat(group.findFlag("--nope")).isNull(); + } + + @Test + public void findFlagRecursesToParents() + { + FlagGroup common = new FlagGroup(); + StringFlag serial = StringFlag.builder() + .setGroup(common).setNames(List.of("--serial")).setHelpText("serial").build(); + FlagGroup group = new FlagGroup(common); + + assertThat(group.findFlag("--serial")).isSameInstanceAs(serial); + } + + @Test + public void noArgFlagDoesNotConsumeFollowingToken() + { + FlagGroup group = new FlagGroup(); + SettableFlag flag = SettableFlag.builder() + .setGroup(group).setNames(List.of("--read-only")).setHelpText("read only").build(); + + List filenames = Flags.parseWithFilenames( + new String[] {"--read-only", "image.dsk"}, unused -> false, group); + + assertThat(flag.get()).isTrue(); + assertThat(filenames).containsExactly("image.dsk"); + } +} From a9d4e1ee21fa34fc983a8d9de805ce5ac68ad6f9 Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 6 Aug 2026 23:15:43 +0200 Subject: [PATCH 071/192] Fiddle with flags again. Start on building configs from them. --- AGENTS.md | 4 ++ .../fluxengine/cli/TestBandwidthCommand.java | 4 ++ .../com/cowlark/fluxengine/config/BUILD.bazel | 1 + .../fluxengine/config/ConfigBuilder.java | 36 +-------------- .../fluxengine/config/ConfigFlagGroup.java | 27 +++++++++++ .../fluxengine/core/flags/ActionFlag.java | 3 +- .../fluxengine/core/flags/BoolFlag.java | 3 +- .../fluxengine/core/flags/DoubleFlag.java | 3 +- .../fluxengine/core/flags/HexIntFlag.java | 3 +- .../fluxengine/core/flags/IntFlag.java | 3 +- .../fluxengine/core/flags/SettableFlag.java | 5 ++- .../fluxengine/core/flags/StringFlag.java | 3 +- .../fluxengine/core/flags/FlagsTest.java | 45 ++++++++++++++----- 13 files changed, 86 insertions(+), 54 deletions(-) create mode 100644 java/com/cowlark/fluxengine/config/ConfigFlagGroup.java diff --git a/AGENTS.md b/AGENTS.md index 37a2896c..7f973048 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,6 +62,10 @@ Useful commands: so normalize defaults in the constructor body (e.g. `defaultValue != null ? defaultValue : ""`). `FlagGroup.addFlag(this)` happens in the base `Flag` constructor, so `build()` registers the flag. +- The `names` parameter is annotated `@Singular`, so builders offer `setName("--foo")` + (one name at a time), `setNames(collection)`, and `clearNames()` — Lombok can't generate a + varargs setter, and `@SuperBuilder` is unusable here because its auto-generated constructor + can't run the `addFlag` side-effect, so `@Singular` avoids hand-writing a builder per class. - `HexIntFlag` extends `ValueFlag` directly (not `IntFlag`): two `@Builder`s would both generate a static `builder()` and clash via hiding. diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 3edc3e24..9a802738 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,9 +1,11 @@ package com.cowlark.fluxengine.cli; import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigFlagGroup; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; +import com.google.common.collect.ImmutableList; import picocli.CommandLine.Command; /** @@ -15,7 +17,9 @@ public class TestBandwidthCommand extends CommandWithConfig implements Runnable @Override public void run() { + var configGroup = new ConfigFlagGroup(); ConfigProto config = new ConfigBuilder() + .fromFlags(ImmutableList.of(), configGroup) .build(); UsbDevice device = UsbFactory.connect(config); diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 598ca219..99e9f052 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -75,6 +75,7 @@ java_library( srcs = glob(["*.java"]), deps = [ ":config_java_proto", + "//java/com/cowlark/fluxengine/core/flags", "@maven//:com_google_guava_guava", "@maven//:com_jayway_jsonpath_json_path", ], diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index d0a4616a..50bcd374 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.config; import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.flags.FlagGroup; import com.google.common.collect.ImmutableList; /** @@ -15,41 +16,8 @@ public ConfigBuilder() { } - public ConfigBuilder parse(ImmutableList args) + public ConfigBuilder fromFlags(ImmutableList args, FlagGroup... group) { - int i = 0; - while (i < args.size()) - { - String arg = args.get(i); - if (arg.startsWith("--")) - { - int eq = arg.indexOf('='); - if (eq >= 0) - { - set(arg.substring(2, eq), arg.substring(eq + 1)); - } - else if (i + 1 < args.size()) - { - set(arg.substring(2), args.get(i + 1)); - i++; - } - } - else if (arg.startsWith("-") && arg.length() > 1) - { - int eq = arg.indexOf('='); - if (eq >= 0) - { - set(arg.substring(1, eq), arg.substring(eq + 1)); - } - else if (i + 1 < args.size()) - { - set(arg.substring(1), args.get(i + 1)); - i++; - } - } - /* bare arguments are ignored */ - i++; - } return this; } diff --git a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java new file mode 100644 index 00000000..0a2a058b --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java @@ -0,0 +1,27 @@ +package com.cowlark.fluxengine.config; + +import com.cowlark.fluxengine.core.flags.ActionFlag; +import com.cowlark.fluxengine.core.flags.Flag; +import com.cowlark.fluxengine.core.flags.FlagGroup; + +public class ConfigFlagGroup extends FlagGroup +{ + public ConfigFlagGroup() + { + addFlag(ActionFlag.builder() + .setName("-c") + .setName("--config") + .build()); + addFlag(ActionFlag.builder() + .setName("--show-config") + .build()); + } + + @Override + public Flag findFlag(String key) + { + if (key.contains(".")) + return ActionFlag.builder().build(); + return null; + } +} diff --git a/java/com/cowlark/fluxengine/core/flags/ActionFlag.java b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java index 39acd634..add32642 100644 --- a/java/com/cowlark/fluxengine/core/flags/ActionFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.function.Consumer; import lombok.Builder; +import lombok.Singular; public class ActionFlag extends Flag { @@ -11,7 +12,7 @@ public class ActionFlag extends Flag private final boolean hasArgument; @Builder(setterPrefix = "set") - private ActionFlag(FlagGroup group, List names, String helpText, + private ActionFlag(FlagGroup group, @Singular List names, String helpText, Runnable voidCallback, Consumer valueCallback) { super(group, names, helpText); diff --git a/java/com/cowlark/fluxengine/core/flags/BoolFlag.java b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java index 3d0059ae..a5253d3a 100644 --- a/java/com/cowlark/fluxengine/core/flags/BoolFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java @@ -4,11 +4,12 @@ import java.util.List; import java.util.function.Consumer; import lombok.Builder; +import lombok.Singular; public class BoolFlag extends ValueFlag { @Builder(setterPrefix = "set") - private BoolFlag(FlagGroup group, List names, String helpText, + private BoolFlag(FlagGroup group, @Singular List names, String helpText, boolean defaultValue, Consumer callback) { super(group, names, helpText, defaultValue, diff --git a/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java index 18b1f2c6..61405f80 100644 --- a/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java @@ -3,11 +3,12 @@ import java.util.List; import java.util.function.Consumer; import lombok.Builder; +import lombok.Singular; public class DoubleFlag extends ValueFlag { @Builder(setterPrefix = "set") - private DoubleFlag(FlagGroup group, List names, String helpText, + private DoubleFlag(FlagGroup group, @Singular List names, String helpText, Double defaultValue, Consumer callback) { super(group, names, helpText, diff --git a/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java index af3fef18..d73799fc 100644 --- a/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java @@ -2,11 +2,12 @@ import java.util.List; import lombok.Builder; +import lombok.Singular; public class HexIntFlag extends ValueFlag { @Builder(setterPrefix = "set") - private HexIntFlag(FlagGroup group, List names, String helpText, + private HexIntFlag(FlagGroup group, @Singular List names, String helpText, Integer defaultValue) { super(group, names, helpText, diff --git a/java/com/cowlark/fluxengine/core/flags/IntFlag.java b/java/com/cowlark/fluxengine/core/flags/IntFlag.java index 83f0f704..932d01da 100644 --- a/java/com/cowlark/fluxengine/core/flags/IntFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/IntFlag.java @@ -3,11 +3,12 @@ import java.util.List; import java.util.function.Consumer; import lombok.Builder; +import lombok.Singular; public class IntFlag extends ValueFlag { @Builder(setterPrefix = "set") - private IntFlag(FlagGroup group, List names, String helpText, + private IntFlag(FlagGroup group, @Singular List names, String helpText, int defaultValue, Consumer callback) { super(group, names, helpText, defaultValue, diff --git a/java/com/cowlark/fluxengine/core/flags/SettableFlag.java b/java/com/cowlark/fluxengine/core/flags/SettableFlag.java index 1971ea5f..91b9ba3e 100644 --- a/java/com/cowlark/fluxengine/core/flags/SettableFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/SettableFlag.java @@ -1,14 +1,15 @@ package com.cowlark.fluxengine.core.flags; -import lombok.Builder; import java.util.List; +import lombok.Builder; +import lombok.Singular; public class SettableFlag extends Flag { private boolean value; @Builder(setterPrefix = "set") - private SettableFlag(FlagGroup group, List names, String helpText) + private SettableFlag(FlagGroup group, @Singular List names, String helpText) { super(group, names, helpText); } diff --git a/java/com/cowlark/fluxengine/core/flags/StringFlag.java b/java/com/cowlark/fluxengine/core/flags/StringFlag.java index 9d2c3f58..c3a410dd 100644 --- a/java/com/cowlark/fluxengine/core/flags/StringFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/StringFlag.java @@ -3,11 +3,12 @@ import java.util.List; import java.util.function.Consumer; import lombok.Builder; +import lombok.Singular; public class StringFlag extends ValueFlag { @Builder(setterPrefix = "set") - private StringFlag(FlagGroup group, List names, String helpText, + private StringFlag(FlagGroup group, @Singular List names, String helpText, String defaultValue, Consumer callback) { super(group, names, helpText, diff --git a/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java index e14f7bf6..6d8cc2c9 100644 --- a/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java +++ b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java @@ -18,11 +18,11 @@ public void parsesFlags() { FlagGroup group = new FlagGroup(); StringFlag config = StringFlag.builder() - .setGroup(group).setNames(List.of("--config", "-c")).setHelpText("config file").build(); + .setGroup(group).setName("--config").setName("-c").setHelpText("config file").build(); IntFlag count = IntFlag.builder() - .setGroup(group).setNames(List.of("--count")).setHelpText("count").build(); + .setGroup(group).setName("--count").setHelpText("count").build(); BoolFlag verbose = BoolFlag.builder() - .setGroup(group).setNames(List.of("--verbose")).setHelpText("verbose").build(); + .setGroup(group).setName("--verbose").setHelpText("verbose").build(); Flags.parse(new String[] { "--config=foo", "-c", "bar", "--count", "7", "--verbose=true"}, group); @@ -37,10 +37,10 @@ public void parsesParentGroups() { FlagGroup common = new FlagGroup(); StringFlag serial = StringFlag.builder() - .setGroup(common).setNames(List.of("--serial")).setHelpText("serial").build(); + .setGroup(common).setName("--serial").setHelpText("serial").build(); FlagGroup group = new FlagGroup(common); StringFlag thing = StringFlag.builder() - .setGroup(group).setNames(List.of("--thing")).setHelpText("thing").build(); + .setGroup(group).setName("--thing").setHelpText("thing").build(); Flags.parse(new String[] {"--serial=abc", "--thing=xyz"}, group); @@ -54,7 +54,7 @@ public void searchesAcrossMultipleRootGroups() FlagGroup first = new FlagGroup(); FlagGroup second = new FlagGroup(); StringFlag thing = StringFlag.builder() - .setGroup(second).setNames(List.of("--thing")).setHelpText("thing").build(); + .setGroup(second).setName("--thing").setHelpText("thing").build(); Flags.parse(new String[] {"--thing=xyz"}, first, second); @@ -65,8 +65,8 @@ public void searchesAcrossMultipleRootGroups() public void duplicateNamesThrow() { FlagGroup group = new FlagGroup(); - StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("one").build(); - StringFlag.builder().setGroup(group).setNames(List.of("--foo")).setHelpText("two").build(); + StringFlag.builder().setGroup(group).setName("--foo").setHelpText("one").build(); + StringFlag.builder().setGroup(group).setName("--foo").setHelpText("two").build(); assertThrows(IllegalStateException.class, () -> Flags.parse(new String[] {"--foo=x"}, group)); @@ -95,7 +95,7 @@ public void uninitialisedFlagThrows() { FlagGroup group = new FlagGroup(); StringFlag flag = StringFlag.builder() - .setGroup(group).setNames(List.of("--foo")).setHelpText("foo").build(); + .setGroup(group).setName("--foo").setHelpText("foo").build(); assertThrows(IllegalStateException.class, flag::get); } @@ -105,19 +105,40 @@ public void findFlagReturnsTheFlag() { FlagGroup group = new FlagGroup(); StringFlag foo = StringFlag.builder() - .setGroup(group).setNames(List.of("--foo", "-f")).setHelpText("foo").build(); + .setGroup(group).setName("--foo").setName("-f").setHelpText("foo").build(); assertThat(group.findFlag("--foo")).isSameInstanceAs(foo); assertThat(group.findFlag("-f")).isSameInstanceAs(foo); assertThat(group.findFlag("--nope")).isNull(); } + @Test + public void setNameAddsEachName() + { + FlagGroup group = new FlagGroup(); + StringFlag flag = StringFlag.builder() + .setGroup(group).setName("--long").setName("-l").setName("-long") + .setHelpText("flag").build(); + + assertThat(flag.names()).containsExactly("--long", "-l", "-long"); + } + + @Test + public void setNamesTakesACollection() + { + FlagGroup group = new FlagGroup(); + StringFlag flag = StringFlag.builder() + .setGroup(group).setNames(List.of("--long", "-l")).setHelpText("flag").build(); + + assertThat(flag.names()).containsExactly("--long", "-l"); + } + @Test public void findFlagRecursesToParents() { FlagGroup common = new FlagGroup(); StringFlag serial = StringFlag.builder() - .setGroup(common).setNames(List.of("--serial")).setHelpText("serial").build(); + .setGroup(common).setName("--serial").setHelpText("serial").build(); FlagGroup group = new FlagGroup(common); assertThat(group.findFlag("--serial")).isSameInstanceAs(serial); @@ -128,7 +149,7 @@ public void noArgFlagDoesNotConsumeFollowingToken() { FlagGroup group = new FlagGroup(); SettableFlag flag = SettableFlag.builder() - .setGroup(group).setNames(List.of("--read-only")).setHelpText("read only").build(); + .setGroup(group).setName("--read-only").setHelpText("read only").build(); List filenames = Flags.parseWithFilenames( new String[] {"--read-only", "image.dsk"}, unused -> false, group); From 9da63c67191168f0f438e4629d4c0f455ee5e9fa Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 00:21:19 +0200 Subject: [PATCH 072/192] Start wiring up the commands. --- AGENTS.md | 25 +++-- MODULE.bazel | 2 - java/com/cowlark/fluxengine/cli/BUILD.bazel | 11 +- java/com/cowlark/fluxengine/cli/Command.java | 100 ++++++++++++++++++ .../cowlark/fluxengine/cli/CommandGroup.java | 33 ++++++ .../fluxengine/cli/CommandWithConfig.java | 22 ---- java/com/cowlark/fluxengine/cli/Main.java | 38 +++++-- .../cowlark/fluxengine/cli/MainCommand.java | 14 --- .../cowlark/fluxengine/cli/StubCommand.java | 25 +++++ .../fluxengine/cli/TestBandwidthCommand.java | 16 +-- .../cowlark/fluxengine/cli/TestCommand.java | 9 -- .../fluxengine/cli/TestDevicesCommand.java | 15 ++- .../fluxengine/config/ConfigFlagGroup.java | 12 ++- .../cowlark/fluxengine/reflect-config.json | 15 --- 14 files changed, 234 insertions(+), 103 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/Command.java create mode 100644 java/com/cowlark/fluxengine/cli/CommandGroup.java delete mode 100644 java/com/cowlark/fluxengine/cli/CommandWithConfig.java delete mode 100644 java/com/cowlark/fluxengine/cli/MainCommand.java create mode 100644 java/com/cowlark/fluxengine/cli/StubCommand.java delete mode 100644 java/com/cowlark/fluxengine/cli/TestCommand.java diff --git a/AGENTS.md b/AGENTS.md index 7f973048..42741f51 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -93,14 +93,23 @@ Useful commands: commands. Classes are injectable via `@Inject` constructors; there are no module bindings for own classes unless needed. -## CLI (picocli) - -- Commands live in `com.cowlark.fluxengine.cli`: `MainCommand` (root, `@Command(name = - "fluxengine")`), `TestCommand` (`test`), `TestDevicesCommand` (`test devices`). -- Subcommands are declared with `subcommands = {FooCommand.class}` on the parent and are - constructed by Dagger via a `CommandLine.IFactory` in `Main` that delegates to the - Dagger component. Keep this factory updated when adding subcommands. -- Picocli commands have package-private `@Inject` constructors. +## CLI + +- Commands live in `com.cowlark.fluxengine.cli` and implement the `Command` interface + (`String getHelp()`, `void run(String[] args)`), receiving the tail of the argv array after + the command name (modelled on `src/fluxengine.cc`'s `command_cb`). +- `Main.main` holds the command/subcommand tables as `ImmutableMap>`: `COMMANDS` (top level), `ANALYSABLES`, `FLUXFILEABLES`, + `TESTABLES`. The tables mirror `src/fluxengine.cc`; unported commands map to + `StubCommand(name, help)`, which prints "not implemented yet". +- Each command carries its own help text, returned by `getHelp()`; `Main.help` prints the + table by instantiating each command and calling `getHelp()`. +- `Main.dispatch(commands, args)` consumes arguments until it reaches a real command, + instantiates it via the supplier (`TestDevicesCommand::new`, not reflection, so GraalVM + needs no extra reachability config), and calls `run()` with the tail. Group commands + (`analyse`, `fluxfile`, `test`) are `CommandGroup(subcommands, help)` instances, which + dispatch again on their sub-table and print extended help if nothing matches. Add new + commands by updating the relevant table. ## USB diff --git a/MODULE.bazel b/MODULE.bazel index 598e477f..e1bddb1b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -16,8 +16,6 @@ maven.install( "com.google.guava:guava:33.6.0-jre", "com.google.truth:truth:1.4.5", "com.jayway.jsonpath:json-path:3.0.0", - "info.picocli:picocli:4.7.7", - "info.picocli:picocli-codegen:4.7.7", "javax.usb:usb-api:1.0.2", "junit:junit:4.13.2", "org.openjfx:javafx-controls:23.0.2", diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 92abc48c..3f887b80 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -1,22 +1,15 @@ -load("@rules_java//java:defs.bzl", "java_library", "java_plugin") +load("@rules_java//java:defs.bzl", "java_library") package(default_visibility = ["//visibility:public"]) -java_plugin( - name = "picocli", - processor_class = "picocli.codegen.aot.graalvm.processor.NativeImageConfigGeneratorProcessor", - deps = ["@maven//:info_picocli_picocli_codegen"], -) - java_library( name = "cli", srcs = glob(["*.java"]), - plugins = [":picocli"], deps = [ "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core/flags", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", - "@maven//:info_picocli_picocli", ], ) diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java new file mode 100644 index 00000000..70d64fbf --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -0,0 +1,100 @@ +package com.cowlark.fluxengine.cli; + +import com.google.common.collect.ImmutableMap; +import java.util.Arrays; +import java.util.Map; +import java.util.function.Supplier; + +public interface Command +{ + ImmutableMap> ANALYSABLES = + ImmutableMap.>builder() + .put( + "driveresponse", stub( + "driveresponse", + "Measures the drive's ability to read and write pulses.")) + .put( + "layout", + stub("layout", "Produces a visualisation of the track/sector layout.")) + .build(); + + ImmutableMap> FLUXFILEABLES = + ImmutableMap.>builder() + .put("ls", stub("ls", "Lists the contents of a flux file.")) + .put("rm", stub("rm", "Removes flux from a flux file.")) + .put("cp", stub("cp", "Copies flux from one flux file to another.")) + .build(); + + ImmutableMap> TESTABLES = + ImmutableMap.>builder() + .put("bandwidth", TestBandwidthCommand::new) + .put("devices", TestDevicesCommand::new) + .put("voltages", stub("voltages", "Measures the FDD bus voltages.")) + .build(); + + ImmutableMap> COMMANDS = + ImmutableMap.>builder() + .put("inspect", stub("inspect", "Low-level analysis and inspection of a disk.")) + .put( + "analyse", + () -> new CommandGroup(ANALYSABLES, "Disk and drive analysis tools.")) + .put("read", stub("read", "Reads a disk, producing a sector image.")) + .put("write", stub("write", "Writes a sector image to a disk.")) + .put( + "fluxfile", + () -> new CommandGroup( + FLUXFILEABLES, + "Flux file manipulation operations.")) + .put("format", stub("format", "Format a disk and make a file system on it.")) + .put( + "rawwrite", stub( + "rawwrite", + "Writes a flux file to a disk. Warning: you can't use this to" + + " copy disks.")) + .put( + "convert", + stub("convert", "Converts a flux file from one format to another.")) + .put( + "getdiskinfo", + stub("getdiskinfo", "Read volume metadata off a disk (or image).")) + .put("ls", stub("ls", "Show files on disk (or image).")) + .put("mv", stub("mv", "Rename a file on a disk (or image).")) + .put("rm", stub("rm", "Deletes a file (or directory) off a disk (or image).")) + .put("getfile", stub("getfile", "Read a file off a disk (or image).")) + .put( + "getfileinfo", + stub("getfileinfo", "Read file metadata off a disk (or image).")) + .put("putfile", stub("putfile", "Write a file to disk (or image).")) + .put("mkdir", stub("mkdir", "Create a directory on disk (or image).")) + .put("rpm", stub("rpm", "Measures the disk rotational speed.")) + .put("seek", stub("seek", "Moves the disk head.")) + .put("test", () -> new CommandGroup(TESTABLES, "Various testing commands.")) + .build(); + + /* Consume arguments until we reach a real command, instantiate it, and + * run it with the tail of the argv array. */ + static boolean dispatch(Map> commands, String[] args) + { + for (int index = 0; index < args.length; index++) + { + Supplier supplier = commands.get(args[index]); + if (supplier != null) + { + supplier.get().run(Arrays.copyOfRange(args, index + 1, args.length)); + return true; + } + } + + return false; + } + + static Supplier stub(String name, String help) + { + return () -> new StubCommand(name, help); + } + + String getHelp(); + + void run(String[] args); + +} diff --git a/java/com/cowlark/fluxengine/cli/CommandGroup.java b/java/com/cowlark/fluxengine/cli/CommandGroup.java new file mode 100644 index 00000000..d01eb1b8 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/CommandGroup.java @@ -0,0 +1,33 @@ +package com.cowlark.fluxengine.cli; + +import java.util.Map; +import java.util.function.Supplier; + +/** + * A command which dispatches to a table of subcommands, modelled on the + * mainExtended() helper in src/fluxengine.cc. + */ +public class CommandGroup implements Command +{ + private final Map> subcommands; + private final String help; + + public CommandGroup(Map> subcommands, String help) + { + this.subcommands = subcommands; + this.help = help; + } + + @Override + public String getHelp() + { + return help; + } + + @Override + public void run(String[] args) + { + if (!Command.dispatch(subcommands, args)) + Main.help(subcommands, " [...]"); + } +} diff --git a/java/com/cowlark/fluxengine/cli/CommandWithConfig.java b/java/com/cowlark/fluxengine/cli/CommandWithConfig.java deleted file mode 100644 index f3fce94c..00000000 --- a/java/com/cowlark/fluxengine/cli/CommandWithConfig.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.cowlark.fluxengine.cli; - -import java.util.List; -import com.google.common.collect.ImmutableList; -import picocli.CommandLine.Command; -import picocli.CommandLine.Unmatched; - -/** - * Base class for commands which accept dotted `--config.flag=value` - * arguments, which picocli collects as unmatched arguments. - */ -@Command -public abstract class CommandWithConfig -{ - @Unmatched - private List unmatched; - - protected ImmutableList unmatchedArguments() - { - return unmatched == null ? ImmutableList.of() : ImmutableList.copyOf(unmatched); - } -} diff --git a/java/com/cowlark/fluxengine/cli/Main.java b/java/com/cowlark/fluxengine/cli/Main.java index e87e6e60..0715ad1c 100644 --- a/java/com/cowlark/fluxengine/cli/Main.java +++ b/java/com/cowlark/fluxengine/cli/Main.java @@ -1,22 +1,40 @@ package com.cowlark.fluxengine.cli; -import picocli.CommandLine; +import java.util.Map; +import java.util.function.Supplier; +/** + * Command-line entry point, ported from src/fluxengine.cc. The command and + * subcommand tables live here; main() consumes arguments until it reaches a + * real command, instantiates it, and runs it with the tail of the argv array. + */ public class Main { + public static void main(String[] args) { - CommandLine commandLine = new CommandLine(new MainCommand()); - allowUnmatchedArguments(commandLine); - commandLine.execute(args); + if (args.length == 0 || args[0].equals("--help")) + { + help(Command.COMMANDS, " [...]"); + return; + } + + if (!Command.dispatch(Command.COMMANDS, args)) + { + System.err.println("fluxengine: unrecognised command (try --help)"); + System.exit(1); + } + } + + static void help(Map> commands, String syntax) + { + System.out.printf("fluxengine: syntax: fluxengine %s\n", syntax); + System.out.println("Try one of these commands:"); + for (Map.Entry> entry : commands.entrySet()) + System.out.printf(" %s: %s\n", entry.getKey(), entry.getValue().get().getHelp()); } - /* The dotted --config.flag=value arguments don't match any declared - * option, so allow them to be collected as unmatched arguments. */ - private static void allowUnmatchedArguments(CommandLine commandLine) + private Main() { - commandLine.setUnmatchedArgumentsAllowed(true); - for (CommandLine sub : commandLine.getSubcommands().values()) - allowUnmatchedArguments(sub); } } diff --git a/java/com/cowlark/fluxengine/cli/MainCommand.java b/java/com/cowlark/fluxengine/cli/MainCommand.java deleted file mode 100644 index a91ffef0..00000000 --- a/java/com/cowlark/fluxengine/cli/MainCommand.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.cowlark.fluxengine.cli; - -import picocli.CommandLine.Command; - -@Command(name = "fluxengine", mixinStandardHelpOptions = true, subcommands = {TestCommand.class}, - description = "FluxEngine CLI") -public class MainCommand implements Runnable -{ - @Override - public void run() - { - System.out.println("run!"); - } -} diff --git a/java/com/cowlark/fluxengine/cli/StubCommand.java b/java/com/cowlark/fluxengine/cli/StubCommand.java new file mode 100644 index 00000000..473d2e7e --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/StubCommand.java @@ -0,0 +1,25 @@ +package com.cowlark.fluxengine.cli; + +public class StubCommand implements Command +{ + private final String name; + private final String help; + + public StubCommand(String name, String help) + { + this.name = name; + this.help = help; + } + + @Override + public String getHelp() + { + return help; + } + + @Override + public void run(String[] args) + { + System.err.printf("fluxengine: '%s' is not implemented yet.\n", name); + } +} diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 9a802738..3eb4ff56 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -6,20 +6,24 @@ import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; import com.google.common.collect.ImmutableList; -import picocli.CommandLine.Command; /** * Test USB bulk transfer bandwidth, modelled after src/fe-testbandwidth.cc. */ -@Command(name = "bandwidth", description = "Test USB bulk transfer bandwidth") -public class TestBandwidthCommand extends CommandWithConfig implements Runnable +public class TestBandwidthCommand implements Command { @Override - public void run() + public String getHelp() { - var configGroup = new ConfigFlagGroup(); + return "Measures your USB bandwidth."; + } + + @Override + public void run(String[] args) + { + ConfigFlagGroup configGroup = new ConfigFlagGroup(); ConfigProto config = new ConfigBuilder() - .fromFlags(ImmutableList.of(), configGroup) + .fromFlags(ImmutableList.copyOf(args), configGroup) .build(); UsbDevice device = UsbFactory.connect(config); diff --git a/java/com/cowlark/fluxengine/cli/TestCommand.java b/java/com/cowlark/fluxengine/cli/TestCommand.java deleted file mode 100644 index af51f0f7..00000000 --- a/java/com/cowlark/fluxengine/cli/TestCommand.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.cowlark.fluxengine.cli; - -import picocli.CommandLine.Command; - -@Command(name = "test", description = "Test commands", - subcommands = {TestDevicesCommand.class, TestBandwidthCommand.class}) -public class TestCommand -{ -} diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java index 2487b30f..2d3ab71c 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java @@ -2,16 +2,23 @@ import static com.google.common.base.Strings.nullToEmpty; +import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.usb.UsbFactory; import com.cowlark.fluxengine.usb.UsbFactory.CandidateDevice; -import picocli.CommandLine.Command; import java.util.List; -@Command(name = "devices", description = "List attached USB devices") -public class TestDevicesCommand extends CommandWithConfig implements Runnable +public class TestDevicesCommand implements Command { + private static final FlagGroup EMPTY = new FlagGroup(); + + @Override + public String getHelp() + { + return "Displays all detected devices."; + } + @Override - public void run() + public void run(String[] args) { List candidates = UsbFactory.findUsbDevices(); switch (candidates.size()) diff --git a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java index 0a2a058b..65cf47ba 100644 --- a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java +++ b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java @@ -8,13 +8,17 @@ public class ConfigFlagGroup extends FlagGroup { public ConfigFlagGroup() { - addFlag(ActionFlag.builder() + ActionFlag.builder() + .setGroup(this) .setName("-c") .setName("--config") - .build()); - addFlag(ActionFlag.builder() + .setHelpText("Reads an internal or external configuration file.") + .build(); + ActionFlag.builder() + .setGroup(this) .setName("--show-config") - .build()); + .setHelpText("Shows the currently set configuration and halts.") + .build(); } @Override diff --git a/java/com/cowlark/fluxengine/reflect-config.json b/java/com/cowlark/fluxengine/reflect-config.json index 4b35ca56..4433c193 100644 --- a/java/com/cowlark/fluxengine/reflect-config.json +++ b/java/com/cowlark/fluxengine/reflect-config.json @@ -1,14 +1,4 @@ [ -{ - "name":"com.cowlark.fluxengine.cli.MainCommand", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, -{ - "name":"com.cowlark.fluxengine.cli.TestCommand", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, { "name":"com.cowlark.fluxengine.cli.TestDevicesCommand", "allDeclaredFields":true, @@ -113,11 +103,6 @@ "name":"org.usb4java.javax.Services", "methods":[{"name":"","parameterTypes":[] }] }, -{ - "name":"picocli.CommandLine$AutoHelpMixin", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, { "name":"sun.security.provider.NativePRNG", "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] From 27da6d84ebee4c1c34786a5b655d8cdf6fdb53ce Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 00:46:25 +0200 Subject: [PATCH 073/192] You can load config files now! --- AGENTS.md | 7 +- .../fluxengine/cli/TestBandwidthCommand.java | 3 +- .../com/cowlark/fluxengine/config/BUILD.bazel | 2 + .../fluxengine/config/ConfigBuilder.java | 45 ++++++++- .../fluxengine/config/ConfigException.java | 19 ++++ .../fluxengine/config/ConfigFlagGroup.java | 8 +- .../cowlark/fluxengine/core/flags/Flags.java | 49 +++++----- .../com/cowlark/fluxengine/config/BUILD.bazel | 15 +++ .../fluxengine/config/ConfigBuilderTest.java | 62 +++++++++++++ .../cowlark/fluxengine/core/flags/BUILD.bazel | 1 + .../fluxengine/core/flags/FlagsTest.java | 91 +++++++++++++------ 11 files changed, 246 insertions(+), 56 deletions(-) create mode 100644 java/com/cowlark/fluxengine/config/ConfigException.java create mode 100644 javatests/com/cowlark/fluxengine/config/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java diff --git a/AGENTS.md b/AGENTS.md index 42741f51..d0bd6b71 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -71,9 +71,10 @@ Useful commands: ## Flags parsing -- Parsing is done by the static `Flags.parse(String[] argv, FlagGroup... groups)` / - `Flags.parseWithFilenames(String[] argv, Predicate callback, FlagGroup... groups)`. - It first runs `FlagGroup.initialise` over every root group (recursive duplicate-name check +- Parsing is done by the static `Flags.parse(ImmutableList argv, FlagGroup... groups)` / + `Flags.parseWithFilenames(ImmutableList argv, Predicate callback, + FlagGroup... groups)` (both also accept `ImmutableList`). It first runs + `FlagGroup.initialise` over every root group (recursive duplicate-name check into a shared `Set`, marking groups initialised), then walks argv and resolves each flag via `FlagGroup.findFlag(key)`, which scans the group's own flags then recurses into its parents. `Flags.parse` calls `flag.set(value)` and only consumes a space-separated value when diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 3eb4ff56..6e73ae7a 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -21,9 +21,8 @@ public String getHelp() @Override public void run(String[] args) { - ConfigFlagGroup configGroup = new ConfigFlagGroup(); ConfigProto config = new ConfigBuilder() - .fromFlags(ImmutableList.copyOf(args), configGroup) + .fromFlags(ImmutableList.copyOf(args)) .build(); UsbDevice device = UsbFactory.connect(config); diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 99e9f052..aeed7484 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -75,7 +75,9 @@ java_library( srcs = glob(["*.java"]), deps = [ ":config_java_proto", + "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", + "@com_google_protobuf//java/core:core", "@maven//:com_google_guava_guava", "@maven//:com_jayway_jsonpath_json_path", ], diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index 50bcd374..b3695141 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -1,8 +1,12 @@ package com.cowlark.fluxengine.config; -import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.Flags; import com.google.common.collect.ImmutableList; +import com.google.protobuf.TextFormat; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; /** * The assembled configuration, built from the unmatched command-line @@ -10,13 +14,48 @@ */ public class ConfigBuilder { - private ConfigProto proto = ConfigProto.getDefaultInstance(); + private ConfigProto.Builder proto = ConfigProto.newBuilder(); public ConfigBuilder() { } public ConfigBuilder fromFlags(ImmutableList args, FlagGroup... group) + { + ImmutableList allGroups = ImmutableList.builder() + .add(group) + .add(new ConfigFlagGroup(this)) + .build(); + Flags.parse(args, allGroups); + + return this; + } + + public ConfigBuilder loadConfigFile(String name) + { + String contents; + try + { + contents = Files.readString(Path.of(name)); + } + catch (IOException e) + { + throw new ConfigException("Cannot open '" + name + "': " + e.getMessage()); + } + + try + { + TextFormat.merge(contents, proto); + } + catch (TextFormat.ParseException e) + { + throw new ConfigException("couldn't load external config proto"); + } + + return this; + } + + public ConfigBuilder showCurrentConfig() { return this; } @@ -28,7 +67,7 @@ public ConfigBuilder set(String key, String value) public ConfigProto build() { - return proto; + return proto.build(); } } diff --git a/java/com/cowlark/fluxengine/config/ConfigException.java b/java/com/cowlark/fluxengine/config/ConfigException.java new file mode 100644 index 00000000..70f9afe7 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ConfigException.java @@ -0,0 +1,19 @@ +package com.cowlark.fluxengine.config; + +import com.cowlark.fluxengine.core.FluxEngineException; + +/** + * An error relating to loading or processing the configuration. + */ +public class ConfigException extends FluxEngineException +{ + public ConfigException(String message) + { + super(message); + } + + public ConfigException(String message, Throwable cause) + { + super(message, cause); + } +} diff --git a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java index 65cf47ba..e34343fe 100644 --- a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java +++ b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java @@ -6,18 +6,24 @@ public class ConfigFlagGroup extends FlagGroup { - public ConfigFlagGroup() + private final ConfigBuilder builder; + + public ConfigFlagGroup(ConfigBuilder builder) { + this.builder = builder; + ActionFlag.builder() .setGroup(this) .setName("-c") .setName("--config") .setHelpText("Reads an internal or external configuration file.") + .setValueCallback(builder::loadConfigFile) .build(); ActionFlag.builder() .setGroup(this) .setName("--show-config") .setHelpText("Shows the currently set configuration and halts.") + .setVoidCallback(builder::showCurrentConfig) .build(); } diff --git a/java/com/cowlark/fluxengine/core/flags/Flags.java b/java/com/cowlark/fluxengine/core/flags/Flags.java index be030c70..1fef7d50 100644 --- a/java/com/cowlark/fluxengine/core/flags/Flags.java +++ b/java/com/cowlark/fluxengine/core/flags/Flags.java @@ -11,20 +11,33 @@ */ public class Flags { - public static void parse(String[] argv, FlagGroup... groups) + public static void parse(ImmutableList argv, FlagGroup... groups) + { + parse(argv, ImmutableList.copyOf(groups)); + } + + public static void parse(ImmutableList argv, ImmutableList groups) { ImmutableList filenames = parseWithFilenames(argv, unused -> false, groups); if (!filenames.isEmpty()) throw new FluxEngineException( - "non-option parameter '" + filenames.get(0) + "' seen (try --help)"); + "non-option parameter '" + filenames.get(0) + "' seen (try --help)"); + } + + public static ImmutableList parseWithFilenames(ImmutableList argv, + Predicate callback, + FlagGroup... groups) + { + return parseWithFilenames(argv, callback, ImmutableList.copyOf(groups)); } - public static ImmutableList parseWithFilenames( - String[] argv, Predicate callback, FlagGroup... groups) + public static ImmutableList parseWithFilenames(ImmutableList argv, + Predicate callback, + ImmutableList groups) { - if (groups.length == 0) + if (groups.isEmpty()) throw new IllegalArgumentException("no flag groups"); - if (groups[0].isInitialised()) + if (groups.get(0).isInitialised()) throw new IllegalStateException("called parse() twice"); /* Recursively accumulate a list of all flag names, checking for duplicates. */ @@ -34,10 +47,10 @@ public static ImmutableList parseWithFilenames( ImmutableList.Builder filenames = ImmutableList.builder(); int index = 0; - while (index < argv.length) + while (index < argv.size()) { - String thisArg = argv[index]; - String thatArg = (index < argv.length - 1) ? argv[index + 1] : ""; + String thisArg = argv.get(index); + String thatArg = (index < argv.size() - 1) ? argv.get(index + 1) : ""; String key; String value; @@ -46,14 +59,12 @@ public static ImmutableList parseWithFilenames( if (thisArg.isEmpty()) { /* Ignore this argument. */ - } - else if (thisArg.charAt(0) != '-') + } else if (thisArg.charAt(0) != '-') { /* This is a filename. */ if (!callback.test(thisArg)) filenames.add(thisArg); - } - else + } else { if (thisArg.length() > 1 && thisArg.charAt(1) == '-') { @@ -63,23 +74,20 @@ else if (thisArg.charAt(0) != '-') { key = thisArg.substring(0, equals); value = thisArg.substring(equals + 1); - } - else + } else { key = thisArg; value = thatArg; useThat = true; } - } - else + } else { /* Short option. */ if (thisArg.length() > 2) { key = thisArg.substring(0, 2); value = thisArg.substring(2); - } - else + } else { key = thisArg; value = thatArg; @@ -96,8 +104,7 @@ else if (thisArg.charAt(0) != '-') } if (flag == null) - throw new FluxEngineException( - "unrecognised flag '" + key + "'; try --help"); + throw new FluxEngineException("unrecognised flag '" + key + "'; try --help"); flag.set(value); if (useThat && flag.hasArgument()) index++; diff --git a/javatests/com/cowlark/fluxengine/config/BUILD.bazel b/javatests/com/cowlark/fluxengine/config/BUILD.bazel new file mode 100644 index 00000000..383aa89a --- /dev/null +++ b/javatests/com/cowlark/fluxengine/config/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "ConfigBuilderTest", + srcs = ["ConfigBuilderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java new file mode 100644 index 00000000..66f6e04e --- /dev/null +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -0,0 +1,62 @@ +package com.cowlark.fluxengine.config; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ConfigBuilderTest +{ + @Test + public void loadConfigFileMergesTextproto() throws IOException + { + Path file = Files.createTempFile("config", ".textproto"); + Files.writeString(file, "shortname: \"myconfig\"\ntracks: \"c=0:2\"\n"); + + ConfigProto proto = new ConfigBuilder().loadConfigFile(file.toString()).build(); + + assertThat(proto.getShortname()).isEqualTo("myconfig"); + assertThat(proto.getTracks()).isEqualTo("c=0:2"); + } + + @Test + public void loadConfigFileMergesAcrossFiles() throws IOException + { + Path first = Files.createTempFile("config", ".textproto"); + Path second = Files.createTempFile("config", ".textproto"); + Files.writeString(first, "shortname: \"first\"\n"); + Files.writeString(second, "tracks: \"c=0:2\"\n"); + + ConfigProto proto = new ConfigBuilder() + .loadConfigFile(first.toString()) + .loadConfigFile(second.toString()) + .build(); + + assertThat(proto.getShortname()).isEqualTo("first"); + assertThat(proto.getTracks()).isEqualTo("c=0:2"); + } + + @Test + public void loadConfigFileMissingFileThrows() + { + assertThrows(ConfigException.class, + () -> new ConfigBuilder().loadConfigFile("/nonexistent/config")); + } + + @Test + public void loadConfigFileBadTextprotoThrows() throws IOException + { + Path file = Files.createTempFile("config", ".textproto"); + Files.writeString(file, "this is not a valid textproto\n"); + + assertThrows(ConfigException.class, + () -> new ConfigBuilder().loadConfigFile(file.toString())); + } +} diff --git a/javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel index b16819f7..1db69314 100644 --- a/javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/flags/BUILD.bazel @@ -8,6 +8,7 @@ java_test( deps = [ "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", + "@maven//:com_google_guava_guava", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], diff --git a/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java index 6d8cc2c9..2aa435dd 100644 --- a/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java +++ b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java @@ -1,14 +1,14 @@ package com.cowlark.fluxengine.core.flags; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertThrows; import com.cowlark.fluxengine.core.FluxEngineException; -import java.util.List; +import com.google.common.collect.ImmutableList; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.List; @RunWith(JUnit4.class) public class FlagsTest @@ -18,14 +18,22 @@ public void parsesFlags() { FlagGroup group = new FlagGroup(); StringFlag config = StringFlag.builder() - .setGroup(group).setName("--config").setName("-c").setHelpText("config file").build(); - IntFlag count = IntFlag.builder() - .setGroup(group).setName("--count").setHelpText("count").build(); + .setGroup(group) + .setName("--config") + .setName("-c") + .setHelpText("config file") + .build(); + IntFlag count = + IntFlag.builder().setGroup(group).setName("--count").setHelpText("count").build(); BoolFlag verbose = BoolFlag.builder() - .setGroup(group).setName("--verbose").setHelpText("verbose").build(); + .setGroup(group) + .setName("--verbose") + .setHelpText("verbose") + .build(); - Flags.parse(new String[] { - "--config=foo", "-c", "bar", "--count", "7", "--verbose=true"}, group); + Flags.parse( + ImmutableList.of("--config=foo", "-c", "bar", "--count", "7", "--verbose=true"), + group); assertThat(config.get()).isEqualTo("bar"); assertThat(count.get()).isEqualTo(7); @@ -37,12 +45,18 @@ public void parsesParentGroups() { FlagGroup common = new FlagGroup(); StringFlag serial = StringFlag.builder() - .setGroup(common).setName("--serial").setHelpText("serial").build(); + .setGroup(common) + .setName("--serial") + .setHelpText("serial") + .build(); FlagGroup group = new FlagGroup(common); StringFlag thing = StringFlag.builder() - .setGroup(group).setName("--thing").setHelpText("thing").build(); + .setGroup(group) + .setName("--thing") + .setHelpText("thing") + .build(); - Flags.parse(new String[] {"--serial=abc", "--thing=xyz"}, group); + Flags.parse(ImmutableList.of("--serial=abc", "--thing=xyz"), group); assertThat(serial.get()).isEqualTo("abc"); assertThat(thing.get()).isEqualTo("xyz"); @@ -54,9 +68,12 @@ public void searchesAcrossMultipleRootGroups() FlagGroup first = new FlagGroup(); FlagGroup second = new FlagGroup(); StringFlag thing = StringFlag.builder() - .setGroup(second).setName("--thing").setHelpText("thing").build(); + .setGroup(second) + .setName("--thing") + .setHelpText("thing") + .build(); - Flags.parse(new String[] {"--thing=xyz"}, first, second); + Flags.parse(ImmutableList.of("--thing=xyz"), first, second); assertThat(thing.get()).isEqualTo("xyz"); } @@ -68,8 +85,9 @@ public void duplicateNamesThrow() StringFlag.builder().setGroup(group).setName("--foo").setHelpText("one").build(); StringFlag.builder().setGroup(group).setName("--foo").setHelpText("two").build(); - assertThrows(IllegalStateException.class, - () -> Flags.parse(new String[] {"--foo=x"}, group)); + assertThrows( + IllegalStateException.class, + () -> Flags.parse(ImmutableList.of("--foo=x"), group)); } @Test @@ -77,7 +95,7 @@ public void unknownFlagThrows() { FlagGroup group = new FlagGroup(); assertThrows(FluxEngineException.class, - () -> Flags.parse(new String[] {"--nope=x"}, group)); + () -> Flags.parse(ImmutableList.of("--nope=x"), group)); } @Test @@ -85,7 +103,9 @@ public void filenames() { FlagGroup group = new FlagGroup(); List filenames = Flags.parseWithFilenames( - new String[] {"one.dsk", "two.dsk"}, name -> name.equals("one.dsk"), group); + ImmutableList.of("one.dsk", "two.dsk"), + name -> name.equals("one.dsk"), + group); assertThat(filenames).containsExactly("two.dsk"); } @@ -94,8 +114,8 @@ public void filenames() public void uninitialisedFlagThrows() { FlagGroup group = new FlagGroup(); - StringFlag flag = StringFlag.builder() - .setGroup(group).setName("--foo").setHelpText("foo").build(); + StringFlag flag = + StringFlag.builder().setGroup(group).setName("--foo").setHelpText("foo").build(); assertThrows(IllegalStateException.class, flag::get); } @@ -105,7 +125,11 @@ public void findFlagReturnsTheFlag() { FlagGroup group = new FlagGroup(); StringFlag foo = StringFlag.builder() - .setGroup(group).setName("--foo").setName("-f").setHelpText("foo").build(); + .setGroup(group) + .setName("--foo") + .setName("-f") + .setHelpText("foo") + .build(); assertThat(group.findFlag("--foo")).isSameInstanceAs(foo); assertThat(group.findFlag("-f")).isSameInstanceAs(foo); @@ -117,8 +141,12 @@ public void setNameAddsEachName() { FlagGroup group = new FlagGroup(); StringFlag flag = StringFlag.builder() - .setGroup(group).setName("--long").setName("-l").setName("-long") - .setHelpText("flag").build(); + .setGroup(group) + .setName("--long") + .setName("-l") + .setName("-long") + .setHelpText("flag") + .build(); assertThat(flag.names()).containsExactly("--long", "-l", "-long"); } @@ -128,7 +156,10 @@ public void setNamesTakesACollection() { FlagGroup group = new FlagGroup(); StringFlag flag = StringFlag.builder() - .setGroup(group).setNames(List.of("--long", "-l")).setHelpText("flag").build(); + .setGroup(group) + .setNames(List.of("--long", "-l")) + .setHelpText("flag") + .build(); assertThat(flag.names()).containsExactly("--long", "-l"); } @@ -138,7 +169,10 @@ public void findFlagRecursesToParents() { FlagGroup common = new FlagGroup(); StringFlag serial = StringFlag.builder() - .setGroup(common).setName("--serial").setHelpText("serial").build(); + .setGroup(common) + .setName("--serial") + .setHelpText("serial") + .build(); FlagGroup group = new FlagGroup(common); assertThat(group.findFlag("--serial")).isSameInstanceAs(serial); @@ -149,10 +183,15 @@ public void noArgFlagDoesNotConsumeFollowingToken() { FlagGroup group = new FlagGroup(); SettableFlag flag = SettableFlag.builder() - .setGroup(group).setName("--read-only").setHelpText("read only").build(); + .setGroup(group) + .setName("--read-only") + .setHelpText("read only") + .build(); List filenames = Flags.parseWithFilenames( - new String[] {"--read-only", "image.dsk"}, unused -> false, group); + ImmutableList.of("--read-only", "image.dsk"), + unused -> false, + group); assertThat(flag.get()).isTrue(); assertThat(filenames).containsExactly("image.dsk"); From 42998e693fb7f5cd4e00e79ebc26c2a63f65ff53 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 01:02:21 +0200 Subject: [PATCH 074/192] Avoid nasty warning on startup. --- java/com/cowlark/fluxengine/BUILD.bazel | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index a1360d43..9ac102b3 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -5,6 +5,7 @@ package(default_visibility = ["//visibility:public"]) java_binary( name = "fluxengine", + jvm_flags = ["--enable-native-access=ALL-UNNAMED"], main_class = "com.cowlark.fluxengine.cli.Main", runtime_deps = ["//java/com/cowlark/fluxengine/cli"], ) @@ -16,9 +17,9 @@ native_image( "-O2", "-H:IncludeResources=(javax.usb.properties|org/usb4java/.*/libusb4java\\..*|.*/libjSerialComm.*|.*/jSerialComm.dll)", ], + jar = ":fluxengine_deploy.jar", jni_config = ["jni-config.json"], reflection_config = ["reflect-config.json"], resource_config = ["resource-config.json"], serialization_config = ["serialization-config.json"], - jar = ":fluxengine_deploy.jar", ) From 1be525b7dbf0caf85e24268ee8f07097356b19df Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 01:25:32 +0200 Subject: [PATCH 075/192] Protopath setting works. --- AGENTS.md | 7 + .../fluxengine/config/ConfigBuilder.java | 1 + .../fluxengine/config/ConfigFlagGroup.java | 12 +- .../cowlark/fluxengine/config/ProtoPath.java | 222 ++++++++++++++++++ .../com/cowlark/fluxengine/config/BUILD.bazel | 13 + .../fluxengine/config/ConfigBuilderTest.java | 27 +++ .../fluxengine/config/ProtoPathTest.java | 129 ++++++++++ 7 files changed, 408 insertions(+), 3 deletions(-) create mode 100644 java/com/cowlark/fluxengine/config/ProtoPath.java create mode 100644 javatests/com/cowlark/fluxengine/config/ProtoPathTest.java diff --git a/AGENTS.md b/AGENTS.md index d0bd6b71..aa940807 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -82,6 +82,13 @@ Useful commands: intercept/absorb flags (e.g. a config group) before they fall through to its parents. - `parseWithFilenames` returns `ImmutableList` (Guava). Duplicate flag names throw `IllegalStateException`; unknown flags throw `FluxEngineException`. +- `ConfigFlagGroup` (config package) overrides `findFlag` to intercept dotted `--key.subkey=value` + arguments: it strips the leading `--` and routes them to `ConfigBuilder.set(path, value)`, + which delegates to `ProtoPath.set(builder, path, value)`. `ProtoPath` resolves the dotted + path (with optional `field[4]` indices) against the `ConfigProto` builder via + `com.google.protobuf` reflection, creating intermediate messages and coercing the string + value (int/uint/long/float/double/bool/enum) as needed. Unknown paths and bad values throw + `ConfigException`. ## Dependency injection (Dagger) diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index b3695141..f14d1407 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -62,6 +62,7 @@ public ConfigBuilder showCurrentConfig() public ConfigBuilder set(String key, String value) { + ProtoPath.set(proto, key, value); return this; } diff --git a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java index e34343fe..7320ab73 100644 --- a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java +++ b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java @@ -30,8 +30,14 @@ public ConfigFlagGroup(ConfigBuilder builder) @Override public Flag findFlag(String key) { - if (key.contains(".")) - return ActionFlag.builder().build(); - return null; + if (key.startsWith("--") && key.contains(".")) + { + String path = key.substring(2); + return ActionFlag.builder() + .setGroup(this) + .setValueCallback(value -> builder.set(path, value)) + .build(); + } + return super.findFlag(key); } } diff --git a/java/com/cowlark/fluxengine/config/ProtoPath.java b/java/com/cowlark/fluxengine/config/ProtoPath.java new file mode 100644 index 00000000..5a21a8e9 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ProtoPath.java @@ -0,0 +1,222 @@ +package com.cowlark.fluxengine.config; + +import com.google.protobuf.Descriptors.EnumValueDescriptor; +import com.google.protobuf.Descriptors.FieldDescriptor; +import com.google.protobuf.Message; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Resolves dotted paths (e.g. "drive.drive_type" or "option[0].comment") + * against a protobuf builder and sets the leaf value, ported from + * lib/config/proto.cc's makeProtoPath/ProtoField. + */ +public class ProtoPath +{ + private static final Pattern PATH_COMPONENT = + Pattern.compile("^(\\w+)(?:\\[(\\d+)\\])?$"); + + public static void set(Message.Builder builder, String path, String value) + { + List components = parsePath(path); + setRecursive(builder, components, 0, value, path); + } + + private record PathComponent(String name, int index) + { + } + + private static List parsePath(String path) + { + List components = new ArrayList<>(); + for (String token : path.split("\\.", -1)) + { + Matcher matcher = PATH_COMPONENT.matcher(token); + if (!matcher.matches()) + throw new ConfigException("invalid config path '" + path + "'"); + String index = matcher.group(2); + components.add(new PathComponent(matcher.group(1), + index == null ? -1 : Integer.parseInt(index))); + } + return components; + } + + private static void setRecursive(Message.Builder builder, List path, + int pos, String value, String originalPath) + { + PathComponent component = path.get(pos); + FieldDescriptor field = findField(builder, component, originalPath); + + if (pos == path.size() - 1) + { + setLeaf(builder, component, field, value); + return; + } + + if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) + throw new ConfigException("config field '" + component.name() + "' in '" + + originalPath + "' is not a message"); + + if (field.isRepeated()) + { + int index = requireIndex(component, field); + extendTo(builder, field, index); + Message element = (Message) builder.getRepeatedField(field, index); + Message.Builder elementBuilder = element.toBuilder(); + setRecursive(elementBuilder, path, pos + 1, value, originalPath); + builder.setRepeatedField(field, index, elementBuilder.build()); + } + else + { + if (component.index() >= 0) + throw new ConfigException("config field '" + component.name() + + "' is not repeated but an index is provided"); + Message.Builder elementBuilder; + if (builder.hasField(field)) + elementBuilder = ((Message) builder.getField(field)).toBuilder(); + else + elementBuilder = builder.newBuilderForField(field); + setRecursive(elementBuilder, path, pos + 1, value, originalPath); + builder.setField(field, elementBuilder.build()); + } + } + + private static void setLeaf(Message.Builder builder, PathComponent component, + FieldDescriptor field, String value) + { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) + throw new ConfigException("config field '" + component.name() + + "' is a message and can't be directly set"); + + Object coerced = coerce(field, value); + + if (field.isRepeated()) + { + int index = requireIndex(component, field); + extendScalarTo(builder, field, index); + builder.setRepeatedField(field, index, coerced); + } + else + { + if (component.index() >= 0) + throw new ConfigException("config field '" + component.name() + + "' is not repeated but an index is provided"); + builder.setField(field, coerced); + } + } + + private static FieldDescriptor findField(Message.Builder builder, + PathComponent component, String path) + { + FieldDescriptor field = builder.getDescriptorForType().findFieldByName(component.name()); + if (field == null) + throw new ConfigException( + "no such config field '" + component.name() + "' in '" + path + "'"); + return field; + } + + private static int requireIndex(PathComponent component, FieldDescriptor field) + { + if (component.index() < 0) + throw new ConfigException("config field '" + component.name() + + "' is repeated and must be indexed"); + return component.index(); + } + + private static void extendTo(Message.Builder builder, FieldDescriptor field, int index) + { + while (builder.getRepeatedFieldCount(field) <= index) + builder.addRepeatedField(field, builder.newBuilderForField(field).build()); + } + + private static void extendScalarTo(Message.Builder builder, FieldDescriptor field, int index) + { + Object defaultValue = scalarDefault(field); + while (builder.getRepeatedFieldCount(field) <= index) + builder.addRepeatedField(field, defaultValue); + } + + private static Object scalarDefault(FieldDescriptor field) + { + switch (field.getType()) + { + case FLOAT: return 0.0f; + case DOUBLE: return 0.0; + case INT32: + case SINT32: + case SFIXED32: + case UINT32: + case FIXED32: return 0; + case INT64: + case SINT64: + case SFIXED64: + case UINT64: + case FIXED64: return 0L; + case STRING: return ""; + case BOOL: return false; + case ENUM: return field.getEnumType().getValues().get(0); + default: throw new ConfigException("can't set this config value type"); + } + } + + private static Object coerce(FieldDescriptor field, String value) + { + try + { + switch (field.getType()) + { + case FLOAT: return Float.parseFloat(value); + case DOUBLE: return Double.parseDouble(value); + case INT32: + case SINT32: + case SFIXED32: return Integer.parseInt(value); + case UINT32: + case FIXED32: return Integer.parseUnsignedInt(value); + case INT64: + case SINT64: + case SFIXED64: return Long.parseLong(value); + case UINT64: + case FIXED64: return Long.parseUnsignedLong(value); + case STRING: return value; + case BOOL: return parseBoolean(value); + case ENUM: + EnumValueDescriptor enumValue = field.getEnumType().findValueByName(value); + if (enumValue == null) + throw new ConfigException("unrecognised enum value '" + value + "'"); + return enumValue; + default: throw new ConfigException("can't set this config value type"); + } + } + catch (NumberFormatException e) + { + throw new ConfigException("invalid number '" + value + "'"); + } + } + + private static boolean parseBoolean(String value) + { + switch (value) + { + case "false": + case "f": + case "no": + case "n": + case "0": + return false; + case "true": + case "t": + case "yes": + case "y": + case "1": + return true; + default: + throw new ConfigException("invalid boolean value"); + } + } + + private ProtoPath() + { + } +} diff --git a/javatests/com/cowlark/fluxengine/config/BUILD.bazel b/javatests/com/cowlark/fluxengine/config/BUILD.bazel index 383aa89a..74927bce 100644 --- a/javatests/com/cowlark/fluxengine/config/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/config/BUILD.bazel @@ -9,6 +9,19 @@ java_test( "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/core/flags", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "ProtoPathTest", + srcs = ["ProtoPathTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index 66f6e04e..ed292d08 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -4,6 +4,8 @@ import static org.junit.Assert.assertThrows; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -59,4 +61,29 @@ public void loadConfigFileBadTextprotoThrows() throws IOException assertThrows(ConfigException.class, () -> new ConfigBuilder().loadConfigFile(file.toString())); } + + @Test + public void setMergesWithLoadedConfig() throws IOException + { + Path file = Files.createTempFile("config", ".textproto"); + Files.writeString(file, "shortname: \"myconfig\"\n"); + + ConfigProto proto = new ConfigBuilder() + .loadConfigFile(file.toString()) + .set("tracks", "c=0:2") + .build(); + + assertThat(proto.getShortname()).isEqualTo("myconfig"); + assertThat(proto.getTracks()).isEqualTo("c=0:2"); + } + + @Test + public void fromFlagsSetsDottedConfig() + { + ConfigProto proto = new ConfigBuilder() + .fromFlags(ImmutableList.of("--drive.drive=1"), new FlagGroup()) + .build(); + + assertThat(proto.getDrive().getDrive()).isEqualTo(1); + } } diff --git a/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java b/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java new file mode 100644 index 00000000..3b1bb0cb --- /dev/null +++ b/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java @@ -0,0 +1,129 @@ +package com.cowlark.fluxengine.config; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProtoPathTest +{ + @Test + public void setTopLevelString() + { + assertThat(set("tracks", "c=0:2").getTracks()).isEqualTo("c=0:2"); + } + + @Test + public void setNestedInt() + { + assertThat(set("drive.drive", "0").getDrive().getDrive()).isEqualTo(0); + } + + @Test + public void setNestedBool() + { + assertThat(set("drive.high_density", "y").getDrive().getHighDensity()).isTrue(); + } + + @Test + public void setNestedEnum() + { + assertThat(set("drive.drive_type", "DRIVETYPE_80TRACK") + .getDrive().getDriveType().name()).isEqualTo("DRIVETYPE_80TRACK"); + } + + @Test + public void setRepeatedStringWithIndex() + { + assertThat(set("documentation[2]", "hello").getDocumentationList()) + .containsExactly("", "", "hello"); + } + + @Test + public void setRepeatedMessageField() + { + assertThat(set("option[0].comment", "hello").getOption(0).getComment()) + .isEqualTo("hello"); + } + + @Test + public void setMultipleFieldsMerges() + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, "tracks", "c=0:2"); + ProtoPath.set(builder, "drive.drive", "0"); + ProtoPath.set(builder, "drive.high_density", "y"); + + ConfigProto proto = builder.build(); + + assertThat(proto.getTracks()).isEqualTo("c=0:2"); + assertThat(proto.getDrive().getDrive()).isEqualTo(0); + assertThat(proto.getDrive().getHighDensity()).isTrue(); + } + + @Test + public void setRepeatedMessageFieldsMerge() + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, "option[0].comment", "first"); + ProtoPath.set(builder, "option[1].name", "second"); + + ConfigProto proto = builder.build(); + + assertThat(proto.getOption(0).getComment()).isEqualTo("first"); + assertThat(proto.getOption(1).getName()).isEqualTo("second"); + } + + @Test + public void setUnknownFieldThrows() + { + assertThrows(ConfigException.class, () -> set("bogus", "x")); + } + + @Test + public void setUnknownNestedFieldThrows() + { + assertThrows(ConfigException.class, () -> set("drive.bogus", "x")); + } + + @Test + public void setMessageDirectlyThrows() + { + assertThrows(ConfigException.class, () -> set("drive", "x")); + } + + @Test + public void setBadNumberThrows() + { + assertThrows(ConfigException.class, () -> set("drive.drive", "notanumber")); + } + + @Test + public void setBadEnumThrows() + { + assertThrows(ConfigException.class, () -> set("drive.drive_type", "BOGUS")); + } + + @Test + public void setRepeatedWithoutIndexThrows() + { + assertThrows(ConfigException.class, () -> set("documentation", "x")); + } + + @Test + public void setIndexOnScalarThrows() + { + assertThrows(ConfigException.class, () -> set("tracks[0]", "x")); + } + + private static ConfigProto set(String path, String value) + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, path, value); + return builder.build(); + } +} From 8717a8393bf2f02af9811d7e793bf6c3b98e86d4 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 19:27:15 +0200 Subject: [PATCH 076/192] Fiddle with the USB configuration. --- java/com/cowlark/fluxengine/cli/Command.java | 2 +- ...evicesCommand.java => DevicesCommand.java} | 8 +- .../com/cowlark/fluxengine/config/BUILD.bazel | 7 +- .../fluxengine/config/ConfigBuilder.java | 25 ++- .../cowlark/fluxengine/config/UsbFinder.java | 152 +++++++++++++++ .../cowlark/fluxengine/config/common.proto | 62 +++--- .../cowlark/fluxengine/usb/UsbFactory.java | 179 +----------------- 7 files changed, 218 insertions(+), 217 deletions(-) rename java/com/cowlark/fluxengine/cli/{TestDevicesCommand.java => DevicesCommand.java} (85%) create mode 100644 java/com/cowlark/fluxengine/config/UsbFinder.java diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 70d64fbf..99af333f 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -28,7 +28,6 @@ public interface Command ImmutableMap> TESTABLES = ImmutableMap.>builder() .put("bandwidth", TestBandwidthCommand::new) - .put("devices", TestDevicesCommand::new) .put("voltages", stub("voltages", "Measures the FDD bus voltages.")) .build(); @@ -68,6 +67,7 @@ public interface Command .put("mkdir", stub("mkdir", "Create a directory on disk (or image).")) .put("rpm", stub("rpm", "Measures the disk rotational speed.")) .put("seek", stub("seek", "Moves the disk head.")) + .put("devices", DevicesCommand::new) .put("test", () -> new CommandGroup(TESTABLES, "Various testing commands.")) .build(); diff --git a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java b/java/com/cowlark/fluxengine/cli/DevicesCommand.java similarity index 85% rename from java/com/cowlark/fluxengine/cli/TestDevicesCommand.java rename to java/com/cowlark/fluxengine/cli/DevicesCommand.java index 2d3ab71c..f71f3619 100644 --- a/java/com/cowlark/fluxengine/cli/TestDevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/DevicesCommand.java @@ -2,12 +2,12 @@ import static com.google.common.base.Strings.nullToEmpty; +import com.cowlark.fluxengine.config.UsbFinder; import com.cowlark.fluxengine.core.flags.FlagGroup; -import com.cowlark.fluxengine.usb.UsbFactory; -import com.cowlark.fluxengine.usb.UsbFactory.CandidateDevice; +import com.cowlark.fluxengine.config.UsbFinder.CandidateDevice; import java.util.List; -public class TestDevicesCommand implements Command +public class DevicesCommand implements Command { private static final FlagGroup EMPTY = new FlagGroup(); @@ -20,7 +20,7 @@ public String getHelp() @Override public void run(String[] args) { - List candidates = UsbFactory.findUsbDevices(); + List candidates = UsbFinder.findUsbDevices(); switch (candidates.size()) { case 0: diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index aeed7484..3ebb645e 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -74,11 +74,16 @@ java_library( name = "config", srcs = glob(["*.java"]), deps = [ + ":common_java_proto", ":config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", - "@com_google_protobuf//java/core:core", + "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_fazecast_jSerialComm", "@maven//:com_google_guava_guava", "@maven//:com_jayway_jsonpath_json_path", + "@maven//:javax_usb_usb_api", + "@maven//:org_usb4java_usb4java_javax", ], ) diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index f14d1407..59b2c80d 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -1,7 +1,10 @@ package com.cowlark.fluxengine.config; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; + import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.Flags; +import com.cowlark.fluxengine.fluxsource.Fluxsource; import com.google.common.collect.ImmutableList; import com.google.protobuf.TextFormat; import java.io.IOException; @@ -14,7 +17,8 @@ */ public class ConfigBuilder { - private ConfigProto.Builder proto = ConfigProto.newBuilder(); + private ConfigProto.Builder proto = ConfigProto.newBuilder() + .setFluxSource(Fluxsource.FluxSourceProto.newBuilder().setType(FLUXTYPE_DRIVE).build()); public ConfigBuilder() { @@ -37,8 +41,7 @@ public ConfigBuilder loadConfigFile(String name) try { contents = Files.readString(Path.of(name)); - } - catch (IOException e) + } catch (IOException e) { throw new ConfigException("Cannot open '" + name + "': " + e.getMessage()); } @@ -46,8 +49,7 @@ public ConfigBuilder loadConfigFile(String name) try { TextFormat.merge(contents, proto); - } - catch (TextFormat.ParseException e) + } catch (TextFormat.ParseException e) { throw new ConfigException("couldn't load external config proto"); } @@ -68,7 +70,20 @@ public ConfigBuilder set(String key, String value) public ConfigProto build() { + validate(); return proto.build(); } + private void validate() + { + if ((proto.getFluxSource().getType() == FLUXTYPE_DRIVE) || + (proto.getFluxSink().getType() == FLUXTYPE_DRIVE)) + validateUsb(); + } + + private void validateUsb() + { + if (!proto.getUsb().hasSerial()) + proto.getUsbBuilder().setSerial(UsbFinder.selectDevice(proto).serial); + } } diff --git a/java/com/cowlark/fluxengine/config/UsbFinder.java b/java/com/cowlark/fluxengine/config/UsbFinder.java new file mode 100644 index 00000000..3bab7234 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/UsbFinder.java @@ -0,0 +1,152 @@ +package com.cowlark.fluxengine.config; + +import com.fazecast.jSerialComm.SerialPort; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; +import org.usb4java.javax.Services; +import javax.usb.UsbDeviceDescriptor; +import javax.usb.UsbException; +import javax.usb.UsbHub; +import javax.usb.UsbServices; +import java.util.Set; + +public class UsbFinder +{ + private static final int GREASEWEAZLE_ID = 0x12094d69; + private static final int FLUXENGINE_ID = 0x12096e00; + private static final int APPLESAUCE_ID = 0x16c00483; + private static final Set VALID_DEVICES = + Set.of(GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID); + + private static String getSerialNumber(javax.usb.UsbDevice device) + { + try + { + return device.getSerialNumberString(); + } catch (UsbException | java.io.UnsupportedEncodingException e) + { + return "n/a"; + } + } + + public static ImmutableList findUsbDevices() + { + ImmutableList.Builder candidates = ImmutableList.builder(); + try + { + UsbServices services = new Services(); + UsbHub rootHub = services.getRootUsbHub(); + walkHub(rootHub, candidates); + } catch (UsbException e) + { + System.err.println("USB error: " + e.getMessage()); + } + return candidates.build(); + } + + /* Selects a device to use, based on the configuration, ported from + * lib/usb/usb.cc. */ + public static CandidateDevice selectDevice(ConfigProtoOrBuilder config) + { + ImmutableList candidates = findUsbDevices(); + if (candidates.isEmpty()) + throw new ConfigException("no devices found (is one plugged in? Do you have the " + + "appropriate permissions?"); + + String wantedSerial = config.getUsb().getSerial(); + if (!Strings.isNullOrEmpty(wantedSerial)) + { + for (CandidateDevice candidate : candidates) + { + if (candidate.serial.equals(wantedSerial)) + return candidate; + } + throw new ConfigException("serial number not found"); + } + + if (candidates.size() == 1) + return Iterables.getOnlyElement(candidates); + + throw new ConfigException( + "more than one device detected; you'll need to explicitly specify the serial " + + "number of the device you want"); + } + + private static void walkHub(UsbHub hub, ImmutableList.Builder candidates) + { + for (Object o : hub.getAttachedUsbDevices()) + { + javax.usb.UsbDevice usbDevice = (javax.usb.UsbDevice) o; + if (usbDevice.isUsbHub()) + walkHub((UsbHub) usbDevice, candidates); + + UsbDeviceDescriptor descriptor = usbDevice.getUsbDeviceDescriptor(); + int id = ((descriptor.idVendor() & 0xffff) << 16) | (descriptor.idProduct() & 0xffff); + if (!VALID_DEVICES.contains(id)) + continue; + + CandidateDevice candidate = new CandidateDevice(); + candidate.device = usbDevice; + candidate.id = id; + candidate.serial = getSerialNumber(usbDevice); + + if (id == GREASEWEAZLE_ID) + candidate.type = DeviceType.GREASEWEAZLE; + else if (id == APPLESAUCE_ID) + candidate.type = DeviceType.APPLESAUCE; + else + candidate.type = DeviceType.FLUXENGINE; + + if (id == GREASEWEAZLE_ID || id == APPLESAUCE_ID) + candidate.serialPort = findSerialPort(id, candidate.serial); + + candidates.add(candidate); + } + } + + private static String findSerialPort(int id, String serial) + { + int vendorId = id >>> 16; + int productId = id & 0xffff; + for (SerialPort port : SerialPort.getCommPorts()) + { + if (port.getVendorID() == vendorId && port.getProductID() == productId) + { + String portSerial = port.getSerialNumber(); + if (serial == null || serial.isEmpty() || portSerial == null || + serial.equals(portSerial)) + { + return port.getSystemPortName(); + } + } + } + return null; + } + + public enum DeviceType + { + FLUXENGINE("FluxEngine"), GREASEWEAZLE("Greaseweazle"), APPLESAUCE("Applesauce"); + + private final String deviceName; + + DeviceType(String deviceName) + { + this.deviceName = deviceName; + } + + public String getDeviceName() + { + return deviceName; + } + } + + public static final class CandidateDevice + { + public DeviceType type; + public javax.usb.UsbDevice device; + public int id; + public String serial; + public String serialPort; + } +} diff --git a/java/com/cowlark/fluxengine/config/common.proto b/java/com/cowlark/fluxengine/config/common.proto index 5b6c2474..eeaf6782 100644 --- a/java/com/cowlark/fluxengine/config/common.proto +++ b/java/com/cowlark/fluxengine/config/common.proto @@ -6,48 +6,50 @@ import "google/protobuf/descriptor.proto"; extend google.protobuf.FieldOptions { - optional string help = 50000; + optional string help = 50000; } extend google.protobuf.MessageOptions { - optional bool recurse = 50001 [default = true]; + optional bool recurse = 50001 [default = true]; } enum IndexMode { - INDEXMODE_DRIVE = 0; - INDEXMODE_300 = 1; - INDEXMODE_360 = 2; + INDEXMODE_DRIVE = 0; + INDEXMODE_300 = 1; + INDEXMODE_360 = 2; } enum FluxSourceSinkType { - FLUXTYPE_NOT_SET = 0; - FLUXTYPE_A2R = 1; - FLUXTYPE_AU = 2; - FLUXTYPE_CWF = 3; - FLUXTYPE_DRIVE = 4; - FLUXTYPE_ERASE = 5; - FLUXTYPE_FLUX = 6; - FLUXTYPE_FLX = 7; - FLUXTYPE_KRYOFLUX = 8; - FLUXTYPE_SCP = 9; - FLUXTYPE_TEST_PATTERN = 10; - FLUXTYPE_VCD = 11; - FLUXTYPE_DMK = 12; + FLUXTYPE_NOT_SET = 0; + FLUXTYPE_A2R = 1; + FLUXTYPE_AU = 2; + FLUXTYPE_CWF = 3; + FLUXTYPE_DRIVE = 4; + FLUXTYPE_ERASE = 5; + FLUXTYPE_FLUX = 6; + FLUXTYPE_FLX = 7; + FLUXTYPE_KRYOFLUX = 8; + FLUXTYPE_SCP = 9; + FLUXTYPE_TEST_PATTERN = 10; + FLUXTYPE_VCD = 11; + FLUXTYPE_DMK = 12; } enum ImageReaderWriterType { - IMAGETYPE_NOT_SET = 0; IMAGETYPE_D64 = 1; IMAGETYPE_D88 = 2; - IMAGETYPE_DIM = 3; - IMAGETYPE_DISKCOPY = 4; - IMAGETYPE_FDI = 5; - IMAGETYPE_IMD = 6; - IMAGETYPE_IMG = 7; - IMAGETYPE_JV3 = 8; - IMAGETYPE_LDBS = 9; - IMAGETYPE_NFD = 10; - IMAGETYPE_NSI = 11; - IMAGETYPE_RAW = 12; - IMAGETYPE_TD0 = 13; + IMAGETYPE_NOT_SET = 0; + IMAGETYPE_D64 = 1; + IMAGETYPE_D88 = 2; + IMAGETYPE_DIM = 3; + IMAGETYPE_DISKCOPY = 4; + IMAGETYPE_FDI = 5; + IMAGETYPE_IMD = 6; + IMAGETYPE_IMG = 7; + IMAGETYPE_JV3 = 8; + IMAGETYPE_LDBS = 9; + IMAGETYPE_NFD = 10; + IMAGETYPE_NSI = 11; + IMAGETYPE_RAW = 12; + IMAGETYPE_TD0 = 13; } diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 88b3eccd..9d035e8e 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -1,88 +1,19 @@ package com.cowlark.fluxengine.usb; -import static com.google.common.base.Strings.nullToEmpty; - import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.FluxEngineException; -import com.fazecast.jSerialComm.SerialPort; -import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import org.usb4java.javax.Services; -import javax.usb.UsbDeviceDescriptor; -import javax.usb.UsbException; -import javax.usb.UsbHub; -import javax.usb.UsbServices; -import java.util.Set; +import com.cowlark.fluxengine.config.UsbFinder; +import com.cowlark.fluxengine.config.UsbFinder.CandidateDevice; /** * USB device finder, ported from lib/usb/usbfinder.cc. */ public final class UsbFactory { - public enum DeviceType - { - FLUXENGINE("FluxEngine"), GREASEWEAZLE("Greaseweazle"), APPLESAUCE("Applesauce"); - - private final String deviceName; - - DeviceType(String deviceName) - { - this.deviceName = deviceName; - } - - public String getDeviceName() - { - return deviceName; - } - } - - public static final class CandidateDevice - { - public DeviceType type; - public javax.usb.UsbDevice device; - public int id; - public String serial; - public String serialPort; - } - - private static final int GREASEWEAZLE_ID = 0x12094d69; - private static final int FLUXENGINE_ID = 0x12096e00; - private static final int APPLESAUCE_ID = 0x16c00483; - - private static final Set VALID_DEVICES = - Set.of(GREASEWEAZLE_ID, FLUXENGINE_ID, APPLESAUCE_ID); private UsbFactory() { } - private static String getSerialNumber(javax.usb.UsbDevice device) - { - try - { - return device.getSerialNumberString(); - } catch (UsbException | java.io.UnsupportedEncodingException e) - { - return "n/a"; - } - } - - public static ImmutableList findUsbDevices() - { - ImmutableList.Builder candidates = ImmutableList.builder(); - try - { - UsbServices services = new Services(); - UsbHub rootHub = services.getRootUsbHub(); - walkHub(rootHub, candidates); - } catch (UsbException e) - { - System.err.println("USB error: " + e.getMessage()); - } - return candidates.build(); - } - public static UsbDevice connect(CandidateDevice device) { return null; @@ -90,111 +21,7 @@ public static UsbDevice connect(CandidateDevice device) public static UsbDevice connect(ConfigProto config) { - return connect(selectDevice(config)); - } - - /* Selects a device to use, based on the configuration, ported from - * lib/usb/usb.cc. */ - public static CandidateDevice selectDevice(ConfigProto config) - { - ImmutableList candidates = findUsbDevices(); - if (candidates.isEmpty()) - throw new FluxEngineException("no devices found (is one plugged in? Do you have the " + - "appropriate permissions?"); - - String wantedSerial = config.getUsb().getSerial(); - if (Strings.isNullOrEmpty(wantedSerial)) - { - for (CandidateDevice candidate : candidates) - { - if (candidate.serial.equals(wantedSerial)) - return candidate; - } - throw new FluxEngineException( - "serial number not found (try without one to list or autodetect devices)"); - } - - if (candidates.size() == 1) - return Iterables.getOnlyElement(candidates); - - System.err.println( - "More than one device detected; use --usb.serial= to " + "select one:"); - for (CandidateDevice candidate : candidates) - { - System.err.print(" "); - switch (candidate.type) - { - case FLUXENGINE: - System.err.printf("FluxEngine: %s\n", candidate.serial); - break; - - case GREASEWEAZLE: - System.err.printf( - "Greaseweazle: %s on %s\n", - candidate.serial, - nullToEmpty(candidate.serialPort)); - break; - - case APPLESAUCE: - System.err.printf( - "Applesauce: %s on %s\n", - candidate.serial, - nullToEmpty(candidate.serialPort)); - break; - } - } - System.exit(1); - return null; /* unreachable */ - } - - private static void walkHub(UsbHub hub, ImmutableList.Builder candidates) - { - for (Object o : hub.getAttachedUsbDevices()) - { - javax.usb.UsbDevice usbDevice = (javax.usb.UsbDevice) o; - if (usbDevice.isUsbHub()) - walkHub((UsbHub) usbDevice, candidates); - - UsbDeviceDescriptor descriptor = usbDevice.getUsbDeviceDescriptor(); - int id = ((descriptor.idVendor() & 0xffff) << 16) | (descriptor.idProduct() & 0xffff); - if (!VALID_DEVICES.contains(id)) - continue; - - CandidateDevice candidate = new CandidateDevice(); - candidate.device = usbDevice; - candidate.id = id; - candidate.serial = getSerialNumber(usbDevice); - - if (id == GREASEWEAZLE_ID) - candidate.type = DeviceType.GREASEWEAZLE; - else if (id == APPLESAUCE_ID) - candidate.type = DeviceType.APPLESAUCE; - else - candidate.type = DeviceType.FLUXENGINE; - - if (id == GREASEWEAZLE_ID || id == APPLESAUCE_ID) - candidate.serialPort = findSerialPort(id, candidate.serial); - - candidates.add(candidate); - } + return connect(UsbFinder.selectDevice(config)); } - private static String findSerialPort(int id, String serial) - { - int vendorId = id >>> 16; - int productId = id & 0xffff; - for (SerialPort port : SerialPort.getCommPorts()) - { - if (port.getVendorID() == vendorId && port.getProductID() == productId) - { - String portSerial = port.getSerialNumber(); - if (serial == null || serial.isEmpty() || portSerial == null || - serial.equals(portSerial)) - { - return port.getSystemPortName(); - } - } - } - return null; - } } From 15d7ac50efcd9426a2ba91d65da78080ac5917e6 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 19:27:37 +0200 Subject: [PATCH 077/192] Format. --- java/com/cowlark/fluxengine/arch/BUILD.bazel | 2 +- .../cowlark/fluxengine/arch/agat/agat.proto | 20 +- .../cowlark/fluxengine/arch/amiga/amiga.proto | 8 +- .../fluxengine/arch/apple2/apple2.proto | 22 +- .../fluxengine/arch/brother/brother.proto | 14 +- java/com/cowlark/fluxengine/arch/build.py | 9 +- .../com/cowlark/fluxengine/arch/c64/c64.proto | 4 +- .../com/cowlark/fluxengine/arch/ibm/ibm.proto | 60 +-- .../fluxengine/arch/macintosh/macintosh.proto | 4 +- .../arch/micropolis/micropolis.proto | 50 +-- .../fluxengine/arch/northstar/northstar.proto | 8 +- .../cowlark/fluxengine/arch/tartu/tartu.proto | 38 +- .../fluxengine/arch/tids990/tids990.proto | 32 +- .../fluxengine/arch/victor9k/victor9k.proto | 48 +-- java/com/cowlark/fluxengine/cli/Command.java | 15 +- .../fluxengine/cli/DevicesCommand.java | 8 +- java/com/cowlark/fluxengine/cli/Main.java | 8 +- .../fluxengine/cli/TestBandwidthCommand.java | 5 +- .../cowlark/fluxengine/config/ProtoPath.java | 117 +++--- .../cowlark/fluxengine/config/config.proto | 86 ++-- .../com/cowlark/fluxengine/config/drive.proto | 70 ++-- .../cowlark/fluxengine/config/layout.proto | 98 ++--- java/com/cowlark/fluxengine/core/Bytes.java | 77 ++-- .../fluxengine/core/flags/ActionFlag.java | 11 +- .../cowlark/fluxengine/core/flags/BUILD.bazel | 2 +- .../fluxengine/core/flags/BoolFlag.java | 19 +- .../fluxengine/core/flags/DoubleFlag.java | 21 +- .../fluxengine/core/flags/FlagGroup.java | 47 ++- .../cowlark/fluxengine/core/flags/Flags.java | 8 +- .../fluxengine/core/flags/HexIntFlag.java | 13 +- .../fluxengine/core/flags/IntFlag.java | 16 +- .../fluxengine/core/flags/SettableFlag.java | 2 +- .../fluxengine/core/flags/StringFlag.java | 21 +- .../fluxengine/core/flags/ValueFlag.java | 9 +- .../cowlark/fluxengine/decoders/BUILD.bazel | 2 +- .../fluxengine/decoders/decoders.proto | 84 ++-- .../cowlark/fluxengine/encoders/BUILD.bazel | 2 +- .../fluxengine/encoders/encoders.proto | 30 +- .../cowlark/fluxengine/external/BUILD.bazel | 2 +- .../fluxengine/external/FluxEngine.java | 13 +- .../external/GreaseweazleUtils.java | 15 +- .../com/cowlark/fluxengine/external/fl2.proto | 44 +- .../cowlark/fluxengine/fluxsink/BUILD.bazel | 2 +- .../fluxengine/fluxsink/fluxsink.proto | 34 +- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 2 +- .../fluxengine/fluxsource/fluxsource.proto | 52 +-- .../fluxengine/imagereader/BUILD.bazel | 2 +- .../fluxengine/imagereader/imagereader.proto | 36 +- .../fluxengine/imagewriter/BUILD.bazel | 2 +- .../fluxengine/imagewriter/imagewriter.proto | 108 ++--- java/com/cowlark/fluxengine/jni-config.json | 329 ++++++++++++--- .../cowlark/fluxengine/reflect-config.json | 378 ++++++++++++------ .../cowlark/fluxengine/resource-config.json | 23 +- .../fluxengine/serialization-config.json | 6 +- .../fluxengine/usb/GreaseweazleUsbDevice.java | 122 +++--- .../com/cowlark/fluxengine/usb/UsbDevice.java | 3 +- java/com/cowlark/fluxengine/usb/usb.proto | 36 +- java/com/cowlark/fluxengine/vfs/BUILD.bazel | 2 +- java/com/cowlark/fluxengine/vfs/vfs.proto | 200 ++++----- 59 files changed, 1444 insertions(+), 1057 deletions(-) diff --git a/java/com/cowlark/fluxengine/arch/BUILD.bazel b/java/com/cowlark/fluxengine/arch/BUILD.bazel index a3a6db07..29216cae 100644 --- a/java/com/cowlark/fluxengine/arch/BUILD.bazel +++ b/java/com/cowlark/fluxengine/arch/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/arch/agat/agat.proto b/java/com/cowlark/fluxengine/arch/agat/agat.proto index 1ee851cb..8e712bce 100644 --- a/java/com/cowlark/fluxengine/arch/agat/agat.proto +++ b/java/com/cowlark/fluxengine/arch/agat/agat.proto @@ -7,15 +7,15 @@ import "com/cowlark/fluxengine/config/common.proto"; message AgatDecoderProto {} message AgatEncoderProto { - optional double target_clock_period_us = 1 - [default=2.00, (help)="Data clock period of target format."]; - optional double target_rotational_period_ms = 2 - [default=200.0, (help)="Rotational period of target format."]; - optional int32 post_index_gap_bytes = 3 - [default=40, (help)="Post-index gap before first sector header."]; - optional int32 pre_sector_gap_bytes = 4 - [default=11, (help)="Gap before each sector header."]; - optional int32 pre_data_gap_bytes = 5 - [default=2, (help)="Gap before each sector data record."]; + optional double target_clock_period_us = 1 + [default = 2.00, (help) = "Data clock period of target format."]; + optional double target_rotational_period_ms = 2 + [default = 200.0, (help) = "Rotational period of target format."]; + optional int32 post_index_gap_bytes = 3 + [default = 40, (help) = "Post-index gap before first sector header."]; + optional int32 pre_sector_gap_bytes = 4 + [default = 11, (help) = "Gap before each sector header."]; + optional int32 pre_data_gap_bytes = 5 + [default = 2, (help) = "Gap before each sector data record."]; } diff --git a/java/com/cowlark/fluxengine/arch/amiga/amiga.proto b/java/com/cowlark/fluxengine/arch/amiga/amiga.proto index 2c76629d..84bf510b 100644 --- a/java/com/cowlark/fluxengine/arch/amiga/amiga.proto +++ b/java/com/cowlark/fluxengine/arch/amiga/amiga.proto @@ -7,9 +7,9 @@ import "com/cowlark/fluxengine/config/common.proto"; message AmigaDecoderProto {} message AmigaEncoderProto { - optional double clock_rate_us = 1 - [default=2.00, (help)="Encoded data clock rate."]; - optional double post_index_gap_ms = 2 - [default=0.5, (help)="Post-index gap before first sector header."]; + optional double clock_rate_us = 1 + [default = 2.00, (help) = "Encoded data clock rate."]; + optional double post_index_gap_ms = 2 + [default = 0.5, (help) = "Post-index gap before first sector header."]; } diff --git a/java/com/cowlark/fluxengine/arch/apple2/apple2.proto b/java/com/cowlark/fluxengine/arch/apple2/apple2.proto index 8720ba4e..af11e1a4 100644 --- a/java/com/cowlark/fluxengine/arch/apple2/apple2.proto +++ b/java/com/cowlark/fluxengine/arch/apple2/apple2.proto @@ -5,20 +5,20 @@ option java_package = "com.cowlark.fluxengine.apple2"; import "com/cowlark/fluxengine/config/common.proto"; message Apple2DecoderProto { - optional uint32 side_one_track_offset = 1 - [ default = 0, (help) = "offset to apply to track numbers on side 1" ]; + optional uint32 side_one_track_offset = 1 + [default = 0, (help) = "offset to apply to track numbers on side 1"]; } message Apple2EncoderProto { - /* 245kHz. */ - optional double clock_period_us = 1 - [ default = 4, (help) = "clock rate on the real device" ]; - - /* Apple II disk drives spin at 300rpm. */ - optional double rotational_period_ms = 2 - [ default = 200.0, (help) = "rotational period on the real device" ]; + /* 245kHz. */ + optional double clock_period_us = 1 + [default = 4, (help) = "clock rate on the real device"]; - optional uint32 side_one_track_offset = 3 - [ default = 0, (help) = "offset to apply to track numbers on side 1" ]; + /* Apple II disk drives spin at 300rpm. */ + optional double rotational_period_ms = 2 + [default = 200.0, (help) = "rotational period on the real device"]; + + optional uint32 side_one_track_offset = 3 + [default = 0, (help) = "offset to apply to track numbers on side 1"]; } diff --git a/java/com/cowlark/fluxengine/arch/brother/brother.proto b/java/com/cowlark/fluxengine/arch/brother/brother.proto index 3acd9219..0cb15a33 100644 --- a/java/com/cowlark/fluxengine/arch/brother/brother.proto +++ b/java/com/cowlark/fluxengine/arch/brother/brother.proto @@ -5,16 +5,16 @@ option java_package = "com.cowlark.fluxengine.brother"; message BrotherDecoderProto {} enum BrotherFormat { - BROTHER240 = 0; - BROTHER120 = 1; + BROTHER240 = 0; + BROTHER120 = 1; }; message BrotherEncoderProto { - optional double clock_rate_us = 1 [default = 3.83]; - optional double post_index_gap_ms = 2 [default = 1.0]; - optional double sector_spacing_ms = 3 [default = 16.2]; - optional double post_header_spacing_ms = 4 [default = 0.69]; + optional double clock_rate_us = 1 [default = 3.83]; + optional double post_index_gap_ms = 2 [default = 1.0]; + optional double sector_spacing_ms = 3 [default = 16.2]; + optional double post_header_spacing_ms = 4 [default = 0.69]; - optional BrotherFormat format = 6 [default = BROTHER240]; + optional BrotherFormat format = 6 [default = BROTHER240]; } diff --git a/java/com/cowlark/fluxengine/arch/build.py b/java/com/cowlark/fluxengine/arch/build.py index c0ce8e05..47d6cdf8 100644 --- a/java/com/cowlark/fluxengine/arch/build.py +++ b/java/com/cowlark/fluxengine/arch/build.py @@ -1,8 +1,9 @@ +import sys +from glob import glob +from os.path import * + from build.c import cxxlibrary from build.protobuf import proto, protocc, protolib -from os.path import * -from glob import glob -import sys archs = {basename(dirname(f)) for f in glob("arch/*/*.proto")} @@ -57,5 +58,5 @@ "arch/arch.h": "./arch.h", }, deps=cls - + ["lib/core", "lib/data", "lib/config", "lib/encoders", "lib/decoders"], + + ["lib/core", "lib/data", "lib/config", "lib/encoders", "lib/decoders"], ) diff --git a/java/com/cowlark/fluxengine/arch/c64/c64.proto b/java/com/cowlark/fluxengine/arch/c64/c64.proto index 9624f6b8..0c6b178f 100644 --- a/java/com/cowlark/fluxengine/arch/c64/c64.proto +++ b/java/com/cowlark/fluxengine/arch/c64/c64.proto @@ -7,7 +7,7 @@ import "com/cowlark/fluxengine/config/common.proto"; message Commodore64DecoderProto {} message Commodore64EncoderProto { - optional double post_index_gap_us = 1 [default=0.0, - (help) = "post-index gap before first sector header."]; + optional double post_index_gap_us = 1 [default = 0.0, + (help) = "post-index gap before first sector header."]; } diff --git a/java/com/cowlark/fluxengine/arch/ibm/ibm.proto b/java/com/cowlark/fluxengine/arch/ibm/ibm.proto index 063ebcf6..419fc5d4 100644 --- a/java/com/cowlark/fluxengine/arch/ibm/ibm.proto +++ b/java/com/cowlark/fluxengine/arch/ibm/ibm.proto @@ -5,41 +5,41 @@ option java_package = "com.cowlark.fluxengine.ibm"; import "com/cowlark/fluxengine/config/common.proto"; message IbmDecoderProto { - // Next: 11 - message TrackdataProto { - optional int32 track = 7 [(help) = "if set, the format applies only to this track"]; - optional int32 head = 8 [(help) = "if set, the format applies only to this head"]; + // Next: 11 + message TrackdataProto { + optional int32 track = 7 [(help) = "if set, the format applies only to this track"]; + optional int32 head = 8 [(help) = "if set, the format applies only to this head"]; - optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"]; - optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"]; - optional bool invert_side_byte = 4 [default = false, (help) = "invert the side byte in the sector header"]; + optional bool ignore_side_byte = 2 [default = false, (help) = "ignore side byte in sector header"]; + optional bool ignore_track_byte = 6 [default = false, (help) = "ignore track byte in sector header"]; + optional bool invert_side_byte = 4 [default = false, (help) = "invert the side byte in the sector header"]; - repeated int32 ignore_sector = 10 [(help) = "sectors with these IDs will not be read"]; - } + repeated int32 ignore_sector = 10 [(help) = "sectors with these IDs will not be read"]; + } - repeated TrackdataProto trackdata = 1; + repeated TrackdataProto trackdata = 1; } message IbmEncoderProto { - // Next: 20 - message TrackdataProto { - optional int32 track = 15 [(help) = "if set, the format applies only to this track"]; - optional int32 head = 16 [(help) = "if set, the format applies only to this head"]; - - optional bool emit_iam = 3 [default=true, (help) = "whether to emit an IAM record"]; - optional double target_clock_period_us = 5 [default=4, (help) = "data clock rate on target disk"]; - optional bool use_fm = 6 [default=false, (help) = "whether to use FM encoding rather than MFM"]; - optional int32 idam_byte = 7 [default=0x5554, (help) = "16-bit raw bit pattern of IDAM byte"]; - optional int32 dam_byte = 8 [default=0x5545, (help) = "16-bit raw bit pattern of DAM byte"]; - optional int32 gap0 = 9 [default=80, (help) = "size of gap 1 (the post-index gap)"]; - optional int32 gap1 = 10 [default=50, (help) = "size of gap 2 (the post-ID gap)"]; - optional int32 gap2 = 11 [default=22, (help) = "size of gap 3 (the pre-data gap)"]; - optional int32 gap3 = 12 [default=80, (help) = "size of gap 4 (the post-data or format gap)"]; - optional bool invert_side_byte = 19 [default=false, (help) = "invert the side byte before writing"]; - optional int32 gap_fill_byte = 18 [default=0x9254, (help) = "16-bit raw bit pattern of gap fill byte"]; - optional double target_rotational_period_ms = 1 [default=200, (help) = "rotational period of target disk"]; - } - - repeated TrackdataProto trackdata = 1; + // Next: 20 + message TrackdataProto { + optional int32 track = 15 [(help) = "if set, the format applies only to this track"]; + optional int32 head = 16 [(help) = "if set, the format applies only to this head"]; + + optional bool emit_iam = 3 [default = true, (help) = "whether to emit an IAM record"]; + optional double target_clock_period_us = 5 [default = 4, (help) = "data clock rate on target disk"]; + optional bool use_fm = 6 [default = false, (help) = "whether to use FM encoding rather than MFM"]; + optional int32 idam_byte = 7 [default = 0x5554, (help) = "16-bit raw bit pattern of IDAM byte"]; + optional int32 dam_byte = 8 [default = 0x5545, (help) = "16-bit raw bit pattern of DAM byte"]; + optional int32 gap0 = 9 [default = 80, (help) = "size of gap 1 (the post-index gap)"]; + optional int32 gap1 = 10 [default = 50, (help) = "size of gap 2 (the post-ID gap)"]; + optional int32 gap2 = 11 [default = 22, (help) = "size of gap 3 (the pre-data gap)"]; + optional int32 gap3 = 12 [default = 80, (help) = "size of gap 4 (the post-data or format gap)"]; + optional bool invert_side_byte = 19 [default = false, (help) = "invert the side byte before writing"]; + optional int32 gap_fill_byte = 18 [default = 0x9254, (help) = "16-bit raw bit pattern of gap fill byte"]; + optional double target_rotational_period_ms = 1 [default = 200, (help) = "rotational period of target disk"]; + } + + repeated TrackdataProto trackdata = 1; } diff --git a/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto b/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto index 57c97771..72c0c44a 100644 --- a/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto +++ b/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto @@ -7,7 +7,7 @@ import "com/cowlark/fluxengine/config/common.proto"; message MacintoshDecoderProto {} message MacintoshEncoderProto { - optional double post_index_gap_us = 1 [default = 0.0, - (help) = "post-index gap before first sector header (microseconds)."]; + optional double post_index_gap_us = 1 [default = 0.0, + (help) = "post-index gap before first sector header (microseconds)."]; } diff --git a/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto b/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto index 53b79d75..c6bf4cc3 100644 --- a/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto +++ b/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto @@ -5,35 +5,35 @@ option java_package = "com.cowlark.fluxengine.micropolis"; import "com/cowlark/fluxengine/config/common.proto"; message MicropolisDecoderProto { - enum ChecksumType { - AUTO = 0; - MICROPOLIS = 1; - MZOS = 2; - } - enum EccType { - NONE = 0; - VECTOR = 1; - } + enum ChecksumType { + AUTO = 0; + MICROPOLIS = 1; + MZOS = 2; + } + enum EccType { + NONE = 0; + VECTOR = 1; + } - optional int32 sector_output_size = 1 [default = 256, - (help) = "How much of the raw sector should be saved. Must be 256 or 275"]; - optional ChecksumType checksum_type = 2 [default = AUTO, - (help) = "Checksum type to use: AUTO, MICROPOLIS, MZOS"]; - optional EccType ecc_type = 3 [default = NONE, - (help) = "ECC type to use: NONE, VECTOR"]; + optional int32 sector_output_size = 1 [default = 256, + (help) = "How much of the raw sector should be saved. Must be 256 or 275"]; + optional ChecksumType checksum_type = 2 [default = AUTO, + (help) = "Checksum type to use: AUTO, MICROPOLIS, MZOS"]; + optional EccType ecc_type = 3 [default = NONE, + (help) = "ECC type to use: NONE, VECTOR"]; } message MicropolisEncoderProto { - enum EccType { - NONE = 0; - VECTOR = 1; - } + enum EccType { + NONE = 0; + VECTOR = 1; + } - optional double clock_period_us = 1 - [ default = 2.0, (help) = "clock rate on the real device" ]; - optional double rotational_period_ms = 2 - [ default = 200.0, (help) = "rotational period on the real device" ]; - optional EccType ecc_type = 3 [default = NONE, - (help) = "ECC type to use for IMG data: NONE, VECTOR"]; + optional double clock_period_us = 1 + [default = 2.0, (help) = "clock rate on the real device"]; + optional double rotational_period_ms = 2 + [default = 200.0, (help) = "rotational period on the real device"]; + optional EccType ecc_type = 3 [default = NONE, + (help) = "ECC type to use for IMG data: NONE, VECTOR"]; } diff --git a/java/com/cowlark/fluxengine/arch/northstar/northstar.proto b/java/com/cowlark/fluxengine/arch/northstar/northstar.proto index 35e1f115..32acebd6 100644 --- a/java/com/cowlark/fluxengine/arch/northstar/northstar.proto +++ b/java/com/cowlark/fluxengine/arch/northstar/northstar.proto @@ -7,9 +7,9 @@ import "com/cowlark/fluxengine/config/common.proto"; message NorthstarDecoderProto {} message NorthstarEncoderProto { - optional double clock_period_us = 1 - [ default = 4.0, (help) = "clock rate on the real device (for FM)" ]; - optional double rotational_period_ms = 2 - [ default = 166.0, (help) = "rotational period on the real device" ]; + optional double clock_period_us = 1 + [default = 4.0, (help) = "clock rate on the real device (for FM)"]; + optional double rotational_period_ms = 2 + [default = 166.0, (help) = "rotational period on the real device"]; } diff --git a/java/com/cowlark/fluxengine/arch/tartu/tartu.proto b/java/com/cowlark/fluxengine/arch/tartu/tartu.proto index a2712260..752327c5 100644 --- a/java/com/cowlark/fluxengine/arch/tartu/tartu.proto +++ b/java/com/cowlark/fluxengine/arch/tartu/tartu.proto @@ -7,23 +7,23 @@ import "com/cowlark/fluxengine/config/common.proto"; message TartuDecoderProto {} message TartuEncoderProto { - optional double clock_period_us = 1 - [ default = 2.0, (help) = "clock rate on the real device (for MFM)" ]; - optional double target_rotational_period_ms = 2 - [ default=200, (help) = "rotational period of target disk" ]; - optional double gap1_us = 3 - [ default = 1200, - (help) = "size of gap 1 (the post-index gap)" ]; - optional double gap3_us = 4 - [ default = 150, - (help) = "size of gap 3 (the pre-data gap)" ]; - optional double gap4_us = 5 - [ default = 180, - (help) = "size of gap 4 (the post-data or format gap)" ]; - optional uint64 header_marker = 6 - [ default = 0xaaaaaaaa44895554, - (help) = "64-bit raw bit pattern of header record marker" ]; - optional uint64 data_marker = 7 - [ default = 0xaaaaaaaa44895545, - (help) = "64-bit raw bit pattern of data record marker" ]; + optional double clock_period_us = 1 + [default = 2.0, (help) = "clock rate on the real device (for MFM)"]; + optional double target_rotational_period_ms = 2 + [default = 200, (help) = "rotational period of target disk"]; + optional double gap1_us = 3 + [default = 1200, + (help) = "size of gap 1 (the post-index gap)"]; + optional double gap3_us = 4 + [default = 150, + (help) = "size of gap 3 (the pre-data gap)"]; + optional double gap4_us = 5 + [default = 180, + (help) = "size of gap 4 (the post-data or format gap)"]; + optional uint64 header_marker = 6 + [default = 0xaaaaaaaa44895554, + (help) = "64-bit raw bit pattern of header record marker"]; + optional uint64 data_marker = 7 + [default = 0xaaaaaaaa44895545, + (help) = "64-bit raw bit pattern of data record marker"]; } diff --git a/java/com/cowlark/fluxengine/arch/tids990/tids990.proto b/java/com/cowlark/fluxengine/arch/tids990/tids990.proto index 1edcc33e..aa8bcf99 100644 --- a/java/com/cowlark/fluxengine/arch/tids990/tids990.proto +++ b/java/com/cowlark/fluxengine/arch/tids990/tids990.proto @@ -7,21 +7,21 @@ import "com/cowlark/fluxengine/config/common.proto"; message Tids990DecoderProto {} message Tids990EncoderProto { - optional double rotational_period_ms = 1 [ default = 166, - (help) = "length of a track" ]; - optional int32 sector_count = 2 [ default = 26, - (help) = "number of sectors per track" ]; - optional double clock_period_us = 3 [ default = 2, - (help) = "clock rate of data to write" ]; - optional int32 am1_byte = 4 [ default = 0x2244, - (help) = "16-bit RAW bit pattern to use for the AM1 ID byte" ]; - optional int32 am2_byte = 5 [ default = 0x2245, - (help) = "16-bit RAW bit pattern to use for the AM2 ID byte" ]; - optional int32 gap1_bytes = 6 [ default = 80, - (help) = "size of gap 1 (the post-index gap)" ]; - optional int32 gap2_bytes = 7 [ default = 21, - (help) = "size of gap 2 (the post-ID gap)" ]; - optional int32 gap3_bytes = 8 [ default = 51, - (help) = "size of gap 3 (the post-data or format gap)" ]; + optional double rotational_period_ms = 1 [default = 166, + (help) = "length of a track"]; + optional int32 sector_count = 2 [default = 26, + (help) = "number of sectors per track"]; + optional double clock_period_us = 3 [default = 2, + (help) = "clock rate of data to write"]; + optional int32 am1_byte = 4 [default = 0x2244, + (help) = "16-bit RAW bit pattern to use for the AM1 ID byte"]; + optional int32 am2_byte = 5 [default = 0x2245, + (help) = "16-bit RAW bit pattern to use for the AM2 ID byte"]; + optional int32 gap1_bytes = 6 [default = 80, + (help) = "size of gap 1 (the post-index gap)"]; + optional int32 gap2_bytes = 7 [default = 21, + (help) = "size of gap 2 (the post-ID gap)"]; + optional int32 gap3_bytes = 8 [default = 51, + (help) = "size of gap 3 (the post-data or format gap)"]; } diff --git a/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto b/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto index 2465062f..f4145bd9 100644 --- a/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto +++ b/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto @@ -9,30 +9,30 @@ message Victor9kDecoderProto {} // NEXT: 12 message Victor9kEncoderProto { - message TrackdataProto - { - optional int32 min_track = 1 - [ (help) = "minimum track this format applies to" ]; - optional int32 max_track = 2 - [ (help) = "maximum track this format applies to" ]; - optional int32 head = 3 - [ (help) = "which head this format applies to" ]; + message TrackdataProto + { + optional int32 min_track = 1 + [(help) = "minimum track this format applies to"]; + optional int32 max_track = 2 + [(help) = "maximum track this format applies to"]; + optional int32 head = 3 + [(help) = "which head this format applies to"]; - optional double rotational_period_ms = 4 - [ (help) = "original rotational period of this track" ]; - optional double clock_period_us = 5 - [ (help) = "original data rate of this track" ]; - optional double post_index_gap_us = 6 - [ (help) = "size of post-index gap" ]; - optional int32 pre_header_sync_bits = 10 - [ (help) = "number of sync bits before the sector header" ]; - optional int32 pre_data_sync_bits = 8 - [ (help) = "number of sync bits before the sector data" ]; - optional int32 post_data_gap_bits = 9 - [ (help) = "size of gap between data and the next header" ]; - optional int32 post_header_gap_bits = 11 - [ (help) = "size of gap between header and the data" ]; - } + optional double rotational_period_ms = 4 + [(help) = "original rotational period of this track"]; + optional double clock_period_us = 5 + [(help) = "original data rate of this track"]; + optional double post_index_gap_us = 6 + [(help) = "size of post-index gap"]; + optional int32 pre_header_sync_bits = 10 + [(help) = "number of sync bits before the sector header"]; + optional int32 pre_data_sync_bits = 8 + [(help) = "number of sync bits before the sector data"]; + optional int32 post_data_gap_bits = 9 + [(help) = "size of gap between data and the next header"]; + optional int32 post_header_gap_bits = 11 + [(help) = "size of gap between header and the data"]; + } - repeated TrackdataProto trackdata = 1; + repeated TrackdataProto trackdata = 1; } diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 99af333f..47b25ec4 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -8,15 +8,12 @@ public interface Command { ImmutableMap> ANALYSABLES = - ImmutableMap.>builder() - .put( - "driveresponse", stub( - "driveresponse", - "Measures the drive's ability to read and write pulses.")) - .put( - "layout", - stub("layout", "Produces a visualisation of the track/sector layout.")) - .build(); + ImmutableMap.>builder().put( + "driveresponse", stub( + "driveresponse", + "Measures the drive's ability to read and write pulses.")).put( + "layout", + stub("layout", "Produces a visualisation of the track/sector layout.")).build(); ImmutableMap> FLUXFILEABLES = ImmutableMap.>builder() diff --git a/java/com/cowlark/fluxengine/cli/DevicesCommand.java b/java/com/cowlark/fluxengine/cli/DevicesCommand.java index f71f3619..6eff021c 100644 --- a/java/com/cowlark/fluxengine/cli/DevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/DevicesCommand.java @@ -3,8 +3,8 @@ import static com.google.common.base.Strings.nullToEmpty; import com.cowlark.fluxengine.config.UsbFinder; -import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.config.UsbFinder.CandidateDevice; +import com.cowlark.fluxengine.core.flags.FlagGroup; import java.util.List; public class DevicesCommand implements Command @@ -37,11 +37,7 @@ public void run(String[] args) if (!candidates.isEmpty()) { - System.out.printf( - "%-15s %-30s %s\n", - "Type", - "Serial number", - "Port (if any)"); + System.out.printf("%-15s %-30s %s\n", "Type", "Serial number", "Port (if any)"); for (CandidateDevice candidate : candidates) { System.out.printf( diff --git a/java/com/cowlark/fluxengine/cli/Main.java b/java/com/cowlark/fluxengine/cli/Main.java index 0715ad1c..7b22152f 100644 --- a/java/com/cowlark/fluxengine/cli/Main.java +++ b/java/com/cowlark/fluxengine/cli/Main.java @@ -11,6 +11,10 @@ public class Main { + private Main() + { + } + public static void main(String[] args) { if (args.length == 0 || args[0].equals("--help")) @@ -33,8 +37,4 @@ static void help(Map> commands, String synta for (Map.Entry> entry : commands.entrySet()) System.out.printf(" %s: %s\n", entry.getKey(), entry.getValue().get().getHelp()); } - - private Main() - { - } } diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 6e73ae7a..1815b22f 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.cli; import com.cowlark.fluxengine.config.ConfigBuilder; -import com.cowlark.fluxengine.config.ConfigFlagGroup; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; @@ -21,9 +20,7 @@ public String getHelp() @Override public void run(String[] args) { - ConfigProto config = new ConfigBuilder() - .fromFlags(ImmutableList.copyOf(args)) - .build(); + ConfigProto config = new ConfigBuilder().fromFlags(ImmutableList.copyOf(args)).build(); UsbDevice device = UsbFactory.connect(config); device.testBulkWrite(); diff --git a/java/com/cowlark/fluxengine/config/ProtoPath.java b/java/com/cowlark/fluxengine/config/ProtoPath.java index 5a21a8e9..de45d935 100644 --- a/java/com/cowlark/fluxengine/config/ProtoPath.java +++ b/java/com/cowlark/fluxengine/config/ProtoPath.java @@ -15,17 +15,16 @@ */ public class ProtoPath { - private static final Pattern PATH_COMPONENT = - Pattern.compile("^(\\w+)(?:\\[(\\d+)\\])?$"); + private static final Pattern PATH_COMPONENT = Pattern.compile("^(\\w+)(?:\\[(\\d+)\\])?$"); - public static void set(Message.Builder builder, String path, String value) + private ProtoPath() { - List components = parsePath(path); - setRecursive(builder, components, 0, value, path); } - private record PathComponent(String name, int index) + public static void set(Message.Builder builder, String path, String value) { + List components = parsePath(path); + setRecursive(builder, components, 0, value, path); } private static List parsePath(String path) @@ -37,14 +36,18 @@ private static List parsePath(String path) if (!matcher.matches()) throw new ConfigException("invalid config path '" + path + "'"); String index = matcher.group(2); - components.add(new PathComponent(matcher.group(1), - index == null ? -1 : Integer.parseInt(index))); + components.add(new PathComponent( + matcher.group(1), + index == null ? -1 : Integer.parseInt(index))); } return components; } - private static void setRecursive(Message.Builder builder, List path, - int pos, String value, String originalPath) + private static void setRecursive(Message.Builder builder, + List path, + int pos, + String value, + String originalPath) { PathComponent component = path.get(pos); FieldDescriptor field = findField(builder, component, originalPath); @@ -56,8 +59,9 @@ private static void setRecursive(Message.Builder builder, List pa } if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) - throw new ConfigException("config field '" + component.name() + "' in '" - + originalPath + "' is not a message"); + throw new ConfigException( + "config field '" + component.name() + "' in '" + originalPath + + "' is not a message"); if (field.isRepeated()) { @@ -67,12 +71,11 @@ private static void setRecursive(Message.Builder builder, List pa Message.Builder elementBuilder = element.toBuilder(); setRecursive(elementBuilder, path, pos + 1, value, originalPath); builder.setRepeatedField(field, index, elementBuilder.build()); - } - else + } else { if (component.index() >= 0) - throw new ConfigException("config field '" + component.name() - + "' is not repeated but an index is provided"); + throw new ConfigException("config field '" + component.name() + + "' is not repeated but an index is provided"); Message.Builder elementBuilder; if (builder.hasField(field)) elementBuilder = ((Message) builder.getField(field)).toBuilder(); @@ -83,12 +86,14 @@ private static void setRecursive(Message.Builder builder, List pa } } - private static void setLeaf(Message.Builder builder, PathComponent component, - FieldDescriptor field, String value) + private static void setLeaf(Message.Builder builder, + PathComponent component, + FieldDescriptor field, + String value) { if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) - throw new ConfigException("config field '" + component.name() - + "' is a message and can't be directly set"); + throw new ConfigException("config field '" + component.name() + + "' is a message and can't be directly set"); Object coerced = coerce(field, value); @@ -97,31 +102,31 @@ private static void setLeaf(Message.Builder builder, PathComponent component, int index = requireIndex(component, field); extendScalarTo(builder, field, index); builder.setRepeatedField(field, index, coerced); - } - else + } else { if (component.index() >= 0) - throw new ConfigException("config field '" + component.name() - + "' is not repeated but an index is provided"); + throw new ConfigException("config field '" + component.name() + + "' is not repeated but an index is provided"); builder.setField(field, coerced); } } private static FieldDescriptor findField(Message.Builder builder, - PathComponent component, String path) + PathComponent component, + String path) { FieldDescriptor field = builder.getDescriptorForType().findFieldByName(component.name()); if (field == null) throw new ConfigException( - "no such config field '" + component.name() + "' in '" + path + "'"); + "no such config field '" + component.name() + "' in '" + path + "'"); return field; } private static int requireIndex(PathComponent component, FieldDescriptor field) { if (component.index() < 0) - throw new ConfigException("config field '" + component.name() - + "' is repeated and must be indexed"); + throw new ConfigException( + "config field '" + component.name() + "' is repeated and must be indexed"); return component.index(); } @@ -142,22 +147,30 @@ private static Object scalarDefault(FieldDescriptor field) { switch (field.getType()) { - case FLOAT: return 0.0f; - case DOUBLE: return 0.0; + case FLOAT: + return 0.0f; + case DOUBLE: + return 0.0; case INT32: case SINT32: case SFIXED32: case UINT32: - case FIXED32: return 0; + case FIXED32: + return 0; case INT64: case SINT64: case SFIXED64: case UINT64: - case FIXED64: return 0L; - case STRING: return ""; - case BOOL: return false; - case ENUM: return field.getEnumType().getValues().get(0); - default: throw new ConfigException("can't set this config value type"); + case FIXED64: + return 0L; + case STRING: + return ""; + case BOOL: + return false; + case ENUM: + return field.getEnumType().getValues().get(0); + default: + throw new ConfigException("can't set this config value type"); } } @@ -167,29 +180,37 @@ private static Object coerce(FieldDescriptor field, String value) { switch (field.getType()) { - case FLOAT: return Float.parseFloat(value); - case DOUBLE: return Double.parseDouble(value); + case FLOAT: + return Float.parseFloat(value); + case DOUBLE: + return Double.parseDouble(value); case INT32: case SINT32: - case SFIXED32: return Integer.parseInt(value); + case SFIXED32: + return Integer.parseInt(value); case UINT32: - case FIXED32: return Integer.parseUnsignedInt(value); + case FIXED32: + return Integer.parseUnsignedInt(value); case INT64: case SINT64: - case SFIXED64: return Long.parseLong(value); + case SFIXED64: + return Long.parseLong(value); case UINT64: - case FIXED64: return Long.parseUnsignedLong(value); - case STRING: return value; - case BOOL: return parseBoolean(value); + case FIXED64: + return Long.parseUnsignedLong(value); + case STRING: + return value; + case BOOL: + return parseBoolean(value); case ENUM: EnumValueDescriptor enumValue = field.getEnumType().findValueByName(value); if (enumValue == null) throw new ConfigException("unrecognised enum value '" + value + "'"); return enumValue; - default: throw new ConfigException("can't set this config value type"); + default: + throw new ConfigException("can't set this config value type"); } - } - catch (NumberFormatException e) + } catch (NumberFormatException e) { throw new ConfigException("invalid number '" + value + "'"); } @@ -216,7 +237,7 @@ private static boolean parseBoolean(String value) } } - private ProtoPath() + private record PathComponent(String name, int index) { } } diff --git a/java/com/cowlark/fluxengine/config/config.proto b/java/com/cowlark/fluxengine/config/config.proto index 968d2d4e..28818dfa 100644 --- a/java/com/cowlark/fluxengine/config/config.proto +++ b/java/com/cowlark/fluxengine/config/config.proto @@ -17,77 +17,77 @@ import "com/cowlark/fluxengine/config/layout.proto"; enum SupportStatus { - UNSUPPORTED = 0; DINOSAUR = 1; UNICORN = 2; + UNSUPPORTED = 0; DINOSAUR = 1; UNICORN = 2; } // NEXT_TAG: 27 message ConfigProto { - option(recurse) = false; + option(recurse) = false; - optional string shortname = 1; - optional string comment = 2; - optional bool is_extension = 3; - repeated string documentation = 4; - optional SupportStatus read_support_status = 5 [default = UNSUPPORTED]; - optional SupportStatus write_support_status = 6 [default = UNSUPPORTED]; + optional string shortname = 1; + optional string comment = 2; + optional bool is_extension = 3; + repeated string documentation = 4; + optional SupportStatus read_support_status = 5 [default = UNSUPPORTED]; + optional SupportStatus write_support_status = 6 [default = UNSUPPORTED]; - optional LayoutProto layout = 7; + optional LayoutProto layout = 7; - optional ImageReaderProto image_reader = 8; - optional ImageWriterProto image_writer = 9; - optional FluxSourceProto flux_source = 10; - optional FluxSinkProto flux_sink = 11; - optional DriveProto drive = 12; + optional ImageReaderProto image_reader = 8; + optional ImageWriterProto image_writer = 9; + optional FluxSourceProto flux_source = 10; + optional FluxSinkProto flux_sink = 11; + optional DriveProto drive = 12; - optional EncoderProto encoder = 13; - optional DecoderProto decoder = 14; - optional UsbProto usb = 15; + optional EncoderProto encoder = 13; + optional DecoderProto decoder = 14; + optional UsbProto usb = 15; - optional string tracks = 16; + optional string tracks = 16; - optional FilesystemProto filesystem = 18; + optional FilesystemProto filesystem = 18; - repeated OptionProto option = 19; - repeated OptionGroupProto option_group = 20; + repeated OptionProto option = 19; + repeated OptionGroupProto option_group = 20; } message OptionPrerequisiteProto { - optional string key = 1 [(help) = "path to config value"]; - repeated string value = 2 [(help) = "list of required values"]; + optional string key = 1 [(help) = "path to config value"]; + repeated string value = 2 [(help) = "list of required values"]; } enum OptionApplicabilityHint { - FORMAT = 0; - ANY_SOURCESINK = 1; - HARDWARE_SOURCESINK = 2; - MANUAL_SOURCESINK = 3; - FLUXFILE_SOURCESINK = 4; + FORMAT = 0; + ANY_SOURCESINK = 1; + HARDWARE_SOURCESINK = 2; + MANUAL_SOURCESINK = 3; + FLUXFILE_SOURCESINK = 4; } // NEXT_TAG: 9 message OptionProto { - optional string name = 1 [(help) = "option name"]; - optional string comment = 2 [(help) = "help text for option"]; - optional string message = - 3 [(help) = "message to display when option is in use"]; - optional bool set_by_default = - 6 [(help) = "this option is applied by default", default = false]; - repeated OptionPrerequisiteProto prerequisite = - 7 [(help) = "prerequisites for this option"]; - - optional ConfigProto config = 4 [(help) = "option data"]; - repeated OptionApplicabilityHint applicability = 8; + optional string name = 1 [(help) = "option name"]; + optional string comment = 2 [(help) = "help text for option"]; + optional string message = + 3 [(help) = "message to display when option is in use"]; + optional bool set_by_default = + 6 [(help) = "this option is applied by default", default = false]; + repeated OptionPrerequisiteProto prerequisite = + 7 [(help) = "prerequisites for this option"]; + + optional ConfigProto config = 4 [(help) = "option data"]; + repeated OptionApplicabilityHint applicability = 8; } // NEXT_TAG: 5 message OptionGroupProto { - optional string comment = 1 [(help) = "help text for option group"]; - optional string name = 2 [(help) = "option group name"]; - repeated OptionProto option = 3; - repeated OptionApplicabilityHint applicability = 4; + optional string comment = 1 [(help) = "help text for option group"]; + optional string name = 2 [(help) = "option group name"]; + repeated OptionProto option = 3; + repeated OptionApplicabilityHint applicability = 4; } diff --git a/java/com/cowlark/fluxengine/config/drive.proto b/java/com/cowlark/fluxengine/config/drive.proto index 72b71dd0..392d9b0a 100644 --- a/java/com/cowlark/fluxengine/config/drive.proto +++ b/java/com/cowlark/fluxengine/config/drive.proto @@ -8,44 +8,44 @@ import "com/cowlark/fluxengine/external/fl2.proto"; // Next: 14 message DriveProto { - optional int32 drive = 1 - [ default = 0, (help) = "which drive to write to (0 or 1)" ]; - optional IndexMode index_mode = 2 - [ default = INDEXMODE_DRIVE, (help) = "index pulse source" ]; - optional int32 hard_sector_count = 3 - [ default = 0, (help) = "number of hard sectors on disk" ]; - optional double hard_sector_threshold_ns = 4 - [ default = 0, (help) = "index pulses longer than this interval are " - "considered sector markers; shorter indicates an true index marker" ]; - optional bool high_density = 5 - [ default = false, (help) = "set if this is a high density disk" ]; - optional bool sync_with_index = 6 - [ default = false, (help) = "start reading at index mark" ]; - optional double revolutions = 7 - [ default = 2.5, (help) = "number of revolutions to read" ]; + optional int32 drive = 1 + [default = 0, (help) = "which drive to write to (0 or 1)"]; + optional IndexMode index_mode = 2 + [default = INDEXMODE_DRIVE, (help) = "index pulse source"]; + optional int32 hard_sector_count = 3 + [default = 0, (help) = "number of hard sectors on disk"]; + optional double hard_sector_threshold_ns = 4 + [default = 0, (help) = "index pulses longer than this interval are " + "considered sector markers; shorter indicates an true index marker"]; + optional bool high_density = 5 + [default = false, (help) = "set if this is a high density disk"]; + optional bool sync_with_index = 6 + [default = false, (help) = "start reading at index mark"]; + optional double revolutions = 7 + [default = 2.5, (help) = "number of revolutions to read"]; - optional string tracks = 8 - [ default = "c0-80h0-1", (help) = "Tracks supported by drive" ]; - optional int32 head_bias = 9 [ - default = 0, - (help) = "Bias to apply to the head position (in tracks)" - ]; - optional int32 group_offset = 10 [ - default = 0, - (help) = "When writing groups, erase all tracks except this one in each group" - ]; - optional DriveType drive_type = 11 [ default = DRIVETYPE_UNKNOWN, (help) = "Type of drive" ]; - optional double rotational_period_ms = 12 - [ default = 0, (help) = "Rotational period of the drive in milliseconds (0 to autodetect)"]; + optional string tracks = 8 + [default = "c0-80h0-1", (help) = "Tracks supported by drive"]; + optional int32 head_bias = 9 [ + default = 0, + (help) = "Bias to apply to the head position (in tracks)" + ]; + optional int32 group_offset = 10 [ + default = 0, + (help) = "When writing groups, erase all tracks except this one in each group" + ]; + optional DriveType drive_type = 11 [default = DRIVETYPE_UNKNOWN, (help) = "Type of drive"]; + optional double rotational_period_ms = 12 + [default = 0, (help) = "Rotational period of the drive in milliseconds (0 to autodetect)"]; - enum ErrorBehaviour { - NOTHING = 0; - JIGGLE = 1; - RECALIBRATE = 2; - } + enum ErrorBehaviour { + NOTHING = 0; + JIGGLE = 1; + RECALIBRATE = 2; + } - optional ErrorBehaviour error_behaviour = 13 - [ default = JIGGLE, (help) = "what to do when an error occurs during reads" ]; + optional ErrorBehaviour error_behaviour = 13 + [default = JIGGLE, (help) = "what to do when an error occurs during reads"]; } // vim: ts=4 sw=4 et diff --git a/java/com/cowlark/fluxengine/config/layout.proto b/java/com/cowlark/fluxengine/config/layout.proto index ea798fbb..c37d5d76 100644 --- a/java/com/cowlark/fluxengine/config/layout.proto +++ b/java/com/cowlark/fluxengine/config/layout.proto @@ -7,62 +7,62 @@ import "com/cowlark/fluxengine/external/fl2.proto"; message SectorListProto { - /* either */ - repeated int32 sector = 1 [ (help) = "sector ID" ]; + /* either */ + repeated int32 sector = 1 [(help) = "sector ID"]; - /* or */ - optional int32 start_sector = 2 - [ (help) = "first sector of a continuous run" ]; - optional int32 count = 3 - [ (help) = "number of sectors in a continuous run" ]; - optional int32 skew = 4 - [ default = 1, (help) = "apply this skew between sectors" ]; + /* or */ + optional int32 start_sector = 2 + [(help) = "first sector of a continuous run"]; + optional int32 count = 3 + [(help) = "number of sectors in a continuous run"]; + optional int32 skew = 4 + [default = 1, (help) = "apply this skew between sectors"]; } message LayoutProto { - enum Order - { - UNDEFINED = 0; - CHS = 1; // sort by cylinder, then head, then sector -- libdsk 'alt' - HCS = 2; // sort by head, then cylinder, then sector -- libdsk 'outout' - HCS_RH1 = 3; // as HCS, except the cylinder count for head 1 is reversed -- libdsk 'outback' - } + enum Order + { + UNDEFINED = 0; + CHS = 1; // sort by cylinder, then head, then sector -- libdsk 'alt' + HCS = 2; // sort by head, then cylinder, then sector -- libdsk 'outout' + HCS_RH1 = 3; // as HCS, except the cylinder count for head 1 is reversed -- libdsk 'outback' + } - message LayoutdataProto - { - optional int32 track = 1 [ - (help) = - "if present, this format only applies to this logical track" - ]; - optional int32 up_to_track = 5 - [ (help) = "if present, forms a range with track" ]; - optional int32 side = 2 [ - (help) = - "if present, this format only applies to this logical side" - ]; + message LayoutdataProto + { + optional int32 track = 1 [ + (help) = + "if present, this format only applies to this logical track" + ]; + optional int32 up_to_track = 5 + [(help) = "if present, forms a range with track"]; + optional int32 side = 2 [ + (help) = + "if present, this format only applies to this logical side" + ]; - optional int32 sector_size = 3 - [ default = 512, (help) = "number of bytes per sector" ]; + optional int32 sector_size = 3 + [default = 512, (help) = "number of bytes per sector"]; - optional SectorListProto physical = 4 - [ (help) = "physical order of sectors on disk" ]; - optional SectorListProto filesystem = 6 - [ (help) = "logical order of sectors in filesystem" ]; - } + optional SectorListProto physical = 4 + [(help) = "physical order of sectors on disk"]; + optional SectorListProto filesystem = 6 + [(help) = "logical order of sectors in filesystem"]; + } - repeated LayoutdataProto layoutdata = 1 - [ (help) = "per-track layout information (repeatable)" ]; - optional int32 tracks = 2 - [ default = 0, (help) = "number of tracks in image" ]; - optional int32 sides = 3 - [ default = 0, (help) = "number of sides in image" ]; - optional Order filesystem_track_order = 4 - [ default = CHS, (help) = "the order of sectors in the filesystem" ]; - optional Order image_track_order = 5 - [ default = CHS, (help) = "the order of sectors in disk images" ]; - optional bool swap_sides = 6 - [ default = false, (help) = "the sides are inverted on this disk" ]; - optional FormatType format_type = 7 - [ default = FORMATTYPE_UNKNOWN, (help) = "Format type of image" ]; + repeated LayoutdataProto layoutdata = 1 + [(help) = "per-track layout information (repeatable)"]; + optional int32 tracks = 2 + [default = 0, (help) = "number of tracks in image"]; + optional int32 sides = 3 + [default = 0, (help) = "number of sides in image"]; + optional Order filesystem_track_order = 4 + [default = CHS, (help) = "the order of sectors in the filesystem"]; + optional Order image_track_order = 5 + [default = CHS, (help) = "the order of sectors in disk images"]; + optional bool swap_sides = 6 + [default = false, (help) = "the sides are inverted on this disk"]; + optional FormatType format_type = 7 + [default = FORMATTYPE_UNKNOWN, (help) = "Format type of image"]; } diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index b51aea2b..d1926a83 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -19,22 +19,9 @@ */ public final class Bytes implements List { - private static final class Storage - { - byte[] data; - int refcount; - - Storage(int capacity) - { - data = new byte[capacity]; - refcount = 1; - } - } - private Storage storage; private int low; private int high; - public Bytes() { this(0); @@ -58,14 +45,6 @@ public Bytes(String data) this(data.getBytes(StandardCharsets.UTF_8)); } - public static Bytes of(int... values) - { - byte[] data = new byte[values.length]; - for (int i = 0; i < values.length; i++) - data[i] = (byte) values[i]; - return new Bytes(data); - } - private Bytes(Storage storage, int low, int high) { this.storage = storage; @@ -74,6 +53,14 @@ private Bytes(Storage storage, int low, int high) storage.refcount++; } + public static Bytes of(int... values) + { + byte[] data = new byte[values.length]; + for (int i = 0; i < values.length; i++) + data[i] = (byte) values[i]; + return new Bytes(data); + } + public int size() { return high - low; @@ -162,8 +149,7 @@ public Bytes slice(int start, int len) if (available < len) { Bytes result = new Bytes(len); - System.arraycopy(storage.data, low + start, result.storage.data, 0, - available); + System.arraycopy(storage.data, low + start, result.storage.data, 0, available); return result; } return new Bytes(storage, low + start, low + start + len); @@ -271,13 +257,10 @@ public Bytes decompress() if (n == 0) throw new FluxEngineException("failed to decompress data"); } - } - catch (DataFormatException e) + } catch (DataFormatException e) { - throw new FluxEngineException( - "failed to decompress data: " + e.getMessage()); - } - finally + throw new FluxEngineException("failed to decompress data: " + e.getMessage()); + } finally { inflater.end(); } @@ -288,8 +271,7 @@ public Bytes concat(Bytes other) { Bytes result = new Bytes(size() + other.size()); System.arraycopy(storage.data, low, result.storage.data, 0, size()); - System.arraycopy(other.storage.data, other.low, result.storage.data, - size(), other.size()); + System.arraycopy(other.storage.data, other.low, result.storage.data, size(), other.size()); return result; } @@ -297,8 +279,7 @@ public Bytes repeat(int count) { Bytes result = new Bytes(size() * count); for (int i = 0; i < count; i++) - System.arraycopy(storage.data, low, result.storage.data, i * size(), - size()); + System.arraycopy(storage.data, low, result.storage.data, i * size(), size()); return result; } @@ -335,8 +316,7 @@ public void add(int index, Byte value) throw new IndexOutOfBoundsException(String.valueOf(index)); detach(); ensureCapacity(high + 1); - System.arraycopy(storage.data, low + index, storage.data, low + index + 1, - size() - index); + System.arraycopy(storage.data, low + index, storage.data, low + index + 1, size() - index); storage.data[low + index] = value; high++; } @@ -348,8 +328,12 @@ public Byte remove(int index) throw new IndexOutOfBoundsException(String.valueOf(index)); detach(); byte old = storage.data[low + index]; - System.arraycopy(storage.data, low + index + 1, storage.data, - low + index, size() - index - 1); + System.arraycopy( + storage.data, + low + index + 1, + storage.data, + low + index, + size() - index - 1); high--; return old; } @@ -570,8 +554,11 @@ public int hashCode() @Override public String toString() { - return String.format("Bytes(hash=%08x, refcount=%d, size=%d)", - System.identityHashCode(this), storage.refcount, size()); + return String.format( + "Bytes(hash=%08x, refcount=%d, size=%d)", + System.identityHashCode(this), + storage.refcount, + size()); } /* Copy-on-write: if this window shares its storage with other windows, @@ -606,4 +593,16 @@ private void ensureCapacity(int capacity) System.arraycopy(storage.data, 0, newData, 0, storage.data.length); storage.data = newData; } + + private static final class Storage + { + byte[] data; + int refcount; + + Storage(int capacity) + { + data = new byte[capacity]; + refcount = 1; + } + } } diff --git a/java/com/cowlark/fluxengine/core/flags/ActionFlag.java b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java index add32642..497447d2 100644 --- a/java/com/cowlark/fluxengine/core/flags/ActionFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/ActionFlag.java @@ -1,9 +1,9 @@ package com.cowlark.fluxengine.core.flags; -import java.util.List; -import java.util.function.Consumer; import lombok.Builder; import lombok.Singular; +import java.util.List; +import java.util.function.Consumer; public class ActionFlag extends Flag { @@ -12,8 +12,11 @@ public class ActionFlag extends Flag private final boolean hasArgument; @Builder(setterPrefix = "set") - private ActionFlag(FlagGroup group, @Singular List names, String helpText, - Runnable voidCallback, Consumer valueCallback) + private ActionFlag(FlagGroup group, + @Singular List names, + String helpText, + Runnable voidCallback, + Consumer valueCallback) { super(group, names, helpText); this.voidCallback = voidCallback; diff --git a/java/com/cowlark/fluxengine/core/flags/BUILD.bazel b/java/com/cowlark/fluxengine/core/flags/BUILD.bazel index 4679ecef..5ce39a05 100644 --- a/java/com/cowlark/fluxengine/core/flags/BUILD.bazel +++ b/java/com/cowlark/fluxengine/core/flags/BUILD.bazel @@ -4,8 +4,8 @@ package(default_visibility = ["//visibility:public"]) java_plugin( name = "lombok_plugin", - processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", generates_api = True, + processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", deps = ["@maven//:org_projectlombok_lombok"], ) diff --git a/java/com/cowlark/fluxengine/core/flags/BoolFlag.java b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java index a5253d3a..3409b399 100644 --- a/java/com/cowlark/fluxengine/core/flags/BoolFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/BoolFlag.java @@ -1,19 +1,23 @@ package com.cowlark.fluxengine.core.flags; import com.cowlark.fluxengine.core.FluxEngineException; -import java.util.List; -import java.util.function.Consumer; import lombok.Builder; import lombok.Singular; +import java.util.List; +import java.util.function.Consumer; public class BoolFlag extends ValueFlag { @Builder(setterPrefix = "set") - private BoolFlag(FlagGroup group, @Singular List names, String helpText, - boolean defaultValue, Consumer callback) + private BoolFlag(FlagGroup group, + @Singular List names, + String helpText, + boolean defaultValue, + Consumer callback) { - super(group, names, helpText, defaultValue, - callback != null ? callback : unused -> {}); + super( + group, names, helpText, defaultValue, callback != null ? callback : unused -> { + }); } @Override @@ -36,7 +40,6 @@ public void set(String value) else if (value.equals("false") || value.equals("n")) setValue(false); else - throw new FluxEngineException( - "can't parse '" + value + "'; try 'true' or 'false'"); + throw new FluxEngineException("can't parse '" + value + "'; try 'true' or 'false'"); } } diff --git a/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java index 61405f80..55dfe3e0 100644 --- a/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/DoubleFlag.java @@ -1,19 +1,26 @@ package com.cowlark.fluxengine.core.flags; -import java.util.List; -import java.util.function.Consumer; import lombok.Builder; import lombok.Singular; +import java.util.List; +import java.util.function.Consumer; public class DoubleFlag extends ValueFlag { @Builder(setterPrefix = "set") - private DoubleFlag(FlagGroup group, @Singular List names, String helpText, - Double defaultValue, Consumer callback) + private DoubleFlag(FlagGroup group, + @Singular List names, + String helpText, + Double defaultValue, + Consumer callback) { - super(group, names, helpText, - defaultValue != null ? defaultValue : 1.0, - callback != null ? callback : unused -> {}); + super( + group, + names, + helpText, + defaultValue != null ? defaultValue : 1.0, + callback != null ? callback : unused -> { + }); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/FlagGroup.java b/java/com/cowlark/fluxengine/core/flags/FlagGroup.java index e2b2ade4..6e8d4c60 100644 --- a/java/com/cowlark/fluxengine/core/flags/FlagGroup.java +++ b/java/com/cowlark/fluxengine/core/flags/FlagGroup.java @@ -1,18 +1,17 @@ package com.cowlark.fluxengine.core.flags; import com.google.common.collect.ImmutableList; +import lombok.AccessLevel; +import lombok.Getter; import java.util.ArrayList; import java.util.List; import java.util.Set; -import lombok.AccessLevel; -import lombok.Getter; public class FlagGroup { private final ImmutableList parents; private final List flags = new ArrayList<>(); - @Getter(AccessLevel.PACKAGE) - private boolean initialised; + @Getter(AccessLevel.PACKAGE) private boolean initialised; public FlagGroup() { @@ -24,6 +23,26 @@ public FlagGroup(FlagGroup... parents) this.parents = ImmutableList.copyOf(parents); } + static void initialise(FlagGroup group, Set names) + { + if (group.initialised) + return; + + for (FlagGroup parent : group.parents) + initialise(parent, names); + + for (Flag flag : group.flags) + { + for (String name : flag.names()) + { + if (!names.add(name)) + throw new IllegalStateException("two flags use the name '" + name + "'"); + } + } + + group.initialised = true; + } + public void addFlag(Flag flag) { flags.add(flag); @@ -55,24 +74,4 @@ public void checkInitialised() if (!initialised) throw new IllegalStateException("Attempt to access uninitialised flag"); } - - static void initialise(FlagGroup group, Set names) - { - if (group.initialised) - return; - - for (FlagGroup parent : group.parents) - initialise(parent, names); - - for (Flag flag : group.flags) - { - for (String name : flag.names()) - { - if (!names.add(name)) - throw new IllegalStateException("two flags use the name '" + name + "'"); - } - } - - group.initialised = true; - } } diff --git a/java/com/cowlark/fluxengine/core/flags/Flags.java b/java/com/cowlark/fluxengine/core/flags/Flags.java index 1fef7d50..f9da19c4 100644 --- a/java/com/cowlark/fluxengine/core/flags/Flags.java +++ b/java/com/cowlark/fluxengine/core/flags/Flags.java @@ -11,6 +11,10 @@ */ public class Flags { + private Flags() + { + } + public static void parse(ImmutableList argv, FlagGroup... groups) { parse(argv, ImmutableList.copyOf(groups)); @@ -115,8 +119,4 @@ public static ImmutableList parseWithFilenames(ImmutableList arg return filenames.build(); } - - private Flags() - { - } } diff --git a/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java index d73799fc..9d4d7916 100644 --- a/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/HexIntFlag.java @@ -1,17 +1,20 @@ package com.cowlark.fluxengine.core.flags; -import java.util.List; import lombok.Builder; import lombok.Singular; +import java.util.List; public class HexIntFlag extends ValueFlag { @Builder(setterPrefix = "set") - private HexIntFlag(FlagGroup group, @Singular List names, String helpText, - Integer defaultValue) + private HexIntFlag(FlagGroup group, + @Singular List names, + String helpText, + Integer defaultValue) { - super(group, names, helpText, - defaultValue != null ? defaultValue : 0, unused -> {}); + super( + group, names, helpText, defaultValue != null ? defaultValue : 0, unused -> { + }); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/IntFlag.java b/java/com/cowlark/fluxengine/core/flags/IntFlag.java index 932d01da..555304b3 100644 --- a/java/com/cowlark/fluxengine/core/flags/IntFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/IntFlag.java @@ -1,18 +1,22 @@ package com.cowlark.fluxengine.core.flags; -import java.util.List; -import java.util.function.Consumer; import lombok.Builder; import lombok.Singular; +import java.util.List; +import java.util.function.Consumer; public class IntFlag extends ValueFlag { @Builder(setterPrefix = "set") - private IntFlag(FlagGroup group, @Singular List names, String helpText, - int defaultValue, Consumer callback) + private IntFlag(FlagGroup group, + @Singular List names, + String helpText, + int defaultValue, + Consumer callback) { - super(group, names, helpText, defaultValue, - callback != null ? callback : unused -> {}); + super( + group, names, helpText, defaultValue, callback != null ? callback : unused -> { + }); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/SettableFlag.java b/java/com/cowlark/fluxengine/core/flags/SettableFlag.java index 91b9ba3e..f5f4d4de 100644 --- a/java/com/cowlark/fluxengine/core/flags/SettableFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/SettableFlag.java @@ -1,8 +1,8 @@ package com.cowlark.fluxengine.core.flags; -import java.util.List; import lombok.Builder; import lombok.Singular; +import java.util.List; public class SettableFlag extends Flag { diff --git a/java/com/cowlark/fluxengine/core/flags/StringFlag.java b/java/com/cowlark/fluxengine/core/flags/StringFlag.java index c3a410dd..1ae38339 100644 --- a/java/com/cowlark/fluxengine/core/flags/StringFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/StringFlag.java @@ -1,19 +1,26 @@ package com.cowlark.fluxengine.core.flags; -import java.util.List; -import java.util.function.Consumer; import lombok.Builder; import lombok.Singular; +import java.util.List; +import java.util.function.Consumer; public class StringFlag extends ValueFlag { @Builder(setterPrefix = "set") - private StringFlag(FlagGroup group, @Singular List names, String helpText, - String defaultValue, Consumer callback) + private StringFlag(FlagGroup group, + @Singular List names, + String helpText, + String defaultValue, + Consumer callback) { - super(group, names, helpText, - defaultValue != null ? defaultValue : "", - callback != null ? callback : unused -> {}); + super( + group, + names, + helpText, + defaultValue != null ? defaultValue : "", + callback != null ? callback : unused -> { + }); } @Override diff --git a/java/com/cowlark/fluxengine/core/flags/ValueFlag.java b/java/com/cowlark/fluxengine/core/flags/ValueFlag.java index 53a22588..df39e6b4 100644 --- a/java/com/cowlark/fluxengine/core/flags/ValueFlag.java +++ b/java/com/cowlark/fluxengine/core/flags/ValueFlag.java @@ -5,13 +5,16 @@ public abstract class ValueFlag extends Flag { - private T defaultValue; private final Consumer callback; protected T value; + private T defaultValue; private boolean isSet; - protected ValueFlag(FlagGroup group, List names, String helpText, - T defaultValue, Consumer callback) + protected ValueFlag(FlagGroup group, + List names, + String helpText, + T defaultValue, + Consumer callback) { super(group, names, helpText); this.defaultValue = defaultValue; diff --git a/java/com/cowlark/fluxengine/decoders/BUILD.bazel b/java/com/cowlark/fluxengine/decoders/BUILD.bazel index d905a5d3..70f75b3c 100644 --- a/java/com/cowlark/fluxengine/decoders/BUILD.bazel +++ b/java/com/cowlark/fluxengine/decoders/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/decoders/decoders.proto b/java/com/cowlark/fluxengine/decoders/decoders.proto index 14867521..a83c886c 100644 --- a/java/com/cowlark/fluxengine/decoders/decoders.proto +++ b/java/com/cowlark/fluxengine/decoders/decoders.proto @@ -26,50 +26,50 @@ import "com/cowlark/fluxengine/config/common.proto"; //NEXT: 33 message DecoderProto { - optional double pulse_debounce_threshold = 1 [default = 0.30, - (help) = "ignore pulses with intervals shorter than this, in fractions of a clock"]; - optional double bit_error_threshold = 2 [default = 0.40, - (help) = "amount of error to tolerate in pulse timing, in fractions of a clock"]; - optional double minimum_clock_us = 4 [default = 0.75, - (help) = "refuse to detect clocks shorter than this, to avoid false positives"]; + optional double pulse_debounce_threshold = 1 [default = 0.30, + (help) = "ignore pulses with intervals shorter than this, in fractions of a clock"]; + optional double bit_error_threshold = 2 [default = 0.40, + (help) = "amount of error to tolerate in pulse timing, in fractions of a clock"]; + optional double minimum_clock_us = 4 [default = 0.75, + (help) = "refuse to detect clocks shorter than this, to avoid false positives"]; - optional double pll_adjust = 25 [default = 0.04]; - optional double pll_phase = 26 [default = 0.60]; - optional double flux_scale = 27 [default = 1.0]; + optional double pll_adjust = 25 [default = 0.04]; + optional double pll_phase = 26 [default = 0.60]; + optional double flux_scale = 27 [default = 1.0]; - oneof format { - AesLanierDecoderProto aeslanier = 7; - AgatDecoderProto agat = 28; - AmigaDecoderProto amiga = 8; - Apple2DecoderProto apple2 = 13; - BrotherDecoderProto brother = 6; - Commodore64DecoderProto c64 = 9; - F85DecoderProto f85 = 10; - Fb100DecoderProto fb100 = 11; - IbmDecoderProto ibm = 5; - MacintoshDecoderProto macintosh = 12; - MicropolisDecoderProto micropolis = 14; - MxDecoderProto mx = 15; - NorthstarDecoderProto northstar = 24; - RolandD20DecoderProto rolandd20 = 31; - Smaky6DecoderProto smaky6 = 30; - TartuDecoderProto tartu = 32; - Tids990DecoderProto tids990 = 16; - Victor9kDecoderProto victor9k = 17; - ZilogMczDecoderProto zilogmcz = 18; - } + oneof format { + AesLanierDecoderProto aeslanier = 7; + AgatDecoderProto agat = 28; + AmigaDecoderProto amiga = 8; + Apple2DecoderProto apple2 = 13; + BrotherDecoderProto brother = 6; + Commodore64DecoderProto c64 = 9; + F85DecoderProto f85 = 10; + Fb100DecoderProto fb100 = 11; + IbmDecoderProto ibm = 5; + MacintoshDecoderProto macintosh = 12; + MicropolisDecoderProto micropolis = 14; + MxDecoderProto mx = 15; + NorthstarDecoderProto northstar = 24; + RolandD20DecoderProto rolandd20 = 31; + Smaky6DecoderProto smaky6 = 30; + TartuDecoderProto tartu = 32; + Tids990DecoderProto tids990 = 16; + Victor9kDecoderProto victor9k = 17; + ZilogMczDecoderProto zilogmcz = 18; + } - optional FluxSinkProto copy_flux_to = 19 - [(help) = "while decoding, write a copy of the flux here"]; - optional bool dump_records = 20 [default = false, - (help) = "if set, then dump the parsed but undecoded disk records"]; - optional bool dump_sectors = 21 [default = false, - (help) = "if set, then dump the decoded sectors to this file"]; - optional int32 retries = 22 [default = 5, - (help) = "how many times to retry each track in the event of a read failure"]; - optional string write_csv_to = 23 - [(help) = "if set, write a CSV report of the disk state"]; - optional bool skip_unnecessary_tracks = 29 [default = true, - (help) = "don't read tracks if we already have all necessary sectors"]; + optional FluxSinkProto copy_flux_to = 19 + [(help) = "while decoding, write a copy of the flux here"]; + optional bool dump_records = 20 [default = false, + (help) = "if set, then dump the parsed but undecoded disk records"]; + optional bool dump_sectors = 21 [default = false, + (help) = "if set, then dump the decoded sectors to this file"]; + optional int32 retries = 22 [default = 5, + (help) = "how many times to retry each track in the event of a read failure"]; + optional string write_csv_to = 23 + [(help) = "if set, write a CSV report of the disk state"]; + optional bool skip_unnecessary_tracks = 29 [default = true, + (help) = "don't read tracks if we already have all necessary sectors"]; } diff --git a/java/com/cowlark/fluxengine/encoders/BUILD.bazel b/java/com/cowlark/fluxengine/encoders/BUILD.bazel index fd71a7f5..19616040 100644 --- a/java/com/cowlark/fluxengine/encoders/BUILD.bazel +++ b/java/com/cowlark/fluxengine/encoders/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/encoders/encoders.proto b/java/com/cowlark/fluxengine/encoders/encoders.proto index 6af8be39..f33c80bd 100644 --- a/java/com/cowlark/fluxengine/encoders/encoders.proto +++ b/java/com/cowlark/fluxengine/encoders/encoders.proto @@ -17,19 +17,19 @@ import "com/cowlark/fluxengine/arch/victor9k/victor9k.proto"; message EncoderProto { - oneof format - { - IbmEncoderProto ibm = 3; - BrotherEncoderProto brother = 4; - AmigaEncoderProto amiga = 5; - MacintoshEncoderProto macintosh = 6; - Tids990EncoderProto tids990 = 7; - Commodore64EncoderProto c64 = 8; - NorthstarEncoderProto northstar = 9; - MicropolisEncoderProto micropolis = 10; - Victor9kEncoderProto victor9k = 11; - Apple2EncoderProto apple2 = 12; - AgatEncoderProto agat = 13; - TartuEncoderProto tartu = 14; - } + oneof format + { + IbmEncoderProto ibm = 3; + BrotherEncoderProto brother = 4; + AmigaEncoderProto amiga = 5; + MacintoshEncoderProto macintosh = 6; + Tids990EncoderProto tids990 = 7; + Commodore64EncoderProto c64 = 8; + NorthstarEncoderProto northstar = 9; + MicropolisEncoderProto micropolis = 10; + Victor9kEncoderProto victor9k = 11; + Apple2EncoderProto apple2 = 12; + AgatEncoderProto agat = 13; + TartuEncoderProto tartu = 14; + } } diff --git a/java/com/cowlark/fluxengine/external/BUILD.bazel b/java/com/cowlark/fluxengine/external/BUILD.bazel index d0ae6237..c54e640d 100644 --- a/java/com/cowlark/fluxengine/external/BUILD.bazel +++ b/java/com/cowlark/fluxengine/external/BUILD.bazel @@ -1,6 +1,6 @@ +load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") -load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/external/FluxEngine.java b/java/com/cowlark/fluxengine/external/FluxEngine.java index 2e510a38..2e6fd31e 100644 --- a/java/com/cowlark/fluxengine/external/FluxEngine.java +++ b/java/com/cowlark/fluxengine/external/FluxEngine.java @@ -34,11 +34,8 @@ public final class FluxEngine public static final int FRAME_SIZE = 64; public static final int TICK_FREQUENCY = 12000000; public static final int TICKS_PER_US = TICK_FREQUENCY / 1000000; + public static final int PRECOMPENSATION_THRESHOLD_TICKS = (int) (2.25 * TICKS_PER_US); public static final int TICKS_PER_MS = TICK_FREQUENCY / 1000; - - public static final int PRECOMPENSATION_THRESHOLD_TICKS = - (int) (2.25 * TICKS_PER_US); - public static final double NS_PER_TICK = 1000000000.0 / TICK_FREQUENCY; public static final double US_PER_TICK = 1000000.0 / TICK_FREQUENCY; public static final double MS_PER_TICK = 1000.0 / TICK_FREQUENCY; @@ -83,6 +80,10 @@ public final class FluxEngine public static final int F_DESYNC = 0x00; /* obsolete */ public static final int F_EOF = 0x100; /* synthetic, only produced by library */ + private FluxEngine() + { + } + public static class FrameHeader { public int type; @@ -182,8 +183,4 @@ public static class VoltagesFrame public Voltages inputDrive0Running = new Voltages(); public Voltages inputDrive1Running = new Voltages(); } - - private FluxEngine() - { - } } diff --git a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java index d4ab1da0..cdce700a 100644 --- a/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java +++ b/java/com/cowlark/fluxengine/external/GreaseweazleUtils.java @@ -78,8 +78,7 @@ public static Bytes fluxEngineToGreaseweazle(Bytes fldata, double clock) { bw.write8((int) (250 + high)); bw.write8((int) (1 + (delta - 250) % 255)); - } - else + } else { bw.write8(255); bw.write8(FLUXOP_SPACE); @@ -125,8 +124,7 @@ public static Bytes greaseweazleToFluxEngine(Bytes gwdata, double clock) default: throw new FluxEngineException("bad opcode in Greaseweazle stream"); } - } - else + } else { if (b < 250) ticksGw += b; @@ -155,8 +153,7 @@ public static Bytes greaseweazleToFluxEngine(Bytes gwdata, double clock) bw.write8((int) (deltaFl | F_BIT_INDEX)); lastEventFl = indexFl; indexGw = -1; - } - else if (indexFl == ticksFl) + } else if (indexFl == ticksFl) event |= F_BIT_INDEX; } @@ -196,9 +193,7 @@ private static void write28(ByteWriter out, long val) private static long read28(ByteReader in) { - return (long) ((in.read8() & 0xfe) >> 1) | - (long) (in.read8() & 0xfe) << 6 | - (long) (in.read8() & 0xfe) << 13 | - (long) (in.read8() & 0xfe) << 20; + return (long) ((in.read8() & 0xfe) >> 1) | (long) (in.read8() & 0xfe) << 6 | + (long) (in.read8() & 0xfe) << 13 | (long) (in.read8() & 0xfe) << 20; } } diff --git a/java/com/cowlark/fluxengine/external/fl2.proto b/java/com/cowlark/fluxengine/external/fl2.proto index 2ba53343..79c0fab7 100644 --- a/java/com/cowlark/fluxengine/external/fl2.proto +++ b/java/com/cowlark/fluxengine/external/fl2.proto @@ -6,46 +6,46 @@ import "google/protobuf/descriptor.proto"; extend google.protobuf.FieldOptions { - optional bool isflux = 60000 [default = false]; + optional bool isflux = 60000 [default = false]; } enum FluxMagic { - MAGIC = 0x466c7578; + MAGIC = 0x466c7578; } enum FluxFileVersion { - VERSION_1 = 1; - VERSION_2 = 2; + VERSION_1 = 1; + VERSION_2 = 2; } message TrackFluxProto { - optional int32 track = 1; - optional int32 head = 2; - repeated bytes flux = 3 [(isflux) = true]; + optional int32 track = 1; + optional int32 head = 2; + repeated bytes flux = 3 [(isflux) = true]; } enum DriveType { - DRIVETYPE_UNKNOWN = 0; - DRIVETYPE_40TRACK = 1; - DRIVETYPE_80TRACK = 2; - DRIVETYPE_APPLE2 = 3; + DRIVETYPE_UNKNOWN = 0; + DRIVETYPE_40TRACK = 1; + DRIVETYPE_80TRACK = 2; + DRIVETYPE_APPLE2 = 3; } enum FormatType { - FORMATTYPE_UNKNOWN = 0; - FORMATTYPE_40TRACK = 1; - FORMATTYPE_80TRACK = 2; + FORMATTYPE_UNKNOWN = 0; + FORMATTYPE_40TRACK = 1; + FORMATTYPE_80TRACK = 2; } // NEXT: 8 message FluxFileProto { - optional int32 magic = 1; - optional FluxFileVersion version = 2; - repeated TrackFluxProto track = 3; - optional double rotational_period_ms = 4; - optional DriveType drive_type = 6 [default = DRIVETYPE_UNKNOWN]; - optional FormatType format_type = 7 [default = FORMATTYPE_UNKNOWN]; - - reserved 5; + optional int32 magic = 1; + optional FluxFileVersion version = 2; + repeated TrackFluxProto track = 3; + optional double rotational_period_ms = 4; + optional DriveType drive_type = 6 [default = DRIVETYPE_UNKNOWN]; + optional FormatType format_type = 7 [default = FORMATTYPE_UNKNOWN]; + + reserved 5; } diff --git a/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel index 66a83b10..692431e5 100644 --- a/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto b/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto index 8632e756..8cc38bf5 100644 --- a/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto +++ b/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto @@ -7,38 +7,38 @@ import "com/cowlark/fluxengine/config/common.proto"; message HardwareFluxSinkProto {} message AuFluxSinkProto { - optional string directory = 1 [default = "aufiles", (help) = "directory to write .au files to"]; - optional bool index_markers = 2 [default = true, (help) = "show index markers in the right-hand channel"]; + optional string directory = 1 [default = "aufiles", (help) = "directory to write .au files to"]; + optional bool index_markers = 2 [default = true, (help) = "show index markers in the right-hand channel"]; } message A2RFluxSinkProto { - optional string filename = 1 [default = "flux.a2r", (help) = ".a2r file to write to"]; + optional string filename = 1 [default = "flux.a2r", (help) = ".a2r file to write to"]; } message VcdFluxSinkProto { - optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"]; + optional string directory = 1 [default = "vcdfiles", (help) = "directory to write .vcd files to"]; } message ScpFluxSinkProto { - optional string filename = 2 [default = "flux.scp", (help) = ".scp file to write to"]; - optional bool align_with_index = 3 [default = false, (help) = "discard data before the first index pulse"]; - optional int32 type_byte = 4 [default = 0xff, (help) = "set the SCP disk type byte"]; + optional string filename = 2 [default = "flux.scp", (help) = ".scp file to write to"]; + optional bool align_with_index = 3 [default = false, (help) = "discard data before the first index pulse"]; + optional int32 type_byte = 4 [default = 0xff, (help) = "set the SCP disk type byte"]; } message Fl2FluxSinkProto { - optional string filename = 1 [default = "flux.fl2", (help) = ".fl2 file to write to"]; + optional string filename = 1 [default = "flux.fl2", (help) = ".fl2 file to write to"]; } // Next: 10 message FluxSinkProto { - optional FluxSourceSinkType type = 9 - [default = FLUXTYPE_NOT_SET, (help) = "flux sink type"]; - - optional HardwareFluxSinkProto drive = 2; - optional A2RFluxSinkProto a2r = 8; - optional AuFluxSinkProto au = 3; - optional VcdFluxSinkProto vcd = 4; - optional ScpFluxSinkProto scp = 5; - optional Fl2FluxSinkProto fl2 = 6; + optional FluxSourceSinkType type = 9 + [default = FLUXTYPE_NOT_SET, (help) = "flux sink type"]; + + optional HardwareFluxSinkProto drive = 2; + optional A2RFluxSinkProto a2r = 8; + optional AuFluxSinkProto au = 3; + optional VcdFluxSinkProto vcd = 4; + optional ScpFluxSinkProto scp = 5; + optional Fl2FluxSinkProto fl2 = 6; } diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 86d9f3dc..21c307a2 100644 --- a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto b/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto index b70a0b75..d8f2fa8a 100644 --- a/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto +++ b/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto @@ -7,59 +7,59 @@ import "com/cowlark/fluxengine/config/common.proto"; message HardwareFluxSourceProto {} message TestPatternFluxSourceProto { - optional double interval_us = 1 [default = 4.0, (help) = "interval between pulses"]; - optional double sequence_length_ms = 2 [default = 166.0, (help) = "length of test sequence"]; + optional double interval_us = 1 [default = 4.0, (help) = "interval between pulses"]; + optional double sequence_length_ms = 2 [default = 166.0, (help) = "length of test sequence"]; } message EraseFluxSourceProto {} message KryofluxFluxSourceProto { - optional string directory = 1 [(help) = "path to Kryoflux stream directory"]; + optional string directory = 1 [(help) = "path to Kryoflux stream directory"]; } message ScpFluxSourceProto { - optional string filename = 1 [default = "flux.scp", - (help) = ".scp file to read flux from"]; + optional string filename = 1 [default = "flux.scp", + (help) = ".scp file to read flux from"]; } message A2rFluxSourceProto { - optional string filename = 1 [default = "flux.a2r", - (help) = ".a2r file to read flux from"]; + optional string filename = 1 [default = "flux.a2r", + (help) = ".a2r file to read flux from"]; } message CwfFluxSourceProto { - optional string filename = 1 [default = "flux.cwf", - (help) = ".cwf file to read flux from"]; + optional string filename = 1 [default = "flux.cwf", + (help) = ".cwf file to read flux from"]; } message DmkFluxSourceProto { - optional string directory = 1 [ - (help) = "path to DMK directory"]; + optional string directory = 1 [ + (help) = "path to DMK directory"]; } message Fl2FluxSourceProto { - optional string filename = 1 [default = "flux.fl2", - (help) = ".fl2 file to read flux from"]; + optional string filename = 1 [default = "flux.fl2", + (help) = ".fl2 file to read flux from"]; } message FlxFluxSourceProto { - optional string directory = 1 [(help) = "path to FLX stream directory"]; + optional string directory = 1 [(help) = "path to FLX stream directory"]; } // NEXT: 13 message FluxSourceProto { - optional FluxSourceSinkType type = 9 - [default = FLUXTYPE_NOT_SET, (help) = "flux source type"]; + optional FluxSourceSinkType type = 9 + [default = FLUXTYPE_NOT_SET, (help) = "flux source type"]; - optional A2rFluxSourceProto a2r = 11; - optional CwfFluxSourceProto cwf = 7; - optional DmkFluxSourceProto dmk = 12; - optional EraseFluxSourceProto erase = 4; - optional Fl2FluxSourceProto fl2 = 8; - optional FlxFluxSourceProto flx = 10; - optional HardwareFluxSourceProto drive = 2; - optional KryofluxFluxSourceProto kryoflux = 5; - optional ScpFluxSourceProto scp = 6; - optional TestPatternFluxSourceProto test_pattern = 3; + optional A2rFluxSourceProto a2r = 11; + optional CwfFluxSourceProto cwf = 7; + optional DmkFluxSourceProto dmk = 12; + optional EraseFluxSourceProto erase = 4; + optional Fl2FluxSourceProto fl2 = 8; + optional FlxFluxSourceProto flx = 10; + optional HardwareFluxSourceProto drive = 2; + optional KryofluxFluxSourceProto kryoflux = 5; + optional ScpFluxSourceProto scp = 6; + optional TestPatternFluxSourceProto test_pattern = 3; } diff --git a/java/com/cowlark/fluxengine/imagereader/BUILD.bazel b/java/com/cowlark/fluxengine/imagereader/BUILD.bazel index b4ac9a05..be823b6f 100644 --- a/java/com/cowlark/fluxengine/imagereader/BUILD.bazel +++ b/java/com/cowlark/fluxengine/imagereader/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/imagereader/imagereader.proto b/java/com/cowlark/fluxengine/imagereader/imagereader.proto index 8aa5a420..be56e700 100644 --- a/java/com/cowlark/fluxengine/imagereader/imagereader.proto +++ b/java/com/cowlark/fluxengine/imagereader/imagereader.proto @@ -5,10 +5,10 @@ option java_package = "com.cowlark.fluxengine.imagereader"; import "com/cowlark/fluxengine/config/common.proto"; message ImgInputOutputProto { - optional bool filesystem_sector_order = 1 [ - (help) = "read/write sector image in filesystem order", - default = false - ]; + optional bool filesystem_sector_order = 1 [ + (help) = "read/write sector image in filesystem order", + default = false + ]; } message DiskCopyInputProto {} @@ -25,20 +25,20 @@ message NfdInputProto {} // NEXT_TAG: 14 message ImageReaderProto { - optional string filename = 1 [ (help) = "filename of input sector image" ]; + optional string filename = 1 [(help) = "filename of input sector image"]; - optional ImageReaderWriterType type = 13 - [default = IMAGETYPE_NOT_SET, (help) = "input image type"]; + optional ImageReaderWriterType type = 13 + [default = IMAGETYPE_NOT_SET, (help) = "input image type"]; - optional ImgInputOutputProto img = 2; - optional DiskCopyInputProto diskcopy = 3; - optional ImdInputProto imd = 4; - optional Jv3InputProto jv3 = 5; - optional D64InputProto d64 = 6; - optional NsiInputProto nsi = 7; - optional Td0InputProto td0 = 8; - optional DimInputProto dim = 9; - optional FdiInputProto fdi = 10; - optional D88InputProto d88 = 11; - optional NfdInputProto nfd = 12; + optional ImgInputOutputProto img = 2; + optional DiskCopyInputProto diskcopy = 3; + optional ImdInputProto imd = 4; + optional Jv3InputProto jv3 = 5; + optional D64InputProto d64 = 6; + optional NsiInputProto nsi = 7; + optional Td0InputProto td0 = 8; + optional DimInputProto dim = 9; + optional FdiInputProto fdi = 10; + optional D88InputProto d88 = 11; + optional NfdInputProto nfd = 12; } diff --git a/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel index 51fefc4b..7b30e6e5 100644 --- a/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel +++ b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto b/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto index d5769a5d..dab3bded 100644 --- a/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto +++ b/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto @@ -9,31 +9,31 @@ message D64OutputProto {} message LDBSOutputProto { - enum DataRate - { - RATE_HD = 0; - RATE_DD = 1; - RATE_SD = 2; - RATE_ED = 3; - RATE_GUESS = -1; - } + enum DataRate + { + RATE_HD = 0; + RATE_DD = 1; + RATE_SD = 2; + RATE_ED = 3; + RATE_GUESS = -1; + } - enum RecordingMode - { - RECMODE_MFM = 0; - RECMODE_FM = 1; - RECMODE_GCR_MAC = 0x12; - RECMODE_GCR_PRODOS = 0x14; - RECMODE_GCR_LISA = 0x22; - RECMODE_GUESS = -1; - } + enum RecordingMode + { + RECMODE_MFM = 0; + RECMODE_FM = 1; + RECMODE_GCR_MAC = 0x12; + RECMODE_GCR_PRODOS = 0x14; + RECMODE_GCR_LISA = 0x22; + RECMODE_GUESS = -1; + } - optional DataRate data_rate = 1 - [ default = RATE_GUESS, (help) = "data rate to use in LDBS file" ]; - optional RecordingMode recording_mode = 2 [ - default = RECMODE_GUESS, - (help) = "recording mode to use in LDBS file" - ]; + optional DataRate data_rate = 1 + [default = RATE_GUESS, (help) = "data rate to use in LDBS file"]; + optional RecordingMode recording_mode = 2 [ + default = RECMODE_GUESS, + (help) = "recording mode to use in LDBS file" + ]; } message DiskCopyOutputProto {} @@ -42,43 +42,43 @@ message RawOutputProto {} message D88OutputProto {} message ImdOutputProto { - enum DataRate - { - RATE_HD = 0; - RATE_DD = 1; - RATE_SD = 2; - RATE_GUESS = -1; - } + enum DataRate + { + RATE_HD = 0; + RATE_DD = 1; + RATE_SD = 2; + RATE_GUESS = -1; + } - enum RecordingMode - { - RECMODE_MFM = 0; - RECMODE_FM = 1; - RECMODE_GUESS = -1; - } - optional DataRate data_rate = 1 - [ default = RATE_GUESS, (help) = "data rate to use in IMD file" ]; - optional RecordingMode recording_mode = 2 [ - default = RECMODE_GUESS, - (help) = "recording mode (FM or MFM encoding) to use in IMD file" - ]; - optional string comment = 3 [ (help) = "comment to set in IMD file" ]; + enum RecordingMode + { + RECMODE_MFM = 0; + RECMODE_FM = 1; + RECMODE_GUESS = -1; + } + optional DataRate data_rate = 1 + [default = RATE_GUESS, (help) = "data rate to use in IMD file"]; + optional RecordingMode recording_mode = 2 [ + default = RECMODE_GUESS, + (help) = "recording mode (FM or MFM encoding) to use in IMD file" + ]; + optional string comment = 3 [(help) = "comment to set in IMD file"]; } // NEXT_TAG: 12 message ImageWriterProto { - optional string filename = 1 [ (help) = "filename of output sector image" ]; + optional string filename = 1 [(help) = "filename of output sector image"]; - optional ImageReaderWriterType type = 10 - [ default = IMAGETYPE_NOT_SET, (help) = "image writer type" ]; + optional ImageReaderWriterType type = 10 + [default = IMAGETYPE_NOT_SET, (help) = "image writer type"]; - optional ImgInputOutputProto img = 2; - optional D64OutputProto d64 = 3; - optional LDBSOutputProto ldbs = 4; - optional DiskCopyOutputProto diskcopy = 5; - optional NsiOutputProto nsi = 6; - optional RawOutputProto raw = 7; - optional D88OutputProto d88 = 8; - optional ImdOutputProto imd = 9; + optional ImgInputOutputProto img = 2; + optional D64OutputProto d64 = 3; + optional LDBSOutputProto ldbs = 4; + optional DiskCopyOutputProto diskcopy = 5; + optional NsiOutputProto nsi = 6; + optional RawOutputProto raw = 7; + optional D88OutputProto d88 = 8; + optional ImdOutputProto imd = 9; } diff --git a/java/com/cowlark/fluxengine/jni-config.json b/java/com/cowlark/fluxengine/jni-config.json index 10a04b31..d16fa69b 100644 --- a/java/com/cowlark/fluxengine/jni-config.json +++ b/java/com/cowlark/fluxengine/jni-config.json @@ -1,66 +1,267 @@ [ -{ - "name":"[Lcom.fazecast.jSerialComm.SerialPort;" -}, -{ - "name":"[Lorg.usb4java.EndpointDescriptor;" -}, -{ - "name":"[Lorg.usb4java.Interface;" -}, -{ - "name":"[Lorg.usb4java.InterfaceDescriptor;" -}, -{ - "name":"com.fazecast.jSerialComm.SerialPort", - "fields":[{"name":"autoFlushIOBuffers"}, {"name":"baudRate"}, {"name":"comPort"}, {"name":"dataBits"}, {"name":"disableConfig"}, {"name":"disableExclusiveLock"}, {"name":"eventFlags"}, {"name":"eventListenerRunning"}, {"name":"flowControl"}, {"name":"friendlyName"}, {"name":"isDtrEnabled"}, {"name":"isRtsEnabled"}, {"name":"manufacturer"}, {"name":"parity"}, {"name":"portDescription"}, {"name":"portHandle"}, {"name":"portLocation"}, {"name":"productID"}, {"name":"readTimeout"}, {"name":"receiveDeviceQueueSize"}, {"name":"requestElevatedPermissions"}, {"name":"rs485ActiveHigh"}, {"name":"rs485DelayAfter"}, {"name":"rs485DelayBefore"}, {"name":"rs485EnableTermination"}, {"name":"rs485Mode"}, {"name":"rs485ModeControlEnabled"}, {"name":"rs485RxDuringTx"}, {"name":"sendDeviceQueueSize"}, {"name":"serialNumber"}, {"name":"stopBits"}, {"name":"timeoutMode"}, {"name":"vendorID"}, {"name":"writeTimeout"}, {"name":"xoffStopChar"}, {"name":"xonStartChar"}], - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"java.lang.Exception" -}, -{ - "name":"org.usb4java.ConfigDescriptor", - "fields":[{"name":"configDescriptorPointer"}] -}, -{ - "name":"org.usb4java.Context", - "fields":[{"name":"contextPointer"}] -}, -{ - "name":"org.usb4java.Device", - "fields":[{"name":"devicePointer"}], - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.usb4java.DeviceDescriptor", - "fields":[{"name":"deviceDescriptorBuffer"}, {"name":"deviceDescriptorPointer"}] -}, -{ - "name":"org.usb4java.DeviceHandle", - "fields":[{"name":"deviceHandlePointer"}] -}, -{ - "name":"org.usb4java.DeviceList", - "fields":[{"name":"deviceListPointer"}, {"name":"size"}] -}, -{ - "name":"org.usb4java.EndpointDescriptor", - "fields":[{"name":"endpointDescriptorPointer"}], - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.usb4java.Interface", - "fields":[{"name":"interfacePointer"}], - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.usb4java.InterfaceDescriptor", - "fields":[{"name":"interfaceDescriptorPointer"}], - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"org.usb4java.LibUsb", - "methods":[{"name":"hotplugCallback","parameterTypes":["org.usb4java.Context","org.usb4java.Device","int","long"] }, {"name":"triggerPollfdAdded","parameterTypes":["java.io.FileDescriptor","int","long"] }, {"name":"triggerPollfdRemoved","parameterTypes":["java.io.FileDescriptor","long"] }] -} + { + "name": "[Lcom.fazecast.jSerialComm.SerialPort;" + }, + { + "name": "[Lorg.usb4java.EndpointDescriptor;" + }, + { + "name": "[Lorg.usb4java.Interface;" + }, + { + "name": "[Lorg.usb4java.InterfaceDescriptor;" + }, + { + "name": "com.fazecast.jSerialComm.SerialPort", + "fields": [ + { + "name": "autoFlushIOBuffers" + }, + { + "name": "baudRate" + }, + { + "name": "comPort" + }, + { + "name": "dataBits" + }, + { + "name": "disableConfig" + }, + { + "name": "disableExclusiveLock" + }, + { + "name": "eventFlags" + }, + { + "name": "eventListenerRunning" + }, + { + "name": "flowControl" + }, + { + "name": "friendlyName" + }, + { + "name": "isDtrEnabled" + }, + { + "name": "isRtsEnabled" + }, + { + "name": "manufacturer" + }, + { + "name": "parity" + }, + { + "name": "portDescription" + }, + { + "name": "portHandle" + }, + { + "name": "portLocation" + }, + { + "name": "productID" + }, + { + "name": "readTimeout" + }, + { + "name": "receiveDeviceQueueSize" + }, + { + "name": "requestElevatedPermissions" + }, + { + "name": "rs485ActiveHigh" + }, + { + "name": "rs485DelayAfter" + }, + { + "name": "rs485DelayBefore" + }, + { + "name": "rs485EnableTermination" + }, + { + "name": "rs485Mode" + }, + { + "name": "rs485ModeControlEnabled" + }, + { + "name": "rs485RxDuringTx" + }, + { + "name": "sendDeviceQueueSize" + }, + { + "name": "serialNumber" + }, + { + "name": "stopBits" + }, + { + "name": "timeoutMode" + }, + { + "name": "vendorID" + }, + { + "name": "writeTimeout" + }, + { + "name": "xoffStopChar" + }, + { + "name": "xonStartChar" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "java.lang.Exception" + }, + { + "name": "org.usb4java.ConfigDescriptor", + "fields": [ + { + "name": "configDescriptorPointer" + } + ] + }, + { + "name": "org.usb4java.Context", + "fields": [ + { + "name": "contextPointer" + } + ] + }, + { + "name": "org.usb4java.Device", + "fields": [ + { + "name": "devicePointer" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.usb4java.DeviceDescriptor", + "fields": [ + { + "name": "deviceDescriptorBuffer" + }, + { + "name": "deviceDescriptorPointer" + } + ] + }, + { + "name": "org.usb4java.DeviceHandle", + "fields": [ + { + "name": "deviceHandlePointer" + } + ] + }, + { + "name": "org.usb4java.DeviceList", + "fields": [ + { + "name": "deviceListPointer" + }, + { + "name": "size" + } + ] + }, + { + "name": "org.usb4java.EndpointDescriptor", + "fields": [ + { + "name": "endpointDescriptorPointer" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.usb4java.Interface", + "fields": [ + { + "name": "interfacePointer" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.usb4java.InterfaceDescriptor", + "fields": [ + { + "name": "interfaceDescriptorPointer" + } + ], + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "org.usb4java.LibUsb", + "methods": [ + { + "name": "hotplugCallback", + "parameterTypes": [ + "org.usb4java.Context", + "org.usb4java.Device", + "int", + "long" + ] + }, + { + "name": "triggerPollfdAdded", + "parameterTypes": [ + "java.io.FileDescriptor", + "int", + "long" + ] + }, + { + "name": "triggerPollfdRemoved", + "parameterTypes": [ + "java.io.FileDescriptor", + "long" + ] + } + ] + } ] diff --git a/java/com/cowlark/fluxengine/reflect-config.json b/java/com/cowlark/fluxengine/reflect-config.json index 4433c193..54ec922c 100644 --- a/java/com/cowlark/fluxengine/reflect-config.json +++ b/java/com/cowlark/fluxengine/reflect-config.json @@ -1,114 +1,268 @@ [ -{ - "name":"com.cowlark.fluxengine.cli.TestDevicesCommand", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, -{ - "name":"groovy.lang.Closure" -}, -{ - "name":"java.lang.Object", - "allDeclaredFields":true, - "queryAllDeclaredMethods":true -}, -{ - "name":"java.nio.file.Path" -}, -{ - "name":"java.nio.file.Paths", - "methods":[{"name":"get","parameterTypes":["java.lang.String","java.lang.String[]"] }] -}, -{ - "name":"java.security.SecureRandomParameters" -}, -{ - "name":"java.sql.Connection" -}, -{ - "name":"java.sql.Driver" -}, -{ - "name":"java.sql.DriverManager", - "methods":[{"name":"getConnection","parameterTypes":["java.lang.String"] }, {"name":"getDriver","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.sql.Time", - "methods":[{"name":"","parameterTypes":["long"] }] -}, -{ - "name":"java.sql.Timestamp", - "methods":[{"name":"valueOf","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.time.Duration", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.Instant", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.LocalDate", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.LocalDateTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.LocalTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.MonthDay", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.OffsetDateTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.OffsetTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.Period", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.Year", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.YearMonth", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"java.time.ZoneId", - "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.time.ZoneOffset", - "methods":[{"name":"of","parameterTypes":["java.lang.String"] }] -}, -{ - "name":"java.time.ZonedDateTime", - "methods":[{"name":"parse","parameterTypes":["java.lang.CharSequence"] }] -}, -{ - "name":"javax.usb.UsbHostManager" -}, -{ - "name":"org.usb4java.javax.Services", - "methods":[{"name":"","parameterTypes":[] }] -}, -{ - "name":"sun.security.provider.NativePRNG", - "methods":[{"name":"","parameterTypes":[] }, {"name":"","parameterTypes":["java.security.SecureRandomParameters"] }] -}, -{ - "name":"sun.security.provider.SHA", - "methods":[{"name":"","parameterTypes":[] }] -} + { + "name": "com.cowlark.fluxengine.cli.TestDevicesCommand", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "groovy.lang.Closure" + }, + { + "name": "java.lang.Object", + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "java.nio.file.Path" + }, + { + "name": "java.nio.file.Paths", + "methods": [ + { + "name": "get", + "parameterTypes": [ + "java.lang.String", + "java.lang.String[]" + ] + } + ] + }, + { + "name": "java.security.SecureRandomParameters" + }, + { + "name": "java.sql.Connection" + }, + { + "name": "java.sql.Driver" + }, + { + "name": "java.sql.DriverManager", + "methods": [ + { + "name": "getConnection", + "parameterTypes": [ + "java.lang.String" + ] + }, + { + "name": "getDriver", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.sql.Time", + "methods": [ + { + "name": "", + "parameterTypes": [ + "long" + ] + } + ] + }, + { + "name": "java.sql.Timestamp", + "methods": [ + { + "name": "valueOf", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.time.Duration", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.Instant", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.LocalDate", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.LocalDateTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.LocalTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.MonthDay", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.OffsetDateTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.OffsetTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.Period", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.Year", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.YearMonth", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "java.time.ZoneId", + "methods": [ + { + "name": "of", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.time.ZoneOffset", + "methods": [ + { + "name": "of", + "parameterTypes": [ + "java.lang.String" + ] + } + ] + }, + { + "name": "java.time.ZonedDateTime", + "methods": [ + { + "name": "parse", + "parameterTypes": [ + "java.lang.CharSequence" + ] + } + ] + }, + { + "name": "javax.usb.UsbHostManager" + }, + { + "name": "org.usb4java.javax.Services", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + }, + { + "name": "sun.security.provider.NativePRNG", + "methods": [ + { + "name": "", + "parameterTypes": [] + }, + { + "name": "", + "parameterTypes": [ + "java.security.SecureRandomParameters" + ] + } + ] + }, + { + "name": "sun.security.provider.SHA", + "methods": [ + { + "name": "", + "parameterTypes": [] + } + ] + } ] diff --git a/java/com/cowlark/fluxengine/resource-config.json b/java/com/cowlark/fluxengine/resource-config.json index 4f1c7bfe..5f5eabb9 100644 --- a/java/com/cowlark/fluxengine/resource-config.json +++ b/java/com/cowlark/fluxengine/resource-config.json @@ -1,11 +1,16 @@ { - "resources":{ - "includes":[{ - "pattern":"\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" - }, { - "pattern":"\\Qjavax.usb.properties\\E" - }, { - "pattern":"\\Qorg/usb4java/linux-x86-64/libusb4java.so\\E" - }]}, - "bundles":[] + "resources": { + "includes": [ + { + "pattern": "\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" + }, + { + "pattern": "\\Qjavax.usb.properties\\E" + }, + { + "pattern": "\\Qorg/usb4java/linux-x86-64/libusb4java.so\\E" + } + ] + }, + "bundles": [] } diff --git a/java/com/cowlark/fluxengine/serialization-config.json b/java/com/cowlark/fluxengine/serialization-config.json index f3d7e06e..681d9fd4 100644 --- a/java/com/cowlark/fluxengine/serialization-config.json +++ b/java/com/cowlark/fluxengine/serialization-config.json @@ -1,8 +1,8 @@ { - "types":[ + "types": [ ], - "lambdaCapturingTypes":[ + "lambdaCapturingTypes": [ ], - "proxies":[ + "proxies": [ ] } diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index e2a55ada..5ab502a6 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -46,15 +46,11 @@ */ class GreaseweazleUsbDevice extends UsbDevice { - private enum Version - {V22, V24, V29} - private final SerialPort serial; private final GreaseweazleProto config; private Version version; private long clock; private long revolutions; - GreaseweazleUsbDevice(String port, GreaseweazleProto config) { this.config = config; @@ -74,8 +70,7 @@ else if (version == 22) else throw new FluxEngineException(String.format( "only Greaseweazle firmware versions 22 and 24 or above are currently " + - "supported, but you have version %d. Please file a bug.", - version)); + "supported, but you have version %d. Please file a bug.", version)); /* Twiddle the baud rate, which indicates to the Greaseweazle that the * data stream has been reset. */ @@ -87,6 +82,49 @@ else if (version == 22) doCommand(CMD_SET_BUS_TYPE, config.getBusType().getNumber()); } + private static String gwError(int e) + { + switch (e) + { + case ACK_OKAY: + return "OK"; + case ACK_BAD_COMMAND: + return "Bad command"; + case ACK_NO_INDEX: + return "No index"; + case ACK_NO_TRK0: + return "No track 0"; + case ACK_FLUX_OVERFLOW: + return "Overflow"; + case ACK_FLUX_UNDERFLOW: + return "Underflow"; + case ACK_WRPROT: + return "Write protected"; + case ACK_NO_UNIT: + return "No unit"; + case ACK_NO_BUS: + return "No bus"; + case ACK_BAD_UNIT: + return "Invalid unit"; + case ACK_BAD_PIN: + return "Invalid pin"; + case ACK_BAD_CYLINDER: + return "Invalid track"; + default: + return "Unknown error"; + } + } + + private static long ssRandNext(long x) + { + return (x & 1) != 0 ? (x >> 1) ^ 0x80000062L : x >> 1; + } + + private static double getCurrentTime() + { + return System.nanoTime() / 1e9; + } + private int getVersion() { doCommand(CMD_GET_INFO, GETINFO_FIRMWARE); @@ -103,10 +141,8 @@ private int getVersion() private long read28() { ByteReader buffer = new ByteReader(readBytes(4)); - return (long) ((buffer.read8() & 0xfe) >> 1) | - (long) (buffer.read8() & 0xfe) << 6 | - (long) (buffer.read8() & 0xfe) << 13 | - (long) (buffer.read8() & 0xfe) << 20; + return (long) ((buffer.read8() & 0xfe) >> 1) | (long) (buffer.read8() & 0xfe) << 6 | + (long) (buffer.read8() & 0xfe) << 13 | (long) (buffer.read8() & 0xfe) << 20; } private void doCommand(int cmd, int... payload) @@ -137,7 +173,8 @@ private void doCommand(byte[] command) command[0], buffer.getByte(1))); if (buffer.getByte(1) != 0) - throw new FluxEngineException("Greaseweazle error: " + gwError(buffer.getByte(1) & 0xff)); + throw new FluxEngineException( + "Greaseweazle error: " + gwError(buffer.getByte(1) & 0xff)); } @Override @@ -150,8 +187,8 @@ public void seek(int track) public long getRotationalPeriod(int hardSectorCount) { if (hardSectorCount != 0) - throw new FluxEngineException("hard sectors are currently unsupported on the " + - "Greaseweazle"); + throw new FluxEngineException( + "hard sectors are currently unsupported on the " + "Greaseweazle"); /* The Greaseweazle doesn't have a command to fetch the period directly, * so we have to do a flux read. */ @@ -316,8 +353,8 @@ public void testBulkRead() public Bytes read(int side, boolean synced, long readTime, long hardSectorThreshold) { if (hardSectorThreshold != 0) - throw new FluxEngineException("hard sectors are currently unsupported on the " + - "Greaseweazle"); + throw new FluxEngineException( + "hard sectors are currently unsupported on the " + "Greaseweazle"); doCommand(CMD_HEAD, side); @@ -370,8 +407,8 @@ public Bytes read(int side, boolean synced, long readTime, long hardSectorThresh public void write(int side, Bytes fldata, long hardSectorThreshold) { if (hardSectorThreshold != 0) - throw new FluxEngineException("hard sectors are currently unsupported on the " + - "Greaseweazle"); + throw new FluxEngineException( + "hard sectors are currently unsupported on the " + "Greaseweazle"); doCommand(CMD_HEAD, side); switch (version) @@ -396,8 +433,8 @@ public void write(int side, Bytes fldata, long hardSectorThreshold) public void erase(int side, long hardSectorThreshold) { if (hardSectorThreshold != 0) - throw new FluxEngineException("hard sectors are currently unsupported on the " + - "Greaseweazle"); + throw new FluxEngineException( + "hard sectors are currently unsupported on the " + "Greaseweazle"); doCommand(CMD_HEAD, side); @@ -426,44 +463,6 @@ public void measureVoltages(Voltages[] voltages) throw new FluxEngineException("unsupported operation on the Greaseweazle"); } - private static String gwError(int e) - { - switch (e) - { - case ACK_OKAY: - return "OK"; - case ACK_BAD_COMMAND: - return "Bad command"; - case ACK_NO_INDEX: - return "No index"; - case ACK_NO_TRK0: - return "No track 0"; - case ACK_FLUX_OVERFLOW: - return "Overflow"; - case ACK_FLUX_UNDERFLOW: - return "Underflow"; - case ACK_WRPROT: - return "Write protected"; - case ACK_NO_UNIT: - return "No unit"; - case ACK_NO_BUS: - return "No bus"; - case ACK_BAD_UNIT: - return "Invalid unit"; - case ACK_BAD_PIN: - return "Invalid pin"; - case ACK_BAD_CYLINDER: - return "Invalid track"; - default: - return "Unknown error"; - } - } - - private static long ssRandNext(long x) - { - return (x & 1) != 0 ? (x >> 1) ^ 0x80000062L : x >> 1; - } - private int readByte() { return readBytes(1).get(0) & 0xff; @@ -476,8 +475,7 @@ private Bytes readBytes(int count) byte[] chunk = new byte[4096]; while (bw.pos() < count) { - int read = serial.readBytes(chunk, - Math.min(chunk.length, count - bw.pos())); + int read = serial.readBytes(chunk, Math.min(chunk.length, count - bw.pos())); if (read < 0) throw new FluxEngineException("serial read failed"); for (int i = 0; i < read; i++) @@ -498,8 +496,6 @@ private void writeBytes(Bytes data) writeBytes(data.toByteArray()); } - private static double getCurrentTime() - { - return System.nanoTime() / 1e9; - } + private enum Version + {V22, V24, V29} } diff --git a/java/com/cowlark/fluxengine/usb/UsbDevice.java b/java/com/cowlark/fluxengine/usb/UsbDevice.java index c469ede7..9d103f27 100644 --- a/java/com/cowlark/fluxengine/usb/UsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/UsbDevice.java @@ -20,8 +20,7 @@ public void recalibrate() public abstract void testBulkRead(); - public abstract Bytes read(int side, boolean synced, long readTime, - long hardSectorThreshold); + public abstract Bytes read(int side, boolean synced, long readTime, long hardSectorThreshold); public abstract void write(int side, Bytes bytes, long hardSectorThreshold); diff --git a/java/com/cowlark/fluxengine/usb/usb.proto b/java/com/cowlark/fluxengine/usb/usb.proto index 0461eecd..54da620a 100644 --- a/java/com/cowlark/fluxengine/usb/usb.proto +++ b/java/com/cowlark/fluxengine/usb/usb.proto @@ -5,30 +5,30 @@ option java_package = "com.cowlark.fluxengine.usb"; import "com/cowlark/fluxengine/config/common.proto"; message GreaseweazleProto { - enum BusType { /* note that these must match CMD_SET_BUS codes */ - BUSTYPE_INVALID = 0; - IBMPC = 1; - SHUGART = 2; - APPLE2 = 3; - }; + enum BusType {/* note that these must match CMD_SET_BUS codes */ + BUSTYPE_INVALID = 0; + IBMPC = 1; + SHUGART = 2; + APPLE2 = 3; + }; - optional string port = 1 - [(help) = "Greaseweazle serial port to use"]; - optional BusType bus_type = 2 - [(help) = "which FDD bus type is in use", default = IBMPC]; + optional string port = 1 + [(help) = "Greaseweazle serial port to use"]; + optional BusType bus_type = 2 + [(help) = "which FDD bus type is in use", default = IBMPC]; } message ApplesauceProto { - optional string port = 1 - [(help) = "Applesauce serial port to use"]; - optional bool verbose = 2 - [(help) = "Enable verbose protocol logging", default = false]; + optional string port = 1 + [(help) = "Applesauce serial port to use"]; + optional bool verbose = 2 + [(help) = "Enable verbose protocol logging", default = false]; } message UsbProto { - optional string serial = 1 - [(help) = "serial number of FluxEngine or Greaseweazle device to use"]; + optional string serial = 1 + [(help) = "serial number of FluxEngine or Greaseweazle device to use"]; - optional GreaseweazleProto greaseweazle = 2 [(help) = "Greaseweazle-specific options"]; - optional ApplesauceProto applesauce = 3 [(help) = "Applesauce-specific options"]; + optional GreaseweazleProto greaseweazle = 2 [(help) = "Greaseweazle-specific options"]; + optional ApplesauceProto applesauce = 3 [(help) = "Applesauce-specific options"]; } diff --git a/java/com/cowlark/fluxengine/vfs/BUILD.bazel b/java/com/cowlark/fluxengine/vfs/BUILD.bazel index 807279d8..d8869385 100644 --- a/java/com/cowlark/fluxengine/vfs/BUILD.bazel +++ b/java/com/cowlark/fluxengine/vfs/BUILD.bazel @@ -1,5 +1,5 @@ -load("@rules_proto//proto:defs.bzl", "proto_library") load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) diff --git a/java/com/cowlark/fluxengine/vfs/vfs.proto b/java/com/cowlark/fluxengine/vfs/vfs.proto index 98f0db77..481073a0 100644 --- a/java/com/cowlark/fluxengine/vfs/vfs.proto +++ b/java/com/cowlark/fluxengine/vfs/vfs.proto @@ -6,51 +6,51 @@ import "com/cowlark/fluxengine/config/common.proto"; message AcornDfsProto { - enum Flavour - { - UNDEFINED = 0; - ACORN_DFS = 1; - } - - optional Flavour flavour = 1 - [ default = ACORN_DFS, (help) = "which flavour of DFS to implement" ]; + enum Flavour + { + UNDEFINED = 0; + ACORN_DFS = 1; + } + + optional Flavour flavour = 1 + [default = ACORN_DFS, (help) = "which flavour of DFS to implement"]; } message Brother120FsProto {} message FatFsProto { - optional uint32 cluster_size = 1 - [ (help) = "cluster size (for new filesystems); 0 to select automatically", - default = 0 ]; - optional uint32 root_directory_entries = 2 - [ (help) = "number of entries in the root directory (for new filesystems); 0 to select automatically", - default = 0 ]; + optional uint32 cluster_size = 1 + [(help) = "cluster size (for new filesystems); 0 to select automatically", + default = 0]; + optional uint32 root_directory_entries = 2 + [(help) = "number of entries in the root directory (for new filesystems); 0 to select automatically", + default = 0]; } message CpmFsProto { - message Location - { - optional uint32 track = 1 [ (help) = "track number" ]; - optional uint32 side = 2 [ (help) = "side number" ]; - optional uint32 sector = 3 [ (help) = "sector ID" ]; - } - - message Padding - { - optional uint32 amount = 1 - [ (help) = "number of sectors of padding to insert" ]; - optional uint32 every = 2 - [ (help) = "insert padding after this many sectors" ]; - } - - optional Location filesystem_start = 1 - [ (help) = "position of the start of the filesystem" ]; - optional int32 block_size = 2 [ (help) = "allocation block size" ]; - optional int32 dir_entries = 3 - [ (help) = "number of entries in the directory" ]; - optional Padding padding = 4 - [ (help) = "wasted sectors not considered part of the filesystem" ]; + message Location + { + optional uint32 track = 1 [(help) = "track number"]; + optional uint32 side = 2 [(help) = "side number"]; + optional uint32 sector = 3 [(help) = "sector ID"]; + } + + message Padding + { + optional uint32 amount = 1 + [(help) = "number of sectors of padding to insert"]; + optional uint32 every = 2 + [(help) = "insert padding after this many sectors"]; + } + + optional Location filesystem_start = 1 + [(help) = "position of the start of the filesystem"]; + optional int32 block_size = 2 [(help) = "allocation block size"]; + optional int32 dir_entries = 3 + [(help) = "number of entries in the directory"]; + optional Padding padding = 4 + [(help) = "wasted sectors not considered part of the filesystem"]; } message AmigaFfsProto {} @@ -59,34 +59,34 @@ message MacHfsProto {} message CbmfsProto { - optional uint32 directory_track = 1 [ - default = 17, - (help) = "which track the directory is on (zero-based numbering)" - ]; + optional uint32 directory_track = 1 [ + default = 17, + (help) = "which track the directory is on (zero-based numbering)" + ]; } message ProdosProto {} message AppledosProto { - optional uint32 filesystem_offset_sectors = 1 [ - default = 0, - (help) = "offset the entire offset up the disk this many sectors" - ]; + optional uint32 filesystem_offset_sectors = 1 [ + default = 0, + (help) = "offset the entire offset up the disk this many sectors" + ]; } message Smaky6FsProto {} message PhileProto { - optional uint32 block_size = 1 - [ default = 1024, (help) = "Phile filesystem block size" ]; + optional uint32 block_size = 1 + [default = 1024, (help) = "Phile filesystem block size"]; } message LifProto { - optional uint32 block_size = 1 - [ default = 256, (help) = "LIF filesystem block size" ]; + optional uint32 block_size = 1 + [default = 256, (help) = "LIF filesystem block size"]; } message MicrodosProto {} @@ -94,65 +94,65 @@ message MicrodosProto {} // NEXT_TAG: 16 message ZDosProto { - message Location - { - optional uint32 track = 1 [ (help) = "track number" ]; - optional uint32 sector = 3 [ (help) = "sector ID" ]; - } - - optional Location filesystem_start = 1 - [ (help) = "position of the filesystem superblock" ]; + message Location + { + optional uint32 track = 1 [(help) = "track number"]; + optional uint32 sector = 3 [(help) = "sector ID"]; + } + + optional Location filesystem_start = 1 + [(help) = "position of the filesystem superblock"]; } message RolandFsProto { - optional uint32 directory_track = 1 - [ (help) = "position of the directory", default = 39 ]; - optional uint32 block_size = 2 - [ (help) = "filesystem block size", default = 3072 ]; - optional uint32 directory_entries = 3 - [ (help) = "number of directory entries", default = 79 ]; + optional uint32 directory_track = 1 + [(help) = "position of the directory", default = 39]; + optional uint32 block_size = 2 + [(help) = "filesystem block size", default = 3072]; + optional uint32 directory_entries = 3 + [(help) = "number of directory entries", default = 79]; } // NEXT_TAG: 18 message FilesystemProto { - enum FilesystemType - { - NOT_SET = 0; - ACORNDFS = 1; - BROTHER120 = 2; - FATFS = 3; - CPMFS = 4; - AMIGAFFS = 5; - MACHFS = 6; - CBMFS = 7; - PRODOS = 8; - SMAKY6 = 9; - APPLEDOS = 10; - PHILE = 11; - LIF = 12; - MICRODOS = 13; - ZDOS = 14; - ROLAND = 15; - } - - optional FilesystemType type = 10 - [ default = NOT_SET, (help) = "filesystem type" ]; - - optional AcornDfsProto acorndfs = 1; - optional Brother120FsProto brother120 = 2; - optional FatFsProto fatfs = 3; - optional CpmFsProto cpmfs = 4; - optional AmigaFfsProto amigaffs = 5; - optional MacHfsProto machfs = 6; - optional CbmfsProto cbmfs = 7; - optional ProdosProto prodos = 8; - optional AppledosProto appledos = 12; - optional Smaky6FsProto smaky6 = 11; - optional PhileProto phile = 13; - optional LifProto lif = 14; - optional MicrodosProto microdos = 15; - optional ZDosProto zdos = 16; - optional RolandFsProto roland = 17; + enum FilesystemType + { + NOT_SET = 0; + ACORNDFS = 1; + BROTHER120 = 2; + FATFS = 3; + CPMFS = 4; + AMIGAFFS = 5; + MACHFS = 6; + CBMFS = 7; + PRODOS = 8; + SMAKY6 = 9; + APPLEDOS = 10; + PHILE = 11; + LIF = 12; + MICRODOS = 13; + ZDOS = 14; + ROLAND = 15; + } + + optional FilesystemType type = 10 + [default = NOT_SET, (help) = "filesystem type"]; + + optional AcornDfsProto acorndfs = 1; + optional Brother120FsProto brother120 = 2; + optional FatFsProto fatfs = 3; + optional CpmFsProto cpmfs = 4; + optional AmigaFfsProto amigaffs = 5; + optional MacHfsProto machfs = 6; + optional CbmfsProto cbmfs = 7; + optional ProdosProto prodos = 8; + optional AppledosProto appledos = 12; + optional Smaky6FsProto smaky6 = 11; + optional PhileProto phile = 13; + optional LifProto lif = 14; + optional MicrodosProto microdos = 15; + optional ZDosProto zdos = 16; + optional RolandFsProto roland = 17; } From 65dea83b7dabd71d754dd7e77a558d38a8c7d249 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 19:32:07 +0200 Subject: [PATCH 078/192] Did a bandwidth test against a Greaseweazle! --- java/com/cowlark/fluxengine/core/Bytes.java | 1 + .../fluxengine/usb/GreaseweazleUsbDevice.java | 1 + java/com/cowlark/fluxengine/usb/UsbFactory.java | 16 +++++++++------- 3 files changed, 11 insertions(+), 7 deletions(-) diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index d1926a83..323ff8a6 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -22,6 +22,7 @@ public final class Bytes implements List private Storage storage; private int low; private int high; + public Bytes() { this(0); diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index 5ab502a6..0f207824 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -51,6 +51,7 @@ class GreaseweazleUsbDevice extends UsbDevice private Version version; private long clock; private long revolutions; + GreaseweazleUsbDevice(String port, GreaseweazleProto config) { this.config = config; diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 9d035e8e..9d229dca 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -2,7 +2,7 @@ import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.config.UsbFinder; -import com.cowlark.fluxengine.config.UsbFinder.CandidateDevice; +import com.cowlark.fluxengine.core.FluxEngineException; /** * USB device finder, ported from lib/usb/usbfinder.cc. @@ -14,14 +14,16 @@ private UsbFactory() { } - public static UsbDevice connect(CandidateDevice device) - { - return null; - } - public static UsbDevice connect(ConfigProto config) { - return connect(UsbFinder.selectDevice(config)); + var device = UsbFinder.selectDevice(config); + return switch (device.type) + { + case GREASEWEAZLE -> + new GreaseweazleUsbDevice(device.serialPort, config.getUsb().getGreaseweazle()); + default -> throw new FluxEngineException("unsupported hardware device"); + + }; } } From fb921dd09aa3318f0fa545a199f3cc80e3ced7c9 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 19:50:18 +0200 Subject: [PATCH 079/192] Finished converting 'test bandwidth'. --- src/fe-testbandwidth.cc | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 src/fe-testbandwidth.cc diff --git a/src/fe-testbandwidth.cc b/src/fe-testbandwidth.cc deleted file mode 100644 index 99b58e85..00000000 --- a/src/fe-testbandwidth.cc +++ /dev/null @@ -1,13 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/config/flags.h" -#include "lib/usb/usb.h" - -static FlagGroup flags; - -int mainTestBandwidth(int argc, const char* argv[]) -{ - flags.parseFlagsWithConfigFiles(argc, argv, {}); - usbTestBulkWrite(); - usbTestBulkRead(); - return 0; -} From d6e78df7c90b02049dbe9c3bfc5fe55fb71dd00e Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 20:01:48 +0200 Subject: [PATCH 080/192] Convert 'rpm'. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 2 + java/com/cowlark/fluxengine/cli/Command.java | 2 +- .../cowlark/fluxengine/cli/RpmCommand.java | 47 ++++++++++++++++++ .../cowlark/fluxengine/usb/UsbFactory.java | 14 ++++-- .../fluxengine/config/ConfigBuilderTest.java | 15 ++++-- src/fe-rpm.cc | 48 ------------------- 6 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/RpmCommand.java delete mode 100644 src/fe-rpm.cc diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 3f887b80..04c61473 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -7,7 +7,9 @@ java_library( srcs = glob(["*.java"]), deps = [ "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 47b25ec4..3bccdadd 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -62,7 +62,7 @@ public interface Command stub("getfileinfo", "Read file metadata off a disk (or image).")) .put("putfile", stub("putfile", "Write a file to disk (or image).")) .put("mkdir", stub("mkdir", "Create a directory on disk (or image).")) - .put("rpm", stub("rpm", "Measures the disk rotational speed.")) + .put("rpm", RpmCommand::new) .put("seek", stub("seek", "Moves the disk head.")) .put("devices", DevicesCommand::new) .put("test", () -> new CommandGroup(TESTABLES, "Various testing commands.")) diff --git a/java/com/cowlark/fluxengine/cli/RpmCommand.java b/java/com/cowlark/fluxengine/cli/RpmCommand.java new file mode 100644 index 00000000..d0352bcc --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/RpmCommand.java @@ -0,0 +1,47 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; +import com.google.common.collect.ImmutableList; + +/** + * Measure the disk rotational speed, modelled after src/fe-rpm.cc. + */ +public class RpmCommand implements Command +{ + @Override + public String getHelp() + { + return "Measures the disk rotational speed."; + } + + @Override + public void run(String[] args) + { + ConfigProto config = new ConfigBuilder().fromFlags(ImmutableList.copyOf(args)).build(); + + if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) + throw new FluxEngineException("this only makes sense with a real disk drive"); + + UsbDevice device = UsbFactory.connect(config); + + long period = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); + if (period != 0) + System.out.printf( + "Rotational period is %d ms (%.0f rpm)\n", + period / 1000000, + 60e9 / period); + else + System.out.println(""" + No index pulses detected from the disk. Common causes of this are: + - no drive is connected + - the drive doesn't have an index sensor (e.g. BBC Micro drives) + - the disk has no index holes (e.g. reversed flippy disks) + - (most common) no disk is inserted in the drive!"""); + } +} diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 9d229dca..f85ad2df 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -16,14 +16,20 @@ private UsbFactory() public static UsbDevice connect(ConfigProto config) { - var device = UsbFinder.selectDevice(config); - return switch (device.type) + var candidateDevice = UsbFinder.selectDevice(config); + var device = switch (candidateDevice.type) { - case GREASEWEAZLE -> - new GreaseweazleUsbDevice(device.serialPort, config.getUsb().getGreaseweazle()); + case GREASEWEAZLE -> new GreaseweazleUsbDevice( + candidateDevice.serialPort, + config.getUsb().getGreaseweazle()); default -> throw new FluxEngineException("unsupported hardware device"); }; + device.setDrive( + config.getDrive().getDrive(), + config.getDrive().getHighDensity(), + config.getDrive().getIndexMode().getNumber()); + return device; } } diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index ed292d08..5918e54b 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -16,13 +16,20 @@ @RunWith(JUnit4.class) public class ConfigBuilderTest { + /* ConfigBuilder defaults to a drive flux source, which makes build() + * select a USB device; stub the serial so no hardware is needed. */ + private static ConfigBuilder builder() + { + return new ConfigBuilder().set("usb.serial", "test-serial"); + } + @Test public void loadConfigFileMergesTextproto() throws IOException { Path file = Files.createTempFile("config", ".textproto"); Files.writeString(file, "shortname: \"myconfig\"\ntracks: \"c=0:2\"\n"); - ConfigProto proto = new ConfigBuilder().loadConfigFile(file.toString()).build(); + ConfigProto proto = builder().loadConfigFile(file.toString()).build(); assertThat(proto.getShortname()).isEqualTo("myconfig"); assertThat(proto.getTracks()).isEqualTo("c=0:2"); @@ -36,7 +43,7 @@ public void loadConfigFileMergesAcrossFiles() throws IOException Files.writeString(first, "shortname: \"first\"\n"); Files.writeString(second, "tracks: \"c=0:2\"\n"); - ConfigProto proto = new ConfigBuilder() + ConfigProto proto = builder() .loadConfigFile(first.toString()) .loadConfigFile(second.toString()) .build(); @@ -68,7 +75,7 @@ public void setMergesWithLoadedConfig() throws IOException Path file = Files.createTempFile("config", ".textproto"); Files.writeString(file, "shortname: \"myconfig\"\n"); - ConfigProto proto = new ConfigBuilder() + ConfigProto proto = builder() .loadConfigFile(file.toString()) .set("tracks", "c=0:2") .build(); @@ -80,7 +87,7 @@ public void setMergesWithLoadedConfig() throws IOException @Test public void fromFlagsSetsDottedConfig() { - ConfigProto proto = new ConfigBuilder() + ConfigProto proto = builder() .fromFlags(ImmutableList.of("--drive.drive=1"), new FlagGroup()) .build(); diff --git a/src/fe-rpm.cc b/src/fe-rpm.cc deleted file mode 100644 index f54c294f..00000000 --- a/src/fe-rpm.cc +++ /dev/null @@ -1,48 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/config/config.h" -#include "lib/config/flags.h" -#include "lib/usb/usb.h" -#include "lib/fluxsource/fluxsource.h" -#include "protocol.h" -#include "lib/config/proto.h" - -static FlagGroup flags; - -static StringFlag sourceFlux({"-s", "--source"}, - "'drive:' flux source to use", - "", - [](const auto& value) - { - globalConfig().setFluxSource(value); - }); - -int mainRpm(int argc, const char* argv[]) -{ - globalConfig().set("flux_source.type", "FLUXTYPE_DRIVE"); - flags.parseFlagsWithConfigFiles(argc, argv, {}); - - if (globalConfig()->flux_source().type() != FLUXTYPE_DRIVE) - error("this only makes sense with a real disk drive"); - - usbSetDrive(globalConfig()->drive().drive(), - false, - globalConfig()->drive().index_mode()); - nanoseconds_t period = - usbGetRotationalPeriod(globalConfig()->drive().hard_sector_count()); - if (period != 0) - std::cout << "Rotational period is " << period / 1000000 << " ms (" - << 60e9 / period << " rpm)" << std::endl; - else - { - std::cout - << "No index pulses detected from the disk. Common causes of this " - "are:\n" - " - no drive is connected\n" - " - the drive doesn't have an index sensor (e.g. BBC Micro " - "drives)\n" - " - the disk has no index holes (e.g. reversed flippy disks)\n" - " - (most common) no disk is inserted in the drive!\n"; - } - - return 0; -} From f8deb535ef60bcd8b80ce903b5740553018f4758 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 20:06:15 +0200 Subject: [PATCH 081/192] Convert 'seek'. --- java/com/cowlark/fluxengine/cli/Command.java | 2 +- .../cowlark/fluxengine/cli/SeekCommand.java | 45 +++++++++++++++++++ src/fe-seek.cc | 35 --------------- 3 files changed, 46 insertions(+), 36 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/SeekCommand.java delete mode 100644 src/fe-seek.cc diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 3bccdadd..d7a322a6 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -63,7 +63,7 @@ public interface Command .put("putfile", stub("putfile", "Write a file to disk (or image).")) .put("mkdir", stub("mkdir", "Create a directory on disk (or image).")) .put("rpm", RpmCommand::new) - .put("seek", stub("seek", "Moves the disk head.")) + .put("seek", SeekCommand::new) .put("devices", DevicesCommand::new) .put("test", () -> new CommandGroup(TESTABLES, "Various testing commands.")) .build(); diff --git a/java/com/cowlark/fluxengine/cli/SeekCommand.java b/java/com/cowlark/fluxengine/cli/SeekCommand.java new file mode 100644 index 00000000..08dc39b6 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/SeekCommand.java @@ -0,0 +1,45 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.IntFlag; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; +import com.google.common.collect.ImmutableList; + +/** + * Seek to a given track, modelled after src/fe-seek.cc. + */ +public class SeekCommand implements Command +{ + private static FlagGroup flags = new FlagGroup(); + private static IntFlag track = IntFlag.builder() + .setGroup(flags) + .setName("--cylinder") + .setName("-t") + .setHelpText("track to seek to") + .build(); + + @Override + public String getHelp() + { + return "Moves the disk head."; + } + + @Override + public void run(String[] args) + { + ConfigProto config = + new ConfigBuilder().fromFlags(ImmutableList.copyOf(args), flags).build(); + + if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) + throw new FluxEngineException("this only makes sense with a real disk drive"); + + UsbDevice device = UsbFactory.connect(config); + device.seek(track.get()); + } +} diff --git a/src/fe-seek.cc b/src/fe-seek.cc deleted file mode 100644 index 038f6169..00000000 --- a/src/fe-seek.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/config/config.h" -#include "lib/config/flags.h" -#include "lib/usb/usb.h" -#include "lib/fluxsource/fluxsource.h" -#include "lib/config/proto.h" -#include "protocol.h" - -static FlagGroup flags; - -static StringFlag sourceFlux({"-s", "--source"}, - "'drive:' flux source to use", - "", - [](const auto& value) - { - globalConfig().setFluxSource(value); - }); - -static IntFlag track({"--cylinder", "-t"}, "track to seek to", 0); - -extern const std::map readables; - -int mainSeek(int argc, const char* argv[]) -{ - flags.parseFlagsWithConfigFiles(argc, argv, {}); - - if (globalConfig()->flux_source().type() != FLUXTYPE_DRIVE) - error("this only makes sense with a real disk drive"); - - usbSetDrive(globalConfig()->drive().drive(), - false, - globalConfig()->drive().index_mode()); - usbSeek(track); - return 0; -} From 7c013d93c6d3f0053a219d4f3665298411d6f9a0 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 20:12:09 +0200 Subject: [PATCH 082/192] Adjust the Command API. --- AGENTS.md | 3 ++- java/com/cowlark/fluxengine/cli/Command.java | 13 +++++++------ java/com/cowlark/fluxengine/cli/CommandGroup.java | 3 ++- java/com/cowlark/fluxengine/cli/DevicesCommand.java | 3 ++- java/com/cowlark/fluxengine/cli/Main.java | 3 ++- java/com/cowlark/fluxengine/cli/RpmCommand.java | 4 ++-- java/com/cowlark/fluxengine/cli/SeekCommand.java | 4 ++-- java/com/cowlark/fluxengine/cli/StubCommand.java | 4 +++- .../fluxengine/cli/TestBandwidthCommand.java | 4 ++-- 9 files changed, 24 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index aa940807..b10dc33b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -104,7 +104,8 @@ Useful commands: ## CLI - Commands live in `com.cowlark.fluxengine.cli` and implement the `Command` interface - (`String getHelp()`, `void run(String[] args)`), receiving the tail of the argv array after + (`String getHelp()`, `void run(ImmutableList args)`), receiving the tail of the argv + array after the command name (modelled on `src/fluxengine.cc`'s `command_cb`). - `Main.main` holds the command/subcommand tables as `ImmutableMap>`: `COMMANDS` (top level), `ANALYSABLES`, `FLUXFILEABLES`, diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index d7a322a6..6cab27eb 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -1,7 +1,7 @@ package com.cowlark.fluxengine.cli; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; -import java.util.Arrays; import java.util.Map; import java.util.function.Supplier; @@ -70,14 +70,15 @@ public interface Command /* Consume arguments until we reach a real command, instantiate it, and * run it with the tail of the argv array. */ - static boolean dispatch(Map> commands, String[] args) + static boolean dispatch( + Map> commands, ImmutableList args) { - for (int index = 0; index < args.length; index++) + for (int index = 0; index < args.size(); index++) { - Supplier supplier = commands.get(args[index]); + Supplier supplier = commands.get(args.get(index)); if (supplier != null) { - supplier.get().run(Arrays.copyOfRange(args, index + 1, args.length)); + supplier.get().run(ImmutableList.copyOf(args.subList(index + 1, args.size()))); return true; } } @@ -92,6 +93,6 @@ static Supplier stub(String name, String help) String getHelp(); - void run(String[] args); + void run(ImmutableList args); } diff --git a/java/com/cowlark/fluxengine/cli/CommandGroup.java b/java/com/cowlark/fluxengine/cli/CommandGroup.java index d01eb1b8..68438ce3 100644 --- a/java/com/cowlark/fluxengine/cli/CommandGroup.java +++ b/java/com/cowlark/fluxengine/cli/CommandGroup.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.cli; +import com.google.common.collect.ImmutableList; import java.util.Map; import java.util.function.Supplier; @@ -25,7 +26,7 @@ public String getHelp() } @Override - public void run(String[] args) + public void run(ImmutableList args) { if (!Command.dispatch(subcommands, args)) Main.help(subcommands, " [...]"); diff --git a/java/com/cowlark/fluxengine/cli/DevicesCommand.java b/java/com/cowlark/fluxengine/cli/DevicesCommand.java index 6eff021c..847c6cca 100644 --- a/java/com/cowlark/fluxengine/cli/DevicesCommand.java +++ b/java/com/cowlark/fluxengine/cli/DevicesCommand.java @@ -5,6 +5,7 @@ import com.cowlark.fluxengine.config.UsbFinder; import com.cowlark.fluxengine.config.UsbFinder.CandidateDevice; import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.google.common.collect.ImmutableList; import java.util.List; public class DevicesCommand implements Command @@ -18,7 +19,7 @@ public String getHelp() } @Override - public void run(String[] args) + public void run(ImmutableList args) { List candidates = UsbFinder.findUsbDevices(); switch (candidates.size()) diff --git a/java/com/cowlark/fluxengine/cli/Main.java b/java/com/cowlark/fluxengine/cli/Main.java index 7b22152f..96f64297 100644 --- a/java/com/cowlark/fluxengine/cli/Main.java +++ b/java/com/cowlark/fluxengine/cli/Main.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.cli; +import com.google.common.collect.ImmutableList; import java.util.Map; import java.util.function.Supplier; @@ -23,7 +24,7 @@ public static void main(String[] args) return; } - if (!Command.dispatch(Command.COMMANDS, args)) + if (!Command.dispatch(Command.COMMANDS, ImmutableList.copyOf(args))) { System.err.println("fluxengine: unrecognised command (try --help)"); System.exit(1); diff --git a/java/com/cowlark/fluxengine/cli/RpmCommand.java b/java/com/cowlark/fluxengine/cli/RpmCommand.java index d0352bcc..41e17c6b 100644 --- a/java/com/cowlark/fluxengine/cli/RpmCommand.java +++ b/java/com/cowlark/fluxengine/cli/RpmCommand.java @@ -21,9 +21,9 @@ public String getHelp() } @Override - public void run(String[] args) + public void run(ImmutableList args) { - ConfigProto config = new ConfigBuilder().fromFlags(ImmutableList.copyOf(args)).build(); + ConfigProto config = new ConfigBuilder().fromFlags(args).build(); if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) throw new FluxEngineException("this only makes sense with a real disk drive"); diff --git a/java/com/cowlark/fluxengine/cli/SeekCommand.java b/java/com/cowlark/fluxengine/cli/SeekCommand.java index 08dc39b6..ec0ba51b 100644 --- a/java/com/cowlark/fluxengine/cli/SeekCommand.java +++ b/java/com/cowlark/fluxengine/cli/SeekCommand.java @@ -31,10 +31,10 @@ public String getHelp() } @Override - public void run(String[] args) + public void run(ImmutableList args) { ConfigProto config = - new ConfigBuilder().fromFlags(ImmutableList.copyOf(args), flags).build(); + new ConfigBuilder().fromFlags(args, flags).build(); if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) throw new FluxEngineException("this only makes sense with a real disk drive"); diff --git a/java/com/cowlark/fluxengine/cli/StubCommand.java b/java/com/cowlark/fluxengine/cli/StubCommand.java index 473d2e7e..933e9f76 100644 --- a/java/com/cowlark/fluxengine/cli/StubCommand.java +++ b/java/com/cowlark/fluxengine/cli/StubCommand.java @@ -1,5 +1,7 @@ package com.cowlark.fluxengine.cli; +import com.google.common.collect.ImmutableList; + public class StubCommand implements Command { private final String name; @@ -18,7 +20,7 @@ public String getHelp() } @Override - public void run(String[] args) + public void run(ImmutableList args) { System.err.printf("fluxengine: '%s' is not implemented yet.\n", name); } diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 1815b22f..93313bf9 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -18,9 +18,9 @@ public String getHelp() } @Override - public void run(String[] args) + public void run(ImmutableList args) { - ConfigProto config = new ConfigBuilder().fromFlags(ImmutableList.copyOf(args)).build(); + ConfigProto config = new ConfigBuilder().fromFlags(args).build(); UsbDevice device = UsbFactory.connect(config); device.testBulkWrite(); From 5368f4a13999bf30618a746067cca5ddc469ebe0 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 20:16:23 +0200 Subject: [PATCH 083/192] Convert 'test voltages'. --- java/com/cowlark/fluxengine/cli/Command.java | 2 +- .../fluxengine/cli/TestVoltagesCommand.java | 74 +++++++++++++++++++ src/fe-testvoltages.cc | 37 ---------- 3 files changed, 75 insertions(+), 38 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java delete mode 100644 src/fe-testvoltages.cc diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 6cab27eb..2d021ff6 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -25,7 +25,7 @@ public interface Command ImmutableMap> TESTABLES = ImmutableMap.>builder() .put("bandwidth", TestBandwidthCommand::new) - .put("voltages", stub("voltages", "Measures the FDD bus voltages.")) + .put("voltages", TestVoltagesCommand::new) .build(); ImmutableMap> COMMANDS = diff --git a/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java b/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java new file mode 100644 index 00000000..4a7172c1 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java @@ -0,0 +1,74 @@ +package com.cowlark.fluxengine.cli; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; +import com.cowlark.fluxengine.usb.Voltages; +import com.google.common.collect.ImmutableList; + +/** + * Measure the FDD bus voltages, modelled after src/fe-testvoltages.cc. + */ +public class TestVoltagesCommand implements Command +{ + /* The device fills the array in the order of the C++ voltages_frame + * struct. */ + private static final int INPUT_BOTH_OFF = 0; + private static final int INPUT_DRIVE_0_SELECTED = 1; + private static final int INPUT_DRIVE_1_SELECTED = 2; + private static final int INPUT_DRIVE_0_RUNNING = 3; + private static final int INPUT_DRIVE_1_RUNNING = 4; + private static final int OUTPUT_BOTH_OFF = 5; + private static final int OUTPUT_DRIVE_0_SELECTED = 6; + private static final int OUTPUT_DRIVE_1_SELECTED = 7; + private static final int OUTPUT_DRIVE_0_RUNNING = 8; + private static final int OUTPUT_DRIVE_1_RUNNING = 9; + + @Override + public String getHelp() + { + return "Measures the FDD bus voltages."; + } + + @Override + public void run(ImmutableList args) + { + ConfigProto config = new ConfigBuilder().fromFlags(args).build(); + + Voltages[] voltages = new Voltages[10]; + UsbDevice device = UsbFactory.connect(config); + device.measureVoltages(voltages); + + System.out.printf(""" + Output voltages: + Both drives deselected + %s Drive 0 selected + %s Drive 1 selected + %s Drive 0 running + %s Drive 1 running + %sInput voltages: + Both drives deselected + %s Drive 0 selected + %s Drive 1 selected + %s Drive 0 running + %s Drive 1 running + %s""", + displayVoltages(voltages[OUTPUT_BOTH_OFF]), + displayVoltages(voltages[OUTPUT_DRIVE_0_SELECTED]), + displayVoltages(voltages[OUTPUT_DRIVE_1_SELECTED]), + displayVoltages(voltages[OUTPUT_DRIVE_0_RUNNING]), + displayVoltages(voltages[OUTPUT_DRIVE_1_RUNNING]), + displayVoltages(voltages[INPUT_BOTH_OFF]), + displayVoltages(voltages[INPUT_DRIVE_0_SELECTED]), + displayVoltages(voltages[INPUT_DRIVE_1_SELECTED]), + displayVoltages(voltages[INPUT_DRIVE_0_RUNNING]), + displayVoltages(voltages[INPUT_DRIVE_1_RUNNING])); + } + + private static String displayVoltages(Voltages v) + { + return String.format(" Logic 1 / 0: %.2fV / %.2fV\n", + v.logic0Mv() / 1000.0, v.logic1Mv() / 1000.0); + } +} diff --git a/src/fe-testvoltages.cc b/src/fe-testvoltages.cc deleted file mode 100644 index 7c91c3bd..00000000 --- a/src/fe-testvoltages.cc +++ /dev/null @@ -1,37 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/config/flags.h" -#include "lib/usb/usb.h" -#include "protocol.h" - -static FlagGroup flags; - -static std::string display_voltages(struct voltages& v) -{ - return fmt::format(" Logic 1 / 0: {:.2f}V / {:.2f}V\n", - v.logic0_mv / 1000.0, - v.logic1_mv / 1000.0); -} - -int mainTestVoltages(int argc, const char* argv[]) -{ - flags.parseFlagsWithConfigFiles(argc, argv, {}); - struct voltages_frame f; - usbMeasureVoltages(&f); - - std::cout - << "Output voltages:\n" - << " Both drives deselected\n" - << display_voltages(f.output_both_off) << " Drive 0 selected\n" - << display_voltages(f.output_drive_0_selected) << " Drive 1 selected\n" - << display_voltages(f.output_drive_1_selected) << " Drive 0 running\n" - << display_voltages(f.output_drive_0_running) << " Drive 1 running\n" - << display_voltages(f.output_drive_1_running) << "Input voltages:\n" - << " Both drives deselected\n" - << display_voltages(f.input_both_off) << " Drive 0 selected\n" - << display_voltages(f.input_drive_0_selected) << " Drive 1 selected\n" - << display_voltages(f.input_drive_1_selected) << " Drive 0 running\n" - << display_voltages(f.input_drive_0_running) << " Drive 1 running\n" - << display_voltages(f.input_drive_1_running); - - return 0; -} From 0222263cecb351a55ceb319e143bfc03d45c5651 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 20:26:32 +0200 Subject: [PATCH 084/192] Tidy the voltages stuff. --- java/com/cowlark/fluxengine/cli/Command.java | 4 +- .../cowlark/fluxengine/cli/SeekCommand.java | 3 +- .../fluxengine/cli/TestVoltagesCommand.java | 78 ++++++++----------- .../fluxengine/usb/GreaseweazleUsbDevice.java | 2 +- .../com/cowlark/fluxengine/usb/UsbDevice.java | 2 +- .../fluxengine/usb/VoltageMeasurements.java | 19 +++++ 6 files changed, 58 insertions(+), 50 deletions(-) create mode 100644 java/com/cowlark/fluxengine/usb/VoltageMeasurements.java diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 2d021ff6..f6342eb2 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -70,8 +70,8 @@ public interface Command /* Consume arguments until we reach a real command, instantiate it, and * run it with the tail of the argv array. */ - static boolean dispatch( - Map> commands, ImmutableList args) + static boolean dispatch(Map> commands, + ImmutableList args) { for (int index = 0; index < args.size(); index++) { diff --git a/java/com/cowlark/fluxengine/cli/SeekCommand.java b/java/com/cowlark/fluxengine/cli/SeekCommand.java index ec0ba51b..218dae34 100644 --- a/java/com/cowlark/fluxengine/cli/SeekCommand.java +++ b/java/com/cowlark/fluxengine/cli/SeekCommand.java @@ -33,8 +33,7 @@ public String getHelp() @Override public void run(ImmutableList args) { - ConfigProto config = - new ConfigBuilder().fromFlags(args, flags).build(); + ConfigProto config = new ConfigBuilder().fromFlags(args, flags).build(); if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) throw new FluxEngineException("this only makes sense with a real disk drive"); diff --git a/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java b/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java index 4a7172c1..7d5ae862 100644 --- a/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java @@ -4,6 +4,7 @@ import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; +import com.cowlark.fluxengine.usb.VoltageMeasurements; import com.cowlark.fluxengine.usb.Voltages; import com.google.common.collect.ImmutableList; @@ -12,18 +13,13 @@ */ public class TestVoltagesCommand implements Command { - /* The device fills the array in the order of the C++ voltages_frame - * struct. */ - private static final int INPUT_BOTH_OFF = 0; - private static final int INPUT_DRIVE_0_SELECTED = 1; - private static final int INPUT_DRIVE_1_SELECTED = 2; - private static final int INPUT_DRIVE_0_RUNNING = 3; - private static final int INPUT_DRIVE_1_RUNNING = 4; - private static final int OUTPUT_BOTH_OFF = 5; - private static final int OUTPUT_DRIVE_0_SELECTED = 6; - private static final int OUTPUT_DRIVE_1_SELECTED = 7; - private static final int OUTPUT_DRIVE_0_RUNNING = 8; - private static final int OUTPUT_DRIVE_1_RUNNING = 9; + private static String displayVoltages(Voltages v) + { + return String.format( + " Logic 1 / 0: %.2fV / %.2fV\n", + v.logic0Mv() / 1000.0, + v.logic1Mv() / 1000.0); + } @Override public String getHelp() @@ -36,39 +32,33 @@ public void run(ImmutableList args) { ConfigProto config = new ConfigBuilder().fromFlags(args).build(); - Voltages[] voltages = new Voltages[10]; UsbDevice device = UsbFactory.connect(config); - device.measureVoltages(voltages); + VoltageMeasurements voltages = device.measureVoltages(); - System.out.printf(""" - Output voltages: - Both drives deselected - %s Drive 0 selected - %s Drive 1 selected - %s Drive 0 running - %s Drive 1 running - %sInput voltages: - Both drives deselected - %s Drive 0 selected - %s Drive 1 selected - %s Drive 0 running - %s Drive 1 running - %s""", - displayVoltages(voltages[OUTPUT_BOTH_OFF]), - displayVoltages(voltages[OUTPUT_DRIVE_0_SELECTED]), - displayVoltages(voltages[OUTPUT_DRIVE_1_SELECTED]), - displayVoltages(voltages[OUTPUT_DRIVE_0_RUNNING]), - displayVoltages(voltages[OUTPUT_DRIVE_1_RUNNING]), - displayVoltages(voltages[INPUT_BOTH_OFF]), - displayVoltages(voltages[INPUT_DRIVE_0_SELECTED]), - displayVoltages(voltages[INPUT_DRIVE_1_SELECTED]), - displayVoltages(voltages[INPUT_DRIVE_0_RUNNING]), - displayVoltages(voltages[INPUT_DRIVE_1_RUNNING])); - } - - private static String displayVoltages(Voltages v) - { - return String.format(" Logic 1 / 0: %.2fV / %.2fV\n", - v.logic0Mv() / 1000.0, v.logic1Mv() / 1000.0); + System.out.printf( + """ + Output voltages: + Both drives deselected + %s Drive 0 selected + %s Drive 1 selected + %s Drive 0 running + %s Drive 1 running + %sInput voltages: + Both drives deselected + %s Drive 0 selected + %s Drive 1 selected + %s Drive 0 running + %s Drive 1 running + %s""", + displayVoltages(voltages.outputBothOff), + displayVoltages(voltages.outputDrive0Selected), + displayVoltages(voltages.outputDrive1Selected), + displayVoltages(voltages.outputDrive0Running), + displayVoltages(voltages.outputDrive1Running), + displayVoltages(voltages.inputBothOff), + displayVoltages(voltages.inputDrive0Selected), + displayVoltages(voltages.inputDrive1Selected), + displayVoltages(voltages.inputDrive0Running), + displayVoltages(voltages.inputDrive1Running)); } } diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index 0f207824..688f9a98 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -459,7 +459,7 @@ public void setDrive(int drive, boolean highDensity, int indexMode) } @Override - public void measureVoltages(Voltages[] voltages) + public VoltageMeasurements measureVoltages() { throw new FluxEngineException("unsupported operation on the Greaseweazle"); } diff --git a/java/com/cowlark/fluxengine/usb/UsbDevice.java b/java/com/cowlark/fluxengine/usb/UsbDevice.java index 9d103f27..b3018fa8 100644 --- a/java/com/cowlark/fluxengine/usb/UsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/UsbDevice.java @@ -28,7 +28,7 @@ public void recalibrate() public abstract void setDrive(int drive, boolean highDensity, int indexMode); - public abstract void measureVoltages(Voltages[] voltages); + public abstract VoltageMeasurements measureVoltages(); protected String usbError(int error) { diff --git a/java/com/cowlark/fluxengine/usb/VoltageMeasurements.java b/java/com/cowlark/fluxengine/usb/VoltageMeasurements.java new file mode 100644 index 00000000..2f40ee89 --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/VoltageMeasurements.java @@ -0,0 +1,19 @@ +package com.cowlark.fluxengine.usb; + +/** + * A set of FDD bus voltage readings, ported from struct voltages_frame in + * protocol.h. + */ +public class VoltageMeasurements +{ + public Voltages inputBothOff; + public Voltages inputDrive0Selected; + public Voltages inputDrive1Selected; + public Voltages inputDrive0Running; + public Voltages inputDrive1Running; + public Voltages outputBothOff; + public Voltages outputDrive0Selected; + public Voltages outputDrive1Selected; + public Voltages outputDrive0Running; + public Voltages outputDrive1Running; +} From d3427fb6284fa3e45ae6fb89f097168dd9e2e9e0 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 20:44:20 +0200 Subject: [PATCH 085/192] Translate Fluxmap. --- AGENTS.md | 2 +- java/com/cowlark/fluxengine/data/BUILD.bazel | 13 ++ java/com/cowlark/fluxengine/data/Fluxmap.java | 193 ++++++++++++++++++ .../com/cowlark/fluxengine/data/BUILD.bazel | 15 ++ .../cowlark/fluxengine/data/FluxmapTest.java | 105 ++++++++++ 5 files changed, 327 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/data/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/data/Fluxmap.java create mode 100644 javatests/com/cowlark/fluxengine/data/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/data/FluxmapTest.java diff --git a/AGENTS.md b/AGENTS.md index b10dc33b..f10dad81 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,7 +15,7 @@ GraalVM extension/rule). - Java tests: `javatests/` - Packages (Java): `com.cowlark.fluxengine` (Main, FluxEngineComponent), `com.cowlark.fluxengine.cli`, `com.cowlark.fluxengine.core`, `com.cowlark.fluxengine.core.flags`, - `com.cowlark.fluxengine.usb`, `com.cowlark.fluxengine.wiring` + `com.cowlark.fluxengine.data`, `com.cowlark.fluxengine.usb`, `com.cowlark.fluxengine.wiring` - Each package directory has its own `BUILD.bazel`. Useful commands: diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel new file mode 100644 index 00000000..573cb2e6 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -0,0 +1,13 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "data", + srcs = glob(["*.java"]), + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/external", + "@maven//:com_google_guava_guava", + ], +) diff --git a/java/com/cowlark/fluxengine/data/Fluxmap.java b/java/com/cowlark/fluxengine/data/Fluxmap.java new file mode 100644 index 00000000..69392089 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Fluxmap.java @@ -0,0 +1,193 @@ +package com.cowlark.fluxengine.data; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.external.FluxEngine; +import com.google.common.collect.ImmutableList; +import java.util.List; + +/** + * A stream of flux transitions, ported from lib/data/fluxmap.{h,cc}. + */ +public class Fluxmap +{ + public record Position(int bytes, int ticks, int zeroes) + { + public long ns() + { + return (long) (ticks * FluxEngine.NS_PER_TICK); + } + + @Override + public String toString() + { + return String.format("[b:%d, t:%d, z:%d]", bytes, ticks, zeroes); + } + } + + private long duration; + private int ticks; + private Bytes bytes; + private ImmutableList indexMarks; + + public Fluxmap() + { + bytes = new Bytes(); + } + + public Fluxmap(String s) + { + this(); + appendBytes(new Bytes(s)); + } + + public Fluxmap(Bytes bytes) + { + this(); + appendBytes(bytes); + } + + public long duration() + { + return duration; + } + + public int ticks() + { + return ticks; + } + + public int bytes() + { + return bytes.size(); + } + + public Bytes rawBytes() + { + return bytes; + } + + public Fluxmap appendInterval(int ticks) + { + while (ticks >= 0x3f) + { + appendByte(0x3f); + ticks -= 0x3f; + } + appendByte(ticks & 0xff); + return this; + } + + public Fluxmap appendPulse() + { + ensureLastByte(); + int index = bytes.size() - 1; + bytes.setByte(index, (byte) (bytes.getByte(index) | FluxEngine.F_BIT_PULSE)); + return this; + } + + public Fluxmap appendIndex() + { + flushIndexMarks(); + ensureLastByte(); + int index = bytes.size() - 1; + bytes.setByte(index, (byte) (bytes.getByte(index) | FluxEngine.F_BIT_INDEX)); + return this; + } + + public Fluxmap appendDesync() + { + appendByte(FluxEngine.F_DESYNC); + return this; + } + + public Fluxmap appendBytes(Bytes data) + { + if (data.isEmpty()) + return this; + + flushIndexMarks(); + + ByteWriter bw = new ByteWriter(bytes); + bw.seekToEnd(); + for (int i = 0; i < data.size(); i++) + { + int b = data.getByte(i) & 0xff; + ticks += b & 0x3f; + bw.write8(b); + } + + duration = (long) (ticks * FluxEngine.NS_PER_TICK); + return this; + } + + public Fluxmap appendByte(int b) + { + return appendBytes(Bytes.of(b)); + } + + public Fluxmap appendBits(List bits, long clock) + { + long now = duration; + for (boolean bit : bits) + { + now += clock; + if (bit) + { + int delta = (int) ((now - duration) / FluxEngine.NS_PER_TICK); + appendInterval(delta); + appendPulse(); + } + } + int delta = (int) ((now - duration) / FluxEngine.NS_PER_TICK); + if (delta != 0) + appendInterval(delta); + return this; + } + + public ImmutableList split() + { + ImmutableList.Builder maps = ImmutableList.builder(); + for (Bytes piece : bytes.split(FluxEngine.F_DESYNC)) + { + if (!piece.isEmpty()) + maps.add(new Fluxmap(piece)); + } + return maps.build(); + } + + public ImmutableList getIndexMarks() + { + if (indexMarks == null) + { + ImmutableList.Builder marks = ImmutableList.builder(); + long totalTicks = 0; + long oldt = -1; + for (int i = 0; i < bytes.size(); i++) + { + int b = bytes.getByte(i) & 0xff; + totalTicks += b & 0x3f; + if ((b & FluxEngine.F_BIT_INDEX) != 0) + { + long t = (long) (totalTicks * FluxEngine.NS_PER_TICK); + if (t != oldt) + marks.add(t); + oldt = t; + } + } + indexMarks = marks.build(); + } + return indexMarks; + } + + private void ensureLastByte() + { + if (bytes.isEmpty()) + appendByte(0x00); + } + + private void flushIndexMarks() + { + indexMarks = null; + } +} diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel new file mode 100644 index 00000000..42c78c93 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "FluxmapTest", + srcs = ["FluxmapTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/data/FluxmapTest.java b/javatests/com/cowlark/fluxengine/data/FluxmapTest.java new file mode 100644 index 00000000..6e208f1b --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/FluxmapTest.java @@ -0,0 +1,105 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.external.FluxEngine; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FluxmapTest +{ + @Test + public void appendIntervalAndPulse() + { + Fluxmap map = new Fluxmap(); + map.appendInterval(0x30); + map.appendPulse(); + + assertThat(map.rawBytes()).isEqualTo(Bytes.of(0x30 | 0x80)); + assertThat(map.ticks()).isEqualTo(0x30); + assertThat(map.duration()).isEqualTo((long) (0x30 * FluxEngine.NS_PER_TICK)); + } + + @Test + public void appendIntervalSplitsLargeValues() + { + Fluxmap map = new Fluxmap(); + map.appendInterval(100); + + assertThat(map.rawBytes()).isEqualTo(Bytes.of(0x3f, 100 - 0x3f)); + assertThat(map.ticks()).isEqualTo(100); + } + + @Test + public void appendIndex() + { + Fluxmap map = new Fluxmap(); + map.appendInterval(0x30); + map.appendIndex(); + + assertThat(map.rawBytes()).isEqualTo(Bytes.of(0x30 | 0x40)); + } + + @Test + public void appendDesync() + { + Fluxmap map = new Fluxmap(); + map.appendInterval(0x30); + map.appendDesync(); + + assertThat(map.rawBytes()).isEqualTo(Bytes.of(0x30, 0x00)); + } + + @Test + public void split() + { + Fluxmap map = new Fluxmap(); + map.appendInterval(0x30); + map.appendDesync(); + map.appendInterval(0x30); + map.appendPulse(); + + List parts = map.split(); + + assertThat(parts).hasSize(2); + assertThat(parts.get(0).bytes()).isEqualTo(1); + assertThat(parts.get(1).bytes()).isEqualTo(1); + } + + @Test + public void getIndexMarks() + { + Fluxmap map = new Fluxmap(); + map.appendInterval(100); + map.appendIndex(); + map.appendInterval(50); + map.appendIndex(); + + List marks = map.getIndexMarks(); + + assertThat(marks).containsExactly( + (long) (100 * FluxEngine.NS_PER_TICK), + (long) (150 * FluxEngine.NS_PER_TICK)); + } + + @Test + public void indexMarksFlushOnAppend() + { + Fluxmap map = new Fluxmap(); + map.appendInterval(100); + map.appendIndex(); + map.appendInterval(50); + map.appendIndex(); + + assertThat(map.getIndexMarks()).hasSize(2); + + map.appendInterval(50); + map.appendIndex(); + + assertThat(map.getIndexMarks()).hasSize(3); + } +} From a2ddf5d1a6a3e14283bca6d3d6423dd9d5b67435 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 20:58:20 +0200 Subject: [PATCH 086/192] Add FluxmapReader. --- java/com/cowlark/fluxengine/data/BUILD.bazel | 1 + .../cowlark/fluxengine/data/FluxPosition.java | 17 ++ java/com/cowlark/fluxengine/data/Fluxmap.java | 39 ++- .../fluxengine/data/FluxmapReader.java | 240 ++++++++++++++++++ .../com/cowlark/fluxengine/data/BUILD.bazel | 13 + .../fluxengine/data/FluxmapReaderTest.java | 118 +++++++++ 6 files changed, 404 insertions(+), 24 deletions(-) create mode 100644 java/com/cowlark/fluxengine/data/FluxPosition.java create mode 100644 java/com/cowlark/fluxengine/data/FluxmapReader.java create mode 100644 javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel index 573cb2e6..db331ea2 100644 --- a/java/com/cowlark/fluxengine/data/BUILD.bazel +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -7,6 +7,7 @@ java_library( srcs = glob(["*.java"]), deps = [ "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", "//java/com/cowlark/fluxengine/external", "@maven//:com_google_guava_guava", ], diff --git a/java/com/cowlark/fluxengine/data/FluxPosition.java b/java/com/cowlark/fluxengine/data/FluxPosition.java new file mode 100644 index 00000000..593d1108 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/FluxPosition.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.data; + +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +public record FluxPosition(int bytes, int ticks, int zeroes) +{ + public long ns() + { + return (long) (ticks * NS_PER_TICK); + } + + @Override + public String toString() + { + return String.format("[b:%d, t:%d, z:%d]", bytes, ticks, zeroes); + } +} diff --git a/java/com/cowlark/fluxengine/data/Fluxmap.java b/java/com/cowlark/fluxengine/data/Fluxmap.java index 69392089..d2e26f17 100644 --- a/java/com/cowlark/fluxengine/data/Fluxmap.java +++ b/java/com/cowlark/fluxengine/data/Fluxmap.java @@ -1,8 +1,12 @@ package com.cowlark.fluxengine.data; -import com.cowlark.fluxengine.core.Bytes; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.F_DESYNC; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + import com.cowlark.fluxengine.core.ByteWriter; -import com.cowlark.fluxengine.external.FluxEngine; +import com.cowlark.fluxengine.core.Bytes; import com.google.common.collect.ImmutableList; import java.util.List; @@ -11,19 +15,6 @@ */ public class Fluxmap { - public record Position(int bytes, int ticks, int zeroes) - { - public long ns() - { - return (long) (ticks * FluxEngine.NS_PER_TICK); - } - - @Override - public String toString() - { - return String.format("[b:%d, t:%d, z:%d]", bytes, ticks, zeroes); - } - } private long duration; private int ticks; @@ -82,7 +73,7 @@ public Fluxmap appendPulse() { ensureLastByte(); int index = bytes.size() - 1; - bytes.setByte(index, (byte) (bytes.getByte(index) | FluxEngine.F_BIT_PULSE)); + bytes.setByte(index, (byte) (bytes.getByte(index) | F_BIT_PULSE)); return this; } @@ -91,13 +82,13 @@ public Fluxmap appendIndex() flushIndexMarks(); ensureLastByte(); int index = bytes.size() - 1; - bytes.setByte(index, (byte) (bytes.getByte(index) | FluxEngine.F_BIT_INDEX)); + bytes.setByte(index, (byte) (bytes.getByte(index) | F_BIT_INDEX)); return this; } public Fluxmap appendDesync() { - appendByte(FluxEngine.F_DESYNC); + appendByte(F_DESYNC); return this; } @@ -117,7 +108,7 @@ public Fluxmap appendBytes(Bytes data) bw.write8(b); } - duration = (long) (ticks * FluxEngine.NS_PER_TICK); + duration = (long) (ticks * NS_PER_TICK); return this; } @@ -134,12 +125,12 @@ public Fluxmap appendBits(List bits, long clock) now += clock; if (bit) { - int delta = (int) ((now - duration) / FluxEngine.NS_PER_TICK); + int delta = (int) ((now - duration) / NS_PER_TICK); appendInterval(delta); appendPulse(); } } - int delta = (int) ((now - duration) / FluxEngine.NS_PER_TICK); + int delta = (int) ((now - duration) / NS_PER_TICK); if (delta != 0) appendInterval(delta); return this; @@ -148,7 +139,7 @@ public Fluxmap appendBits(List bits, long clock) public ImmutableList split() { ImmutableList.Builder maps = ImmutableList.builder(); - for (Bytes piece : bytes.split(FluxEngine.F_DESYNC)) + for (Bytes piece : bytes.split(F_DESYNC)) { if (!piece.isEmpty()) maps.add(new Fluxmap(piece)); @@ -167,9 +158,9 @@ public ImmutableList getIndexMarks() { int b = bytes.getByte(i) & 0xff; totalTicks += b & 0x3f; - if ((b & FluxEngine.F_BIT_INDEX) != 0) + if ((b & F_BIT_INDEX) != 0) { - long t = (long) (totalTicks * FluxEngine.NS_PER_TICK); + long t = (long) (totalTicks * NS_PER_TICK); if (t != oldt) marks.add(t); oldt = t; diff --git a/java/com/cowlark/fluxengine/data/FluxmapReader.java b/java/com/cowlark/fluxengine/data/FluxmapReader.java new file mode 100644 index 00000000..98395f92 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/FluxmapReader.java @@ -0,0 +1,240 @@ +package com.cowlark.fluxengine.data; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.F_EOF; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.decoders.Decoders.DecoderProto; + +/** + * A cursor over a Fluxmap's raw bytes. + */ +public class FluxmapReader +{ + public record Event(int event, long ticks) + { + } + + public record EventResult(boolean found, long ticks) + { + } + + public static class ClockData + { + public long median; + public int noiseFloor; + public int signalLevel; + public long peakStart; + public long peakEnd; + public int[] buckets = new int[256]; + } + + private final Fluxmap fluxmap; + private final Bytes bytes; + private final int size; + private final DecoderProto decoder; + private int posBytes; + private int posTicks; + private int posZeroes; + + public FluxmapReader(Fluxmap fluxmap, DecoderProto decoder) + { + this.fluxmap = fluxmap; + bytes = fluxmap.rawBytes(); + size = fluxmap.bytes(); + this.decoder = decoder; + rewind(); + } + + public void rewind() + { + posBytes = 0; + posTicks = 0; + posZeroes = 0; + } + + public boolean eof() + { + return posBytes == size; + } + + public FluxPosition tell() + { + return new FluxPosition(posBytes, posTicks, posZeroes); + } + + public void seek(FluxPosition pos) + { + posBytes = pos.bytes(); + posTicks = pos.ticks(); + posZeroes = pos.zeroes(); + } + + public int getDuration() + { + return (int) fluxmap.duration(); + } + + public int getCurrentEvent() + { + if (eof()) + return F_EOF; + return bytes.getByte(posBytes) & 0xc0; + } + + public Event getNextEvent() + { + long ticks = 0; + while (!eof()) + { + int b = bytes.getByte(posBytes++) & 0xff; + ticks += b & 0x3f; + if (b == 0 || (b & (F_BIT_PULSE | F_BIT_INDEX)) != 0) + { + posTicks += (int) ticks; + return new Event(b & 0xc0, ticks); + } + } + posTicks += (int) ticks; + return new Event(F_EOF, ticks); + } + + public void skipToEvent(int event) + { + findEvent(event); + } + + public EventResult findEvent(int event) + { + long ticks = 0; + while (!eof()) + { + Event e = getNextEvent(); + ticks += e.ticks(); + if (e.event() == F_EOF) + return new EventResult(false, ticks); + if (event == e.event() || (event & e.event()) != 0) + return new EventResult(true, ticks); + } + return new EventResult(false, ticks); + } + + public long readInterval(long clock) + { + long thresholdTicks = (long) ((clock * decoder.getPulseDebounceThreshold()) / NS_PER_TICK); + long ticks = 0; + while (ticks <= thresholdTicks) + { + EventResult r = findEvent(F_BIT_PULSE); + if (!r.found()) + break; + ticks += r.ticks(); + } + return ticks; + } + + public void seek(long ns) + { + int ticks = (int) (ns / NS_PER_TICK); + if (ticks < posTicks) + { + posTicks = 0; + posBytes = 0; + } + while (!eof() && posTicks < ticks) + getNextEvent(); + posZeroes = 0; + } + + public void seekToByte(int b) + { + if (b < posBytes) + { + posTicks = 0; + posBytes = 0; + } + while (!eof() && posBytes < b) + getNextEvent(); + posZeroes = 0; + } + + public void seekToIndexMark() + { + skipToEvent(F_BIT_INDEX); + posZeroes = 0; + } + + public ClockData guessClock() + { + return guessClock(0.01, 0.05); + } + + public ClockData guessClock(double noiseFloorFactor, double signalLevelFactor) + { + ClockData data = new ClockData(); + while (!eof()) + { + long interval = findEvent(F_BIT_PULSE).ticks(); + if (interval > 0xff) + continue; + data.buckets[(int) interval]++; + } + + int max = Integer.MIN_VALUE; + int min = Integer.MAX_VALUE; + for (int b : data.buckets) + { + max = Math.max(max, b); + min = Math.min(min, b); + } + data.noiseFloor = (int) (min + (max - min) * noiseFloorFactor); + data.signalLevel = (int) (min + (max - min) * signalLevelFactor); + + int pulseindex = 0; + while (pulseindex < 256) + { + if (data.buckets[pulseindex] > data.signalLevel) + break; + pulseindex++; + } + if (pulseindex == 256) + return data; + + int peaklo = pulseindex; + while (peaklo > 0) + { + if (data.buckets[peaklo] < data.noiseFloor) + break; + peaklo--; + } + + int peakhi = pulseindex; + while (peakhi < 255) + { + if (data.buckets[peakhi] < data.noiseFloor) + break; + peakhi++; + } + + int totalSize = 0; + for (int i = peaklo; i < peakhi; i++) + totalSize += data.buckets[i]; + + int count = 0; + int median = peaklo; + while (median < peakhi) + { + count += data.buckets[median]; + if (count > totalSize / 2) + break; + median++; + } + + data.peakStart = (long) (peaklo * NS_PER_TICK); + data.peakEnd = (long) (peakhi * NS_PER_TICK); + data.median = (long) (median * NS_PER_TICK); + return data; + } +} diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index 42c78c93..f14cb8a5 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -13,3 +13,16 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "FluxmapReaderTest", + srcs = ["FluxmapReaderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/external", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java new file mode 100644 index 00000000..d969d325 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java @@ -0,0 +1,118 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.decoders.Decoders.DecoderProto; +import com.cowlark.fluxengine.external.FluxEngine; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FluxmapReaderTest +{ + private static final DecoderProto DECODER = DecoderProto.getDefaultInstance(); + + @Test + public void readsEvents() + { + Fluxmap map = new Fluxmap(Bytes.of( + FluxEngine.F_DESYNC, + FluxEngine.F_BIT_PULSE | 0x30, + FluxEngine.F_BIT_INDEX | 0x30, + FluxEngine.F_BIT_PULSE | FluxEngine.F_BIT_INDEX | 0x30, + FluxEngine.F_DESYNC, + FluxEngine.F_BIT_PULSE | 0x30, + FluxEngine.F_DESYNC, + FluxEngine.F_BIT_PULSE | 0x30)); + + FluxmapReader r = new FluxmapReader(map, DECODER); + + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_DESYNC); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_PULSE); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_INDEX); + assertThat(r.getNextEvent().event()) + .isEqualTo(FluxEngine.F_BIT_PULSE | FluxEngine.F_BIT_INDEX); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_DESYNC); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_PULSE); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_DESYNC); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_PULSE); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_EOF); + assertThat(r.eof()).isTrue(); + } + + @Test + public void ticksAccumulate() + { + Fluxmap map = new Fluxmap(Bytes.of( + FluxEngine.F_BIT_PULSE | 0x30, + FluxEngine.F_BIT_PULSE | 0x30)); + + FluxmapReader r = new FluxmapReader(map, DECODER); + + assertThat(r.getNextEvent().ticks()).isEqualTo(0x30L); + assertThat(r.getNextEvent().ticks()).isEqualTo(0x30L); + assertThat(r.tell().ticks()).isEqualTo(0x30 + 0x30); + } + + @Test + public void findEvent() + { + Fluxmap map = new Fluxmap(Bytes.of( + FluxEngine.F_BIT_PULSE | 0x30, + FluxEngine.F_BIT_INDEX | 0x30)); + + FluxmapReader r = new FluxmapReader(map, DECODER); + + FluxmapReader.EventResult result = r.findEvent(FluxEngine.F_BIT_INDEX); + + assertThat(result.found()).isTrue(); + assertThat(result.ticks()).isEqualTo(0x60L); + } + + @Test + public void findEventNotFound() + { + Fluxmap map = new Fluxmap(Bytes.of( + FluxEngine.F_BIT_PULSE | 0x30, + FluxEngine.F_BIT_PULSE | 0x30)); + + FluxmapReader r = new FluxmapReader(map, DECODER); + + FluxmapReader.EventResult result = r.findEvent(FluxEngine.F_BIT_INDEX); + + assertThat(result.found()).isFalse(); + } + + @Test + public void rewindResets() + { + Fluxmap map = new Fluxmap(Bytes.of(FluxEngine.F_BIT_PULSE | 0x30)); + + FluxmapReader r = new FluxmapReader(map, DECODER); + r.getNextEvent(); + assertThat(r.eof()).isTrue(); + + r.rewind(); + + assertThat(r.eof()).isFalse(); + assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_PULSE); + } + + @Test + public void guessClock() + { + Fluxmap map = new Fluxmap(); + for (int i = 0; i < 100; i++) + { + map.appendInterval(0x30); + map.appendPulse(); + } + + FluxmapReader r = new FluxmapReader(map, DECODER); + FluxmapReader.ClockData data = r.guessClock(); + + assertThat(data.median).isEqualTo((long) (0x30 * FluxEngine.NS_PER_TICK)); + } +} From a0c97873c6f29ccbdd12c33335306579ad39a63a Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 22:05:47 +0200 Subject: [PATCH 087/192] A lot of fluxmap and fluxmapreader cleanup. --- .../cowlark/fluxengine/data/FluxPosition.java | 6 +- java/com/cowlark/fluxengine/data/Fluxmap.java | 33 ++++------ .../fluxengine/data/FluxmapReader.java | 66 +++++++++---------- .../fluxengine/data/FluxmapReaderTest.java | 2 +- .../cowlark/fluxengine/data/FluxmapTest.java | 6 +- 5 files changed, 51 insertions(+), 62 deletions(-) diff --git a/java/com/cowlark/fluxengine/data/FluxPosition.java b/java/com/cowlark/fluxengine/data/FluxPosition.java index 593d1108..0f912fbe 100644 --- a/java/com/cowlark/fluxengine/data/FluxPosition.java +++ b/java/com/cowlark/fluxengine/data/FluxPosition.java @@ -2,11 +2,13 @@ import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; +import java.time.Duration; + public record FluxPosition(int bytes, int ticks, int zeroes) { - public long ns() + public Duration getDuration() { - return (long) (ticks * NS_PER_TICK); + return Duration.ofNanos((long) (ticks * NS_PER_TICK)); } @Override diff --git a/java/com/cowlark/fluxengine/data/Fluxmap.java b/java/com/cowlark/fluxengine/data/Fluxmap.java index d2e26f17..bfb76c06 100644 --- a/java/com/cowlark/fluxengine/data/Fluxmap.java +++ b/java/com/cowlark/fluxengine/data/Fluxmap.java @@ -3,7 +3,6 @@ import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; import static com.cowlark.fluxengine.external.FluxEngine.F_DESYNC; -import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; @@ -16,7 +15,6 @@ public class Fluxmap { - private long duration; private int ticks; private Bytes bytes; private ImmutableList indexMarks; @@ -38,11 +36,6 @@ public Fluxmap(Bytes bytes) appendBytes(bytes); } - public long duration() - { - return duration; - } - public int ticks() { return ticks; @@ -108,7 +101,6 @@ public Fluxmap appendBytes(Bytes data) bw.write8(b); } - duration = (long) (ticks * NS_PER_TICK); return this; } @@ -117,22 +109,22 @@ public Fluxmap appendByte(int b) return appendBytes(Bytes.of(b)); } - public Fluxmap appendBits(List bits, long clock) + public Fluxmap appendBits(List bits, long clockTicks) { - long now = duration; + long nowTicks = ticks; for (boolean bit : bits) { - now += clock; + nowTicks += clockTicks; if (bit) { - int delta = (int) ((now - duration) / NS_PER_TICK); - appendInterval(delta); + int deltaTicks = (int) (nowTicks - ticks); + appendInterval(deltaTicks); appendPulse(); } } - int delta = (int) ((now - duration) / NS_PER_TICK); - if (delta != 0) - appendInterval(delta); + int deltaTicks = (int) (nowTicks - ticks); + if (deltaTicks != 0) + appendInterval(deltaTicks); return this; } @@ -153,17 +145,16 @@ public ImmutableList getIndexMarks() { ImmutableList.Builder marks = ImmutableList.builder(); long totalTicks = 0; - long oldt = -1; + long oldTicks = -1; for (int i = 0; i < bytes.size(); i++) { int b = bytes.getByte(i) & 0xff; totalTicks += b & 0x3f; if ((b & F_BIT_INDEX) != 0) { - long t = (long) (totalTicks * NS_PER_TICK); - if (t != oldt) - marks.add(t); - oldt = t; + if (totalTicks != oldTicks) + marks.add(totalTicks); + oldTicks = totalTicks; } } indexMarks = marks.build(); diff --git a/java/com/cowlark/fluxengine/data/FluxmapReader.java b/java/com/cowlark/fluxengine/data/FluxmapReader.java index 98395f92..c3f6c6be 100644 --- a/java/com/cowlark/fluxengine/data/FluxmapReader.java +++ b/java/com/cowlark/fluxengine/data/FluxmapReader.java @@ -7,6 +7,7 @@ import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.decoders.Decoders.DecoderProto; +import java.time.Duration; /** * A cursor over a Fluxmap's raw bytes. @@ -23,11 +24,11 @@ public record EventResult(boolean found, long ticks) public static class ClockData { - public long median; + public long medianTicks; public int noiseFloor; public int signalLevel; - public long peakStart; - public long peakEnd; + public long peakStartTicks; + public long peakEndTicks; public int[] buckets = new int[256]; } @@ -72,9 +73,9 @@ public void seek(FluxPosition pos) posZeroes = pos.zeroes(); } - public int getDuration() + public Duration getDuration() { - return (int) fluxmap.duration(); + return Duration.ofNanos((long) (fluxmap.ticks() * NS_PER_TICK)); } public int getCurrentEvent() @@ -121,9 +122,9 @@ public EventResult findEvent(int event) return new EventResult(false, ticks); } - public long readInterval(long clock) + public long readInterval(long clockTicks) { - long thresholdTicks = (long) ((clock * decoder.getPulseDebounceThreshold()) / NS_PER_TICK); + long thresholdTicks = (long) (clockTicks * decoder.getPulseDebounceThreshold()); long ticks = 0; while (ticks <= thresholdTicks) { @@ -135,9 +136,8 @@ public long readInterval(long clock) return ticks; } - public void seek(long ns) + public void seek(long ticks) { - int ticks = (int) (ns / NS_PER_TICK); if (ticks < posTicks) { posTicks = 0; @@ -176,10 +176,10 @@ public ClockData guessClock(double noiseFloorFactor, double signalLevelFactor) ClockData data = new ClockData(); while (!eof()) { - long interval = findEvent(F_BIT_PULSE).ticks(); - if (interval > 0xff) + long intervalTicks = findEvent(F_BIT_PULSE).ticks(); + if (intervalTicks > 0xff) continue; - data.buckets[(int) interval]++; + data.buckets[(int) intervalTicks]++; } int max = Integer.MIN_VALUE; @@ -192,49 +192,49 @@ public ClockData guessClock(double noiseFloorFactor, double signalLevelFactor) data.noiseFloor = (int) (min + (max - min) * noiseFloorFactor); data.signalLevel = (int) (min + (max - min) * signalLevelFactor); - int pulseindex = 0; - while (pulseindex < 256) + int pulseindexTicks = 0; + while (pulseindexTicks < 256) { - if (data.buckets[pulseindex] > data.signalLevel) + if (data.buckets[pulseindexTicks] > data.signalLevel) break; - pulseindex++; + pulseindexTicks++; } - if (pulseindex == 256) + if (pulseindexTicks == 256) return data; - int peaklo = pulseindex; - while (peaklo > 0) + int peakloTicks = pulseindexTicks; + while (peakloTicks > 0) { - if (data.buckets[peaklo] < data.noiseFloor) + if (data.buckets[peakloTicks] < data.noiseFloor) break; - peaklo--; + peakloTicks--; } - int peakhi = pulseindex; - while (peakhi < 255) + int peakhiTicks = pulseindexTicks; + while (peakhiTicks < 255) { - if (data.buckets[peakhi] < data.noiseFloor) + if (data.buckets[peakhiTicks] < data.noiseFloor) break; - peakhi++; + peakhiTicks++; } int totalSize = 0; - for (int i = peaklo; i < peakhi; i++) + for (int i = peakloTicks; i < peakhiTicks; i++) totalSize += data.buckets[i]; int count = 0; - int median = peaklo; - while (median < peakhi) + int medianTicks = peakloTicks; + while (medianTicks < peakhiTicks) { - count += data.buckets[median]; + count += data.buckets[medianTicks]; if (count > totalSize / 2) break; - median++; + medianTicks++; } - data.peakStart = (long) (peaklo * NS_PER_TICK); - data.peakEnd = (long) (peakhi * NS_PER_TICK); - data.median = (long) (median * NS_PER_TICK); + data.peakStartTicks = peakloTicks; + data.peakEndTicks = peakhiTicks; + data.medianTicks = medianTicks; return data; } } diff --git a/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java index d969d325..805dd9d3 100644 --- a/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java +++ b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java @@ -113,6 +113,6 @@ public void guessClock() FluxmapReader r = new FluxmapReader(map, DECODER); FluxmapReader.ClockData data = r.guessClock(); - assertThat(data.median).isEqualTo((long) (0x30 * FluxEngine.NS_PER_TICK)); + assertThat(data.medianTicks).isEqualTo(0x30L); } } diff --git a/javatests/com/cowlark/fluxengine/data/FluxmapTest.java b/javatests/com/cowlark/fluxengine/data/FluxmapTest.java index 6e208f1b..9623538c 100644 --- a/javatests/com/cowlark/fluxengine/data/FluxmapTest.java +++ b/javatests/com/cowlark/fluxengine/data/FluxmapTest.java @@ -3,7 +3,6 @@ import static com.google.common.truth.Truth.assertThat; import com.cowlark.fluxengine.core.Bytes; -import com.cowlark.fluxengine.external.FluxEngine; import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,7 +20,6 @@ public void appendIntervalAndPulse() assertThat(map.rawBytes()).isEqualTo(Bytes.of(0x30 | 0x80)); assertThat(map.ticks()).isEqualTo(0x30); - assertThat(map.duration()).isEqualTo((long) (0x30 * FluxEngine.NS_PER_TICK)); } @Test @@ -81,9 +79,7 @@ public void getIndexMarks() List marks = map.getIndexMarks(); - assertThat(marks).containsExactly( - (long) (100 * FluxEngine.NS_PER_TICK), - (long) (150 * FluxEngine.NS_PER_TICK)); + assertThat(marks).containsExactly(100L, 150L); } @Test From 099fd3801dc7ec7b73f144bd6cbbfeb1eec8a38a Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 22:22:31 +0200 Subject: [PATCH 088/192] More Duration fixes. --- .../cowlark/fluxengine/cli/RpmCommand.java | 9 ++++---- .../fluxengine/usb/GreaseweazleUsbDevice.java | 21 ++++++++++--------- .../com/cowlark/fluxengine/usb/UsbDevice.java | 10 +++++---- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/java/com/cowlark/fluxengine/cli/RpmCommand.java b/java/com/cowlark/fluxengine/cli/RpmCommand.java index 41e17c6b..77fb9ce0 100644 --- a/java/com/cowlark/fluxengine/cli/RpmCommand.java +++ b/java/com/cowlark/fluxengine/cli/RpmCommand.java @@ -8,6 +8,7 @@ import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; import com.google.common.collect.ImmutableList; +import java.time.Duration; /** * Measure the disk rotational speed, modelled after src/fe-rpm.cc. @@ -30,12 +31,12 @@ public void run(ImmutableList args) UsbDevice device = UsbFactory.connect(config); - long period = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); - if (period != 0) + Duration period = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); + if (!period.isZero()) System.out.printf( "Rotational period is %d ms (%.0f rpm)\n", - period / 1000000, - 60e9 / period); + period.toMillis(), + 60e9 / period.toNanos()); else System.out.println(""" No index pulses detected from the disk. Common causes of this are: diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index 688f9a98..c558d07b 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -185,7 +185,7 @@ public void seek(int track) } @Override - public long getRotationalPeriod(int hardSectorCount) + public Duration getRotationalPeriod(int hardSectorCount) { if (hardSectorCount != 0) throw new FluxEngineException( @@ -260,7 +260,7 @@ else if (secondIndex == -1) doCommand(CMD_GET_FLUX_STATUS); revolutions = (secondIndex - firstIndex) * clock; - return revolutions; + return Duration.ofNanos(revolutions); } @Override @@ -351,9 +351,9 @@ public void testBulkRead() } @Override - public Bytes read(int side, boolean synced, long readTime, long hardSectorThreshold) + public Bytes read(int side, boolean synced, Duration readTime, Duration hardSectorThreshold) { - if (hardSectorThreshold != 0) + if (!hardSectorThreshold.isZero()) throw new FluxEngineException( "hard sectors are currently unsupported on the " + "Greaseweazle"); @@ -363,7 +363,7 @@ public Bytes read(int side, boolean synced, long readTime, long hardSectorThresh { case V22: { - long revs = (readTime + revolutions - 1) / revolutions; + long revs = (readTime.toNanos() + revolutions - 1) / revolutions; Bytes cmd = new Bytes(0); ByteWriter bw = new ByteWriter(cmd); bw.write8(CMD_READ_FLUX); @@ -380,7 +380,8 @@ public Bytes read(int side, boolean synced, long readTime, long hardSectorThresh ByteWriter bw = new ByteWriter(cmd); bw.write8(CMD_READ_FLUX); bw.write8(8); - bw.writeLe32((int) ((readTime + (synced ? revolutions : 0)) / clock)); + bw.writeLe32( + (int) ((readTime.toNanos() + (synced ? revolutions : 0)) / clock)); bw.writeLe16(0); doCommand(cmd); } @@ -405,9 +406,9 @@ public Bytes read(int side, boolean synced, long readTime, long hardSectorThresh } @Override - public void write(int side, Bytes fldata, long hardSectorThreshold) + public void write(int side, Bytes fldata, Duration hardSectorThreshold) { - if (hardSectorThreshold != 0) + if (!hardSectorThreshold.isZero()) throw new FluxEngineException( "hard sectors are currently unsupported on the " + "Greaseweazle"); @@ -431,9 +432,9 @@ public void write(int side, Bytes fldata, long hardSectorThreshold) } @Override - public void erase(int side, long hardSectorThreshold) + public void erase(int side, Duration hardSectorThreshold) { - if (hardSectorThreshold != 0) + if (!hardSectorThreshold.isZero()) throw new FluxEngineException( "hard sectors are currently unsupported on the " + "Greaseweazle"); diff --git a/java/com/cowlark/fluxengine/usb/UsbDevice.java b/java/com/cowlark/fluxengine/usb/UsbDevice.java index b3018fa8..5ee0cea4 100644 --- a/java/com/cowlark/fluxengine/usb/UsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/UsbDevice.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.usb; import com.cowlark.fluxengine.core.Bytes; +import java.time.Duration; /** * Base class for USB floppy drive devices, ported from lib/usb/usb.h. @@ -14,17 +15,18 @@ public void recalibrate() public abstract void seek(int track); - public abstract long getRotationalPeriod(int hardSectorCount); + public abstract Duration getRotationalPeriod(int hardSectorCount); public abstract void testBulkWrite(); public abstract void testBulkRead(); - public abstract Bytes read(int side, boolean synced, long readTime, long hardSectorThreshold); + public abstract Bytes read(int side, boolean synced, Duration readTime, + Duration hardSectorThreshold); - public abstract void write(int side, Bytes bytes, long hardSectorThreshold); + public abstract void write(int side, Bytes bytes, Duration hardSectorThreshold); - public abstract void erase(int side, long hardSectorThreshold); + public abstract void erase(int side, Duration hardSectorThreshold); public abstract void setDrive(int drive, boolean highDensity, int indexMode); From 6911a9c3ab8070f172ba93282fed4ce99b74d969 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 23:03:45 +0200 Subject: [PATCH 089/192] Add the config factory methods for setting flag values. --- java/com/cowlark/fluxengine/cli/Command.java | 2 +- .../cowlark/fluxengine/cli/ReadCommand.java | 65 +++++++ .../com/cowlark/fluxengine/config/BUILD.bazel | 2 + .../fluxengine/config/ConfigBuilder.java | 166 ++++++++++++++++++ .../fluxengine/config/ConfigTools.java | 8 + .../fluxengine/fluxsource/FluxSource.java | 13 ++ .../com/cowlark/fluxengine/config/BUILD.bazel | 1 + .../fluxengine/config/ConfigBuilderTest.java | 81 +++++++++ 8 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/cli/ReadCommand.java create mode 100644 java/com/cowlark/fluxengine/config/ConfigTools.java create mode 100644 java/com/cowlark/fluxengine/fluxsource/FluxSource.java diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index f6342eb2..2daf34ff 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -34,7 +34,7 @@ public interface Command .put( "analyse", () -> new CommandGroup(ANALYSABLES, "Disk and drive analysis tools.")) - .put("read", stub("read", "Reads a disk, producing a sector image.")) + .put("read", ReadCommand::new) .put("write", stub("write", "Writes a sector image to a disk.")) .put( "fluxfile", diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java new file mode 100644 index 00000000..bb1637da --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -0,0 +1,65 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.google.common.collect.ImmutableList; + +/** + * Read a disk, producing a sector image, modelled after src/fe-read.cc. + */ +public class ReadCommand implements Command +{ + private FlagGroup flags = new FlagGroup(); + private ValueFlag sourceFlag = StringFlag.builder() + .setGroup(flags) + .setName("--source") + .setName("-s") + .setHelpText("flux file to read from") + .build(); + private ValueFlag outputFlag = StringFlag.builder() + .setGroup(flags) + .setName("--output") + .setName("-o") + .setHelpText("destination image to write") + .build(); + private ValueFlag copyFluxToFlag = StringFlag.builder() + .setGroup(flags) + .setName("--copy-flux-to") + .setHelpText("while reading, copy the read flux to this file") + .build(); + + @Override + public String getHelp() + { + return "Reads a disk, producing a sector image."; + } + + @Override + public void run(ImmutableList args) + { + ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); + if (sourceFlag.isSet()) + builder.withFluxSource(sourceFlag.get()); + if (outputFlag.isSet()) + builder.withImageWriter(outputFlag.get()); + if (copyFluxToFlag.isSet()) + builder.withCopyFluxTo(copyFluxToFlag.get()); + ConfigProto config = builder.build(); + + if (config.getDecoder().getCopyFluxTo().getType() == FLUXTYPE_DRIVE) + throw new FluxEngineException("you cannot copy flux to a hardware device"); + + // Unsupported: + // var diskLayout = createDiskLayout(config); + // var fluxSource = FluxSource.create(config); + // var decoder = Arch.createDecoder(config); + // var writer = ImageWriter.create(config); + // readDiskCommand(diskLayout, fluxSource, decoder, writer); + } +} diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 3ebb645e..7cd2bcec 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -78,7 +78,9 @@ java_library( ":config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", + "//java/com/cowlark/fluxengine/fluxsink:fluxsink_java_proto", "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "//java/com/cowlark/fluxengine/imagewriter:imagewriter_java_proto", "@com_google_protobuf//java/core", "@maven//:com_fazecast_jSerialComm", "@maven//:com_google_guava_guava", diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index 59b2c80d..11d18191 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -1,9 +1,32 @@ package com.cowlark.fluxengine.config; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_A2R; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_AU; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_CWF; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DMK; import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_ERASE; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_FLUX; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_FLX; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_KRYOFLUX; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_SCP; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_TEST_PATTERN; +import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_VCD; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_D64; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_D88; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_DIM; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_DISKCOPY; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_FDI; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_IMD; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_IMG; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_JV3; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_NFD; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_NSI; +import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_TD0; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.Flags; +import com.cowlark.fluxengine.fluxsink.Fluxsink; import com.cowlark.fluxengine.fluxsource.Fluxsource; import com.google.common.collect.ImmutableList; import com.google.protobuf.TextFormat; @@ -57,6 +80,148 @@ public ConfigBuilder loadConfigFile(String name) return this; } + public ConfigBuilder withFluxSource(String filename) + { + Fluxsource.FluxSourceProto.Builder fluxSource = proto.getFluxSourceBuilder(); + if (filename.endsWith(".flux")) + { + fluxSource.setType(FLUXTYPE_FLUX); + fluxSource.getFl2Builder().setFilename(filename); + } else if (filename.endsWith(".scp")) + { + fluxSource.setType(FLUXTYPE_SCP); + fluxSource.getScpBuilder().setFilename(filename); + } else if (filename.endsWith(".a2r")) + { + fluxSource.setType(FLUXTYPE_A2R); + fluxSource.getA2RBuilder().setFilename(filename); + } else if (filename.endsWith(".cwf")) + { + fluxSource.setType(FLUXTYPE_CWF); + fluxSource.getCwfBuilder().setFilename(filename); + } else if (filename.startsWith("dmk:")) + { + fluxSource.setType(FLUXTYPE_DMK); + fluxSource.getDmkBuilder().setDirectory(filename.substring(4)); + } else if (filename.equals("erase:")) + { + fluxSource.setType(FLUXTYPE_ERASE); + } else if (filename.startsWith("kryoflux:")) + { + fluxSource.setType(FLUXTYPE_KRYOFLUX); + fluxSource.getKryofluxBuilder().setDirectory(filename.substring(9)); + } else if (filename.startsWith("testpattern:")) + { + fluxSource.setType(FLUXTYPE_TEST_PATTERN); + } else if (filename.startsWith("drive:")) + { + fluxSource.setType(FLUXTYPE_DRIVE); + proto.getDriveBuilder().setDrive(Integer.parseInt(filename.substring(6))); + } else if (filename.startsWith("flx:")) + { + fluxSource.setType(FLUXTYPE_FLX); + fluxSource.getFlxBuilder().setDirectory(filename.substring(4)); + } else + throw new ConfigException("unrecognised flux filename '" + filename + "'"); + return this; + } + + public ConfigBuilder withCopyFluxTo(String filename) + { + setFluxSink(proto.getDecoderBuilder().getCopyFluxToBuilder(), filename); + return this; + } + + public ConfigBuilder withFluxSink(String filename) + { + setFluxSink(proto.getFluxSinkBuilder(), filename); + return this; + } + + private void setFluxSink(Fluxsink.FluxSinkProto.Builder fluxSink, String filename) + { + if (filename.endsWith(".flux")) + { + fluxSink.setType(FLUXTYPE_FLUX); + fluxSink.getFl2Builder().setFilename(filename); + } else if (filename.endsWith(".scp")) + { + fluxSink.setType(FLUXTYPE_SCP); + fluxSink.getScpBuilder().setFilename(filename); + } else if (filename.endsWith(".a2r")) + { + fluxSink.setType(FLUXTYPE_A2R); + fluxSink.getA2RBuilder().setFilename(filename); + } else if (filename.startsWith("drive:")) + { + fluxSink.setType(FLUXTYPE_DRIVE); + proto.getDriveBuilder().setDrive(Integer.parseInt(filename.substring(6))); + } else if (filename.startsWith("vcd:")) + { + fluxSink.setType(FLUXTYPE_VCD); + fluxSink.getVcdBuilder().setDirectory(filename.substring(4)); + } else if (filename.startsWith("au:")) + { + fluxSink.setType(FLUXTYPE_AU); + fluxSink.getAuBuilder().setDirectory(filename.substring(3)); + } else + throw new ConfigException("unrecognised flux filename '" + filename + "'"); + } + + public ConfigBuilder withImageWriter(String filename) + { + Common.ImageReaderWriterType type = imageType(filename); + if (type == null || isReadOnlyImage(filename)) + throw new ConfigException("unrecognised image filename '" + filename + "'"); + proto.getImageWriterBuilder().setType(type).setFilename(filename); + return this; + } + + public ConfigBuilder withImageReader(String filename) + { + Common.ImageReaderWriterType type = imageType(filename); + if (type == null) + throw new ConfigException("unrecognised image filename '" + filename + "'"); + proto.getImageReaderBuilder().setType(type).setFilename(filename); + return this; + } + + private static Common.ImageReaderWriterType imageType(String filename) + { + if (filename.endsWith(".adf") || filename.endsWith(".d81") || filename.endsWith(".dsk") + || filename.endsWith(".img") || filename.endsWith(".st") || filename.endsWith(".vgi") + || filename.endsWith(".xdf")) + return IMAGETYPE_IMG; + else if (filename.endsWith(".d64")) + return IMAGETYPE_D64; + else if (filename.endsWith(".d88")) + return IMAGETYPE_D88; + else if (filename.endsWith(".dim")) + return IMAGETYPE_DIM; + else if (filename.endsWith(".diskcopy")) + return IMAGETYPE_DISKCOPY; + else if (filename.endsWith(".fdi")) + return IMAGETYPE_FDI; + else if (filename.endsWith(".imd")) + return IMAGETYPE_IMD; + else if (filename.endsWith(".jv3")) + return IMAGETYPE_JV3; + else if (filename.endsWith(".nfd")) + return IMAGETYPE_NFD; + else if (filename.endsWith(".nsi")) + return IMAGETYPE_NSI; + else if (filename.endsWith(".td0")) + return IMAGETYPE_TD0; + else + return null; + } + + private static boolean isReadOnlyImage(String filename) + { + return filename.endsWith(".dim") || filename.endsWith(".fdi") || filename.endsWith(".jv3") + || filename.endsWith(".nfd") || filename.endsWith(".td0"); + } + public ConfigBuilder showCurrentConfig() { return this; @@ -86,4 +251,5 @@ private void validateUsb() if (!proto.getUsb().hasSerial()) proto.getUsbBuilder().setSerial(UsbFinder.selectDevice(proto).serial); } + } diff --git a/java/com/cowlark/fluxengine/config/ConfigTools.java b/java/com/cowlark/fluxengine/config/ConfigTools.java new file mode 100644 index 00000000..8e2f4544 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ConfigTools.java @@ -0,0 +1,8 @@ +package com.cowlark.fluxengine.config; + +public class ConfigTools +{ + private ConfigTools() + { + } +} diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java new file mode 100644 index 00000000..19d91091 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -0,0 +1,13 @@ +package com.cowlark.fluxengine.fluxsource.FluxSource; + +public abstract class FluxSource +{ + public static FluxSource create(FluxSourceProto config) + { + return switch (config.getType()) + { + case FLUXTYPE_ERASE -> new EraseFluxSource(config.getErase()); + default -> null; + } + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/config/BUILD.bazel b/javatests/com/cowlark/fluxengine/config/BUILD.bazel index 74927bce..1bba9f76 100644 --- a/javatests/com/cowlark/fluxengine/config/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/config/BUILD.bazel @@ -7,6 +7,7 @@ java_test( srcs = ["ConfigBuilderTest.java"], deps = [ "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index 5918e54b..25d14dfa 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -93,4 +93,85 @@ public void fromFlagsSetsDottedConfig() assertThat(proto.getDrive().getDrive()).isEqualTo(1); } + + @Test + public void withFluxSource() + { + ConfigProto proto = builder().withFluxSource("foo.flux").build(); + + assertThat(proto.getFluxSource().getType()) + .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_FLUX); + assertThat(proto.getFluxSource().getFl2().getFilename()).isEqualTo("foo.flux"); + } + + @Test + public void withFluxSourceDrive() + { + ConfigProto proto = builder().withFluxSource("drive:1").build(); + + assertThat(proto.getFluxSource().getType()) + .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_DRIVE); + assertThat(proto.getDrive().getDrive()).isEqualTo(1); + } + + @Test + public void withImageWriter() + { + ConfigProto proto = builder().withImageWriter("out.dsk").build(); + + assertThat(proto.getImageWriter().getType()) + .isEqualTo(Common.ImageReaderWriterType.IMAGETYPE_IMG); + assertThat(proto.getImageWriter().getFilename()).isEqualTo("out.dsk"); + } + + @Test + public void withCopyFluxTo() + { + ConfigProto proto = builder().withCopyFluxTo("copy.scp").build(); + + assertThat(proto.getDecoder().getCopyFluxTo().getType()) + .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_SCP); + assertThat(proto.getDecoder().getCopyFluxTo().getScp().getFilename()).isEqualTo("copy.scp"); + } + + @Test + public void withFluxSink() + { + ConfigProto proto = builder().withFluxSink("vcd:vcdfiles").build(); + + assertThat(proto.getFluxSink().getType()) + .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_VCD); + assertThat(proto.getFluxSink().getVcd().getDirectory()).isEqualTo("vcdfiles"); + } + + @Test + public void withImageReader() + { + ConfigProto proto = builder().withImageReader("in.dim").build(); + + assertThat(proto.getImageReader().getType()) + .isEqualTo(Common.ImageReaderWriterType.IMAGETYPE_DIM); + assertThat(proto.getImageReader().getFilename()).isEqualTo("in.dim"); + } + + @Test + public void withImageWriterReadOnlyThrows() + { + assertThrows(ConfigException.class, + () -> builder().withImageWriter("out.dim")); + } + + @Test + public void withImageReaderUnrecognisedThrows() + { + assertThrows(ConfigException.class, + () -> builder().withImageReader("bogus")); + } + + @Test + public void withFluxSourceUnrecognisedThrows() + { + assertThrows(ConfigException.class, + () -> builder().withFluxSource("bogus")); + } } From a03d7eb30838dc528880813994f1525d0c907a63 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 23:10:42 +0200 Subject: [PATCH 090/192] Use java_multiple_files everywhere. --- .../fluxengine/arch/aeslanier/aeslanier.proto | 1 + .../cowlark/fluxengine/arch/agat/agat.proto | 1 + .../cowlark/fluxengine/arch/amiga/amiga.proto | 1 + .../fluxengine/arch/apple2/apple2.proto | 1 + .../fluxengine/arch/brother/brother.proto | 1 + .../com/cowlark/fluxengine/arch/c64/c64.proto | 1 + .../com/cowlark/fluxengine/arch/f85/f85.proto | 1 + .../cowlark/fluxengine/arch/fb100/fb100.proto | 1 + .../com/cowlark/fluxengine/arch/ibm/ibm.proto | 1 + .../fluxengine/arch/macintosh/macintosh.proto | 1 + .../arch/micropolis/micropolis.proto | 1 + java/com/cowlark/fluxengine/arch/mx/mx.proto | 1 + .../fluxengine/arch/northstar/northstar.proto | 1 + .../fluxengine/arch/rolandd20/rolandd20.proto | 1 + .../fluxengine/arch/smaky6/smaky6.proto | 1 + .../cowlark/fluxengine/arch/tartu/tartu.proto | 1 + .../fluxengine/arch/tids990/tids990.proto | 1 + .../fluxengine/arch/victor9k/victor9k.proto | 1 + .../fluxengine/arch/zilogmcz/zilogmcz.proto | 1 + .../cowlark/fluxengine/cli/ReadCommand.java | 2 +- .../cowlark/fluxengine/cli/RpmCommand.java | 2 +- .../cowlark/fluxengine/cli/SeekCommand.java | 2 +- .../fluxengine/config/ConfigBuilder.java | 62 +++++++++---------- .../cowlark/fluxengine/config/common.proto | 1 + .../com/cowlark/fluxengine/config/drive.proto | 1 + .../cowlark/fluxengine/config/layout.proto | 1 + .../fluxengine/data/FluxmapReader.java | 2 +- .../fluxengine/decoders/decoders.proto | 1 + .../fluxengine/encoders/encoders.proto | 1 + .../com/cowlark/fluxengine/external/fl2.proto | 1 + .../fluxengine/fluxsink/fluxsink.proto | 1 + .../fluxengine/fluxsource/fluxsource.proto | 1 + .../fluxengine/imagereader/imagereader.proto | 1 + .../fluxengine/imagewriter/imagewriter.proto | 1 + .../fluxengine/usb/GreaseweazleUsbDevice.java | 2 +- java/com/cowlark/fluxengine/usb/usb.proto | 1 + java/com/cowlark/fluxengine/vfs/vfs.proto | 1 + .../fluxengine/config/ConfigBuilderTest.java | 12 ++-- .../fluxengine/data/FluxmapReaderTest.java | 2 +- 39 files changed, 74 insertions(+), 43 deletions(-) diff --git a/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto b/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto index ae971cd8..fb4dcb7d 100644 --- a/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto +++ b/java/com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.aeslanier"; +option java_multiple_files = true; message AesLanierDecoderProto {} diff --git a/java/com/cowlark/fluxengine/arch/agat/agat.proto b/java/com/cowlark/fluxengine/arch/agat/agat.proto index 8e712bce..26f9c812 100644 --- a/java/com/cowlark/fluxengine/arch/agat/agat.proto +++ b/java/com/cowlark/fluxengine/arch/agat/agat.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.agat"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/amiga/amiga.proto b/java/com/cowlark/fluxengine/arch/amiga/amiga.proto index 84bf510b..be6023e8 100644 --- a/java/com/cowlark/fluxengine/arch/amiga/amiga.proto +++ b/java/com/cowlark/fluxengine/arch/amiga/amiga.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.amiga"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/apple2/apple2.proto b/java/com/cowlark/fluxengine/arch/apple2/apple2.proto index af11e1a4..51ff3a14 100644 --- a/java/com/cowlark/fluxengine/arch/apple2/apple2.proto +++ b/java/com/cowlark/fluxengine/arch/apple2/apple2.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.apple2"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/brother/brother.proto b/java/com/cowlark/fluxengine/arch/brother/brother.proto index 0cb15a33..1cf22115 100644 --- a/java/com/cowlark/fluxengine/arch/brother/brother.proto +++ b/java/com/cowlark/fluxengine/arch/brother/brother.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.brother"; +option java_multiple_files = true; message BrotherDecoderProto {} diff --git a/java/com/cowlark/fluxengine/arch/c64/c64.proto b/java/com/cowlark/fluxengine/arch/c64/c64.proto index 0c6b178f..6ce1fd76 100644 --- a/java/com/cowlark/fluxengine/arch/c64/c64.proto +++ b/java/com/cowlark/fluxengine/arch/c64/c64.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.c64"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/f85/f85.proto b/java/com/cowlark/fluxengine/arch/f85/f85.proto index 7daa6d5c..c9eaa17c 100644 --- a/java/com/cowlark/fluxengine/arch/f85/f85.proto +++ b/java/com/cowlark/fluxengine/arch/f85/f85.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.f85"; +option java_multiple_files = true; message F85DecoderProto {} diff --git a/java/com/cowlark/fluxengine/arch/fb100/fb100.proto b/java/com/cowlark/fluxengine/arch/fb100/fb100.proto index b165ed42..e4a2c0a0 100644 --- a/java/com/cowlark/fluxengine/arch/fb100/fb100.proto +++ b/java/com/cowlark/fluxengine/arch/fb100/fb100.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.fb100"; +option java_multiple_files = true; message Fb100DecoderProto {} diff --git a/java/com/cowlark/fluxengine/arch/ibm/ibm.proto b/java/com/cowlark/fluxengine/arch/ibm/ibm.proto index 419fc5d4..32d988a0 100644 --- a/java/com/cowlark/fluxengine/arch/ibm/ibm.proto +++ b/java/com/cowlark/fluxengine/arch/ibm/ibm.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.ibm"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto b/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto index 72c0c44a..395254b8 100644 --- a/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto +++ b/java/com/cowlark/fluxengine/arch/macintosh/macintosh.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.macintosh"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto b/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto index c6bf4cc3..e4352320 100644 --- a/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto +++ b/java/com/cowlark/fluxengine/arch/micropolis/micropolis.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.micropolis"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/mx/mx.proto b/java/com/cowlark/fluxengine/arch/mx/mx.proto index 0a98d724..9deec481 100644 --- a/java/com/cowlark/fluxengine/arch/mx/mx.proto +++ b/java/com/cowlark/fluxengine/arch/mx/mx.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.mx"; +option java_multiple_files = true; message MxDecoderProto {} diff --git a/java/com/cowlark/fluxengine/arch/northstar/northstar.proto b/java/com/cowlark/fluxengine/arch/northstar/northstar.proto index 32acebd6..3cbca249 100644 --- a/java/com/cowlark/fluxengine/arch/northstar/northstar.proto +++ b/java/com/cowlark/fluxengine/arch/northstar/northstar.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.northstar"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto b/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto index 8af930a3..e16cd829 100644 --- a/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto +++ b/java/com/cowlark/fluxengine/arch/rolandd20/rolandd20.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.rolandd20"; +option java_multiple_files = true; message RolandD20DecoderProto {} diff --git a/java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto b/java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto index 9a45309a..13c60ec8 100644 --- a/java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto +++ b/java/com/cowlark/fluxengine/arch/smaky6/smaky6.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.smaky6"; +option java_multiple_files = true; message Smaky6DecoderProto {} diff --git a/java/com/cowlark/fluxengine/arch/tartu/tartu.proto b/java/com/cowlark/fluxengine/arch/tartu/tartu.proto index 752327c5..a99112a8 100644 --- a/java/com/cowlark/fluxengine/arch/tartu/tartu.proto +++ b/java/com/cowlark/fluxengine/arch/tartu/tartu.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.tartu"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/tids990/tids990.proto b/java/com/cowlark/fluxengine/arch/tids990/tids990.proto index aa8bcf99..0f69e98c 100644 --- a/java/com/cowlark/fluxengine/arch/tids990/tids990.proto +++ b/java/com/cowlark/fluxengine/arch/tids990/tids990.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.tids990"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto b/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto index f4145bd9..4cd1105a 100644 --- a/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto +++ b/java/com/cowlark/fluxengine/arch/victor9k/victor9k.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.victor9k"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto b/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto index 55b15099..3a9bda3b 100644 --- a/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto +++ b/java/com/cowlark/fluxengine/arch/zilogmcz/zilogmcz.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.zilogmcz"; +option java_multiple_files = true; message ZilogMczDecoderProto {} diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index bb1637da..00a450c3 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -1,6 +1,6 @@ package com.cowlark.fluxengine.cli; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; diff --git a/java/com/cowlark/fluxengine/cli/RpmCommand.java b/java/com/cowlark/fluxengine/cli/RpmCommand.java index 77fb9ce0..8c37ae84 100644 --- a/java/com/cowlark/fluxengine/cli/RpmCommand.java +++ b/java/com/cowlark/fluxengine/cli/RpmCommand.java @@ -1,6 +1,6 @@ package com.cowlark.fluxengine.cli; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; diff --git a/java/com/cowlark/fluxengine/cli/SeekCommand.java b/java/com/cowlark/fluxengine/cli/SeekCommand.java index 218dae34..04394e07 100644 --- a/java/com/cowlark/fluxengine/cli/SeekCommand.java +++ b/java/com/cowlark/fluxengine/cli/SeekCommand.java @@ -1,6 +1,6 @@ package com.cowlark.fluxengine.cli; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index 11d18191..18cd2034 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -1,33 +1,33 @@ package com.cowlark.fluxengine.config; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_A2R; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_AU; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_CWF; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DMK; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_DRIVE; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_ERASE; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_FLUX; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_FLX; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_KRYOFLUX; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_SCP; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_TEST_PATTERN; -import static com.cowlark.fluxengine.config.Common.FluxSourceSinkType.FLUXTYPE_VCD; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_D64; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_D88; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_DIM; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_DISKCOPY; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_FDI; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_IMD; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_IMG; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_JV3; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_NFD; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_NSI; -import static com.cowlark.fluxengine.config.Common.ImageReaderWriterType.IMAGETYPE_TD0; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_A2R; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_AU; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_CWF; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DMK; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_ERASE; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_FLUX; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_FLX; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_KRYOFLUX; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_SCP; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_TEST_PATTERN; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_VCD; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_D64; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_D88; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_DIM; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_DISKCOPY; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_FDI; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_IMD; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_IMG; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_JV3; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_NFD; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_NSI; +import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_TD0; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.Flags; -import com.cowlark.fluxengine.fluxsink.Fluxsink; -import com.cowlark.fluxengine.fluxsource.Fluxsource; +import com.cowlark.fluxengine.fluxsink.FluxSinkProto; +import com.cowlark.fluxengine.fluxsource.FluxSourceProto; import com.google.common.collect.ImmutableList; import com.google.protobuf.TextFormat; import java.io.IOException; @@ -41,7 +41,7 @@ public class ConfigBuilder { private ConfigProto.Builder proto = ConfigProto.newBuilder() - .setFluxSource(Fluxsource.FluxSourceProto.newBuilder().setType(FLUXTYPE_DRIVE).build()); + .setFluxSource(FluxSourceProto.newBuilder().setType(FLUXTYPE_DRIVE).build()); public ConfigBuilder() { @@ -82,7 +82,7 @@ public ConfigBuilder loadConfigFile(String name) public ConfigBuilder withFluxSource(String filename) { - Fluxsource.FluxSourceProto.Builder fluxSource = proto.getFluxSourceBuilder(); + FluxSourceProto.Builder fluxSource = proto.getFluxSourceBuilder(); if (filename.endsWith(".flux")) { fluxSource.setType(FLUXTYPE_FLUX); @@ -138,7 +138,7 @@ public ConfigBuilder withFluxSink(String filename) return this; } - private void setFluxSink(Fluxsink.FluxSinkProto.Builder fluxSink, String filename) + private void setFluxSink(FluxSinkProto.Builder fluxSink, String filename) { if (filename.endsWith(".flux")) { @@ -170,7 +170,7 @@ private void setFluxSink(Fluxsink.FluxSinkProto.Builder fluxSink, String filenam public ConfigBuilder withImageWriter(String filename) { - Common.ImageReaderWriterType type = imageType(filename); + ImageReaderWriterType type = imageType(filename); if (type == null || isReadOnlyImage(filename)) throw new ConfigException("unrecognised image filename '" + filename + "'"); proto.getImageWriterBuilder().setType(type).setFilename(filename); @@ -179,14 +179,14 @@ public ConfigBuilder withImageWriter(String filename) public ConfigBuilder withImageReader(String filename) { - Common.ImageReaderWriterType type = imageType(filename); + ImageReaderWriterType type = imageType(filename); if (type == null) throw new ConfigException("unrecognised image filename '" + filename + "'"); proto.getImageReaderBuilder().setType(type).setFilename(filename); return this; } - private static Common.ImageReaderWriterType imageType(String filename) + private static ImageReaderWriterType imageType(String filename) { if (filename.endsWith(".adf") || filename.endsWith(".d81") || filename.endsWith(".dsk") || filename.endsWith(".img") || filename.endsWith(".st") || filename.endsWith(".vgi") diff --git a/java/com/cowlark/fluxengine/config/common.proto b/java/com/cowlark/fluxengine/config/common.proto index eeaf6782..2fc059a1 100644 --- a/java/com/cowlark/fluxengine/config/common.proto +++ b/java/com/cowlark/fluxengine/config/common.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.config"; +option java_multiple_files = true; import "google/protobuf/descriptor.proto"; diff --git a/java/com/cowlark/fluxengine/config/drive.proto b/java/com/cowlark/fluxengine/config/drive.proto index 392d9b0a..89e2f603 100644 --- a/java/com/cowlark/fluxengine/config/drive.proto +++ b/java/com/cowlark/fluxengine/config/drive.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.config"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; import "com/cowlark/fluxengine/external/fl2.proto"; diff --git a/java/com/cowlark/fluxengine/config/layout.proto b/java/com/cowlark/fluxengine/config/layout.proto index c37d5d76..2ee9ce9b 100644 --- a/java/com/cowlark/fluxengine/config/layout.proto +++ b/java/com/cowlark/fluxengine/config/layout.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.config"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; import "com/cowlark/fluxengine/external/fl2.proto"; diff --git a/java/com/cowlark/fluxengine/data/FluxmapReader.java b/java/com/cowlark/fluxengine/data/FluxmapReader.java index c3f6c6be..a8c229f6 100644 --- a/java/com/cowlark/fluxengine/data/FluxmapReader.java +++ b/java/com/cowlark/fluxengine/data/FluxmapReader.java @@ -6,7 +6,7 @@ import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; import com.cowlark.fluxengine.core.Bytes; -import com.cowlark.fluxengine.decoders.Decoders.DecoderProto; +import com.cowlark.fluxengine.decoders.DecoderProto; import java.time.Duration; /** diff --git a/java/com/cowlark/fluxengine/decoders/decoders.proto b/java/com/cowlark/fluxengine/decoders/decoders.proto index a83c886c..8cbf79cb 100644 --- a/java/com/cowlark/fluxengine/decoders/decoders.proto +++ b/java/com/cowlark/fluxengine/decoders/decoders.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.decoders"; +option java_multiple_files = true; import "com/cowlark/fluxengine/arch/agat/agat.proto"; import "com/cowlark/fluxengine/arch/aeslanier/aeslanier.proto"; diff --git a/java/com/cowlark/fluxengine/encoders/encoders.proto b/java/com/cowlark/fluxengine/encoders/encoders.proto index f33c80bd..cfdb3de7 100644 --- a/java/com/cowlark/fluxengine/encoders/encoders.proto +++ b/java/com/cowlark/fluxengine/encoders/encoders.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.encoders"; +option java_multiple_files = true; import "com/cowlark/fluxengine/arch/agat/agat.proto"; import "com/cowlark/fluxengine/arch/amiga/amiga.proto"; diff --git a/java/com/cowlark/fluxengine/external/fl2.proto b/java/com/cowlark/fluxengine/external/fl2.proto index 79c0fab7..4edb10cb 100644 --- a/java/com/cowlark/fluxengine/external/fl2.proto +++ b/java/com/cowlark/fluxengine/external/fl2.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.external"; +option java_multiple_files = true; import "google/protobuf/descriptor.proto"; diff --git a/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto b/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto index 8cc38bf5..f9d0fff6 100644 --- a/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto +++ b/java/com/cowlark/fluxengine/fluxsink/fluxsink.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.fluxsink"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto b/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto index d8f2fa8a..c6ff73eb 100644 --- a/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto +++ b/java/com/cowlark/fluxengine/fluxsource/fluxsource.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.fluxsource"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/imagereader/imagereader.proto b/java/com/cowlark/fluxengine/imagereader/imagereader.proto index be56e700..cd6c23d0 100644 --- a/java/com/cowlark/fluxengine/imagereader/imagereader.proto +++ b/java/com/cowlark/fluxengine/imagereader/imagereader.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.imagereader"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto b/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto index dab3bded..6ca63fb5 100644 --- a/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto +++ b/java/com/cowlark/fluxengine/imagewriter/imagewriter.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.imagewriter"; +option java_multiple_files = true; import "com/cowlark/fluxengine/imagereader/imagereader.proto"; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index c558d07b..f37c8c97 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -36,7 +36,7 @@ import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.external.GreaseweazleUtils; -import com.cowlark.fluxengine.usb.Usb.GreaseweazleProto; +import com.cowlark.fluxengine.usb.GreaseweazleProto; import com.fazecast.jSerialComm.SerialPort; import com.google.common.util.concurrent.Uninterruptibles; import java.time.Duration; diff --git a/java/com/cowlark/fluxengine/usb/usb.proto b/java/com/cowlark/fluxengine/usb/usb.proto index 54da620a..81c1a385 100644 --- a/java/com/cowlark/fluxengine/usb/usb.proto +++ b/java/com/cowlark/fluxengine/usb/usb.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.usb"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/java/com/cowlark/fluxengine/vfs/vfs.proto b/java/com/cowlark/fluxengine/vfs/vfs.proto index 481073a0..d98f4759 100644 --- a/java/com/cowlark/fluxengine/vfs/vfs.proto +++ b/java/com/cowlark/fluxengine/vfs/vfs.proto @@ -1,6 +1,7 @@ syntax = "proto2"; option java_package = "com.cowlark.fluxengine.vfs"; +option java_multiple_files = true; import "com/cowlark/fluxengine/config/common.proto"; diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index 25d14dfa..ba1558c4 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -100,7 +100,7 @@ public void withFluxSource() ConfigProto proto = builder().withFluxSource("foo.flux").build(); assertThat(proto.getFluxSource().getType()) - .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_FLUX); + .isEqualTo(FluxSourceSinkType.FLUXTYPE_FLUX); assertThat(proto.getFluxSource().getFl2().getFilename()).isEqualTo("foo.flux"); } @@ -110,7 +110,7 @@ public void withFluxSourceDrive() ConfigProto proto = builder().withFluxSource("drive:1").build(); assertThat(proto.getFluxSource().getType()) - .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_DRIVE); + .isEqualTo(FluxSourceSinkType.FLUXTYPE_DRIVE); assertThat(proto.getDrive().getDrive()).isEqualTo(1); } @@ -120,7 +120,7 @@ public void withImageWriter() ConfigProto proto = builder().withImageWriter("out.dsk").build(); assertThat(proto.getImageWriter().getType()) - .isEqualTo(Common.ImageReaderWriterType.IMAGETYPE_IMG); + .isEqualTo(ImageReaderWriterType.IMAGETYPE_IMG); assertThat(proto.getImageWriter().getFilename()).isEqualTo("out.dsk"); } @@ -130,7 +130,7 @@ public void withCopyFluxTo() ConfigProto proto = builder().withCopyFluxTo("copy.scp").build(); assertThat(proto.getDecoder().getCopyFluxTo().getType()) - .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_SCP); + .isEqualTo(FluxSourceSinkType.FLUXTYPE_SCP); assertThat(proto.getDecoder().getCopyFluxTo().getScp().getFilename()).isEqualTo("copy.scp"); } @@ -140,7 +140,7 @@ public void withFluxSink() ConfigProto proto = builder().withFluxSink("vcd:vcdfiles").build(); assertThat(proto.getFluxSink().getType()) - .isEqualTo(Common.FluxSourceSinkType.FLUXTYPE_VCD); + .isEqualTo(FluxSourceSinkType.FLUXTYPE_VCD); assertThat(proto.getFluxSink().getVcd().getDirectory()).isEqualTo("vcdfiles"); } @@ -150,7 +150,7 @@ public void withImageReader() ConfigProto proto = builder().withImageReader("in.dim").build(); assertThat(proto.getImageReader().getType()) - .isEqualTo(Common.ImageReaderWriterType.IMAGETYPE_DIM); + .isEqualTo(ImageReaderWriterType.IMAGETYPE_DIM); assertThat(proto.getImageReader().getFilename()).isEqualTo("in.dim"); } diff --git a/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java index 805dd9d3..211b4fcd 100644 --- a/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java +++ b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java @@ -3,7 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import com.cowlark.fluxengine.core.Bytes; -import com.cowlark.fluxengine.decoders.Decoders.DecoderProto; +import com.cowlark.fluxengine.decoders.DecoderProto; import com.cowlark.fluxengine.external.FluxEngine; import org.junit.Test; import org.junit.runner.RunWith; From a0bb55bffc8807932bae91957b4e541540a3f157 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 23:21:29 +0200 Subject: [PATCH 091/192] Translate FluxSource. --- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 13 ++++ .../fluxsource/EmptyFluxSourceIterator.java | 22 +++++++ .../fluxengine/fluxsource/FluxSource.java | 51 +++++++++++++-- .../fluxsource/FluxSourceIterator.java | 14 ++++ .../fluxsource/TrivialFluxSource.java | 18 ++++++ .../fluxsource/TrivialFluxSourceIterator.java | 35 ++++++++++ .../cowlark/fluxengine/fluxsource/BUILD.bazel | 17 +++++ .../fluxengine/fluxsource/FluxSourceTest.java | 64 +++++++++++++++++++ 8 files changed, 229 insertions(+), 5 deletions(-) create mode 100644 java/com/cowlark/fluxengine/fluxsource/EmptyFluxSourceIterator.java create mode 100644 java/com/cowlark/fluxengine/fluxsource/FluxSourceIterator.java create mode 100644 java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java create mode 100644 java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 21c307a2..b2d78d20 100644 --- a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -1,4 +1,5 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) @@ -14,3 +15,15 @@ java_proto_library( name = "fluxsource_java_proto", deps = [":fluxsource_proto"], ) + +java_library( + name = "fluxsource", + srcs = glob(["*.java"]), + deps = [ + ":fluxsource_java_proto", + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + ], +) diff --git a/java/com/cowlark/fluxengine/fluxsource/EmptyFluxSourceIterator.java b/java/com/cowlark/fluxengine/fluxsource/EmptyFluxSourceIterator.java new file mode 100644 index 00000000..a843c34b --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/EmptyFluxSourceIterator.java @@ -0,0 +1,22 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; + +/** + * An iterator over no flux at all, ported from lib/fluxsource/fluxsource.h. + */ +public class EmptyFluxSourceIterator implements FluxSourceIterator +{ + @Override + public boolean hasNext() + { + return false; + } + + @Override + public Fluxmap next() + { + throw new FluxEngineException("no flux to read"); + } +} diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index 19d91091..e88820cd 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -1,13 +1,54 @@ -package com.cowlark.fluxengine.fluxsource.FluxSource; +package com.cowlark.fluxengine.fluxsource; +import com.cowlark.fluxengine.config.FluxSourceSinkType; +import com.cowlark.fluxengine.core.FluxEngineException; + +/** + * A source of flux data, ported from lib/fluxsource/fluxsource.{h,cc}. + */ public abstract class FluxSource { public static FluxSource create(FluxSourceProto config) { - return switch (config.getType()) + switch (config.getType()) { - case FLUXTYPE_ERASE -> new EraseFluxSource(config.getErase()); - default -> null; + case FLUXTYPE_DRIVE: return notImplemented("drive"); + case FLUXTYPE_ERASE: return notImplemented("erase"); + case FLUXTYPE_KRYOFLUX: return notImplemented("kryoflux"); + case FLUXTYPE_TEST_PATTERN: return notImplemented("test pattern"); + case FLUXTYPE_SCP: return notImplemented("scp"); + case FLUXTYPE_A2R: return notImplemented("a2r"); + case FLUXTYPE_CWF: return notImplemented("cwf"); + case FLUXTYPE_DMK: return notImplemented("dmk"); + case FLUXTYPE_FLUX: return notImplemented("fl2"); + case FLUXTYPE_FLX: return notImplemented("flx"); + default: return null; } } -} \ No newline at end of file + + /* Read flux from a given cylinder and head. */ + public abstract FluxSourceIterator readFlux(int cylinder, int head); + + /* Recalibrates; seeks to cylinder 0 and ensures the head is in the right + * place. */ + public void recalibrate() + { + } + + /* Seeks to a given cylinder (without recalibrating). */ + public void seek(int cylinder) + { + } + + /* Is this real hardware? If so, then flux can be read indefinitely (among + * other things). */ + public boolean isHardware() + { + return false; + } + + private static FluxSource notImplemented(String name) + { + throw new FluxEngineException(name + " flux source is not implemented yet"); + } +} diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSourceIterator.java b/java/com/cowlark/fluxengine/fluxsource/FluxSourceIterator.java new file mode 100644 index 00000000..a61de6ad --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSourceIterator.java @@ -0,0 +1,14 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.data.Fluxmap; + +/** + * Iterator over the flux maps of one track, ported from + * lib/fluxsource/fluxsource.h. + */ +public interface FluxSourceIterator +{ + boolean hasNext(); + + Fluxmap next(); +} diff --git a/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java new file mode 100644 index 00000000..da639388 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java @@ -0,0 +1,18 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.data.Fluxmap; + +/** + * A flux source which provides a single flux map per track, ported from + * lib/fluxsource/fluxsource.h. + */ +public abstract class TrivialFluxSource extends FluxSource +{ + @Override + public FluxSourceIterator readFlux(int cylinder, int head) + { + return new TrivialFluxSourceIterator(this, cylinder, head); + } + + public abstract Fluxmap readSingleFlux(int cylinder, int head); +} diff --git a/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java new file mode 100644 index 00000000..64f94f1a --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java @@ -0,0 +1,35 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.data.Fluxmap; + +/** + * Iterator over the single flux map provided by a TrivialFluxSource, ported + * from lib/fluxsource/fluxsource.cc. + */ +public class TrivialFluxSourceIterator implements FluxSourceIterator +{ + private final TrivialFluxSource fluxSource; + private final int cylinder; + private final int head; + private boolean done; + + public TrivialFluxSourceIterator(TrivialFluxSource fluxSource, int cylinder, int head) + { + this.fluxSource = fluxSource; + this.cylinder = cylinder; + this.head = head; + } + + @Override + public boolean hasNext() + { + return !done; + } + + @Override + public Fluxmap next() + { + done = true; + return fluxSource.readSingleFlux(cylinder, head); + } +} diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel new file mode 100644 index 00000000..d854780c --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "FluxSourceTest", + srcs = ["FluxSourceTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java new file mode 100644 index 00000000..67eabefa --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java @@ -0,0 +1,64 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.config.FluxSourceSinkType; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FluxSourceTest +{ + @Test + public void createUnknownTypeReturnsNull() + { + FluxSourceProto config = FluxSourceProto.newBuilder() + .setType(FluxSourceSinkType.FLUXTYPE_NOT_SET) + .build(); + + assertThat(FluxSource.create(config)).isNull(); + } + + @Test + public void createUnportedTypeThrows() + { + FluxSourceProto config = FluxSourceProto.newBuilder() + .setType(FluxSourceSinkType.FLUXTYPE_DRIVE) + .build(); + + assertThrows(FluxEngineException.class, () -> FluxSource.create(config)); + } + + @Test + public void trivialFluxSourceIteratorYieldsOneMap() + { + TrivialFluxSource source = new TrivialFluxSource() + { + @Override + public Fluxmap readSingleFlux(int cylinder, int head) + { + return new Fluxmap(); + } + }; + + FluxSourceIterator iterator = source.readFlux(0, 0); + + assertThat(iterator.hasNext()).isTrue(); + iterator.next(); + assertThat(iterator.hasNext()).isFalse(); + } + + @Test + public void emptyIterator() + { + FluxSourceIterator iterator = new EmptyFluxSourceIterator(); + + assertThat(iterator.hasNext()).isFalse(); + assertThrows(FluxEngineException.class, iterator::next); + } +} From b77ba84fc0d8496587757db996d136485263e90c Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 23:26:30 +0200 Subject: [PATCH 092/192] Basic translation of FluxSource and a trivial flux source. --- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 1 + .../fluxsource/EraseFluxSource.java | 29 +++++++++++++++++++ .../fluxengine/fluxsource/FluxSource.java | 12 +++++++- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 1 + .../fluxengine/fluxsource/FluxSourceTest.java | 14 +++++++++ 5 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel index b2d78d20..9b8cb683 100644 --- a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -23,6 +23,7 @@ java_library( ":fluxsource_java_proto", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", ], diff --git a/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java new file mode 100644 index 00000000..03197839 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java @@ -0,0 +1,29 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.data.Fluxmap; + +/** + * A flux source which produces no flux, ported from + * lib/fluxsource/erasefluxsource.cc. + */ +public class EraseFluxSource extends TrivialFluxSource +{ + public EraseFluxSource(EraseFluxSourceProto config) + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + builder.getDriveBuilder().setTracks("c0-255h0-1"); + extraConfig = builder.build(); + } + + @Override + public Fluxmap readSingleFlux(int cylinder, int head) + { + return null; + } + + @Override + public void recalibrate() + { + } +} diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index e88820cd..641f3225 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.fluxsource; +import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.config.FluxSourceSinkType; import com.cowlark.fluxengine.core.FluxEngineException; @@ -8,12 +9,14 @@ */ public abstract class FluxSource { + protected ConfigProto extraConfig = ConfigProto.getDefaultInstance(); + public static FluxSource create(FluxSourceProto config) { switch (config.getType()) { case FLUXTYPE_DRIVE: return notImplemented("drive"); - case FLUXTYPE_ERASE: return notImplemented("erase"); + case FLUXTYPE_ERASE: return new EraseFluxSource(config.getErase()); case FLUXTYPE_KRYOFLUX: return notImplemented("kryoflux"); case FLUXTYPE_TEST_PATTERN: return notImplemented("test pattern"); case FLUXTYPE_SCP: return notImplemented("scp"); @@ -26,6 +29,13 @@ public static FluxSource create(FluxSourceProto config) } } + /* Returns any configuration this flux source might be carrying (e.g. tpi + * of the drive which made the capture). */ + public ConfigProto getExtraConfig() + { + return extraConfig; + } + /* Read flux from a given cylinder and head. */ public abstract FluxSourceIterator readFlux(int cylinder, int head); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index d854780c..438bf614 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -7,6 +7,7 @@ java_test( srcs = ["FluxSourceTest.java"], deps = [ "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/fluxsource", diff --git a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java index 67eabefa..c2773033 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java @@ -34,6 +34,20 @@ public void createUnportedTypeThrows() assertThrows(FluxEngineException.class, () -> FluxSource.create(config)); } + @Test + public void createEraseFluxSource() + { + FluxSourceProto config = FluxSourceProto.newBuilder() + .setType(FluxSourceSinkType.FLUXTYPE_ERASE) + .build(); + + FluxSource source = FluxSource.create(config); + + assertThat(source).isInstanceOf(EraseFluxSource.class); + assertThat(source.readFlux(0, 0).next()).isNull(); + assertThat(source.getExtraConfig().getDrive().getTracks()).isEqualTo("c0-255h0-1"); + } + @Test public void trivialFluxSourceIteratorYieldsOneMap() { From f42da218317f234a99c5abca5a5391c898ef9612 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 7 Aug 2026 23:49:54 +0200 Subject: [PATCH 093/192] Add Fl2FluxSource. --- .../fluxengine/config/ConfigBuilder.java | 16 ++- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 2 + .../fluxsource/EraseFluxSource.java | 9 ++ .../fluxengine/fluxsource/Fl2FluxSource.java | 130 ++++++++++++++++++ .../fluxsource/Fl2FluxSourceIterator.java | 32 +++++ .../fluxengine/fluxsource/FluxSource.java | 41 +++--- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 19 +++ .../fluxsource/Fl2FluxSourceTest.java | 85 ++++++++++++ .../fluxengine/fluxsource/FluxSourceTest.java | 6 +- 9 files changed, 318 insertions(+), 22 deletions(-) create mode 100644 java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java create mode 100644 java/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceIterator.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index 18cd2034..b998966f 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -80,6 +80,12 @@ public ConfigBuilder loadConfigFile(String name) return this; } + public ConfigBuilder mergeConfig(ConfigProto other) + { + proto.mergeFrom(other); + return this; + } + public ConfigBuilder withFluxSource(String filename) { FluxSourceProto.Builder fluxSource = proto.getFluxSourceBuilder(); @@ -188,9 +194,9 @@ public ConfigBuilder withImageReader(String filename) private static ImageReaderWriterType imageType(String filename) { - if (filename.endsWith(".adf") || filename.endsWith(".d81") || filename.endsWith(".dsk") - || filename.endsWith(".img") || filename.endsWith(".st") || filename.endsWith(".vgi") - || filename.endsWith(".xdf")) + if (filename.endsWith(".adf") || filename.endsWith(".d81") || filename.endsWith(".dsk") || + filename.endsWith(".img") || filename.endsWith(".st") || + filename.endsWith(".vgi") || filename.endsWith(".xdf")) return IMAGETYPE_IMG; else if (filename.endsWith(".d64")) return IMAGETYPE_D64; @@ -218,8 +224,8 @@ else if (filename.endsWith(".td0")) private static boolean isReadOnlyImage(String filename) { - return filename.endsWith(".dim") || filename.endsWith(".fdi") || filename.endsWith(".jv3") - || filename.endsWith(".nfd") || filename.endsWith(".td0"); + return filename.endsWith(".dim") || filename.endsWith(".fdi") || + filename.endsWith(".jv3") || filename.endsWith(".nfd") || filename.endsWith(".td0"); } public ConfigBuilder showCurrentConfig() diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 9b8cb683..0b2c8674 100644 --- a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -26,5 +26,7 @@ java_library( "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "@com_google_protobuf//java/core", ], ) diff --git a/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java index 03197839..c5cbb5b1 100644 --- a/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.fluxsource; +import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.data.Fluxmap; @@ -9,6 +10,8 @@ */ public class EraseFluxSource extends TrivialFluxSource { + protected ConfigProto extraConfig; + public EraseFluxSource(EraseFluxSourceProto config) { ConfigProto.Builder builder = ConfigProto.newBuilder(); @@ -16,6 +19,12 @@ public EraseFluxSource(EraseFluxSourceProto config) extraConfig = builder.build(); } + @Override + public void adjustConfig(ConfigBuilder configBuilder) + { + configBuilder.mergeConfig(extraConfig); + } + @Override public Fluxmap readSingleFlux(int cylinder, int head) { diff --git a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java new file mode 100644 index 00000000..ff53e57d --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java @@ -0,0 +1,130 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.external.FluxFileProto; +import com.cowlark.fluxengine.external.FluxFileVersion; +import com.cowlark.fluxengine.external.TrackFluxProto; +import com.google.protobuf.ByteString; +import com.google.protobuf.InvalidProtocolBufferException; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * A flux source which reads an FL2 flux file, ported from + * lib/fluxsource/fl2fluxsource.cc. + */ +public class Fl2FluxSource extends FluxSource +{ + private final FluxFileProto proto; + protected ConfigProto extraConfig; + + public Fl2FluxSource(Fl2FluxSourceProto config) + { + proto = loadFl2File(config.getFilename()); + + ConfigProto.Builder builder = ConfigProto.newBuilder(); + builder.getDriveBuilder().setRotationalPeriodMs(proto.getRotationalPeriodMs()); + if (proto.hasDriveType()) + builder.getDriveBuilder().setDriveType(proto.getDriveType()); + + List tracks = new ArrayList<>(); + for (TrackFluxProto trackFlux : proto.getTrackList()) + tracks.add(String.format("c%dh%d", trackFlux.getTrack(), trackFlux.getHead())); + builder.getDriveBuilder().setTracks(String.join(" ", tracks)); + + extraConfig = builder.build(); + } + + @Override + public void adjustConfig(ConfigBuilder configBuilder) + { + configBuilder.mergeConfig(extraConfig); + } + + @Override + public FluxSourceIterator readFlux(int track, int head) + { + for (TrackFluxProto trackFlux : proto.getTrackList()) + { + if (trackFlux.getTrack() == track && trackFlux.getHead() == head) + return new Fl2FluxSourceIterator(trackFlux); + } + + return new EmptyFluxSourceIterator(); + } + + @Override + public void recalibrate() + { + } + + private static FluxFileProto loadFl2File(String filename) + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(filename))); + } catch (IOException e) + { + throw new FluxEngineException( + "cannot open input file '" + filename + "': " + e.getMessage()); + } + + if (data.size() >= 16 && + new String(data.slice(0, 16).toByteArray(), StandardCharsets.US_ASCII).equals( + "SQLite format 3")) + throw new FluxEngineException( + "this flux file is too old; please use the upgrade-flux-file tool to upgrade " + + "it"); + + FluxFileProto proto; + try + { + proto = FluxFileProto.parseFrom(data.toByteArray()); + } catch (InvalidProtocolBufferException e) + { + throw new FluxEngineException("unable to read input file '" + filename + "'"); + } + + return upgradeFluxFile(proto); + } + + private static FluxFileProto upgradeFluxFile(FluxFileProto proto) + { + if (proto.getVersion() == FluxFileVersion.VERSION_1) + { + /* Change a flux datastream with multiple segments separated by + * F_DESYNC into multiple flux segments. */ + FluxFileProto.Builder builder = proto.toBuilder(); + for (int i = 0; i < proto.getTrackCount(); i++) + { + TrackFluxProto track = proto.getTrack(i); + if (track.getFluxCount() != 0) + { + Fluxmap oldFlux = new Fluxmap(new Bytes(track.getFlux(0).toByteArray())); + TrackFluxProto.Builder trackBuilder = track.toBuilder(); + trackBuilder.clearFlux(); + for (Fluxmap flux : oldFlux.split()) + trackBuilder.addFlux(ByteString.copyFrom(flux.rawBytes().toByteArray())); + builder.setTrack(i, trackBuilder.build()); + } + } + builder.setVersion(FluxFileVersion.VERSION_2); + proto = builder.build(); + } + + if (proto.getVersion().getNumber() > FluxFileVersion.VERSION_2.getNumber()) + throw new FluxEngineException("this is a version " + proto.getVersion().getNumber() + + " flux file, but this build of the client can only handle up to version " + + FluxFileVersion.VERSION_2.getNumber() + " --- please upgrade"); + return proto; + } +} diff --git a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceIterator.java b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceIterator.java new file mode 100644 index 00000000..7b5ac6d3 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceIterator.java @@ -0,0 +1,32 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.external.TrackFluxProto; + +/** + * Iterator over the flux segments of one track in an FL2 file, ported from + * lib/fluxsource/fl2fluxsource.cc. + */ +public class Fl2FluxSourceIterator implements FluxSourceIterator +{ + private final TrackFluxProto proto; + private int count; + + public Fl2FluxSourceIterator(TrackFluxProto proto) + { + this.proto = proto; + } + + @Override + public boolean hasNext() + { + return count < proto.getFluxCount(); + } + + @Override + public Fluxmap next() + { + return new Fluxmap(new Bytes(proto.getFlux(count++).toByteArray())); + } +} diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index 641f3225..d98143f9 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.fluxsource; +import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.config.FluxSourceSinkType; import com.cowlark.fluxengine.core.FluxEngineException; @@ -9,31 +10,39 @@ */ public abstract class FluxSource { - protected ConfigProto extraConfig = ConfigProto.getDefaultInstance(); public static FluxSource create(FluxSourceProto config) { switch (config.getType()) { - case FLUXTYPE_DRIVE: return notImplemented("drive"); - case FLUXTYPE_ERASE: return new EraseFluxSource(config.getErase()); - case FLUXTYPE_KRYOFLUX: return notImplemented("kryoflux"); - case FLUXTYPE_TEST_PATTERN: return notImplemented("test pattern"); - case FLUXTYPE_SCP: return notImplemented("scp"); - case FLUXTYPE_A2R: return notImplemented("a2r"); - case FLUXTYPE_CWF: return notImplemented("cwf"); - case FLUXTYPE_DMK: return notImplemented("dmk"); - case FLUXTYPE_FLUX: return notImplemented("fl2"); - case FLUXTYPE_FLX: return notImplemented("flx"); - default: return null; + case FLUXTYPE_DRIVE: + return notImplemented("drive"); + case FLUXTYPE_ERASE: + return new EraseFluxSource(config.getErase()); + case FLUXTYPE_KRYOFLUX: + return notImplemented("kryoflux"); + case FLUXTYPE_TEST_PATTERN: + return notImplemented("test pattern"); + case FLUXTYPE_SCP: + return notImplemented("scp"); + case FLUXTYPE_A2R: + return notImplemented("a2r"); + case FLUXTYPE_CWF: + return notImplemented("cwf"); + case FLUXTYPE_DMK: + return notImplemented("dmk"); + case FLUXTYPE_FLUX: + return new Fl2FluxSource(config.getFl2()); + case FLUXTYPE_FLX: + return notImplemented("flx"); + default: + return null; } } - /* Returns any configuration this flux source might be carrying (e.g. tpi - * of the drive which made the capture). */ - public ConfigProto getExtraConfig() + /* Adjusts the current configuration based on the contents of this flux source. */ + public void adjustConfig(ConfigBuilder configBuilder) { - return extraConfig; } /* Read flux from a given cylinder and head. */ diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 438bf614..a7a77f33 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -6,12 +6,31 @@ java_test( name = "FluxSourceTest", srcs = ["FluxSourceTest.java"], deps = [ + "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "Fl2FluxSourceTest", + srcs = ["Fl2FluxSourceTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "@com_google_protobuf//java/core", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], diff --git a/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java new file mode 100644 index 00000000..d5f3f1a5 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java @@ -0,0 +1,85 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.external.FluxFileProto; +import com.cowlark.fluxengine.external.FluxFileVersion; +import com.cowlark.fluxengine.external.TrackFluxProto; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class Fl2FluxSourceTest +{ + @Test + public void readsTracks() throws IOException + { + TrackFluxProto track = TrackFluxProto.newBuilder() + .setTrack(0) + .setHead(0) + .addFlux(ByteString.copyFrom(new byte[]{(byte) 0xb0})) + .build(); + Path path = writeTemp(FluxFileProto.newBuilder() + .setVersion(FluxFileVersion.VERSION_2) + .addTrack(track) + .setRotationalPeriodMs(200.0) + .build()); + + Fl2FluxSource source = new Fl2FluxSource(Fl2FluxSourceProto.newBuilder() + .setFilename(path.toString()) + .build()); + + FluxSourceIterator iterator = source.readFlux(0, 0); + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next()).isNotNull(); + assertThat(iterator.hasNext()).isFalse(); + assertThat(source.readFlux(1, 0)).isInstanceOf(EmptyFluxSourceIterator.class); + + ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); + source.adjustConfig(configBuilder); + ConfigProto config = configBuilder.build(); + assertThat(config.getDrive().getTracks()).isEqualTo("c0h0"); + assertThat(config.getDrive().getRotationalPeriodMs()).isEqualTo(200.0); + } + + @Test + public void upgradesVersion1() throws IOException + { + /* A single flux segment containing a desync byte should be split into + * two segments. */ + TrackFluxProto track = TrackFluxProto.newBuilder() + .setTrack(0) + .setHead(0) + .addFlux(ByteString.copyFrom(new byte[]{(byte) 0xb0, 0x00, (byte) 0xb0})) + .build(); + Path path = writeTemp(FluxFileProto.newBuilder() + .setVersion(FluxFileVersion.VERSION_1) + .addTrack(track) + .build()); + + Fl2FluxSource source = new Fl2FluxSource(Fl2FluxSourceProto.newBuilder() + .setFilename(path.toString()) + .build()); + + FluxSourceIterator iterator = source.readFlux(0, 0); + assertThat(iterator.hasNext()).isTrue(); + iterator.next(); + assertThat(iterator.hasNext()).isTrue(); + iterator.next(); + assertThat(iterator.hasNext()).isFalse(); + } + + private static Path writeTemp(FluxFileProto file) throws IOException + { + Path path = Files.createTempFile("flux", ".fl2"); + Files.write(path, file.toByteArray()); + return path; + } +} diff --git a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java index c2773033..456177e8 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java @@ -4,6 +4,7 @@ import static org.junit.Assert.assertThrows; +import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.FluxSourceSinkType; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.data.Fluxmap; @@ -45,7 +46,10 @@ public void createEraseFluxSource() assertThat(source).isInstanceOf(EraseFluxSource.class); assertThat(source.readFlux(0, 0).next()).isNull(); - assertThat(source.getExtraConfig().getDrive().getTracks()).isEqualTo("c0-255h0-1"); + + ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); + source.adjustConfig(configBuilder); + assertThat(configBuilder.build().getDrive().getTracks()).isEqualTo("c0-255h0-1"); } @Test From fede5e977b462d0bd6ba05330a1182e84fbba4e9 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 00:04:50 +0200 Subject: [PATCH 094/192] Add Locations. --- .../cowlark/fluxengine/data/CylinderHead.java | 16 ++ .../cowlark/fluxengine/data/Locations.java | 164 ++++++++++++++++++ .../fluxengine/fluxsource/Fl2FluxSource.java | 8 +- .../com/cowlark/fluxengine/data/BUILD.bazel | 11 ++ .../fluxengine/data/LocationsTest.java | 61 +++++++ 5 files changed, 257 insertions(+), 3 deletions(-) create mode 100644 java/com/cowlark/fluxengine/data/CylinderHead.java create mode 100644 java/com/cowlark/fluxengine/data/Locations.java create mode 100644 javatests/com/cowlark/fluxengine/data/LocationsTest.java diff --git a/java/com/cowlark/fluxengine/data/CylinderHead.java b/java/com/cowlark/fluxengine/data/CylinderHead.java new file mode 100644 index 00000000..d9b559a7 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/CylinderHead.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.data; + +/** + * A cylinder/head location, ported from lib/data/locations.h. + */ +public record CylinderHead(int cylinder, int head) implements Comparable +{ + @Override + public int compareTo(CylinderHead other) + { + int result = Integer.compare(cylinder, other.cylinder); + if (result == 0) + result = Integer.compare(head, other.head); + return result; + } +} diff --git a/java/com/cowlark/fluxengine/data/Locations.java b/java/com/cowlark/fluxengine/data/Locations.java new file mode 100644 index 00000000..0de39ded --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Locations.java @@ -0,0 +1,164 @@ +package com.cowlark.fluxengine.data; + +import com.cowlark.fluxengine.core.FluxEngineException; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Parsing of cylinder/head location descriptor strings, ported from + * lib/data/locations.cc. + */ +public class Locations +{ + public static ImmutableList parseCylinderHeadsString(String s) + { + List result = new ArrayList<>(); + Parser parser = new Parser(s); + parser.skipSpaces(); + while (!parser.eof()) + { + result.addAll(parser.parseCh()); + parser.skipSpaces(); + } + + if (result.isEmpty()) + throw new FluxEngineException( + "track descriptor parse error: no locations specified"); + + Collections.sort(result); + return ImmutableList.copyOf(result); + } + + public static String convertCylinderHeadsToString(List chs) + { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (CylinderHead ch : chs) + { + if (!first) + sb.append(' '); + sb.append(String.format("c%dh%d", ch.cylinder(), ch.head())); + first = false; + } + return sb.toString(); + } + + private static final class Parser + { + private final String s; + private int pos; + + Parser(String s) + { + this.s = s; + } + + boolean eof() + { + return pos >= s.length(); + } + + void skipSpaces() + { + while (pos < s.length() && s.charAt(pos) == ' ') + pos++; + } + + List parseCh() + { + expect('c'); + List cylinders = parseMembers(); + expect('h'); + List heads = parseMembers(); + + List result = new ArrayList<>(); + for (int c : cylinders) + { + for (int h : heads) + result.add(new CylinderHead(c, h)); + } + return result; + } + + List parseMembers() + { + List result = new ArrayList<>(); + result.addAll(parseMember()); + while (peek() == ',') + { + pos++; + result.addAll(parseMember()); + } + return result; + } + + List parseMember() + { + int start = parseUnsigned(); + int end = start; + int step = 1; + if (peek() == '-') + { + pos++; + end = parseUnsigned(); + } + if (peek() == 'x') + { + pos++; + step = parseUnsigned(); + } + + if (start < 0) + throw error("range start " + start + " must be at least 0"); + if (end < start) + throw error("range end " + end + " must be at least the start"); + if (step < 1) + throw error("range step " + step + " must be at least one"); + + List result = new ArrayList<>(); + for (int i = start; i <= end; i += step) + result.add(i); + return result; + } + + int parseUnsigned() + { + int start = pos; + while (pos < s.length() && Character.isDigit(s.charAt(pos))) + pos++; + if (pos == start) + throw error("expected a number at '" + pos + "'"); + try + { + return Integer.parseInt(s.substring(start, pos)); + } + catch (NumberFormatException e) + { + throw error("number out of range at '" + start + "'"); + } + } + + char peek() + { + return pos < s.length() ? s.charAt(pos) : '\0'; + } + + void expect(char c) + { + if (eof() || s.charAt(pos) != c) + throw error("expected '" + c + "' at '" + pos + "'"); + pos++; + } + + FluxEngineException error(String message) + { + return new FluxEngineException("track descriptor parse error: " + message); + } + } + + private Locations() + { + } +} diff --git a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java index ff53e57d..8e311a58 100644 --- a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java @@ -4,7 +4,9 @@ import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Locations; import com.cowlark.fluxengine.external.FluxFileProto; import com.cowlark.fluxengine.external.FluxFileVersion; import com.cowlark.fluxengine.external.TrackFluxProto; @@ -35,10 +37,10 @@ public Fl2FluxSource(Fl2FluxSourceProto config) if (proto.hasDriveType()) builder.getDriveBuilder().setDriveType(proto.getDriveType()); - List tracks = new ArrayList<>(); + List chs = new ArrayList<>(); for (TrackFluxProto trackFlux : proto.getTrackList()) - tracks.add(String.format("c%dh%d", trackFlux.getTrack(), trackFlux.getHead())); - builder.getDriveBuilder().setTracks(String.join(" ", tracks)); + chs.add(new CylinderHead(trackFlux.getTrack(), trackFlux.getHead())); + builder.getDriveBuilder().setTracks(Locations.convertCylinderHeadsToString(chs)); extraConfig = builder.build(); } diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index f14cb8a5..c51af17c 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -26,3 +26,14 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "LocationsTest", + srcs = ["LocationsTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/data/LocationsTest.java b/javatests/com/cowlark/fluxengine/data/LocationsTest.java new file mode 100644 index 00000000..33b90838 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/LocationsTest.java @@ -0,0 +1,61 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.core.FluxEngineException; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class LocationsTest +{ + @Test + public void parseSingle() + { + assertThat(Locations.parseCylinderHeadsString("c0h0")) + .containsExactly(new CylinderHead(0, 0)); + } + + @Test + public void parseRangeAndStep() + { + assertThat(Locations.parseCylinderHeadsString("c0-2h0-2x2")) + .containsExactly( + new CylinderHead(0, 0), + new CylinderHead(0, 2), + new CylinderHead(1, 0), + new CylinderHead(1, 2), + new CylinderHead(2, 0), + new CylinderHead(2, 2)); + } + + @Test + public void parseMultipleGroups() + { + assertThat(Locations.parseCylinderHeadsString("c1h1 c0h0")) + .containsExactly(new CylinderHead(0, 0), new CylinderHead(1, 1)); + } + + @Test + public void convertRoundTrip() + { + List chs = List.of(new CylinderHead(0, 0), new CylinderHead(1, 2)); + + assertThat(Locations.convertCylinderHeadsToString(chs)).isEqualTo("c0h0 c1h2"); + } + + @Test + public void parseMalformedThrows() + { + assertThrows(FluxEngineException.class, + () -> Locations.parseCylinderHeadsString("c0")); + assertThrows(FluxEngineException.class, + () -> Locations.parseCylinderHeadsString("garbage")); + assertThrows(FluxEngineException.class, + () -> Locations.parseCylinderHeadsString("c0h2x0")); + } +} From d43b73e46504d60ec61b49217d60ec8a73b302e5 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 00:29:41 +0200 Subject: [PATCH 095/192] Port DiskLayout. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 2 + .../cowlark/fluxengine/cli/ReadCommand.java | 6 +- java/com/cowlark/fluxengine/data/BUILD.bazel | 3 + .../fluxengine/data/CylinderHeadSector.java | 19 + .../cowlark/fluxengine/data/DiskLayout.java | 433 ++++++++++++++++++ .../fluxengine/data/LogicalLocation.java | 18 + .../fluxengine/data/LogicalTrackLayout.java | 68 +++ .../fluxengine/data/PhysicalTrackLayout.java | 29 ++ .../cowlark/fluxengine/data/TrackInfo.java | 75 +++ .../fluxengine/fluxsource/FluxSource.java | 5 +- .../com/cowlark/fluxengine/data/BUILD.bazel | 15 + .../fluxengine/data/DiskLayoutTest.java | 183 ++++++++ 12 files changed, 853 insertions(+), 3 deletions(-) create mode 100644 java/com/cowlark/fluxengine/data/CylinderHeadSector.java create mode 100644 java/com/cowlark/fluxengine/data/DiskLayout.java create mode 100644 java/com/cowlark/fluxengine/data/LogicalLocation.java create mode 100644 java/com/cowlark/fluxengine/data/LogicalTrackLayout.java create mode 100644 java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java create mode 100644 java/com/cowlark/fluxengine/data/TrackInfo.java create mode 100644 javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 04c61473..f7276857 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -11,6 +11,8 @@ java_library( "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", ], diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index 00a450c3..e064bccc 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -8,6 +8,8 @@ import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.fluxsource.FluxSource; import com.google.common.collect.ImmutableList; /** @@ -56,8 +58,8 @@ public void run(ImmutableList args) throw new FluxEngineException("you cannot copy flux to a hardware device"); // Unsupported: - // var diskLayout = createDiskLayout(config); - // var fluxSource = FluxSource.create(config); + DiskLayout diskLayout = new DiskLayout(config); + FluxSource fluxSource = FluxSource.create(config); // var decoder = Arch.createDecoder(config); // var writer = ImageWriter.create(config); // readDiskCommand(diskLayout, fluxSource, decoder, writer); diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel index db331ea2..05235a84 100644 --- a/java/com/cowlark/fluxengine/data/BUILD.bazel +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -6,9 +6,12 @@ java_library( name = "data", srcs = glob(["*.java"]), deps = [ + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/config:layout_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", "//java/com/cowlark/fluxengine/external", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", "@maven//:com_google_guava_guava", ], ) diff --git a/java/com/cowlark/fluxengine/data/CylinderHeadSector.java b/java/com/cowlark/fluxengine/data/CylinderHeadSector.java new file mode 100644 index 00000000..a2603a0a --- /dev/null +++ b/java/com/cowlark/fluxengine/data/CylinderHeadSector.java @@ -0,0 +1,19 @@ +package com.cowlark.fluxengine.data; + +/** + * A cylinder/head/sector location, ported from lib/data/locations.h. + */ +public record CylinderHeadSector(int cylinder, int head, int sector) + implements Comparable +{ + @Override + public int compareTo(CylinderHeadSector other) + { + int result = Integer.compare(cylinder, other.cylinder); + if (result == 0) + result = Integer.compare(head, other.head); + if (result == 0) + result = Integer.compare(sector, other.sector); + return result; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/DiskLayout.java b/java/com/cowlark/fluxengine/data/DiskLayout.java new file mode 100644 index 00000000..fa12ff74 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/DiskLayout.java @@ -0,0 +1,433 @@ +package com.cowlark.fluxengine.data; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.LayoutProto; +import com.cowlark.fluxengine.config.SectorListProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.external.DriveType; +import com.cowlark.fluxengine.external.FormatType; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * The physical layout of the disk, ported from lib/data/layout.cc. + */ +public class DiskLayout +{ + public record LayoutBounds(int minCylinder, int maxCylinder, int minHead, int maxHead) + { + } + + /* Logical size. */ + public final int numLogicalCylinders; + public final int numLogicalHeads; + + /* Physical size and properties. */ + public final int minPhysicalCylinder; + public final int maxPhysicalCylinder; + public final int minPhysicalHead; + public final int maxPhysicalHead; + public final int groupSize; + public final int headBias; + public final int headWidth; + public final boolean swapSides; + public final long totalBytes; + + /* Physical and logical layouts by location. */ + public final ImmutableMap layoutByPhysicalLocation; + public final ImmutableMap layoutByLogicalLocation; + + /* Ordered lists of physical and logical locations. */ + public final ImmutableList logicalLocations; + public final ImmutableList logicalLocationsInFilesystemOrder; + public final ImmutableList physicalLocations; + + /* Ordered lists of sector locations, plus the reverse mapping. */ + public final ImmutableList logicalSectorLocationsInFilesystemOrder; + public final ImmutableMap blockIdByLogicalSectorLocation; + public final ImmutableList physicalSectorLocationsInFilesystemOrder; + + /* Mapping from logical location to sector offset and back again. */ + public final ImmutableMap logicalSectorLocationBySectorOffset; + public final ImmutableMap sectorOffsetByLogicalSectorLocation; + + public DiskLayout(ConfigProto config) + { + int minPhysicalCylinderLocal = Integer.MAX_VALUE; + int minPhysicalHeadLocal = Integer.MAX_VALUE; + int maxPhysicalCylinderLocal = 0; + int maxPhysicalHeadLocal = 0; + + numLogicalCylinders = config.getLayout().getTracks(); + numLogicalHeads = config.getLayout().getSides(); + + groupSize = getTrackStep(config); + headBias = config.getDrive().getHeadBias(); + swapSides = config.getLayout().getSwapSides(); + + switch (config.getDrive().getDriveType()) + { + case DRIVETYPE_APPLE2: + headWidth = 4; + break; + + default: + headWidth = 1; + break; + } + + Map logicalLayout = new LinkedHashMap<>(); + List logicalLocationsLocal = new ArrayList<>(); + + for (int logicalCylinder = 0; logicalCylinder < numLogicalCylinders; + logicalCylinder++) + for (int logicalHead = 0; logicalHead < numLogicalHeads; + logicalHead++) + { + int physicalCylinder = remapCylinderLogicalToPhysical(logicalCylinder); + int physicalHead = remapHeadLogicalToPhysical(logicalHead); + + minPhysicalCylinderLocal = Math.min(minPhysicalCylinderLocal, physicalCylinder); + maxPhysicalCylinderLocal = + Math.max(maxPhysicalCylinderLocal, physicalCylinder + groupSize - 1); + minPhysicalHeadLocal = Math.min(minPhysicalHeadLocal, physicalHead); + maxPhysicalHeadLocal = Math.max(maxPhysicalHeadLocal, physicalHead); + + LayoutProto.LayoutdataProto layoutdata = + getLayoutData(logicalCylinder, logicalHead, config); + int sectorSize = layoutdata.getSectorSize(); + List diskSectorOrder = expandSectorList(layoutdata.getPhysical()); + List naturalSectorOrder = new ArrayList<>(diskSectorOrder); + Collections.sort(naturalSectorOrder); + int numSectors = naturalSectorOrder.size(); + + List filesystemSectorOrder; + if (layoutdata.hasFilesystem()) + { + filesystemSectorOrder = expandSectorList(layoutdata.getFilesystem()); + if (filesystemSectorOrder.size() != numSectors) + throw new FluxEngineException( + "filesystem sector order list doesn't contain the right number of sectors"); + } + else + filesystemSectorOrder = new ArrayList<>(naturalSectorOrder); + + Map sectorIdToNaturalOrdering = new LinkedHashMap<>(); + Map sectorIdToFilesystemOrdering = new LinkedHashMap<>(); + for (int i = 0; i < numSectors; i++) + { + int fid = naturalSectorOrder.get(i); + sectorIdToNaturalOrdering.put(i, fid); + sectorIdToFilesystemOrdering.put(i, fid); + } + + LogicalTrackLayout ltl = new LogicalTrackLayout( + physicalCylinder, physicalHead, groupSize, + logicalCylinder, logicalHead, numSectors, sectorSize, + ImmutableList.copyOf(naturalSectorOrder), + ImmutableList.copyOf(diskSectorOrder), + ImmutableList.copyOf(filesystemSectorOrder), + ImmutableMap.copyOf(sectorIdToFilesystemOrdering), + ImmutableMap.copyOf(sectorIdToNaturalOrdering)); + logicalLayout.put(new CylinderHead(logicalCylinder, logicalHead), ltl); + logicalLocationsLocal.add(new CylinderHead(logicalCylinder, logicalHead)); + } + + minPhysicalCylinder = minPhysicalCylinderLocal; + maxPhysicalCylinder = maxPhysicalCylinderLocal; + minPhysicalHead = minPhysicalHeadLocal; + maxPhysicalHead = maxPhysicalHeadLocal; + + Map physicalLayout = new LinkedHashMap<>(); + List physicalLocationsLocal = new ArrayList<>(); + + for (int physicalCylinder = minPhysicalCylinder; + physicalCylinder <= maxPhysicalCylinder; physicalCylinder++) + for (int physicalHead = minPhysicalHead; + physicalHead <= maxPhysicalHead; physicalHead++) + { + CylinderHead ch = new CylinderHead(physicalCylinder, physicalHead); + PhysicalTrackLayout ptl = new PhysicalTrackLayout( + physicalCylinder, physicalHead, + (physicalCylinder - headBias) % groupSize, + logicalLayout.get(new CylinderHead( + remapCylinderPhysicalToLogical(physicalCylinder), + remapHeadPhysicalToLogical(physicalHead)))); + physicalLayout.put(ch, ptl); + physicalLocationsLocal.add(ch); + } + + layoutByLogicalLocation = ImmutableMap.copyOf(logicalLayout); + logicalLocations = ImmutableList.copyOf(logicalLocationsLocal); + layoutByPhysicalLocation = ImmutableMap.copyOf(physicalLayout); + physicalLocations = ImmutableList.copyOf(physicalLocationsLocal); + + long sectorOffset = 0; + int blockId = 0; + List logicalLocationsFilesystemLocal = new ArrayList<>(); + List logicalSectorLocationsLocal = new ArrayList<>(); + Map logicalSectorOffsetLocal = new LinkedHashMap<>(); + Map sectorOffsetByLocationLocal = new LinkedHashMap<>(); + Map blockIdByLocationLocal = new LinkedHashMap<>(); + + for (CylinderHead ch : getTrackOrdering( + config.getLayout().getFilesystemTrackOrder(), numLogicalCylinders, numLogicalHeads)) + { + LogicalTrackLayout ltl = logicalLayout.get(ch); + logicalLocationsFilesystemLocal.add(ch); + + for (int lid : ltl.filesystemSectorOrder) + { + LogicalLocation logicalLocation = + new LogicalLocation(ch.cylinder(), ch.head(), lid); + logicalSectorOffsetLocal.put(sectorOffset, logicalLocation); + sectorOffsetByLocationLocal.put(logicalLocation, sectorOffset); + logicalSectorLocationsLocal.add(logicalLocation); + sectorOffset += ltl.sectorSize; + + blockIdByLocationLocal.put(logicalLocation, blockId); + blockId++; + } + } + + logicalLocationsInFilesystemOrder = + ImmutableList.copyOf(logicalLocationsFilesystemLocal); + logicalSectorLocationsInFilesystemOrder = + ImmutableList.copyOf(logicalSectorLocationsLocal); + logicalSectorLocationBySectorOffset = + ImmutableMap.copyOf(logicalSectorOffsetLocal); + sectorOffsetByLogicalSectorLocation = + ImmutableMap.copyOf(sectorOffsetByLocationLocal); + blockIdByLogicalSectorLocation = ImmutableMap.copyOf(blockIdByLocationLocal); + physicalSectorLocationsInFilesystemOrder = ImmutableList.of(); + + totalBytes = sectorOffset; + } + + public DiskLayout(int numCylinders, int numHeads, int numSectors, int sectorSize) + { + this(createTestConfig(numCylinders, numHeads, numSectors, sectorSize)); + } + + public static DiskLayout createDiskLayout(ConfigProto config) + { + return new DiskLayout(config); + } + + public LayoutBounds getPhysicalBounds() + { + return getBounds(layoutByPhysicalLocation.keySet()); + } + + public LayoutBounds getLogicalBounds() + { + return getBounds(layoutByLogicalLocation.keySet()); + } + + public static LayoutBounds getBounds(Iterable keys) + { + int minCylinder = Integer.MAX_VALUE; + int maxCylinder = Integer.MIN_VALUE; + int minHead = Integer.MAX_VALUE; + int maxHead = Integer.MIN_VALUE; + + for (CylinderHead ch : keys) + { + minCylinder = Math.min(minCylinder, ch.cylinder()); + maxCylinder = Math.max(maxCylinder, ch.cylinder()); + minHead = Math.min(minHead, ch.head()); + maxHead = Math.max(maxHead, ch.head()); + } + + return new LayoutBounds(minCylinder, maxCylinder, minHead, maxHead); + } + + public int remapCylinderPhysicalToLogical(int physicalCylinder) + { + return (physicalCylinder - headBias) / groupSize; + } + + public int remapCylinderLogicalToPhysical(int logicalCylinder) + { + return headBias + logicalCylinder * groupSize; + } + + public int remapHeadPhysicalToLogical(int physicalHead) + { + return physicalHead ^ (swapSides ? 1 : 0); + } + + public int remapHeadLogicalToPhysical(int logicalHead) + { + return logicalHead ^ (swapSides ? 1 : 0); + } + + private static int getTrackStep(ConfigProto config) + { + FormatType formatType = config.getLayout().getFormatType(); + DriveType driveType = config.getDrive().getDriveType(); + + switch (formatType) + { + case FORMATTYPE_40TRACK: + switch (driveType) + { + case DRIVETYPE_40TRACK: + return 1; + + case DRIVETYPE_80TRACK: + return 2; + + case DRIVETYPE_APPLE2: + return 4; + + default: + break; + } + + /* Fall through, as in the C++. */ + + case FORMATTYPE_80TRACK: + switch (driveType) + { + case DRIVETYPE_40TRACK: + throw new FluxEngineException( + "you can't read/write an 80 track image from/to a 40 track drive"); + + case DRIVETYPE_80TRACK: + return 1; + + case DRIVETYPE_APPLE2: + throw new FluxEngineException( + "you can't read/write an 80 track image from/to an Apple II drive"); + + default: + break; + } + break; + + default: + break; + } + + return 1; + } + + private static List getTrackOrdering( + LayoutProto.Order ordering, int tracks, int sides) + { + List trackList = new ArrayList<>(); + switch (ordering) + { + case CHS: + for (int track = 0; track < tracks; track++) + for (int side = 0; side < sides; side++) + trackList.add(new CylinderHead(track, side)); + break; + + case HCS: + for (int side = 0; side < sides; side++) + for (int track = 0; track < tracks; track++) + trackList.add(new CylinderHead(track, side)); + break; + + case HCS_RH1: + for (int side = 0; side < sides; side++) + { + if (side == 0) + for (int track = 0; track < tracks; track++) + trackList.add(new CylinderHead(track, side)); + if (side == 1) + for (int track = tracks; track > 0; track--) + trackList.add(new CylinderHead(track - 1, side)); + } + break; + + default: + throw new FluxEngineException("LAYOUT: invalid track trackList"); + } + + return trackList; + } + + private static List expandSectorList(SectorListProto sectorsProto) + { + List sectors = new ArrayList<>(); + + if (sectorsProto.hasCount()) + { + if (sectorsProto.getSectorCount() != 0) + throw new FluxEngineException( + "LAYOUT: if you use a sector count, you can't use an explicit sector list"); + + Set sectorset = new HashSet<>(); + int id = sectorsProto.getStartSector(); + for (int i = 0; i < sectorsProto.getCount(); i++) + { + while (sectorset.contains(id)) + { + id++; + if (id >= (sectorsProto.getStartSector() + sectorsProto.getCount())) + id -= sectorsProto.getCount(); + } + + sectorset.add(id); + sectors.add(id); + + id += sectorsProto.getSkew(); + if (id >= (sectorsProto.getStartSector() + sectorsProto.getCount())) + id -= sectorsProto.getCount(); + } + } + else if (sectorsProto.getSectorCount() > 0) + { + for (int i = 0; i < sectorsProto.getSectorCount(); i++) + sectors.add(sectorsProto.getSector(i)); + } + else + throw new FluxEngineException("LAYOUT: no sectors in sector definition!"); + + return sectors; + } + + private static LayoutProto.LayoutdataProto getLayoutData( + int logicalCylinder, int logicalHead, ConfigProto config) + { + LayoutProto.LayoutdataProto.Builder layoutData = + LayoutProto.LayoutdataProto.newBuilder(); + for (LayoutProto.LayoutdataProto f : config.getLayout().getLayoutdataList()) + { + if (f.hasTrack() && f.hasUpToTrack() && + ((logicalCylinder < f.getTrack()) || (logicalCylinder > f.getUpToTrack()))) + continue; + if (f.hasTrack() && !f.hasUpToTrack() && (logicalCylinder != f.getTrack())) + continue; + if (f.hasSide() && (f.getSide() != logicalHead)) + continue; + + layoutData.mergeFrom(f); + } + return layoutData.build(); + } + + private static ConfigProto createTestConfig(int numCylinders, int numHeads, + int numSectors, int sectorSize) + { + ConfigProto.Builder config = ConfigProto.newBuilder(); + LayoutProto.Builder layout = config.getLayoutBuilder(); + layout.setTracks(numCylinders); + layout.setSides(numHeads); + LayoutProto.LayoutdataProto.Builder layoutData = layout.addLayoutdataBuilder(); + layoutData.setSectorSize(sectorSize); + layoutData.getPhysicalBuilder().setCount(numSectors); + + return config.build(); + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/LogicalLocation.java b/java/com/cowlark/fluxengine/data/LogicalLocation.java new file mode 100644 index 00000000..2275bbe1 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/LogicalLocation.java @@ -0,0 +1,18 @@ +package com.cowlark.fluxengine.data; + +/** + * A logical sector location, ported from lib/data/locations.h. + */ +public record LogicalLocation(int logicalCylinder, int logicalHead, int logicalSector) +{ + public CylinderHead trackLocation() + { + return new CylinderHead(logicalCylinder, logicalHead); + } + + @Override + public String toString() + { + return String.format("c%dh%ds%d", logicalCylinder, logicalHead, logicalSector); + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/LogicalTrackLayout.java b/java/com/cowlark/fluxengine/data/LogicalTrackLayout.java new file mode 100644 index 00000000..05c42688 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/LogicalTrackLayout.java @@ -0,0 +1,68 @@ +package com.cowlark.fluxengine.data; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * The layout of a single logical track, ported from lib/data/layout.h. + */ +public class LogicalTrackLayout +{ + /* Physical cylinder of the first element of the group. */ + public final int physicalCylinder; + + /* Physical head of the first element of the group. */ + public final int physicalHead; + + /* Size of this group. */ + public final int groupSize; + + /* Logical cylinder of this track. */ + public final int logicalCylinder; + + /* Logical side of this track. */ + public final int logicalHead; + + /* The number of sectors in this track. */ + public final int numSectors; + + /* Number of bytes in a sector. */ + public final int sectorSize; + + /* Sector IDs in sector ID order. */ + public final ImmutableList naturalSectorOrder; + + /* Sector IDs in disk order. */ + public final ImmutableList diskSectorOrder; + + /* Sector IDs in filesystem order. */ + public final ImmutableList filesystemSectorOrder; + + /* Mapping of sector ID to filesystem ordering. */ + public final ImmutableMap sectorIdToFilesystemOrdering; + + /* Mapping of sector ID to natural ordering. */ + public final ImmutableMap sectorIdToNaturalOrdering; + + public LogicalTrackLayout( + int physicalCylinder, int physicalHead, int groupSize, + int logicalCylinder, int logicalHead, int numSectors, int sectorSize, + ImmutableList naturalSectorOrder, ImmutableList diskSectorOrder, + ImmutableList filesystemSectorOrder, + ImmutableMap sectorIdToFilesystemOrdering, + ImmutableMap sectorIdToNaturalOrdering) + { + this.physicalCylinder = physicalCylinder; + this.physicalHead = physicalHead; + this.groupSize = groupSize; + this.logicalCylinder = logicalCylinder; + this.logicalHead = logicalHead; + this.numSectors = numSectors; + this.sectorSize = sectorSize; + this.naturalSectorOrder = naturalSectorOrder; + this.diskSectorOrder = diskSectorOrder; + this.filesystemSectorOrder = filesystemSectorOrder; + this.sectorIdToFilesystemOrdering = sectorIdToFilesystemOrdering; + this.sectorIdToNaturalOrdering = sectorIdToNaturalOrdering; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java b/java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java new file mode 100644 index 00000000..c574dd49 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java @@ -0,0 +1,29 @@ +package com.cowlark.fluxengine.data; + +/** + * The layout of a single physical track, ported from lib/data/layout.h. + */ +public class PhysicalTrackLayout +{ + /* Physical location of this track. */ + public final int physicalCylinder; + + /* Physical side of this track. */ + public final int physicalHead; + + /* Which member of the group this is. */ + public final int groupOffset; + + /* The logical track that this track is part of. */ + public final LogicalTrackLayout logicalTrackLayout; + + public PhysicalTrackLayout( + int physicalCylinder, int physicalHead, int groupOffset, + LogicalTrackLayout logicalTrackLayout) + { + this.physicalCylinder = physicalCylinder; + this.physicalHead = physicalHead; + this.groupOffset = groupOffset; + this.logicalTrackLayout = logicalTrackLayout; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/TrackInfo.java b/java/com/cowlark/fluxengine/data/TrackInfo.java new file mode 100644 index 00000000..c9f81119 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/TrackInfo.java @@ -0,0 +1,75 @@ +package com.cowlark.fluxengine.data; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +/** + * Summary information about a track, ported from lib/data/layout.h. + */ +public class TrackInfo +{ + public final int numCylinders; + public final int numHeads; + + /* The number of sectors in this track. */ + public final int numSectors; + + /* Physical location of this track. */ + public final int physicalCylinder; + + /* Physical side of this track. */ + public final int physicalHead; + + /* Logical location of this track. */ + public final int logicalCylinder; + + /* Logical side of this track. */ + public final int logicalHead; + + /* The number of physical tracks which need to be written for one logical + * track. */ + public final int groupSize; + + /* Number of bytes in a sector. */ + public final int sectorSize; + + /* Sector IDs in sector ID order. */ + public final ImmutableList naturalSectorOrder; + + /* Sector IDs in disk order. */ + public final ImmutableList diskSectorOrder; + + /* Sector IDs in filesystem order. */ + public final ImmutableList filesystemSectorOrder; + + /* Mapping of filesystem order to natural order. */ + public final ImmutableMap filesystemToNaturalSectorMap; + + /* Mapping of natural order to filesystem order. */ + public final ImmutableMap naturalToFilesystemSectorMap; + + public TrackInfo( + int numCylinders, int numHeads, int numSectors, + int physicalCylinder, int physicalHead, + int logicalCylinder, int logicalHead, int groupSize, int sectorSize, + ImmutableList naturalSectorOrder, ImmutableList diskSectorOrder, + ImmutableList filesystemSectorOrder, + ImmutableMap filesystemToNaturalSectorMap, + ImmutableMap naturalToFilesystemSectorMap) + { + this.numCylinders = numCylinders; + this.numHeads = numHeads; + this.numSectors = numSectors; + this.physicalCylinder = physicalCylinder; + this.physicalHead = physicalHead; + this.logicalCylinder = logicalCylinder; + this.logicalHead = logicalHead; + this.groupSize = groupSize; + this.sectorSize = sectorSize; + this.naturalSectorOrder = naturalSectorOrder; + this.diskSectorOrder = diskSectorOrder; + this.filesystemSectorOrder = filesystemSectorOrder; + this.filesystemToNaturalSectorMap = filesystemToNaturalSectorMap; + this.naturalToFilesystemSectorMap = naturalToFilesystemSectorMap; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index d98143f9..d3d83ff8 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -2,7 +2,6 @@ import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.config.FluxSourceSinkType; import com.cowlark.fluxengine.core.FluxEngineException; /** @@ -10,6 +9,10 @@ */ public abstract class FluxSource { + public static FluxSource create(ConfigProto config) + { + return create(config.getFluxSource()); + } public static FluxSource create(FluxSourceProto config) { diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index c51af17c..b07611bb 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -37,3 +37,18 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "DiskLayoutTest", + srcs = ["DiskLayoutTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/config:drive_java_proto", + "//java/com/cowlark/fluxengine/config:layout_java_proto", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java b/javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java new file mode 100644 index 00000000..721535dd --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java @@ -0,0 +1,183 @@ +package com.cowlark.fluxengine.data; + +import static com.cowlark.fluxengine.external.DriveType.DRIVETYPE_80TRACK; +import static com.cowlark.fluxengine.external.FormatType.FORMATTYPE_40TRACK; +import static com.cowlark.fluxengine.external.FormatType.FORMATTYPE_80TRACK; +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.DriveProto; +import com.cowlark.fluxengine.config.LayoutProto; +import com.google.common.collect.ImmutableMap; +import java.util.function.Consumer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class DiskLayoutTest +{ + @Test + public void testPhysicalSectors() + { + DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().addSector(0).addSector(2).addSector(1).addSector(3); + }); + + LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); + assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))) + .isSameInstanceAs(layout); + assertThat(layout.naturalSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); + assertThat(layout.diskSectorOrder).containsExactly(0, 2, 1, 3).inOrder(); + assertThat(layout.filesystemSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); + } + + @Test + public void testLogicalSectors() + { + DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().addSector(0).addSector(1).addSector(2).addSector(3); + track.getFilesystemBuilder().addSector(0).addSector(2).addSector(1).addSector(3); + }); + + LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); + assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))) + .isSameInstanceAs(layout); + assertThat(layout.naturalSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); + assertThat(layout.diskSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); + assertThat(layout.filesystemSectorOrder).containsExactly(0, 2, 1, 3).inOrder(); + } + + @Test + public void test_bothSectors() + { + DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().addSector(3).addSector(2).addSector(1).addSector(0); + track.getFilesystemBuilder().addSector(0).addSector(2).addSector(1).addSector(3); + }); + + LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); + assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))) + .isSameInstanceAs(layout); + assertThat(layout.naturalSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); + assertThat(layout.diskSectorOrder).containsExactly(3, 2, 1, 0).inOrder(); + assertThat(layout.filesystemSectorOrder).containsExactly(0, 2, 1, 3).inOrder(); + } + + @Test + public void test_skew() + { + DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().setStartSector(0).setCount(12).setSkew(6); + }); + + LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); + assertThat(layout.naturalSectorOrder) + .containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + .inOrder(); + assertThat(layout.diskSectorOrder) + .containsExactly(0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11) + .inOrder(); + } + + @Test + public void test_bounds() + { + ConfigProto.Builder config = baseConfig(FORMATTYPE_40TRACK); + config.getLayoutBuilder().setTracks(2).setSides(2); + addLayoutData(config).setSectorSize(256) + .getPhysicalBuilder().setStartSector(0).setCount(12).setSkew(6); + + DiskLayout diskLayout = new DiskLayout(config.build()); + assertThat(diskLayout.groupSize).isEqualTo(2); + assertThat(diskLayout.getLogicalBounds()) + .isEqualTo(new DiskLayout.LayoutBounds(0, 1, 0, 1)); + assertThat(diskLayout.getPhysicalBounds()) + .isEqualTo(new DiskLayout.LayoutBounds(0, 3, 0, 1)); + } + + @Test + public void test_sectoroffsets() + { + ConfigProto.Builder config = baseConfig(FORMATTYPE_80TRACK); + config.getLayoutBuilder().setTracks(2).setSides(2); + LayoutProto.LayoutdataProto.Builder layoutData = addLayoutData(config); + layoutData.setSectorSize(256); + layoutData.getPhysicalBuilder().setStartSector(0).setCount(4); + layoutData.getFilesystemBuilder().setStartSector(0).setCount(4).setSkew(2); + + DiskLayout diskLayout = new DiskLayout(config.build()); + assertThat(diskLayout.groupSize).isEqualTo(1); + assertThat(diskLayout.logicalSectorLocationBySectorOffset) + .isEqualTo(ImmutableMap.builder() + .put(0L, new LogicalLocation(0, 0, 0)) + .put(256L, new LogicalLocation(0, 0, 2)) + .put(512L, new LogicalLocation(0, 0, 1)) + .put(768L, new LogicalLocation(0, 0, 3)) + .put(1024L, new LogicalLocation(0, 1, 0)) + .put(1280L, new LogicalLocation(0, 1, 2)) + .put(1536L, new LogicalLocation(0, 1, 1)) + .put(1792L, new LogicalLocation(0, 1, 3)) + .put(2048L, new LogicalLocation(1, 0, 0)) + .put(2304L, new LogicalLocation(1, 0, 2)) + .put(2560L, new LogicalLocation(1, 0, 1)) + .put(2816L, new LogicalLocation(1, 0, 3)) + .put(3072L, new LogicalLocation(1, 1, 0)) + .put(3328L, new LogicalLocation(1, 1, 2)) + .put(3584L, new LogicalLocation(1, 1, 1)) + .put(3840L, new LogicalLocation(1, 1, 3)) + .build()); + assertThat(diskLayout.sectorOffsetByLogicalSectorLocation) + .isEqualTo(ImmutableMap.builder() + .put(new LogicalLocation(0, 0, 0), 0L) + .put(new LogicalLocation(0, 0, 1), 512L) + .put(new LogicalLocation(0, 0, 2), 256L) + .put(new LogicalLocation(0, 0, 3), 768L) + .put(new LogicalLocation(0, 1, 0), 1024L) + .put(new LogicalLocation(0, 1, 1), 1536L) + .put(new LogicalLocation(0, 1, 2), 1280L) + .put(new LogicalLocation(0, 1, 3), 1792L) + .put(new LogicalLocation(1, 0, 0), 2048L) + .put(new LogicalLocation(1, 0, 1), 2560L) + .put(new LogicalLocation(1, 0, 2), 2304L) + .put(new LogicalLocation(1, 0, 3), 2816L) + .put(new LogicalLocation(1, 1, 0), 3072L) + .put(new LogicalLocation(1, 1, 1), 3584L) + .put(new LogicalLocation(1, 1, 2), 3328L) + .put(new LogicalLocation(1, 1, 3), 3840L) + .build()); + } + + private static DiskLayout diskLayout( + com.cowlark.fluxengine.external.FormatType formatType, + Consumer layoutData) + { + ConfigProto.Builder config = baseConfig(formatType); + config.getLayoutBuilder().setTracks(78).setSides(2); + layoutData.accept(addLayoutData(config)); + return new DiskLayout(config.build()); + } + + private static LogicalTrackLayout logicalLayoutAt(DiskLayout diskLayout, int cylinder, int head) + { + return diskLayout.layoutByPhysicalLocation + .get(new CylinderHead(cylinder, head)).logicalTrackLayout; + } + + private static ConfigProto.Builder baseConfig( + com.cowlark.fluxengine.external.FormatType formatType) + { + return ConfigProto.newBuilder() + .setDrive(DriveProto.newBuilder().setDriveType(DRIVETYPE_80TRACK).build()) + .setLayout(LayoutProto.newBuilder().setFormatType(formatType).build()); + } + + private static LayoutProto.LayoutdataProto.Builder addLayoutData(ConfigProto.Builder config) + { + return config.getLayoutBuilder().addLayoutdataBuilder(); + } +} \ No newline at end of file From b813ce51d41e9451b5ac1b29d387ee1d683b3238 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 00:46:39 +0200 Subject: [PATCH 096/192] Make DiskLayout immutable. From f753bbd0ea3bc2c851172f65b9cb5f3802f69ee9 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 00:58:18 +0200 Subject: [PATCH 097/192] Port the Kryoflux libraries. --- .../com/cowlark/fluxengine/data/Kryoflux.java | 246 ++++++++++++++++++ .../com/cowlark/fluxengine/data/BUILD.bazel | 11 + .../cowlark/fluxengine/data/KryofluxTest.java | 73 ++++++ 3 files changed, 330 insertions(+) create mode 100644 java/com/cowlark/fluxengine/data/Kryoflux.java create mode 100644 javatests/com/cowlark/fluxengine/data/KryofluxTest.java diff --git a/java/com/cowlark/fluxengine/data/Kryoflux.java b/java/com/cowlark/fluxengine/data/Kryoflux.java new file mode 100644 index 00000000..25458d87 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Kryoflux.java @@ -0,0 +1,246 @@ +package com.cowlark.fluxengine.data; + +import static com.cowlark.fluxengine.external.FluxEngine.TICK_FREQUENCY; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.TreeSet; + +/** + * Reader for raw Kryoflux stream files, ported from lib/external/kryoflux.cc. + * This file lives in the data package rather than the external one because it + * constructs Fluxmap objects. + */ +public final class Kryoflux +{ + private static final double MCLK_HZ = ((18432000.0 * 73.0) / 14.0) / 2.0; + private static final double SCLK_HZ = MCLK_HZ / 2; + private static final double ICLK_HZ = MCLK_HZ / 16; + + private static final double TICKS_PER_SCLK = TICK_FREQUENCY / SCLK_HZ; + + private Kryoflux() + { + } + + public static Fluxmap readStream(String dir, int track, int side) + { + String suffix = String.format("%02d.%d.raw", track, side); + + File directory = new File(dir); + if (!directory.isDirectory()) + error("cannot access path '%s'", dir); + + String filename = null; + File[] files = directory.listFiles(); + if (files != null) + { + for (File file : files) + { + if (hasSuffix(file.getName(), suffix)) + { + if (filename != null) + error("data is ambiguous --- multiple files end in %s", suffix); + filename = dir + File.separator + file.getName(); + } + } + } + + if (filename == null) + error("failed to find track %d side %d in %s", track, side, dir); + + return readStream(filename); + } + + public static Fluxmap readStream(String filename) + { + try + { + return readStream(new Bytes(Files.readAllBytes(Path.of(filename)))); + } catch (IOException e) + { + throw new FluxEngineException( + String.format("cannot open input file '%s': %s", filename, e.getMessage())); + } + } + + public static Fluxmap readStream(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + + /* Pass 1: scan the stream looking for index marks. */ + + TreeSet indexmarks = new TreeSet<>(); + br.seek(0); + pass1: while (!br.eof()) + { + int b = br.read8(); + int len = 0; + switch (b) + { + case 0x0d: /* OOB block */ + { + int blocktype = br.read8(); + len = br.readLe16(); + if (br.eof()) + break pass1; + + if (blocktype == 0x02) + { + /* index data, sent asynchronously */ + int streampos = br.readLe32(); + indexmarks.add(streampos); + len -= 4; + } + break; + } + + default: + { + if ((b >= 0x00) && (b <= 0x07)) + len = 1; /* Flux2: double byte value */ + else if (b == 0x08) + len = 0; /* Nop1: do nothing */ + else if (b == 0x09) + len = 1; /* Nop2: skip one byte */ + else if (b == 0x0a) + len = 2; /* Nop3: skip two bytes */ + else if (b == 0x0b) + len = 0; /* Ovl16: the next block is 0x10000 sclks + * longer than normal. */ + else if (b == 0x0c) + len = 2; /* Flux3: triple byte value */ + else if ((b >= 0x0e) && (b <= 0xff)) + len = 0; /* Flux1: single byte value */ + else + error("unknown stream block byte 0x%01x at 0x%08x", b, + (long) br.pos() - 1); + } + } + br.skip(len); + } + + /* Pass 2: actually read the data. */ + + Fluxmap fluxmap = new Fluxmap(); + long extrasclks = 0; + int streamdelta = 0; + br.seek(0); + pass2: while (!br.eof()) + { + int b = br.read8(); + switch (b) + { + case 0x0d: /* OOB block */ + { + int blocktype = br.read8(); + int blocklen = br.readLe16(); + if (br.eof()) + break pass2; + + switch (blocktype) + { + case 0x01: /* streaminfo */ + { + int blockpos = br.pos() - 3; + streamdelta = blockpos - br.readLe32(); + blocklen -= 4; + break; + } + } + + br.skip(blocklen); + break; + } + + default: + { + if ((b >= 0x00) && (b <= 0x07)) + { + /* Flux2: double byte value */ + b = (b << 8) | br.read8(); + writeFlux(fluxmap, indexmarks, br, streamdelta, extrasclks + b); + extrasclks = 0; + } + else if (b == 0x08) + { + /* Nop1: do nothing */ + } + else if (b == 0x09) + { + /* Nop2: skip one byte */ + br.skip(1); + } + else if (b == 0x0a) + { + /* Nop3: skip two bytes */ + br.skip(2); + } + else if (b == 0x0b) + { + /* Ovl16: the next flux value is 0x10000 sclks longer + * than normal. */ + extrasclks += 0x10000; + } + else if (b == 0x0c) + { + /* Flux3: triple byte value */ + int ticks = br.readBe16(); /* yes, really big-endian */ + writeFlux(fluxmap, indexmarks, br, streamdelta, extrasclks + ticks); + extrasclks = 0; + } + else if ((b >= 0x0e) && (b <= 0xff)) + { + /* Flux1: single byte value */ + writeFlux(fluxmap, indexmarks, br, streamdelta, extrasclks + b); + extrasclks = 0; + } + else + error("unknown stream block byte 0x%02x at 0x%08x", b, + (long) br.pos() - 1); + } + } + } + + if (!br.eof()) + error("I/O error reading stream"); + return fluxmap; + } + + private static void writeFlux(Fluxmap fluxmap, TreeSet indexmarks, + ByteReader br, int streamdelta, long sclk) + { + if (!indexmarks.isEmpty()) + { + Integer nextindex = indexmarks.first(); + int nextindexpos = nextindex + streamdelta; + if (br.pos() >= nextindexpos) + { + fluxmap.appendIndex(); + indexmarks.remove(nextindex); + } + } + + int ticks = (int) ((double) sclk * TICKS_PER_SCLK); + fluxmap.appendInterval(ticks); + fluxmap.appendPulse(); + } + + private static boolean hasSuffix(String haystack, String needle) + { + if (needle.length() > haystack.length()) + return false; + + return haystack.substring(haystack.length() - needle.length()).equals(needle); + } + + private static void error(String format, Object... args) + { + throw new FluxEngineException(String.format(format, args)); + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index b07611bb..9db0ebe4 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -52,3 +52,14 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "KryofluxTest", + srcs = ["KryofluxTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/data/KryofluxTest.java b/javatests/com/cowlark/fluxengine/data/KryofluxTest.java new file mode 100644 index 00000000..31f3ac1a --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/KryofluxTest.java @@ -0,0 +1,73 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import java.util.Arrays; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class KryofluxTest +{ + @Test + public void test_stream_reader() + { + testConvert(Bytes.of(), Bytes.of()); + + /* Simple one-byte intervals */ + testConvert(Bytes.of(0x20, 0x20, 0x20, 0x20), Bytes.of(0x8f, 0x8f, 0x8f, 0x8f)); + + /* One-and-a-half-byte intervals */ + testConvert( + Bytes.of(0x20, 0x00, 0x10, 0x20, 0x01, 0x10, 0x20), + Bytes.of(0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f)); + + /* Two-byte intervals */ + testConvert( + Bytes.of(0x20, 0x0c, 0x00, 0x10, 0x20, 0x0c, 0x01, 0x10, 0x20), + Bytes.of(0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f)); + + /* Overflow */ + testConvert( + Bytes.of(0x20, 0x0b, 0x10, 0x20), + Bytes.of(0x8f).concat(unsignedBytes(0x207)).concat(Bytes.of(0xa9, 0x8f))); + + /* Single-byte nop */ + testConvert(Bytes.of(0x20, 0x08, 0x20), Bytes.of(0x8f, 0x8f)); + + /* Double-byte nop */ + testConvert(Bytes.of(0x20, 0x09, 0xde, 0x20), Bytes.of(0x8f, 0x8f)); + + /* Triple-byte nop */ + testConvert(Bytes.of(0x20, 0x0a, 0xde, 0xad, 0x20), Bytes.of(0x8f, 0x8f)); + + /* OOB block */ + testConvert( + Bytes.of( + 0x20, /* data before */ + 0x0d, /* OOB */ + 0xaa, /* type byte */ + 0x01, + 0x00, /* size of payload, little-endian */ + 0x55, /* payload */ + 0x20 /* data continues */ + ), + Bytes.of(0x8f, 0x8f)); + } + + private static void testConvert(Bytes kyrofluxBytes, Bytes expectedFluxmapBytes) + { + Fluxmap fluxmap = Kryoflux.readStream(kyrofluxBytes); + assertThat(fluxmap.rawBytes().toByteArray()) + .isEqualTo(expectedFluxmapBytes.toByteArray()); + } + + private static Bytes unsignedBytes(int count) + { + byte[] data = new byte[count]; + Arrays.fill(data, (byte) 0x3f); + return new Bytes(data); + } +} \ No newline at end of file From c38c158aa9f4fbbac09426e5087f79c9a101693a Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 01:05:37 +0200 Subject: [PATCH 098/192] Add the Kryoflux flux source. --- .../fluxengine/fluxsource/FluxSource.java | 2 +- .../fluxsource/KryofluxFluxSource.java | 64 +++++++++++++++++++ .../cowlark/fluxengine/fluxsource/BUILD.bazel | 16 +++++ .../fluxsource/KryofluxFluxSourceTest.java | 45 +++++++++++++ 4 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index d3d83ff8..63de9bc3 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -23,7 +23,7 @@ public static FluxSource create(FluxSourceProto config) case FLUXTYPE_ERASE: return new EraseFluxSource(config.getErase()); case FLUXTYPE_KRYOFLUX: - return notImplemented("kryoflux"); + return new KryofluxFluxSource(config.getKryoflux()); case FLUXTYPE_TEST_PATTERN: return notImplemented("test pattern"); case FLUXTYPE_SCP: diff --git a/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java new file mode 100644 index 00000000..5e13b3fc --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java @@ -0,0 +1,64 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Kryoflux; +import com.cowlark.fluxengine.data.Locations; +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A flux source which reads raw Kryoflux stream files, ported from + * lib/fluxsource/kryofluxfluxsource.cc. + */ +public class KryofluxFluxSource extends TrivialFluxSource +{ + private static final Pattern FILENAME_REGEX = Pattern.compile(".*[^0-9]([0-9]+)\\.([0-9]+)\\.raw"); + + private final String path; + protected ConfigProto extraConfig; + + public KryofluxFluxSource(KryofluxFluxSourceProto config) + { + path = config.getDirectory(); + + List chs = new ArrayList<>(); + File[] files = new File(path).listFiles(); + if (files != null) + { + for (File f : files) + { + Matcher m = FILENAME_REGEX.matcher(f.getName()); + if (m.matches()) + chs.add(new CylinderHead( + Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)))); + } + } + + ConfigProto.Builder builder = ConfigProto.newBuilder(); + builder.getDriveBuilder().setTracks(Locations.convertCylinderHeadsToString(chs)); + extraConfig = builder.build(); + } + + @Override + public void adjustConfig(ConfigBuilder configBuilder) + { + configBuilder.mergeConfig(extraConfig); + } + + @Override + public Fluxmap readSingleFlux(int cylinder, int head) + { + return Kryoflux.readStream(path, cylinder, head); + } + + @Override + public void recalibrate() + { + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index a7a77f33..cc1aec47 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -35,3 +35,19 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "KryofluxFluxSourceTest", + srcs = ["KryofluxFluxSourceTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java new file mode 100644 index 00000000..875820bf --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java @@ -0,0 +1,45 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.data.Fluxmap; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Collectors; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.junit.rules.TemporaryFolder; + +@RunWith(JUnit4.class) +public class KryofluxFluxSourceTest +{ + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void readsSingleFluxFromDirectory() throws Exception + { + Path dir = folder.getRoot().toPath(); + Files.write(dir.resolve("track80.0.raw"), new byte[] {0x20}); + Files.write(dir.resolve("track81.1.raw"), new byte[] {0x20}); + + KryofluxFluxSourceProto config = KryofluxFluxSourceProto.newBuilder() + .setDirectory(dir.toString()) + .build(); + KryofluxFluxSource source = new KryofluxFluxSource(config); + + assertThat(source.readSingleFlux(80, 0).rawBytes().toByteArray()) + .isEqualTo(new byte[]{(byte) 0x8f}); + + ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); + source.adjustConfig(configBuilder); + String tracks = configBuilder.build().getDrive().getTracks(); + String sorted = Arrays.stream(tracks.split(" ")).sorted() + .collect(Collectors.joining(" ")); + assertThat(sorted).isEqualTo("c80h0 c81h1"); + } +} \ No newline at end of file From 0358dfc151d6f59944ef9bdd201afa8c09b64268 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 01:15:39 +0200 Subject: [PATCH 099/192] Port A2RFluxSource. --- java/com/cowlark/fluxengine/external/A2R.java | 35 +++ .../fluxengine/fluxsource/A2RFluxSource.java | 166 ++++++++++++++ .../fluxsource/A2RFluxSourceIterator.java | 66 ++++++ .../cowlark/fluxengine/fluxsource/BUILD.bazel | 1 + .../fluxengine/fluxsource/FluxSource.java | 2 +- .../fluxsource/A2rFluxSourceTest.java | 100 +++++++++ .../cowlark/fluxengine/fluxsource/BUILD.bazel | 6 +- lib/external/a2r.h | 33 --- lib/fluxsource/a2rfluxsource.cc | 203 ------------------ 9 files changed, 372 insertions(+), 240 deletions(-) create mode 100644 java/com/cowlark/fluxengine/external/A2R.java create mode 100644 java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java create mode 100644 java/com/cowlark/fluxengine/fluxsource/A2RFluxSourceIterator.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsource/A2rFluxSourceTest.java delete mode 100644 lib/external/a2r.h delete mode 100644 lib/fluxsource/a2rfluxsource.cc diff --git a/java/com/cowlark/fluxengine/external/A2R.java b/java/com/cowlark/fluxengine/external/A2R.java new file mode 100644 index 00000000..e856d699 --- /dev/null +++ b/java/com/cowlark/fluxengine/external/A2R.java @@ -0,0 +1,35 @@ +package com.cowlark.fluxengine.external; + +/** + * A2R (AppleSauce) format definitions, ported from lib/external/a2r.h. + * + *

The canonical reference for the A2R format is: + * https://applesaucefdc.com/a2r2-reference/ All data is stored little-endian. + * + *

Note: The first chunk begins at byte offset 8, not 12 as given in the + * a2r2 reference version 2.0.1. + */ +public final class A2R +{ + public static final int CHUNK_INFO = 0x4F464E49; + public static final int CHUNK_STRM = 0x4D525453; + public static final int CHUNK_META = 0x4154454D; + + public static final int INFO_CHUNK_VERSION = 1; + + public static final int DISK_525 = 1; + public static final int DISK_35 = 2; + + public static final int TIMING = 1; + public static final int BITS = 2; + public static final int XTIMING = 3; + + public static final int NS_PER_TICK = 125; + + public static final byte[] FILEHEADER = + {'A', '2', 'R', '2', (byte) 0xff, (byte) 0x0a, (byte) 0x0d, (byte) 0x0a}; + + private A2R() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java new file mode 100644 index 00000000..b542748d --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java @@ -0,0 +1,166 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Locations; +import com.cowlark.fluxengine.external.DriveType; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeMap; + +/** + * A flux source which reads an A2R flux file, ported from + * lib/fluxsource/a2rfluxsource.cc. + */ +public class A2RFluxSource extends FluxSource +{ + private final TreeMap v2data = new TreeMap<>(); + private final A2rFluxSourceProto config; + private final Bytes data; + private int version; + protected ConfigProto extraConfig; + + static class A2Rv2Flux + { + List flux = new ArrayList<>(); + double index; + } + + public A2RFluxSource(A2rFluxSourceProto config) + { + this.config = config; + data = readFile(config.getFilename()); + ByteReader br = new ByteReader(data); + + switch (br.readBe32()) + { + case 0x41325232: + { + version = 2; + Bytes info = findChunk(new Bytes("INFO")); + int disktype = info.getByte(33) & 0xff; + DriveType driveType; + if (disktype == 1) + { + /* 5.25" with quarter stepping. */ + driveType = DriveType.DRIVETYPE_APPLE2; + } else + { + /* 3.5". */ + driveType = DriveType.DRIVETYPE_80TRACK; + } + + Bytes stream = findChunk(new Bytes("STRM")); + ByteReader bsr = new ByteReader(stream); + for (; ; ) + { + int location = bsr.read8(); + if (location == 0xff) + break; + CylinderHead key = (disktype == 1) ? + new CylinderHead(location, 0) : + new CylinderHead(location >> 1, location & 1); + + bsr.skip(1); + int len = bsr.readLe32(); + double index = (double) bsr.readLe32() * 125; + A2Rv2Flux entry = v2data.get(key); + if (entry == null) + { + entry = new A2Rv2Flux(); + entry.index = index; + v2data.put(key, entry); + } + + entry.flux.add(bsr.read(len)); + } + + List chs = new ArrayList<>(v2data.keySet()); + + ConfigProto.Builder builder = ConfigProto.newBuilder(); + builder.getDriveBuilder().setDriveType(driveType); + builder.getDriveBuilder().setTracks(Locations.convertCylinderHeadsToString(chs)); + extraConfig = builder.build(); + break; + } + + default: + error("unsupported A2R version"); + } + } + + @Override + public void adjustConfig(ConfigBuilder configBuilder) + { + configBuilder.mergeConfig(extraConfig); + } + + @Override + public FluxSourceIterator readFlux(int track, int head) + { + switch (version) + { + case 2: + { + A2Rv2Flux entry = v2data.get(new CylinderHead(track, head)); + if (entry != null) + return new A2RFluxSourceIterator(entry.flux, entry.index); + else + return new EmptyFluxSourceIterator(); + } + + default: + error("unsupported A2R version"); + return null; + } + } + + @Override + public void recalibrate() + { + } + + private Bytes findChunk(Bytes id) + { + long offset = 8; + while (offset < data.size()) + { + ByteReader br = new ByteReader(data); + br.seek((int) offset); + if (br.read(4).equals(id)) + { + int size = br.readLe32(); + return br.read(size); + } + + offset += (long) br.readLe32() + 8; + } + + error("A2R file missing chunk"); + return null; + } + + private static Bytes readFile(String filename) + { + try + { + return new Bytes(Files.readAllBytes(Path.of(filename))); + } catch (IOException e) + { + throw new FluxEngineException( + "cannot open input file '" + filename + "': " + e.getMessage()); + } + } + + private static void error(String message) + { + throw new FluxEngineException(message); + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/fluxsource/A2RFluxSourceIterator.java b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSourceIterator.java new file mode 100644 index 00000000..2d29a97b --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSourceIterator.java @@ -0,0 +1,66 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Fluxmap; +import java.util.List; + +/** + * Iterator over the flux revolutions of one track in an A2R file, ported from + * lib/fluxsource/a2rfluxsource.cc. + */ +class A2RFluxSourceIterator implements FluxSourceIterator +{ + private final List flux; + private final double index; + private int count; + + A2RFluxSourceIterator(List flux, double index) + { + this.flux = flux; + this.index = index; + } + + @Override + public boolean hasNext() + { + return count != flux.size(); + } + + @Override + public Fluxmap next() + { + double index = this.index; + Bytes asbytes = flux.get(count++); + ByteReader br = new ByteReader(asbytes); + + Fluxmap fluxmap = new Fluxmap(); + while (!br.eof()) + { + long aticks = 0; + for (; ; ) + { + int i = br.read8(); + aticks += i; + if (i != 0xff) + break; + } + + double interval = aticks * 125; + if ((index >= 0) && (index < interval)) + { + fluxmap.appendInterval((int) index); + fluxmap.appendIndex(); + interval -= index; + } + index -= interval; + + fluxmap.appendInterval((int) (interval / NS_PER_TICK)); + fluxmap.appendPulse(); + } + + return fluxmap; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 0b2c8674..26700508 100644 --- a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -26,6 +26,7 @@ java_library( "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external", "//java/com/cowlark/fluxengine/external:fl2_java_proto", "@com_google_protobuf//java/core", ], diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index 63de9bc3..13650d3b 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -29,7 +29,7 @@ public static FluxSource create(FluxSourceProto config) case FLUXTYPE_SCP: return notImplemented("scp"); case FLUXTYPE_A2R: - return notImplemented("a2r"); + return new A2RFluxSource(config.getA2R()); case FLUXTYPE_CWF: return notImplemented("cwf"); case FLUXTYPE_DMK: diff --git a/javatests/com/cowlark/fluxengine/fluxsource/A2rFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/A2rFluxSourceTest.java new file mode 100644 index 00000000..6258afd1 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsource/A2rFluxSourceTest.java @@ -0,0 +1,100 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.external.DriveType; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class A2rFluxSourceTest +{ + @Test + public void readsTracks() throws IOException + { + Path path = writeTempFile(); + + A2rFluxSource source = new A2rFluxSource(A2rFluxSourceProto.newBuilder() + .setFilename(path.toString()) + .build()); + + FluxSourceIterator iterator = source.readFlux(0, 0); + assertThat(iterator.hasNext()).isTrue(); + Bytes expected = Bytes.of(0x40, 0xad, 0xad, 0xad); + assertThat(iterator.next().rawBytes().toByteArray()) + .isEqualTo(expected.toByteArray()); + assertThat(iterator.hasNext()).isFalse(); + assertThat(source.readFlux(1, 0)).isInstanceOf(EmptyFluxSourceIterator.class); + + ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); + source.adjustConfig(configBuilder); + ConfigProto config = configBuilder.build(); + assertThat(config.getDrive().getTracks()).isEqualTo("c0h0"); + assertThat(config.getDrive().getDriveType()) + .isEqualTo(DriveType.DRIVETYPE_80TRACK); + } + + /* Builds an A2R file containing a single track 0/0, encoded as a 3.5" + * disk with two short intervals. */ + private static Path writeTempFile() throws IOException + { + Bytes result = new Bytes(256); + ByteWriter bw = new ByteWriter(result); + + for (int b : new int[]{'A', '2', 'R', '2', 0xff, 0x0a, 0x0d, 0x0a}) + bw.write8(b); + + // INFO chunk: version, 32-char padding, disktype (=2, 3.5"), ... + writeChunk(bw, "INFO"); + int sizePos = bw.pos(); + bw.writeLe32(0); + bw.write8(1); + for (int i = 0; i < 32; i++) + bw.write8('x'); + bw.write8(2); + bw.write8(1); + bw.write8(1); + int infoEnd = bw.pos(); + bw.seek(sizePos); + bw.writeLe32(infoEnd - sizePos - 4); + bw.seek(infoEnd); + + // STRM chunk: one record for track 0 head 0 with flux data 30,30 (pulses + // at 30 a2r ticks). The headed iterating sums bytes until non-0xff, + // so this encodes three intervals: 30, 30 and a trailing 255-less end. + writeChunk(bw, "STRM"); + int sizePos2 = bw.pos(); + bw.writeLe32(0); + bw.write8(0); // location: cylinder 0, head 0 + bw.write8(0); // unused byte + bw.writeLe32(3); // data length + bw.writeLe32(0); // index + bw.write8(30); + bw.write8(30); + bw.write8(30); + bw.write8(0xff); // stream terminator + int strmSize = bw.pos(); + bw.seek(sizePos2); + bw.writeLe32(strmSize - sizePos2 - 4); + bw.seek(strmSize); + + Bytes bytes = result.slice(0, strmSize); + Path path = Files.createTempFile("flux", ".a2r"); + Files.write(path, bytes.toByteArray()); + return path; + } + + private static void writeChunk(ByteWriter bw, String id) + { + for (int i = 0; i < 4; i++) + bw.write8(id.charAt(i)); + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index cc1aec47..161f667e 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -3,14 +3,14 @@ load("@rules_java//java:defs.bzl", "java_test") package(default_visibility = ["//visibility:public"]) java_test( - name = "FluxSourceTest", - srcs = ["FluxSourceTest.java"], + name = "A2rFluxSourceTest", + srcs = ["A2rFluxSourceTest.java"], deps = [ "//java/com/cowlark/fluxengine/config", - "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", "@com_google_protobuf//java/core", diff --git a/lib/external/a2r.h b/lib/external/a2r.h deleted file mode 100644 index 8730be09..00000000 --- a/lib/external/a2r.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef A2R_H -#define A2R_H - -// The canonical reference for the A2R format is: -// https://applesaucefdc.com/a2r2-reference/ All data is stored little-endian - -// Note: The first chunk begins at byte offset 8, not 12 as given in a2r2 -// reference version 2.0.1 - -#define A2R_CHUNK_INFO (0x4F464E49) -#define A2R_CHUNK_STRM (0x4D525453) -#define A2R_CHUNK_META (0x4154454D) - -#define A2R_INFO_CHUNK_VERSION (1) - -enum A2RDiskType -{ - A2R_DISK_525 = 1, - A2R_DISK_35 = 2, -}; - -enum A2RCaptureType -{ - A2R_TIMING = 1, - A2R_BITS = 2, - A2R_XTIMING = 3, -}; - -extern const uint8_t a2r2_fileheader[8]; - -#define A2R_NS_PER_TICK (125) - -#endif diff --git a/lib/fluxsource/a2rfluxsource.cc b/lib/fluxsource/a2rfluxsource.cc deleted file mode 100644 index 3f511709..00000000 --- a/lib/fluxsource/a2rfluxsource.cc +++ /dev/null @@ -1,203 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/data/fluxmap.h" -#include "lib/data/layout.h" -#include "lib/fluxsource/fluxsource.pb.h" -#include "lib/fluxsource/fluxsource.h" -#include "lib/config/proto.h" -#include "lib/data/locations.h" -#include "lib/core/logger.h" -#include -#include - -struct A2Rv2Flux -{ - std::vector flux; - nanoseconds_t index; -}; - -class A2rv2FluxSourceIterator : public FluxSourceIterator -{ -public: - A2rv2FluxSourceIterator(A2Rv2Flux& flux): _flux(flux) {} - - bool hasNext() const override - { - return _count != _flux.flux.size(); - } - - std::unique_ptr next() override - { - nanoseconds_t index = _flux.index; - Bytes& asbytes = _flux.flux[_count++]; - ByteReader br(asbytes); - - auto fluxmap = std::make_unique(); - while (!br.eof()) - { - unsigned aticks = 0; - for (;;) - { - unsigned i = br.read_8(); - aticks += i; - if (i != 0xff) - break; - } - - nanoseconds_t interval = aticks * 125; - if ((index >= 0) && (index < interval)) - { - fluxmap->appendInterval(index); - fluxmap->appendIndex(); - interval -= index; - } - index -= interval; - - fluxmap->appendInterval(interval / NS_PER_TICK); - fluxmap->appendPulse(); - } - - return fluxmap; - } - -private: - A2Rv2Flux& _flux; - int _count = 0; -}; - -class A2rFluxSource : public FluxSource -{ -public: - A2rFluxSource(const A2rFluxSourceProto& config): _config(config) - { - _data = Bytes::readFromFile(_config.filename()); - ByteReader br(_data); - - switch (br.read_be32()) - { - case 0x41325232: - { - _version = 2; - Bytes info = findChunk("INFO"); - int disktype = info[33]; - if (disktype == 1) - { - /* 5.25" with quarter stepping. */ - _extraConfig.mutable_drive()->set_drive_type( - DRIVETYPE_APPLE2); - } - else - { - /* 3.5". */ - _extraConfig.mutable_drive()->set_drive_type( - DRIVETYPE_80TRACK); - } - - Bytes stream = findChunk("STRM"); - ByteReader bsr(stream); - for (;;) - { - unsigned location = bsr.read_8(); - if (location == 0xff) - break; - auto key = (disktype == 1) - ? CylinderHead{location, 0} - : CylinderHead{location >> 1, location & 1}; - - bsr.skip(1); - uint32_t len = bsr.read_le32(); - nanoseconds_t index = (nanoseconds_t)bsr.read_le32() * 125; - auto it = _v2data.find(key); - if (it == _v2data.end()) - { - _v2data[key] = std::make_unique(); - it = _v2data.find(key); - it->second->index = index; - } - - it->second->flux.push_back(bsr.read(len)); - } - - auto keys = std::views::keys(_v2data); - std::vector chs{keys.begin(), keys.end()}; - unsigned minCylinder = std::ranges::min( - chs | std::views::transform(&CylinderHead::cylinder)); - unsigned maxCylinder = std::ranges::min( - chs | std::views::transform(&CylinderHead::cylinder)); - unsigned minHead = std::ranges::min( - chs | std::views::transform(&CylinderHead::head)); - unsigned maxHead = std::ranges::min( - chs | std::views::transform(&CylinderHead::head)); - log("A2R: reading A2R {} file with {} cylinders and {} head{}", - (disktype == 1) ? "Apple II" - : (disktype == 2) ? "normal" - : "unknown", - maxCylinder - minCylinder + 1, - maxHead - minHead + 1, - (maxHead == minHead) ? "" : "s"); - - _extraConfig.mutable_drive()->set_tracks( - convertCylinderHeadsToString(chs)); - break; - } - - default: - error("unsupported A2R version"); - } - } - -public: - std::unique_ptr readFlux(int track, int head) override - { - switch (_version) - { - case 2: - { - auto i = - _v2data.find(CylinderHead{(unsigned)track, (unsigned)head}); - if (i != _v2data.end()) - return std::make_unique( - *i->second); - else - return std::make_unique(); - } - - default: - error("unsupported A2R version"); - } - } - - void recalibrate() override {} - -private: - Bytes findChunk(Bytes id) - { - uint32_t offset = 8; - while (offset < _data.size()) - { - ByteReader br(_data); - br.seek(offset); - if (br.read(4) == id) - { - uint32_t size = br.read_le32(); - return br.read(size); - } - - offset += br.read_le32() + 8; - } - - error("A2R file missing chunk"); - } - -private: - const A2rFluxSourceProto& _config; - Bytes _data; - std::ifstream _if; - int _version; - std::map> _v2data; -}; - -std::unique_ptr FluxSource::createA2rFluxSource( - const A2rFluxSourceProto& config) -{ - return std::unique_ptr(new A2rFluxSource(config)); -} From e67beb329129481c121a5255e9e8a04747bafef1 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 01:29:44 +0200 Subject: [PATCH 100/192] Rename. --- .../{A2rFluxSourceTest.java => A2RFluxSourceTest.java} | 10 ++++------ .../com/cowlark/fluxengine/fluxsource/BUILD.bazel | 4 ++-- 2 files changed, 6 insertions(+), 8 deletions(-) rename javatests/com/cowlark/fluxengine/fluxsource/{A2rFluxSourceTest.java => A2RFluxSourceTest.java} (90%) diff --git a/javatests/com/cowlark/fluxengine/fluxsource/A2rFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java similarity index 90% rename from javatests/com/cowlark/fluxengine/fluxsource/A2rFluxSourceTest.java rename to javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java index 6258afd1..a2f5c291 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/A2rFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java @@ -15,22 +15,21 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) -public class A2rFluxSourceTest +public class A2RFluxSourceTest { @Test public void readsTracks() throws IOException { Path path = writeTempFile(); - A2rFluxSource source = new A2rFluxSource(A2rFluxSourceProto.newBuilder() + A2RFluxSource source = new A2RFluxSource(A2rFluxSourceProto.newBuilder() .setFilename(path.toString()) .build()); FluxSourceIterator iterator = source.readFlux(0, 0); assertThat(iterator.hasNext()).isTrue(); Bytes expected = Bytes.of(0x40, 0xad, 0xad, 0xad); - assertThat(iterator.next().rawBytes().toByteArray()) - .isEqualTo(expected.toByteArray()); + assertThat(iterator.next().rawBytes().toByteArray()).isEqualTo(expected.toByteArray()); assertThat(iterator.hasNext()).isFalse(); assertThat(source.readFlux(1, 0)).isInstanceOf(EmptyFluxSourceIterator.class); @@ -38,8 +37,7 @@ public void readsTracks() throws IOException source.adjustConfig(configBuilder); ConfigProto config = configBuilder.build(); assertThat(config.getDrive().getTracks()).isEqualTo("c0h0"); - assertThat(config.getDrive().getDriveType()) - .isEqualTo(DriveType.DRIVETYPE_80TRACK); + assertThat(config.getDrive().getDriveType()).isEqualTo(DriveType.DRIVETYPE_80TRACK); } /* Builds an A2R file containing a single track 0/0, encoded as a 3.5" diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 161f667e..52bbf3b2 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -3,8 +3,8 @@ load("@rules_java//java:defs.bzl", "java_test") package(default_visibility = ["//visibility:public"]) java_test( - name = "A2rFluxSourceTest", - srcs = ["A2rFluxSourceTest.java"], + name = "A2RFluxSourceTest", + srcs = ["A2RFluxSourceTest.java"], deps = [ "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", From 37476d1a7332966a302f66b47f67d4d88b237c16 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 01:30:53 +0200 Subject: [PATCH 101/192] Port FmMfm. --- .../cowlark/fluxengine/external/FmMfm.java | 139 ++++++++++++++++++ .../cowlark/fluxengine/external/BUILD.bazel | 11 ++ .../fluxengine/external/FmMfmTest.java | 88 +++++++++++ lib/decoders/fmmfm.cc | 122 --------------- 4 files changed, 238 insertions(+), 122 deletions(-) create mode 100644 java/com/cowlark/fluxengine/external/FmMfm.java create mode 100644 javatests/com/cowlark/fluxengine/external/FmMfmTest.java delete mode 100644 lib/decoders/fmmfm.cc diff --git a/java/com/cowlark/fluxengine/external/FmMfm.java b/java/com/cowlark/fluxengine/external/FmMfm.java new file mode 100644 index 00000000..5239902b --- /dev/null +++ b/java/com/cowlark/fluxengine/external/FmMfm.java @@ -0,0 +1,139 @@ +package com.cowlark.fluxengine.external; + +import com.cowlark.fluxengine.core.BitReader; +import com.cowlark.fluxengine.core.BitWriter; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; + +/** + * FM and MFM encode/decode helpers, ported from lib/decoders/fmmfm.cc. + * The {@code cursor} and {@code lastBit} parameters of the encoding functions + * are carried in single-element arrays to provide the in/out semantics of the + * C++ references. + */ +public final class FmMfm +{ + private FmMfm() + { + } + + /* + * FM is dumb as rocks, consisting on regular clock pulses with data pulses + * in the gaps. 0x00 is: + * + * X-X-X-X-X-X-X-X- + * + * 0xff is: + * + * XXXXXXXXXXXXXXXX + * + * So we just need to extract all the odd bits. + * + * MFM and M2FM are slightly more complicated, where the first bit of each + * pair can be either 0 or 1... but the second bit is always the data bit, + * and at this point we simply don't care what the first bit is, so + * decoding MFM uses just the same code! + */ + public static Bytes decodeFmMfm(Bits bits) + { + Bytes bytes = new Bytes(0); + ByteWriter bw = new ByteWriter(bytes); + + int bitcount = 0; + int fifo = 0; + int i = 0; + while (i < bits.size()) + { + i++; /* skip clock bit */ + if (i >= bits.size()) + break; + fifo = (fifo << 1) | (bits.getBit(i++) ? 1 : 0); + + bitcount++; + if (bitcount == 8) + { + bw.write8(fifo); + bitcount = 0; + } + } + + if (bitcount != 0) + { + fifo <<= 8 - bitcount; + bw.write8(fifo); + } + + return bytes; + } + + public static void encodeFm(Bits bits, int[] cursor, Bytes input) + { + if (bits.size() == 0) + return; + int len = bits.size() - 1; + + for (int i = 0; i < input.size(); i++) + { + int b = input.getByte(i) & 0xff; + for (int j = 0; j < 8; j++) + { + boolean bit = (b & 0x80) != 0; + b <<= 1; + + if (cursor[0] >= len) + return; + + bits.set(cursor[0]++, true); + bits.set(cursor[0]++, bit); + } + } + } + + public static void encodeMfm( + Bits bits, int[] cursor, Bytes data, boolean[] lastBit) + { + if (bits.size() == 0) + return; + int len = bits.size() - 1; + + for (int i = 0; i < data.size(); i++) + { + int b = data.getByte(i) & 0xff; + for (int j = 0; j < 8; j++) + { + boolean bit = (b & 0x80) != 0; + b <<= 1; + + if (cursor[0] >= len) + return; + + bits.set(cursor[0]++, !lastBit[0] && !bit); + bits.set(cursor[0]++, bit); + lastBit[0] = bit; + } + } + } + + public static Bytes encodeMfm(Bytes data, boolean[] lastBit) + { + ByteReader br = new ByteReader(data); + BitReader bitr = new BitReader(br); + Bytes out = new Bytes(0); + ByteWriter bw = new ByteWriter(out); + BitWriter bitw = new BitWriter(bw); + + while (bitr.hasNext()) + { + boolean bit = bitr.next(); + + bitw.push(!lastBit[0] && !bit); + bitw.push(bit); + lastBit[0] = bit; + } + + bitw.flush(); + return out; + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/external/BUILD.bazel b/javatests/com/cowlark/fluxengine/external/BUILD.bazel index 17250464..25d5866a 100644 --- a/javatests/com/cowlark/fluxengine/external/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/external/BUILD.bazel @@ -2,6 +2,17 @@ load("@rules_java//java:defs.bzl", "java_test") package(default_visibility = ["//visibility:public"]) +java_test( + name = "FmMfmTest", + srcs = ["FmMfmTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/external", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "GreaseweazleUtilsTest", srcs = ["GreaseweazleUtilsTest.java"], diff --git a/javatests/com/cowlark/fluxengine/external/FmMfmTest.java b/javatests/com/cowlark/fluxengine/external/FmMfmTest.java new file mode 100644 index 00000000..348a3038 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/external/FmMfmTest.java @@ -0,0 +1,88 @@ +package com.cowlark.fluxengine.external; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.external.FmMfm; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FmMfmTest +{ + @Test + public void decode() + { + assertThat(FmMfm.decodeFmMfm(bits(true, + false, true, false, true, false, true, false, + true, false, true, false, true, false, true, false))) + .isEqualTo(Bytes.of(0x00)); + + assertThat(FmMfm.decodeFmMfm(bits(true, + true, true, false, true, false, true, false, + true, false, true, false, true, false, true, true))) + .isEqualTo(Bytes.of(0x81)); + + assertThat(FmMfm.decodeFmMfm(bits(true, true, true, false))) + .isEqualTo(Bytes.of(0x80)); + } + + @Test + public void encodeMfm() + { + assertThat(wrapEncodeMfm(Bytes.of(0xa1))) + .isEqualTo(bits(false, true, false, false, false, true, false, + false, true, false, true, false, true, false, false, true)); + + assertThat(wrapEncodeMfm(Bytes.of(0xc2))) + .isEqualTo(bits(false, true, false, true, false, false, true, + false, true, false, true, false, false, true, false, + false)); + + assertThat(wrapEncodeMfm(Bytes.of(0xb0))) + .isEqualTo(bits(false, true, false, false, false, true, false, + true, false, false, true, false, true, false, true, + false)); + } + + @Test + public void encodeFm() + { + assertThat(wrapEncodeFm(Bytes.of(0x00))) + .isEqualTo(bits(true, false, true, false, true, false, true, + false, true, false, true, false, true, false, true, + false)); + + assertThat(wrapEncodeFm(Bytes.of(0x81))) + .isEqualTo(bits(true, true, true, false, true, false, true, + false, true, false, true, false, true, false, true, + true)); + } + + private static Bits wrapEncodeMfm(Bytes bytes) + { + Bits bits = new Bits(16); + int[] cursor = {0}; + boolean[] lastBit = {false}; + FmMfm.encodeMfm(bits, cursor, bytes, lastBit); + return bits; + } + + private static Bits wrapEncodeFm(Bytes bytes) + { + Bits bits = new Bits(16); + int[] cursor = {0}; + FmMfm.encodeFm(bits, cursor, bytes); + return bits; + } + + private static Bits bits(boolean... values) + { + Bits bits = new Bits(values.length); + for (int i = 0; i < values.length; i++) + bits.setBit(i, values[i]); + return bits; + } +} \ No newline at end of file diff --git a/lib/decoders/fmmfm.cc b/lib/decoders/fmmfm.cc deleted file mode 100644 index 6a75178c..00000000 --- a/lib/decoders/fmmfm.cc +++ /dev/null @@ -1,122 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/decoders/decoders.h" - -Bytes decodeFmMfm( - std::vector::const_iterator ii, std::vector::const_iterator end) -{ - /* - * FM is dumb as rocks, consisting on regular clock pulses with data pulses - * in the gaps. 0x00 is: - * - * X-X-X-X-X-X-X-X- - * - * 0xff is: - * - * XXXXXXXXXXXXXXXX - * - * So we just need to extract all the odd bits. - * - * MFM and M2FM are slightly more complicated, where the first bit of each - * pair can be either 0 or 1... but the second bit is always the data bit, - * and at this point we simply don't care what the first bit is, so - * decoding MFM uses just the same code! - */ - - Bytes bytes; - ByteWriter bw(bytes); - - int bitcount = 0; - uint8_t fifo = 0; - - while (ii != end) - { - ii++; /* skip clock bit */ - if (ii == end) - break; - fifo = (fifo << 1) | *ii++; - - bitcount++; - if (bitcount == 8) - { - bw.write_8(fifo); - bitcount = 0; - } - } - - if (bitcount != 0) - { - fifo <<= 8 - bitcount; - bw.write_8(fifo); - } - - return bytes; -} - -void encodeFm(std::vector& bits, unsigned& cursor, const Bytes& input) -{ - if (bits.size() == 0) - return; - unsigned len = bits.size() - 1; - - for (uint8_t b : input) - { - for (int i = 0; i < 8; i++) - { - bool bit = b & 0x80; - b <<= 1; - - if (cursor >= len) - return; - - bits[cursor++] = true; - bits[cursor++] = bit; - } - } -} - -void encodeMfm(std::vector& bits, - unsigned& cursor, - const Bytes& input, - bool& lastBit) -{ - if (bits.size() == 0) - return; - unsigned len = bits.size() - 1; - - for (uint8_t b : input) - { - for (int i = 0; i < 8; i++) - { - bool bit = b & 0x80; - b <<= 1; - - if (cursor >= len) - return; - - bits[cursor++] = !lastBit && !bit; - bits[cursor++] = bit; - lastBit = bit; - } - } -} - -Bytes encodeMfm(const Bytes& input, bool& lastBit) -{ - ByteReader br(input); - BitReader bitr(br); - Bytes b; - ByteWriter bw(b); - BitWriter bitw(bw); - - while (!bitr.eof()) - { - uint8_t bit = bitr.get(); - - bitw.push(!lastBit && !bit); - bitw.push(bit); - lastBit = bit; - } - - bitw.flush(); - return b; -} From 45fc1e137f27c63f82375789d4ac0b92a1b64da2 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 01:51:49 +0200 Subject: [PATCH 102/192] Port FluxDecoder. --- .../cowlark/fluxengine/decoders/BUILD.bazel | 12 ++ .../fluxengine/decoders/FluxDecoder.java | 146 ++++++++++++++++++ .../cowlark/fluxengine/decoders/BUILD.bazel | 18 +++ .../fluxengine/decoders/FluxDecoderTest.java | 88 +++++++++++ lib/decoders/fluxdecoder.cc | 118 -------------- lib/decoders/fluxdecoder.h | 41 ----- lib/decoders/rawbits.h | 46 ------ 7 files changed, 264 insertions(+), 205 deletions(-) create mode 100644 java/com/cowlark/fluxengine/decoders/FluxDecoder.java create mode 100644 javatests/com/cowlark/fluxengine/decoders/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java delete mode 100644 lib/decoders/fluxdecoder.cc delete mode 100644 lib/decoders/fluxdecoder.h delete mode 100644 lib/decoders/rawbits.h diff --git a/java/com/cowlark/fluxengine/decoders/BUILD.bazel b/java/com/cowlark/fluxengine/decoders/BUILD.bazel index 70f75b3c..9e26ed10 100644 --- a/java/com/cowlark/fluxengine/decoders/BUILD.bazel +++ b/java/com/cowlark/fluxengine/decoders/BUILD.bazel @@ -1,8 +1,20 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) +java_library( + name = "decoders", + srcs = glob(["*.java"]), + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external", + ":decoders_java_proto", + ], +) + proto_library( name = "decoders_proto", srcs = ["decoders.proto"], diff --git a/java/com/cowlark/fluxengine/decoders/FluxDecoder.java b/java/com/cowlark/fluxengine/decoders/FluxDecoder.java new file mode 100644 index 00000000..8d4cdc76 --- /dev/null +++ b/java/com/cowlark/fluxengine/decoders/FluxDecoder.java @@ -0,0 +1,146 @@ +package com.cowlark.fluxengine.decoders; + +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.data.FluxPosition; +import com.cowlark.fluxengine.data.FluxmapReader; +import java.time.Duration; + +/* This is a port of the samdisk code: + * + * https://github.com/simonowen/samdisk/blob/master/src/FluxDecoder.cpp + * + * I'm not actually terribly sure how it works, but it does, and much better + * than my code. + */ +public class FluxDecoder +{ + private final FluxmapReader fmr; + private final double pllPhase; + private final double pllAdjust; + private final double fluxScale; + private double clockNs; + private final double clockCentreNs; + private final double clockMinNs; + private final double clockMaxNs; + private double fluxNs = 0.0; + private int clockedZeroes = 0; + private int goodbits = 0; + private boolean index = false; + private boolean syncLost = false; + private int leadingZeroes; + + public FluxDecoder(FluxmapReader fmr, Duration bitcell, DecoderProto config) + { + this.fmr = fmr; + pllPhase = config.getPllPhase(); + pllAdjust = config.getPllAdjust(); + fluxScale = config.getFluxScale(); + double bitcellNs = bitcell.toNanos(); + clockNs = bitcellNs; + clockCentreNs = bitcellNs; + clockMinNs = bitcellNs * (1.0 - pllAdjust); + clockMaxNs = bitcellNs * (1.0 + pllAdjust); + leadingZeroes = fmr.tell().zeroes(); + } + + public boolean readBit() + { + if (leadingZeroes > 0) + { + leadingZeroes--; + return false; + } + else if (leadingZeroes == 0) + { + leadingZeroes--; + return true; + } + + while (!fmr.eof() && fluxNs < clockNs / 2.0) + { + fluxNs += nextFlux() * fluxScale; + clockedZeroes = 0; + } + + fluxNs -= clockNs; + if (fluxNs >= clockNs / 2.0) + { + clockedZeroes++; + goodbits++; + return false; + } + + /* PLL adjustment: change the clock frequency according to the phase + * mismatch */ + if (clockedZeroes <= 3) + { + /* In sync: adjust base clock */ + + clockNs += fluxNs * pllAdjust; + } + else + { + /* Out of sync: adjust the base clock back towards the centre */ + + clockNs += (clockCentreNs - clockNs) * pllAdjust; + + /* We require 256 good bits before reporting another sync loss + * event. */ + + if (goodbits >= 256) + syncLost = true; + goodbits = 0; + } + + /* Clamp the clock's adjustment range. */ + + clockNs = clampClock(clockMinNs, clockNs, clockMaxNs); + + /* I'm not sure what this does, but the original comment is: + * Authentic PLL: Do not snap the timing window to each flux + * transition */ + + fluxNs *= 1.0 - pllPhase; + + goodbits++; + return true; + } + + public Bits readBits(int count) + { + Bits result = new Bits(); + while (!fmr.eof() && count-- > 0) + result.add(readBit()); + return result; + } + + public Bits readBits(FluxPosition until) + { + Bits result = new Bits(); + while (!fmr.eof() && fmr.tell().bytes() < until.bytes()) + result.add(readBit()); + return result; + } + + public Bits readBits() + { + return readBits(Integer.MAX_VALUE); + } + + private double nextFlux() + { + long ticks = fmr.readInterval((long) (clockCentreNs / NS_PER_TICK)); + return ticks * NS_PER_TICK; + } + + private static double clampClock(double min, double value, double max) + { + if (value > max) + return max; + if (value < min) + return min; + return value; + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/decoders/BUILD.bazel b/javatests/com/cowlark/fluxengine/decoders/BUILD.bazel new file mode 100644 index 00000000..5ec663bf --- /dev/null +++ b/javatests/com/cowlark/fluxengine/decoders/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "FluxDecoderTest", + srcs = ["FluxDecoderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/external", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java new file mode 100644 index 00000000..9c70e27b --- /dev/null +++ b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java @@ -0,0 +1,88 @@ +package com.cowlark.fluxengine.decoders; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.external.FmMfm; +import java.time.Duration; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FluxDecoderTest +{ + private static final int CLOCK_TICKS = 1000; + private static final Duration CLOCK = + Duration.ofNanos((long) (CLOCK_TICKS * 1000000000.0 / 12000000.0)); + + private static Bytes roundTrip(Bytes data) + { + /* Encode the data as an MFM bitstream... */ + Bits encoded = FmMfm.encodeMfm(data, new boolean[1]).toBits(); + + /* ...write it out as flux... */ + Fluxmap map = new Fluxmap(); + map.appendBits(encoded, CLOCK_TICKS); + FluxmapReader reader = new FluxmapReader( + map, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder( + reader, CLOCK, DecoderProto.getDefaultInstance()); + + /* ...and read the raw bits back, skipping the PLL init pulse. */ + Bits decoded = new Bits(); + decoder.readBit(); + while (!reader.eof()) + decoded.add(decoder.readBit()); + + return FmMfm.decodeFmMfm(decoded); + } + + @Test + public void roundTripsMfmData() + { + Bytes data = Bytes.of(0x81, 0x00, 0xa1, 0x4e, 0x4e); + assertThat(roundTrip(data)).isEqualTo(data); + } + + @Test + public void emitsAClockForEveryFluxTransition() + { + /* A pulse at every cell boundary reads back as an unbroken run of + * trues. */ + Fluxmap map = new Fluxmap(); + map.appendBits(java.util.Arrays.asList( + true, true, true, true, true, true, true, true), CLOCK_TICKS); + FluxmapReader reader = new FluxmapReader( + map, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder( + reader, CLOCK, DecoderProto.getDefaultInstance()); + + Bits bits = new Bits(); + while (!reader.eof()) + bits.add(decoder.readBit()); + + assertThat(bits.size()).isEqualTo(9); + for (int i = 0; i < bits.size(); i++) + assertThat(bits.getBit(i)).isTrue(); + } + + @Test + public void firstBitIsAlwaysTrue() + { + /* The initial leading-zeroes state (tell().zeroes() == 0) makes the + * first readBit return true. */ + Fluxmap map = new Fluxmap(); + map.appendBits(java.util.Arrays.asList(true), CLOCK_TICKS); + FluxmapReader reader = new FluxmapReader( + map, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder( + reader, CLOCK, DecoderProto.getDefaultInstance()); + + assertThat(decoder.readBit()).isTrue(); + } +} \ No newline at end of file diff --git a/lib/decoders/fluxdecoder.cc b/lib/decoders/fluxdecoder.cc deleted file mode 100644 index ddc18dc3..00000000 --- a/lib/decoders/fluxdecoder.cc +++ /dev/null @@ -1,118 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/data/fluxmap.h" -#include "lib/data/fluxmapreader.h" -#include "lib/decoders/fluxdecoder.h" -#include "lib/decoders/decoders.pb.h" - -/* This is a port of the samdisk code: - * - * https://github.com/simonowen/samdisk/blob/master/src/FluxDecoder.cpp - * - * I'm not actually terribly sure how it works, but it does, and much better - * than my code. - */ - -FluxDecoder::FluxDecoder( - FluxmapReader* fmr, nanoseconds_t bitcell, const DecoderProto& config): - _fmr(fmr), - _pll_phase(config.pll_phase()), - _pll_adjust(config.pll_adjust()), - _flux_scale(config.flux_scale()), - _clock(bitcell), - _clock_centre(bitcell), - _clock_min(bitcell * (1.0 - _pll_adjust)), - _clock_max(bitcell * (1.0 + _pll_adjust)), - _flux(0), - _leading_zeroes(fmr->tell().zeroes) -{ -} - -bool FluxDecoder::readBit() -{ - if (_leading_zeroes > 0) - { - _leading_zeroes--; - return false; - } - else if (_leading_zeroes == 0) - { - _leading_zeroes--; - return true; - } - - while (!_fmr->eof() && (_flux < (_clock / 2))) - { - _flux += nextFlux() * _flux_scale; - ; - _clocked_zeroes = 0; - } - - _flux -= _clock; - if (_flux >= (_clock / 2)) - { - _clocked_zeroes++; - _goodbits++; - return false; - } - - /* PLL adjustment: change the clock frequency according to the phase - * mismatch */ - if (_clocked_zeroes <= 3) - { - /* In sync: adjust base clock */ - - _clock += _flux * _pll_adjust; - } - else - { - /* Out of sync: adjust the base clock back towards the centre */ - - _clock += (_clock_centre - _clock) * _pll_adjust; - - /* We require 256 good bits before reporting another sync loss event. */ - - if (_goodbits >= 256) - _sync_lost = true; - _goodbits = 0; - } - - /* Clamp the clock's adjustment range. */ - - _clock = std::min(std::max(_clock_min, _clock), _clock_max); - - /* I'm not sure what this does, but the original comment is: - * Authentic PLL: Do not snap the timing window to each flux transition - */ - - _flux = _flux * (1.0 - _pll_phase); - - _goodbits++; - return true; -} - -std::vector FluxDecoder::readBits(unsigned count) -{ - std::vector result; - while (!_fmr->eof() && count--) - { - bool b = readBit(); - result.push_back(b); - } - return result; -} - -std::vector FluxDecoder::readBits(const Fluxmap::Position& until) -{ - std::vector result; - while (!_fmr->eof() && (_fmr->tell().bytes < until.bytes)) - { - bool b = readBit(); - result.push_back(b); - } - return result; -} - -nanoseconds_t FluxDecoder::nextFlux() -{ - return _fmr->readInterval(_clock_centre) * NS_PER_TICK; -} diff --git a/lib/decoders/fluxdecoder.h b/lib/decoders/fluxdecoder.h deleted file mode 100644 index 539338f0..00000000 --- a/lib/decoders/fluxdecoder.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef FLUXDECODER_H -#define FLUXDECODER_H - -class FluxmapReader; - -class FluxDecoder -{ -public: - FluxDecoder( - FluxmapReader* fmr, nanoseconds_t bitcell, const DecoderProto& config); - - bool readBit(); - std::vector readBits(unsigned count); - std::vector readBits(const Fluxmap::Position& until); - - std::vector readBits() - { - return readBits(UINT_MAX); - } - -private: - nanoseconds_t nextFlux(); - -private: - FluxmapReader* _fmr; - double _pll_phase; - double _pll_adjust; - double _flux_scale; - nanoseconds_t _clock = 0; - nanoseconds_t _clock_centre; - nanoseconds_t _clock_min; - nanoseconds_t _clock_max; - nanoseconds_t _flux = 0; - unsigned _clocked_zeroes = 0; - unsigned _goodbits = 0; - bool _index = false; - bool _sync_lost = false; - int _leading_zeroes; -}; - -#endif diff --git a/lib/decoders/rawbits.h b/lib/decoders/rawbits.h deleted file mode 100644 index abb0a5c7..00000000 --- a/lib/decoders/rawbits.h +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef RAWBITS_H -#define RAWBITS_H - -class RawBits -{ -public: - RawBits(std::unique_ptr> bits, - std::unique_ptr> indices): - _bits(std::move(bits)), - _indices(std::move(indices)) - { - } - - typedef std::vector::const_iterator const_iterator; - - const_iterator begin() const - { - return _bits->begin(); - } - - const_iterator end() const - { - return _bits->end(); - } - - size_t size() const - { - return _bits->size(); - } - - const bool operator[](size_t pos) const - { - return _bits->at(pos); - } - - const std::vector indices() const - { - return *_indices; - } - -private: - std::unique_ptr> _bits; - std::unique_ptr> _indices; -}; - -#endif From fbd9e311568e00e1ac1c21654fc6f5cd55b82d1e Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 01:53:13 +0200 Subject: [PATCH 103/192] Format. --- .../fluxengine/config/ConfigBuilder.java | 72 +++--- java/com/cowlark/fluxengine/data/BUILD.bazel | 1 + .../fluxengine/data/CylinderHeadSector.java | 4 +- .../cowlark/fluxengine/data/DiskLayout.java | 178 +++++++------- .../fluxengine/data/FluxmapReader.java | 36 +-- .../com/cowlark/fluxengine/data/Kryoflux.java | 57 ++--- .../cowlark/fluxengine/data/Locations.java | 14 +- .../fluxengine/data/LogicalTrackLayout.java | 19 +- .../fluxengine/data/PhysicalTrackLayout.java | 7 +- .../cowlark/fluxengine/data/TrackInfo.java | 22 +- .../cowlark/fluxengine/decoders/BUILD.bazel | 2 +- .../fluxengine/decoders/FluxDecoder.java | 26 +-- .../cowlark/fluxengine/external/FmMfm.java | 5 +- .../fluxengine/fluxsource/A2RFluxSource.java | 42 ++-- .../fluxengine/fluxsource/Fl2FluxSource.java | 46 ++-- .../fluxengine/fluxsource/FluxSource.java | 10 +- .../fluxsource/KryofluxFluxSource.java | 6 +- .../fluxengine/usb/GreaseweazleUsbDevice.java | 4 +- .../com/cowlark/fluxengine/usb/UsbDevice.java | 6 +- .../fluxengine/config/ConfigBuilderTest.java | 60 ++--- .../fluxengine/config/ProtoPathTest.java | 28 +-- .../fluxengine/core/BitReaderTest.java | 49 ++-- .../fluxengine/core/BitWriterTest.java | 6 +- .../com/cowlark/fluxengine/core/BitsTest.java | 3 +- .../fluxengine/core/ByteReaderTest.java | 36 ++- .../fluxengine/core/ByteWriterTest.java | 99 +++++--- .../cowlark/fluxengine/core/BytesTest.java | 33 ++- .../fluxengine/core/flags/FlagsTest.java | 14 +- .../fluxengine/data/DiskLayoutTest.java | 219 ++++++++++-------- .../fluxengine/data/FluxmapReaderTest.java | 35 ++- .../cowlark/fluxengine/data/FluxmapTest.java | 2 +- .../cowlark/fluxengine/data/KryofluxTest.java | 58 +++-- .../fluxengine/data/LocationsTest.java | 27 ++- .../cowlark/fluxengine/decoders/BUILD.bazel | 2 +- .../fluxengine/decoders/FluxDecoderTest.java | 28 +-- .../fluxengine/external/FmMfmTest.java | 190 +++++++++++---- .../external/GreaseweazleUtilsTest.java | 40 ++-- .../fluxsource/A2RFluxSourceTest.java | 54 ++--- .../fluxsource/Fl2FluxSourceTest.java | 20 +- .../fluxengine/fluxsource/FluxSourceTest.java | 15 +- .../fluxsource/KryofluxFluxSourceTest.java | 31 ++- .../fluxengine/testing/TestHelpers.java | 2 - 42 files changed, 879 insertions(+), 729 deletions(-) diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index b998966f..ca7eec25 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -47,6 +47,42 @@ public ConfigBuilder() { } + private static ImageReaderWriterType imageType(String filename) + { + if (filename.endsWith(".adf") || filename.endsWith(".d81") || filename.endsWith(".dsk") || + filename.endsWith(".img") || filename.endsWith(".st") || + filename.endsWith(".vgi") || filename.endsWith(".xdf")) + return IMAGETYPE_IMG; + else if (filename.endsWith(".d64")) + return IMAGETYPE_D64; + else if (filename.endsWith(".d88")) + return IMAGETYPE_D88; + else if (filename.endsWith(".dim")) + return IMAGETYPE_DIM; + else if (filename.endsWith(".diskcopy")) + return IMAGETYPE_DISKCOPY; + else if (filename.endsWith(".fdi")) + return IMAGETYPE_FDI; + else if (filename.endsWith(".imd")) + return IMAGETYPE_IMD; + else if (filename.endsWith(".jv3")) + return IMAGETYPE_JV3; + else if (filename.endsWith(".nfd")) + return IMAGETYPE_NFD; + else if (filename.endsWith(".nsi")) + return IMAGETYPE_NSI; + else if (filename.endsWith(".td0")) + return IMAGETYPE_TD0; + else + return null; + } + + private static boolean isReadOnlyImage(String filename) + { + return filename.endsWith(".dim") || filename.endsWith(".fdi") || + filename.endsWith(".jv3") || filename.endsWith(".nfd") || filename.endsWith(".td0"); + } + public ConfigBuilder fromFlags(ImmutableList args, FlagGroup... group) { ImmutableList allGroups = ImmutableList.builder() @@ -192,42 +228,6 @@ public ConfigBuilder withImageReader(String filename) return this; } - private static ImageReaderWriterType imageType(String filename) - { - if (filename.endsWith(".adf") || filename.endsWith(".d81") || filename.endsWith(".dsk") || - filename.endsWith(".img") || filename.endsWith(".st") || - filename.endsWith(".vgi") || filename.endsWith(".xdf")) - return IMAGETYPE_IMG; - else if (filename.endsWith(".d64")) - return IMAGETYPE_D64; - else if (filename.endsWith(".d88")) - return IMAGETYPE_D88; - else if (filename.endsWith(".dim")) - return IMAGETYPE_DIM; - else if (filename.endsWith(".diskcopy")) - return IMAGETYPE_DISKCOPY; - else if (filename.endsWith(".fdi")) - return IMAGETYPE_FDI; - else if (filename.endsWith(".imd")) - return IMAGETYPE_IMD; - else if (filename.endsWith(".jv3")) - return IMAGETYPE_JV3; - else if (filename.endsWith(".nfd")) - return IMAGETYPE_NFD; - else if (filename.endsWith(".nsi")) - return IMAGETYPE_NSI; - else if (filename.endsWith(".td0")) - return IMAGETYPE_TD0; - else - return null; - } - - private static boolean isReadOnlyImage(String filename) - { - return filename.endsWith(".dim") || filename.endsWith(".fdi") || - filename.endsWith(".jv3") || filename.endsWith(".nfd") || filename.endsWith(".td0"); - } - public ConfigBuilder showCurrentConfig() { return this; diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel index 05235a84..e9f9f1d8 100644 --- a/java/com/cowlark/fluxengine/data/BUILD.bazel +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -12,6 +12,7 @@ java_library( "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", "//java/com/cowlark/fluxengine/external", "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "@com_google_protobuf//java/core", "@maven//:com_google_guava_guava", ], ) diff --git a/java/com/cowlark/fluxengine/data/CylinderHeadSector.java b/java/com/cowlark/fluxengine/data/CylinderHeadSector.java index a2603a0a..2402d1ef 100644 --- a/java/com/cowlark/fluxengine/data/CylinderHeadSector.java +++ b/java/com/cowlark/fluxengine/data/CylinderHeadSector.java @@ -3,8 +3,8 @@ /** * A cylinder/head/sector location, ported from lib/data/locations.h. */ -public record CylinderHeadSector(int cylinder, int head, int sector) - implements Comparable +public record CylinderHeadSector(int cylinder, int head, int sector) implements + Comparable { @Override public int compareTo(CylinderHeadSector other) diff --git a/java/com/cowlark/fluxengine/data/DiskLayout.java b/java/com/cowlark/fluxengine/data/DiskLayout.java index fa12ff74..728285e0 100644 --- a/java/com/cowlark/fluxengine/data/DiskLayout.java +++ b/java/com/cowlark/fluxengine/data/DiskLayout.java @@ -21,14 +21,9 @@ */ public class DiskLayout { - public record LayoutBounds(int minCylinder, int maxCylinder, int minHead, int maxHead) - { - } - /* Logical size. */ public final int numLogicalCylinders; public final int numLogicalHeads; - /* Physical size and properties. */ public final int minPhysicalCylinder; public final int maxPhysicalCylinder; @@ -39,21 +34,17 @@ public record LayoutBounds(int minCylinder, int maxCylinder, int minHead, int ma public final int headWidth; public final boolean swapSides; public final long totalBytes; - /* Physical and logical layouts by location. */ public final ImmutableMap layoutByPhysicalLocation; public final ImmutableMap layoutByLogicalLocation; - /* Ordered lists of physical and logical locations. */ public final ImmutableList logicalLocations; public final ImmutableList logicalLocationsInFilesystemOrder; public final ImmutableList physicalLocations; - /* Ordered lists of sector locations, plus the reverse mapping. */ public final ImmutableList logicalSectorLocationsInFilesystemOrder; public final ImmutableMap blockIdByLogicalSectorLocation; public final ImmutableList physicalSectorLocationsInFilesystemOrder; - /* Mapping from logical location to sector offset and back again. */ public final ImmutableMap logicalSectorLocationBySectorOffset; public final ImmutableMap sectorOffsetByLogicalSectorLocation; @@ -86,22 +77,20 @@ public DiskLayout(ConfigProto config) Map logicalLayout = new LinkedHashMap<>(); List logicalLocationsLocal = new ArrayList<>(); - for (int logicalCylinder = 0; logicalCylinder < numLogicalCylinders; - logicalCylinder++) - for (int logicalHead = 0; logicalHead < numLogicalHeads; - logicalHead++) + for (int logicalCylinder = 0; logicalCylinder < numLogicalCylinders; logicalCylinder++) + for (int logicalHead = 0; logicalHead < numLogicalHeads; logicalHead++) { int physicalCylinder = remapCylinderLogicalToPhysical(logicalCylinder); int physicalHead = remapHeadLogicalToPhysical(logicalHead); minPhysicalCylinderLocal = Math.min(minPhysicalCylinderLocal, physicalCylinder); maxPhysicalCylinderLocal = - Math.max(maxPhysicalCylinderLocal, physicalCylinder + groupSize - 1); + Math.max(maxPhysicalCylinderLocal, physicalCylinder + groupSize - 1); minPhysicalHeadLocal = Math.min(minPhysicalHeadLocal, physicalHead); maxPhysicalHeadLocal = Math.max(maxPhysicalHeadLocal, physicalHead); LayoutProto.LayoutdataProto layoutdata = - getLayoutData(logicalCylinder, logicalHead, config); + getLayoutData(logicalCylinder, logicalHead, config); int sectorSize = layoutdata.getSectorSize(); List diskSectorOrder = expandSectorList(layoutdata.getPhysical()); List naturalSectorOrder = new ArrayList<>(diskSectorOrder); @@ -114,9 +103,9 @@ public DiskLayout(ConfigProto config) filesystemSectorOrder = expandSectorList(layoutdata.getFilesystem()); if (filesystemSectorOrder.size() != numSectors) throw new FluxEngineException( - "filesystem sector order list doesn't contain the right number of sectors"); - } - else + "filesystem sector order list doesn't contain the right number of" + + " sectors"); + } else filesystemSectorOrder = new ArrayList<>(naturalSectorOrder); Map sectorIdToNaturalOrdering = new LinkedHashMap<>(); @@ -129,13 +118,18 @@ public DiskLayout(ConfigProto config) } LogicalTrackLayout ltl = new LogicalTrackLayout( - physicalCylinder, physicalHead, groupSize, - logicalCylinder, logicalHead, numSectors, sectorSize, - ImmutableList.copyOf(naturalSectorOrder), - ImmutableList.copyOf(diskSectorOrder), - ImmutableList.copyOf(filesystemSectorOrder), - ImmutableMap.copyOf(sectorIdToFilesystemOrdering), - ImmutableMap.copyOf(sectorIdToNaturalOrdering)); + physicalCylinder, + physicalHead, + groupSize, + logicalCylinder, + logicalHead, + numSectors, + sectorSize, + ImmutableList.copyOf(naturalSectorOrder), + ImmutableList.copyOf(diskSectorOrder), + ImmutableList.copyOf(filesystemSectorOrder), + ImmutableMap.copyOf(sectorIdToFilesystemOrdering), + ImmutableMap.copyOf(sectorIdToNaturalOrdering)); logicalLayout.put(new CylinderHead(logicalCylinder, logicalHead), ltl); logicalLocationsLocal.add(new CylinderHead(logicalCylinder, logicalHead)); } @@ -148,18 +142,19 @@ public DiskLayout(ConfigProto config) Map physicalLayout = new LinkedHashMap<>(); List physicalLocationsLocal = new ArrayList<>(); - for (int physicalCylinder = minPhysicalCylinder; - physicalCylinder <= maxPhysicalCylinder; physicalCylinder++) - for (int physicalHead = minPhysicalHead; - physicalHead <= maxPhysicalHead; physicalHead++) + for (int physicalCylinder = minPhysicalCylinder; physicalCylinder <= maxPhysicalCylinder; + physicalCylinder++) + for (int physicalHead = minPhysicalHead; physicalHead <= maxPhysicalHead; + physicalHead++) { CylinderHead ch = new CylinderHead(physicalCylinder, physicalHead); PhysicalTrackLayout ptl = new PhysicalTrackLayout( - physicalCylinder, physicalHead, - (physicalCylinder - headBias) % groupSize, - logicalLayout.get(new CylinderHead( - remapCylinderPhysicalToLogical(physicalCylinder), - remapHeadPhysicalToLogical(physicalHead)))); + physicalCylinder, + physicalHead, + (physicalCylinder - headBias) % groupSize, + logicalLayout.get(new CylinderHead( + remapCylinderPhysicalToLogical(physicalCylinder), + remapHeadPhysicalToLogical(physicalHead)))); physicalLayout.put(ch, ptl); physicalLocationsLocal.add(ch); } @@ -178,7 +173,9 @@ public DiskLayout(ConfigProto config) Map blockIdByLocationLocal = new LinkedHashMap<>(); for (CylinderHead ch : getTrackOrdering( - config.getLayout().getFilesystemTrackOrder(), numLogicalCylinders, numLogicalHeads)) + config.getLayout().getFilesystemTrackOrder(), + numLogicalCylinders, + numLogicalHeads)) { LogicalTrackLayout ltl = logicalLayout.get(ch); logicalLocationsFilesystemLocal.add(ch); @@ -186,7 +183,7 @@ public DiskLayout(ConfigProto config) for (int lid : ltl.filesystemSectorOrder) { LogicalLocation logicalLocation = - new LogicalLocation(ch.cylinder(), ch.head(), lid); + new LogicalLocation(ch.cylinder(), ch.head(), lid); logicalSectorOffsetLocal.put(sectorOffset, logicalLocation); sectorOffsetByLocationLocal.put(logicalLocation, sectorOffset); logicalSectorLocationsLocal.add(logicalLocation); @@ -197,14 +194,10 @@ public DiskLayout(ConfigProto config) } } - logicalLocationsInFilesystemOrder = - ImmutableList.copyOf(logicalLocationsFilesystemLocal); - logicalSectorLocationsInFilesystemOrder = - ImmutableList.copyOf(logicalSectorLocationsLocal); - logicalSectorLocationBySectorOffset = - ImmutableMap.copyOf(logicalSectorOffsetLocal); - sectorOffsetByLogicalSectorLocation = - ImmutableMap.copyOf(sectorOffsetByLocationLocal); + logicalLocationsInFilesystemOrder = ImmutableList.copyOf(logicalLocationsFilesystemLocal); + logicalSectorLocationsInFilesystemOrder = ImmutableList.copyOf(logicalSectorLocationsLocal); + logicalSectorLocationBySectorOffset = ImmutableMap.copyOf(logicalSectorOffsetLocal); + sectorOffsetByLogicalSectorLocation = ImmutableMap.copyOf(sectorOffsetByLocationLocal); blockIdByLogicalSectorLocation = ImmutableMap.copyOf(blockIdByLocationLocal); physicalSectorLocationsInFilesystemOrder = ImmutableList.of(); @@ -221,16 +214,6 @@ public static DiskLayout createDiskLayout(ConfigProto config) return new DiskLayout(config); } - public LayoutBounds getPhysicalBounds() - { - return getBounds(layoutByPhysicalLocation.keySet()); - } - - public LayoutBounds getLogicalBounds() - { - return getBounds(layoutByLogicalLocation.keySet()); - } - public static LayoutBounds getBounds(Iterable keys) { int minCylinder = Integer.MAX_VALUE; @@ -249,26 +232,6 @@ public static LayoutBounds getBounds(Iterable keys) return new LayoutBounds(minCylinder, maxCylinder, minHead, maxHead); } - public int remapCylinderPhysicalToLogical(int physicalCylinder) - { - return (physicalCylinder - headBias) / groupSize; - } - - public int remapCylinderLogicalToPhysical(int logicalCylinder) - { - return headBias + logicalCylinder * groupSize; - } - - public int remapHeadPhysicalToLogical(int physicalHead) - { - return physicalHead ^ (swapSides ? 1 : 0); - } - - public int remapHeadLogicalToPhysical(int logicalHead) - { - return logicalHead ^ (swapSides ? 1 : 0); - } - private static int getTrackStep(ConfigProto config) { FormatType formatType = config.getLayout().getFormatType(); @@ -299,14 +262,14 @@ private static int getTrackStep(ConfigProto config) { case DRIVETYPE_40TRACK: throw new FluxEngineException( - "you can't read/write an 80 track image from/to a 40 track drive"); + "you can't read/write an 80 track image from/to a 40 track drive"); case DRIVETYPE_80TRACK: return 1; case DRIVETYPE_APPLE2: throw new FluxEngineException( - "you can't read/write an 80 track image from/to an Apple II drive"); + "you can't read/write an 80 track image from/to an Apple II drive"); default: break; @@ -320,8 +283,9 @@ private static int getTrackStep(ConfigProto config) return 1; } - private static List getTrackOrdering( - LayoutProto.Order ordering, int tracks, int sides) + private static List getTrackOrdering(LayoutProto.Order ordering, + int tracks, + int sides) { List trackList = new ArrayList<>(); switch (ordering) @@ -365,7 +329,7 @@ private static List expandSectorList(SectorListProto sectorsProto) { if (sectorsProto.getSectorCount() != 0) throw new FluxEngineException( - "LAYOUT: if you use a sector count, you can't use an explicit sector list"); + "LAYOUT: if you use a sector count, you can't use an explicit sector list"); Set sectorset = new HashSet<>(); int id = sectorsProto.getStartSector(); @@ -385,27 +349,25 @@ private static List expandSectorList(SectorListProto sectorsProto) if (id >= (sectorsProto.getStartSector() + sectorsProto.getCount())) id -= sectorsProto.getCount(); } - } - else if (sectorsProto.getSectorCount() > 0) + } else if (sectorsProto.getSectorCount() > 0) { for (int i = 0; i < sectorsProto.getSectorCount(); i++) sectors.add(sectorsProto.getSector(i)); - } - else + } else throw new FluxEngineException("LAYOUT: no sectors in sector definition!"); return sectors; } - private static LayoutProto.LayoutdataProto getLayoutData( - int logicalCylinder, int logicalHead, ConfigProto config) + private static LayoutProto.LayoutdataProto getLayoutData(int logicalCylinder, + int logicalHead, + ConfigProto config) { - LayoutProto.LayoutdataProto.Builder layoutData = - LayoutProto.LayoutdataProto.newBuilder(); + LayoutProto.LayoutdataProto.Builder layoutData = LayoutProto.LayoutdataProto.newBuilder(); for (LayoutProto.LayoutdataProto f : config.getLayout().getLayoutdataList()) { if (f.hasTrack() && f.hasUpToTrack() && - ((logicalCylinder < f.getTrack()) || (logicalCylinder > f.getUpToTrack()))) + ((logicalCylinder < f.getTrack()) || (logicalCylinder > f.getUpToTrack()))) continue; if (f.hasTrack() && !f.hasUpToTrack() && (logicalCylinder != f.getTrack())) continue; @@ -417,8 +379,10 @@ private static LayoutProto.LayoutdataProto getLayoutData( return layoutData.build(); } - private static ConfigProto createTestConfig(int numCylinders, int numHeads, - int numSectors, int sectorSize) + private static ConfigProto createTestConfig(int numCylinders, + int numHeads, + int numSectors, + int sectorSize) { ConfigProto.Builder config = ConfigProto.newBuilder(); LayoutProto.Builder layout = config.getLayoutBuilder(); @@ -430,4 +394,38 @@ private static ConfigProto createTestConfig(int numCylinders, int numHeads, return config.build(); } + + public LayoutBounds getPhysicalBounds() + { + return getBounds(layoutByPhysicalLocation.keySet()); + } + + public LayoutBounds getLogicalBounds() + { + return getBounds(layoutByLogicalLocation.keySet()); + } + + public int remapCylinderPhysicalToLogical(int physicalCylinder) + { + return (physicalCylinder - headBias) / groupSize; + } + + public int remapCylinderLogicalToPhysical(int logicalCylinder) + { + return headBias + logicalCylinder * groupSize; + } + + public int remapHeadPhysicalToLogical(int physicalHead) + { + return physicalHead ^ (swapSides ? 1 : 0); + } + + public int remapHeadLogicalToPhysical(int logicalHead) + { + return logicalHead ^ (swapSides ? 1 : 0); + } + + public record LayoutBounds(int minCylinder, int maxCylinder, int minHead, int maxHead) + { + } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/FluxmapReader.java b/java/com/cowlark/fluxengine/data/FluxmapReader.java index a8c229f6..ec8d49e7 100644 --- a/java/com/cowlark/fluxengine/data/FluxmapReader.java +++ b/java/com/cowlark/fluxengine/data/FluxmapReader.java @@ -14,24 +14,6 @@ */ public class FluxmapReader { - public record Event(int event, long ticks) - { - } - - public record EventResult(boolean found, long ticks) - { - } - - public static class ClockData - { - public long medianTicks; - public int noiseFloor; - public int signalLevel; - public long peakStartTicks; - public long peakEndTicks; - public int[] buckets = new int[256]; - } - private final Fluxmap fluxmap; private final Bytes bytes; private final int size; @@ -237,4 +219,22 @@ public ClockData guessClock(double noiseFloorFactor, double signalLevelFactor) data.medianTicks = medianTicks; return data; } + + public record Event(int event, long ticks) + { + } + + public record EventResult(boolean found, long ticks) + { + } + + public static class ClockData + { + public long medianTicks; + public int noiseFloor; + public int signalLevel; + public long peakStartTicks; + public long peakEndTicks; + public int[] buckets = new int[256]; + } } diff --git a/java/com/cowlark/fluxengine/data/Kryoflux.java b/java/com/cowlark/fluxengine/data/Kryoflux.java index 25458d87..f44da538 100644 --- a/java/com/cowlark/fluxengine/data/Kryoflux.java +++ b/java/com/cowlark/fluxengine/data/Kryoflux.java @@ -20,9 +20,8 @@ public final class Kryoflux { private static final double MCLK_HZ = ((18432000.0 * 73.0) / 14.0) / 2.0; private static final double SCLK_HZ = MCLK_HZ / 2; - private static final double ICLK_HZ = MCLK_HZ / 16; - private static final double TICKS_PER_SCLK = TICK_FREQUENCY / SCLK_HZ; + private static final double ICLK_HZ = MCLK_HZ / 16; private Kryoflux() { @@ -64,8 +63,10 @@ public static Fluxmap readStream(String filename) return readStream(new Bytes(Files.readAllBytes(Path.of(filename)))); } catch (IOException e) { - throw new FluxEngineException( - String.format("cannot open input file '%s': %s", filename, e.getMessage())); + throw new FluxEngineException(String.format( + "cannot open input file '%s': %s", + filename, + e.getMessage())); } } @@ -77,7 +78,8 @@ public static Fluxmap readStream(Bytes bytes) TreeSet indexmarks = new TreeSet<>(); br.seek(0); - pass1: while (!br.eof()) + pass1: + while (!br.eof()) { int b = br.read8(); int len = 0; @@ -112,14 +114,16 @@ else if (b == 0x0a) len = 2; /* Nop3: skip two bytes */ else if (b == 0x0b) len = 0; /* Ovl16: the next block is 0x10000 sclks - * longer than normal. */ + * longer than normal. */ else if (b == 0x0c) len = 2; /* Flux3: triple byte value */ else if ((b >= 0x0e) && (b <= 0xff)) len = 0; /* Flux1: single byte value */ else - error("unknown stream block byte 0x%01x at 0x%08x", b, - (long) br.pos() - 1); + error( + "unknown stream block byte 0x%01x at 0x%08x", + b, + (long) br.pos() - 1); } } br.skip(len); @@ -131,7 +135,8 @@ else if ((b >= 0x0e) && (b <= 0xff)) long extrasclks = 0; int streamdelta = 0; br.seek(0); - pass2: while (!br.eof()) + pass2: + while (!br.eof()) { int b = br.read8(); switch (b) @@ -166,43 +171,38 @@ else if ((b >= 0x0e) && (b <= 0xff)) b = (b << 8) | br.read8(); writeFlux(fluxmap, indexmarks, br, streamdelta, extrasclks + b); extrasclks = 0; - } - else if (b == 0x08) + } else if (b == 0x08) { /* Nop1: do nothing */ - } - else if (b == 0x09) + } else if (b == 0x09) { /* Nop2: skip one byte */ br.skip(1); - } - else if (b == 0x0a) + } else if (b == 0x0a) { /* Nop3: skip two bytes */ br.skip(2); - } - else if (b == 0x0b) + } else if (b == 0x0b) { /* Ovl16: the next flux value is 0x10000 sclks longer * than normal. */ extrasclks += 0x10000; - } - else if (b == 0x0c) + } else if (b == 0x0c) { /* Flux3: triple byte value */ int ticks = br.readBe16(); /* yes, really big-endian */ writeFlux(fluxmap, indexmarks, br, streamdelta, extrasclks + ticks); extrasclks = 0; - } - else if ((b >= 0x0e) && (b <= 0xff)) + } else if ((b >= 0x0e) && (b <= 0xff)) { /* Flux1: single byte value */ writeFlux(fluxmap, indexmarks, br, streamdelta, extrasclks + b); extrasclks = 0; - } - else - error("unknown stream block byte 0x%02x at 0x%08x", b, - (long) br.pos() - 1); + } else + error( + "unknown stream block byte 0x%02x at 0x%08x", + b, + (long) br.pos() - 1); } } } @@ -212,8 +212,11 @@ else if ((b >= 0x0e) && (b <= 0xff)) return fluxmap; } - private static void writeFlux(Fluxmap fluxmap, TreeSet indexmarks, - ByteReader br, int streamdelta, long sclk) + private static void writeFlux(Fluxmap fluxmap, + TreeSet indexmarks, + ByteReader br, + int streamdelta, + long sclk) { if (!indexmarks.isEmpty()) { diff --git a/java/com/cowlark/fluxengine/data/Locations.java b/java/com/cowlark/fluxengine/data/Locations.java index 0de39ded..4a2a1b4b 100644 --- a/java/com/cowlark/fluxengine/data/Locations.java +++ b/java/com/cowlark/fluxengine/data/Locations.java @@ -12,6 +12,10 @@ */ public class Locations { + private Locations() + { + } + public static ImmutableList parseCylinderHeadsString(String s) { List result = new ArrayList<>(); @@ -24,8 +28,7 @@ public static ImmutableList parseCylinderHeadsString(String s) } if (result.isEmpty()) - throw new FluxEngineException( - "track descriptor parse error: no locations specified"); + throw new FluxEngineException("track descriptor parse error: no locations specified"); Collections.sort(result); return ImmutableList.copyOf(result); @@ -133,8 +136,7 @@ int parseUnsigned() try { return Integer.parseInt(s.substring(start, pos)); - } - catch (NumberFormatException e) + } catch (NumberFormatException e) { throw error("number out of range at '" + start + "'"); } @@ -157,8 +159,4 @@ FluxEngineException error(String message) return new FluxEngineException("track descriptor parse error: " + message); } } - - private Locations() - { - } } diff --git a/java/com/cowlark/fluxengine/data/LogicalTrackLayout.java b/java/com/cowlark/fluxengine/data/LogicalTrackLayout.java index 05c42688..9b5978b8 100644 --- a/java/com/cowlark/fluxengine/data/LogicalTrackLayout.java +++ b/java/com/cowlark/fluxengine/data/LogicalTrackLayout.java @@ -44,13 +44,18 @@ public class LogicalTrackLayout /* Mapping of sector ID to natural ordering. */ public final ImmutableMap sectorIdToNaturalOrdering; - public LogicalTrackLayout( - int physicalCylinder, int physicalHead, int groupSize, - int logicalCylinder, int logicalHead, int numSectors, int sectorSize, - ImmutableList naturalSectorOrder, ImmutableList diskSectorOrder, - ImmutableList filesystemSectorOrder, - ImmutableMap sectorIdToFilesystemOrdering, - ImmutableMap sectorIdToNaturalOrdering) + public LogicalTrackLayout(int physicalCylinder, + int physicalHead, + int groupSize, + int logicalCylinder, + int logicalHead, + int numSectors, + int sectorSize, + ImmutableList naturalSectorOrder, + ImmutableList diskSectorOrder, + ImmutableList filesystemSectorOrder, + ImmutableMap sectorIdToFilesystemOrdering, + ImmutableMap sectorIdToNaturalOrdering) { this.physicalCylinder = physicalCylinder; this.physicalHead = physicalHead; diff --git a/java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java b/java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java index c574dd49..975bd0ce 100644 --- a/java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java +++ b/java/com/cowlark/fluxengine/data/PhysicalTrackLayout.java @@ -17,9 +17,10 @@ public class PhysicalTrackLayout /* The logical track that this track is part of. */ public final LogicalTrackLayout logicalTrackLayout; - public PhysicalTrackLayout( - int physicalCylinder, int physicalHead, int groupOffset, - LogicalTrackLayout logicalTrackLayout) + public PhysicalTrackLayout(int physicalCylinder, + int physicalHead, + int groupOffset, + LogicalTrackLayout logicalTrackLayout) { this.physicalCylinder = physicalCylinder; this.physicalHead = physicalHead; diff --git a/java/com/cowlark/fluxengine/data/TrackInfo.java b/java/com/cowlark/fluxengine/data/TrackInfo.java index c9f81119..f2fd067d 100644 --- a/java/com/cowlark/fluxengine/data/TrackInfo.java +++ b/java/com/cowlark/fluxengine/data/TrackInfo.java @@ -48,14 +48,20 @@ public class TrackInfo /* Mapping of natural order to filesystem order. */ public final ImmutableMap naturalToFilesystemSectorMap; - public TrackInfo( - int numCylinders, int numHeads, int numSectors, - int physicalCylinder, int physicalHead, - int logicalCylinder, int logicalHead, int groupSize, int sectorSize, - ImmutableList naturalSectorOrder, ImmutableList diskSectorOrder, - ImmutableList filesystemSectorOrder, - ImmutableMap filesystemToNaturalSectorMap, - ImmutableMap naturalToFilesystemSectorMap) + public TrackInfo(int numCylinders, + int numHeads, + int numSectors, + int physicalCylinder, + int physicalHead, + int logicalCylinder, + int logicalHead, + int groupSize, + int sectorSize, + ImmutableList naturalSectorOrder, + ImmutableList diskSectorOrder, + ImmutableList filesystemSectorOrder, + ImmutableMap filesystemToNaturalSectorMap, + ImmutableMap naturalToFilesystemSectorMap) { this.numCylinders = numCylinders; this.numHeads = numHeads; diff --git a/java/com/cowlark/fluxengine/decoders/BUILD.bazel b/java/com/cowlark/fluxengine/decoders/BUILD.bazel index 9e26ed10..ca0b3255 100644 --- a/java/com/cowlark/fluxengine/decoders/BUILD.bazel +++ b/java/com/cowlark/fluxengine/decoders/BUILD.bazel @@ -8,10 +8,10 @@ java_library( name = "decoders", srcs = glob(["*.java"]), deps = [ + ":decoders_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/external", - ":decoders_java_proto", ], ) diff --git a/java/com/cowlark/fluxengine/decoders/FluxDecoder.java b/java/com/cowlark/fluxengine/decoders/FluxDecoder.java index 8d4cdc76..05741d95 100644 --- a/java/com/cowlark/fluxengine/decoders/FluxDecoder.java +++ b/java/com/cowlark/fluxengine/decoders/FluxDecoder.java @@ -20,10 +20,10 @@ public class FluxDecoder private final double pllPhase; private final double pllAdjust; private final double fluxScale; - private double clockNs; private final double clockCentreNs; private final double clockMinNs; private final double clockMaxNs; + private double clockNs; private double fluxNs = 0.0; private int clockedZeroes = 0; private int goodbits = 0; @@ -45,14 +45,22 @@ public FluxDecoder(FluxmapReader fmr, Duration bitcell, DecoderProto config) leadingZeroes = fmr.tell().zeroes(); } + private static double clampClock(double min, double value, double max) + { + if (value > max) + return max; + if (value < min) + return min; + return value; + } + public boolean readBit() { if (leadingZeroes > 0) { leadingZeroes--; return false; - } - else if (leadingZeroes == 0) + } else if (leadingZeroes == 0) { leadingZeroes--; return true; @@ -79,8 +87,7 @@ else if (leadingZeroes == 0) /* In sync: adjust base clock */ clockNs += fluxNs * pllAdjust; - } - else + } else { /* Out of sync: adjust the base clock back towards the centre */ @@ -134,13 +141,4 @@ private double nextFlux() long ticks = fmr.readInterval((long) (clockCentreNs / NS_PER_TICK)); return ticks * NS_PER_TICK; } - - private static double clampClock(double min, double value, double max) - { - if (value > max) - return max; - if (value < min) - return min; - return value; - } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/external/FmMfm.java b/java/com/cowlark/fluxengine/external/FmMfm.java index 5239902b..6cf34de0 100644 --- a/java/com/cowlark/fluxengine/external/FmMfm.java +++ b/java/com/cowlark/fluxengine/external/FmMfm.java @@ -3,9 +3,9 @@ import com.cowlark.fluxengine.core.BitReader; import com.cowlark.fluxengine.core.BitWriter; import com.cowlark.fluxengine.core.Bits; -import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.ByteReader; import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; /** * FM and MFM encode/decode helpers, ported from lib/decoders/fmmfm.cc. @@ -91,8 +91,7 @@ public static void encodeFm(Bits bits, int[] cursor, Bytes input) } } - public static void encodeMfm( - Bits bits, int[] cursor, Bytes data, boolean[] lastBit) + public static void encodeMfm(Bits bits, int[] cursor, Bytes data, boolean[] lastBit) { if (bits.size() == 0) return; diff --git a/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java index b542748d..877eedba 100644 --- a/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java @@ -24,14 +24,8 @@ public class A2RFluxSource extends FluxSource private final TreeMap v2data = new TreeMap<>(); private final A2rFluxSourceProto config; private final Bytes data; - private int version; protected ConfigProto extraConfig; - - static class A2Rv2Flux - { - List flux = new ArrayList<>(); - double index; - } + private int version; public A2RFluxSource(A2rFluxSourceProto config) { @@ -96,6 +90,23 @@ public A2RFluxSource(A2rFluxSourceProto config) } } + private static Bytes readFile(String filename) + { + try + { + return new Bytes(Files.readAllBytes(Path.of(filename))); + } catch (IOException e) + { + throw new FluxEngineException( + "cannot open input file '" + filename + "': " + e.getMessage()); + } + } + + private static void error(String message) + { + throw new FluxEngineException(message); + } + @Override public void adjustConfig(ConfigBuilder configBuilder) { @@ -147,20 +158,9 @@ private Bytes findChunk(Bytes id) return null; } - private static Bytes readFile(String filename) - { - try - { - return new Bytes(Files.readAllBytes(Path.of(filename))); - } catch (IOException e) - { - throw new FluxEngineException( - "cannot open input file '" + filename + "': " + e.getMessage()); - } - } - - private static void error(String message) + static class A2Rv2Flux { - throw new FluxEngineException(message); + List flux = new ArrayList<>(); + double index; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java index 8e311a58..15ef525b 100644 --- a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java @@ -45,29 +45,6 @@ public Fl2FluxSource(Fl2FluxSourceProto config) extraConfig = builder.build(); } - @Override - public void adjustConfig(ConfigBuilder configBuilder) - { - configBuilder.mergeConfig(extraConfig); - } - - @Override - public FluxSourceIterator readFlux(int track, int head) - { - for (TrackFluxProto trackFlux : proto.getTrackList()) - { - if (trackFlux.getTrack() == track && trackFlux.getHead() == head) - return new Fl2FluxSourceIterator(trackFlux); - } - - return new EmptyFluxSourceIterator(); - } - - @Override - public void recalibrate() - { - } - private static FluxFileProto loadFl2File(String filename) { Bytes data; @@ -129,4 +106,27 @@ private static FluxFileProto upgradeFluxFile(FluxFileProto proto) FluxFileVersion.VERSION_2.getNumber() + " --- please upgrade"); return proto; } + + @Override + public void adjustConfig(ConfigBuilder configBuilder) + { + configBuilder.mergeConfig(extraConfig); + } + + @Override + public FluxSourceIterator readFlux(int track, int head) + { + for (TrackFluxProto trackFlux : proto.getTrackList()) + { + if (trackFlux.getTrack() == track && trackFlux.getHead() == head) + return new Fl2FluxSourceIterator(trackFlux); + } + + return new EmptyFluxSourceIterator(); + } + + @Override + public void recalibrate() + { + } } diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index 13650d3b..102f479c 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -43,6 +43,11 @@ public static FluxSource create(FluxSourceProto config) } } + private static FluxSource notImplemented(String name) + { + throw new FluxEngineException(name + " flux source is not implemented yet"); + } + /* Adjusts the current configuration based on the contents of this flux source. */ public void adjustConfig(ConfigBuilder configBuilder) { @@ -68,9 +73,4 @@ public boolean isHardware() { return false; } - - private static FluxSource notImplemented(String name) - { - throw new FluxEngineException(name + " flux source is not implemented yet"); - } } diff --git a/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java index 5e13b3fc..d2636235 100644 --- a/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java @@ -18,7 +18,8 @@ */ public class KryofluxFluxSource extends TrivialFluxSource { - private static final Pattern FILENAME_REGEX = Pattern.compile(".*[^0-9]([0-9]+)\\.([0-9]+)\\.raw"); + private static final Pattern FILENAME_REGEX = + Pattern.compile(".*[^0-9]([0-9]+)\\.([0-9]+)\\.raw"); private final String path; protected ConfigProto extraConfig; @@ -36,7 +37,8 @@ public KryofluxFluxSource(KryofluxFluxSourceProto config) Matcher m = FILENAME_REGEX.matcher(f.getName()); if (m.matches()) chs.add(new CylinderHead( - Integer.parseInt(m.group(1)), Integer.parseInt(m.group(2)))); + Integer.parseInt(m.group(1)), + Integer.parseInt(m.group(2)))); } } diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index f37c8c97..7f173bd8 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -36,7 +36,6 @@ import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.external.GreaseweazleUtils; -import com.cowlark.fluxengine.usb.GreaseweazleProto; import com.fazecast.jSerialComm.SerialPort; import com.google.common.util.concurrent.Uninterruptibles; import java.time.Duration; @@ -380,8 +379,7 @@ public Bytes read(int side, boolean synced, Duration readTime, Duration hardSect ByteWriter bw = new ByteWriter(cmd); bw.write8(CMD_READ_FLUX); bw.write8(8); - bw.writeLe32( - (int) ((readTime.toNanos() + (synced ? revolutions : 0)) / clock)); + bw.writeLe32((int) ((readTime.toNanos() + (synced ? revolutions : 0)) / clock)); bw.writeLe16(0); doCommand(cmd); } diff --git a/java/com/cowlark/fluxengine/usb/UsbDevice.java b/java/com/cowlark/fluxengine/usb/UsbDevice.java index 5ee0cea4..281ab55f 100644 --- a/java/com/cowlark/fluxengine/usb/UsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/UsbDevice.java @@ -21,8 +21,10 @@ public void recalibrate() public abstract void testBulkRead(); - public abstract Bytes read(int side, boolean synced, Duration readTime, - Duration hardSectorThreshold); + public abstract Bytes read(int side, + boolean synced, + Duration readTime, + Duration hardSectorThreshold); public abstract void write(int side, Bytes bytes, Duration hardSectorThreshold); diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index ba1558c4..a0497b4b 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -1,17 +1,16 @@ package com.cowlark.fluxengine.config; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertThrows; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.google.common.collect.ImmutableList; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; @RunWith(JUnit4.class) public class ConfigBuilderTest @@ -43,8 +42,7 @@ public void loadConfigFileMergesAcrossFiles() throws IOException Files.writeString(first, "shortname: \"first\"\n"); Files.writeString(second, "tracks: \"c=0:2\"\n"); - ConfigProto proto = builder() - .loadConfigFile(first.toString()) + ConfigProto proto = builder().loadConfigFile(first.toString()) .loadConfigFile(second.toString()) .build(); @@ -55,8 +53,9 @@ public void loadConfigFileMergesAcrossFiles() throws IOException @Test public void loadConfigFileMissingFileThrows() { - assertThrows(ConfigException.class, - () -> new ConfigBuilder().loadConfigFile("/nonexistent/config")); + assertThrows( + ConfigException.class, + () -> new ConfigBuilder().loadConfigFile("/nonexistent/config")); } @Test @@ -65,8 +64,9 @@ public void loadConfigFileBadTextprotoThrows() throws IOException Path file = Files.createTempFile("config", ".textproto"); Files.writeString(file, "this is not a valid textproto\n"); - assertThrows(ConfigException.class, - () -> new ConfigBuilder().loadConfigFile(file.toString())); + assertThrows( + ConfigException.class, + () -> new ConfigBuilder().loadConfigFile(file.toString())); } @Test @@ -75,10 +75,8 @@ public void setMergesWithLoadedConfig() throws IOException Path file = Files.createTempFile("config", ".textproto"); Files.writeString(file, "shortname: \"myconfig\"\n"); - ConfigProto proto = builder() - .loadConfigFile(file.toString()) - .set("tracks", "c=0:2") - .build(); + ConfigProto proto = + builder().loadConfigFile(file.toString()).set("tracks", "c=0:2").build(); assertThat(proto.getShortname()).isEqualTo("myconfig"); assertThat(proto.getTracks()).isEqualTo("c=0:2"); @@ -87,9 +85,8 @@ public void setMergesWithLoadedConfig() throws IOException @Test public void fromFlagsSetsDottedConfig() { - ConfigProto proto = builder() - .fromFlags(ImmutableList.of("--drive.drive=1"), new FlagGroup()) - .build(); + ConfigProto proto = + builder().fromFlags(ImmutableList.of("--drive.drive=1"), new FlagGroup()).build(); assertThat(proto.getDrive().getDrive()).isEqualTo(1); } @@ -99,8 +96,7 @@ public void withFluxSource() { ConfigProto proto = builder().withFluxSource("foo.flux").build(); - assertThat(proto.getFluxSource().getType()) - .isEqualTo(FluxSourceSinkType.FLUXTYPE_FLUX); + assertThat(proto.getFluxSource().getType()).isEqualTo(FluxSourceSinkType.FLUXTYPE_FLUX); assertThat(proto.getFluxSource().getFl2().getFilename()).isEqualTo("foo.flux"); } @@ -109,8 +105,7 @@ public void withFluxSourceDrive() { ConfigProto proto = builder().withFluxSource("drive:1").build(); - assertThat(proto.getFluxSource().getType()) - .isEqualTo(FluxSourceSinkType.FLUXTYPE_DRIVE); + assertThat(proto.getFluxSource().getType()).isEqualTo(FluxSourceSinkType.FLUXTYPE_DRIVE); assertThat(proto.getDrive().getDrive()).isEqualTo(1); } @@ -119,8 +114,7 @@ public void withImageWriter() { ConfigProto proto = builder().withImageWriter("out.dsk").build(); - assertThat(proto.getImageWriter().getType()) - .isEqualTo(ImageReaderWriterType.IMAGETYPE_IMG); + assertThat(proto.getImageWriter().getType()).isEqualTo(ImageReaderWriterType.IMAGETYPE_IMG); assertThat(proto.getImageWriter().getFilename()).isEqualTo("out.dsk"); } @@ -129,8 +123,9 @@ public void withCopyFluxTo() { ConfigProto proto = builder().withCopyFluxTo("copy.scp").build(); - assertThat(proto.getDecoder().getCopyFluxTo().getType()) - .isEqualTo(FluxSourceSinkType.FLUXTYPE_SCP); + assertThat(proto.getDecoder() + .getCopyFluxTo() + .getType()).isEqualTo(FluxSourceSinkType.FLUXTYPE_SCP); assertThat(proto.getDecoder().getCopyFluxTo().getScp().getFilename()).isEqualTo("copy.scp"); } @@ -139,8 +134,7 @@ public void withFluxSink() { ConfigProto proto = builder().withFluxSink("vcd:vcdfiles").build(); - assertThat(proto.getFluxSink().getType()) - .isEqualTo(FluxSourceSinkType.FLUXTYPE_VCD); + assertThat(proto.getFluxSink().getType()).isEqualTo(FluxSourceSinkType.FLUXTYPE_VCD); assertThat(proto.getFluxSink().getVcd().getDirectory()).isEqualTo("vcdfiles"); } @@ -149,29 +143,25 @@ public void withImageReader() { ConfigProto proto = builder().withImageReader("in.dim").build(); - assertThat(proto.getImageReader().getType()) - .isEqualTo(ImageReaderWriterType.IMAGETYPE_DIM); + assertThat(proto.getImageReader().getType()).isEqualTo(ImageReaderWriterType.IMAGETYPE_DIM); assertThat(proto.getImageReader().getFilename()).isEqualTo("in.dim"); } @Test public void withImageWriterReadOnlyThrows() { - assertThrows(ConfigException.class, - () -> builder().withImageWriter("out.dim")); + assertThrows(ConfigException.class, () -> builder().withImageWriter("out.dim")); } @Test public void withImageReaderUnrecognisedThrows() { - assertThrows(ConfigException.class, - () -> builder().withImageReader("bogus")); + assertThrows(ConfigException.class, () -> builder().withImageReader("bogus")); } @Test public void withFluxSourceUnrecognisedThrows() { - assertThrows(ConfigException.class, - () -> builder().withFluxSource("bogus")); + assertThrows(ConfigException.class, () -> builder().withFluxSource("bogus")); } } diff --git a/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java b/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java index 3b1bb0cb..10f21e71 100644 --- a/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java +++ b/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java @@ -11,6 +11,13 @@ @RunWith(JUnit4.class) public class ProtoPathTest { + private static ConfigProto set(String path, String value) + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, path, value); + return builder.build(); + } + @Test public void setTopLevelString() { @@ -32,22 +39,24 @@ public void setNestedBool() @Test public void setNestedEnum() { - assertThat(set("drive.drive_type", "DRIVETYPE_80TRACK") - .getDrive().getDriveType().name()).isEqualTo("DRIVETYPE_80TRACK"); + assertThat(set("drive.drive_type", "DRIVETYPE_80TRACK").getDrive() + .getDriveType() + .name()).isEqualTo("DRIVETYPE_80TRACK"); } @Test public void setRepeatedStringWithIndex() { - assertThat(set("documentation[2]", "hello").getDocumentationList()) - .containsExactly("", "", "hello"); + assertThat(set("documentation[2]", "hello").getDocumentationList()).containsExactly( + "", + "", + "hello"); } @Test public void setRepeatedMessageField() { - assertThat(set("option[0].comment", "hello").getOption(0).getComment()) - .isEqualTo("hello"); + assertThat(set("option[0].comment", "hello").getOption(0).getComment()).isEqualTo("hello"); } @Test @@ -119,11 +128,4 @@ public void setIndexOnScalarThrows() { assertThrows(ConfigException.class, () -> set("tracks[0]", "x")); } - - private static ConfigProto set(String path, String value) - { - ConfigProto.Builder builder = ConfigProto.newBuilder(); - ProtoPath.set(builder, path, value); - return builder.build(); - } } diff --git a/javatests/com/cowlark/fluxengine/core/BitReaderTest.java b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java index db77980b..9052d2b6 100644 --- a/javatests/com/cowlark/fluxengine/core/BitReaderTest.java +++ b/javatests/com/cowlark/fluxengine/core/BitReaderTest.java @@ -1,13 +1,12 @@ package com.cowlark.fluxengine.core; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertThrows; -import java.util.Iterator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.Iterator; @RunWith(JUnit4.class) public class BitReaderTest @@ -18,9 +17,22 @@ public void readsBits() Bytes bytes = Bytes.of(0xd6, 0xa0); /* 11010110 10100000 */ BitReader reader = new BitReader(new ByteReader(bytes)); - boolean[] expected = { - true, true, false, true, false, true, true, false, - true, false, true, false, false, false, false, false}; + boolean[] expected = {true, + true, + false, + true, + false, + true, + true, + false, + true, + false, + true, + false, + false, + false, + false, + false}; for (boolean bit : expected) assertThat(reader.get()).isEqualTo(bit); assertThat(reader.eof()).isTrue(); @@ -30,15 +42,25 @@ public void readsBits() public void roundTrip() { Bytes bytes = new Bytes(0); - new BitWriter(new ByteWriter(bytes)) - .push(0b11010110, 8) - .push(0b10101100, 8) - .flush(); + new BitWriter(new ByteWriter(bytes)).push(0b11010110, 8).push(0b10101100, 8).flush(); BitReader reader = new BitReader(new ByteReader(bytes)); - boolean[] expected = { - true, true, false, true, false, true, true, false, - true, false, true, false, true, true, false, false}; + boolean[] expected = {true, + true, + false, + true, + false, + true, + true, + false, + true, + false, + true, + false, + true, + true, + false, + false}; for (boolean bit : expected) assertThat(reader.get()).isEqualTo(bit); assertThat(reader.eof()).isTrue(); @@ -59,8 +81,7 @@ public void readingPastEndThrows() public void iteration() { Iterator iterator = new BitReader(new ByteReader(Bytes.of(0xd6))); - boolean[] expected = { - true, true, false, true, false, true, true, false}; + boolean[] expected = {true, true, false, true, false, true, true, false}; for (boolean bit : expected) { assertThat(iterator.hasNext()).isTrue(); diff --git a/javatests/com/cowlark/fluxengine/core/BitWriterTest.java b/javatests/com/cowlark/fluxengine/core/BitWriterTest.java index 27321f6c..13720e05 100644 --- a/javatests/com/cowlark/fluxengine/core/BitWriterTest.java +++ b/javatests/com/cowlark/fluxengine/core/BitWriterTest.java @@ -16,7 +16,7 @@ public void writesWholeByte() ByteWriter bw = new ByteWriter(bytes); new BitWriter(bw).push(0b11010110, 8).flush(); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {(byte) 0xd6}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{(byte) 0xd6}); } @Test @@ -26,7 +26,7 @@ public void packsAcrossBytes() ByteWriter bw = new ByteWriter(bytes); new BitWriter(bw).push(0b11010110, 8).push(0b101, 3).flush(); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {(byte) 0xd6, 0x05}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{(byte) 0xd6, 0x05}); } @Test @@ -36,6 +36,6 @@ public void flushesPartialByte() ByteWriter bw = new ByteWriter(bytes); new BitWriter(bw).push(0b101, 3).flush(); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {0x05}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{0x05}); } } diff --git a/javatests/com/cowlark/fluxengine/core/BitsTest.java b/javatests/com/cowlark/fluxengine/core/BitsTest.java index 491c23ef..c31b88da 100644 --- a/javatests/com/cowlark/fluxengine/core/BitsTest.java +++ b/javatests/com/cowlark/fluxengine/core/BitsTest.java @@ -1,13 +1,12 @@ package com.cowlark.fluxengine.core; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertThrows; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.List; @RunWith(JUnit4.class) public class BitsTest diff --git a/javatests/com/cowlark/fluxengine/core/ByteReaderTest.java b/javatests/com/cowlark/fluxengine/core/ByteReaderTest.java index c1131635..782814c3 100644 --- a/javatests/com/cowlark/fluxengine/core/ByteReaderTest.java +++ b/javatests/com/cowlark/fluxengine/core/ByteReaderTest.java @@ -35,8 +35,8 @@ public void reads24() @Test public void reads32() { - ByteReader reader = new ByteReader(Bytes.of( - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)); + ByteReader reader = + new ByteReader(Bytes.of(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08)); assertThat(reader.readBe32()).isEqualTo(0x01020304); assertThat(reader.readLe32()).isEqualTo(0x08070605); @@ -46,8 +46,18 @@ public void reads32() public void reads48() { ByteReader reader = new ByteReader(Bytes.of( - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f)); + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f)); assertThat(reader.readBe48()).isEqualTo(0x010203040506L); assertThat(reader.readLe48()).isEqualTo(0x0f0e0d0c0b0aL); @@ -57,8 +67,22 @@ public void reads48() public void reads64() { ByteReader reader = new ByteReader(Bytes.of( - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, - 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10)); + 0x01, + 0x02, + 0x03, + 0x04, + 0x05, + 0x06, + 0x07, + 0x08, + 0x09, + 0x0a, + 0x0b, + 0x0c, + 0x0d, + 0x0e, + 0x0f, + 0x10)); assertThat(reader.readBe64()).isEqualTo(0x0102030405060708L); assertThat(reader.readLe64()).isEqualTo(0x100f0e0d0c0b0a09L); diff --git a/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java index b251a681..e5fc4fb1 100644 --- a/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java +++ b/javatests/com/cowlark/fluxengine/core/ByteWriterTest.java @@ -13,61 +13,88 @@ public class ByteWriterTest public void writes8And16() { Bytes bytes = new Bytes(0); - new ByteWriter(bytes) - .write8(0x01) - .writeBe16(0x0203) - .writeLe16(0x0504) - .write8(0x06); + new ByteWriter(bytes).write8(0x01).writeBe16(0x0203).writeLe16(0x0504).write8(0x06); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {1, 2, 3, 4, 5, 6}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{1, 2, 3, 4, 5, 6}); } @Test public void writes24And32() { Bytes bytes = new Bytes(0); - new ByteWriter(bytes) - .writeBe24(0x010203) - .writeLe24(0x060504) - .writeBe32(0x0708090a) - .writeLe32(0x0e0d0c0b); - - assertThat(bytes.toByteArray()).isEqualTo(new byte[] { - 1, 2, 3, - 4, 5, 6, - 7, 8, 9, 10, - 11, 12, 13, 14}); + new ByteWriter(bytes).writeBe24(0x010203) + .writeLe24(0x060504) + .writeBe32(0x0708090a) + .writeLe32(0x0e0d0c0b); + + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14}); } @Test public void writes48And64() { Bytes bytes = new Bytes(0); - new ByteWriter(bytes) - .writeBe48(0x010203040506L) - .writeLe48(0x0c0b0a090807L) - .writeBe64(0x0102030405060708L) - .writeLe64(0x100f0e0d0c0b0a09L); - - assertThat(bytes.toByteArray()).isEqualTo(new byte[] { - 1, 2, 3, 4, 5, 6, - 7, 8, 9, 10, 11, 12, - 1, 2, 3, 4, 5, 6, 7, 8, - 9, 10, 11, 12, 13, 14, 15, 16}); + new ByteWriter(bytes).writeBe48(0x010203040506L) + .writeLe48(0x0c0b0a090807L) + .writeBe64(0x0102030405060708L) + .writeLe64(0x100f0e0d0c0b0a09L); + + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16}); } @Test public void writesBytesAndPads() { Bytes bytes = new Bytes(0); - new ByteWriter(bytes) - .write(Bytes.of(1, 2)) - .write(new byte[] {3, 4}) - .pad(2, 0xff) - .pad(1); - - assertThat(bytes.toByteArray()).isEqualTo(new byte[] { - 1, 2, 3, 4, (byte) 0xff, (byte) 0xff, 0}); + new ByteWriter(bytes).write(Bytes.of(1, 2)).write(new byte[]{3, 4}).pad(2, 0xff).pad(1); + + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{1, + 2, + 3, + 4, + (byte) 0xff, + (byte) 0xff, + 0}); } @Test diff --git a/javatests/com/cowlark/fluxengine/core/BytesTest.java b/javatests/com/cowlark/fluxengine/core/BytesTest.java index 75172c43..99e2c39c 100644 --- a/javatests/com/cowlark/fluxengine/core/BytesTest.java +++ b/javatests/com/cowlark/fluxengine/core/BytesTest.java @@ -1,14 +1,13 @@ package com.cowlark.fluxengine.core; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertThrows; import com.google.common.collect.ImmutableList; -import java.util.ListIterator; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.ListIterator; @RunWith(JUnit4.class) public class BytesTest @@ -31,10 +30,10 @@ public void sliceZeroPads() { Bytes bytes = Bytes.of(1, 2, 3); - assertThat(bytes.slice(1, 3).toByteArray()).isEqualTo(new byte[] {2, 3, 0}); - assertThat(bytes.slice(5, 2).toByteArray()).isEqualTo(new byte[] {0, 0}); - assertThat(bytes.slice(3, 2).toByteArray()).isEqualTo(new byte[] {0, 0}); - assertThat(bytes.slice(2).toByteArray()).isEqualTo(new byte[] {3}); + assertThat(bytes.slice(1, 3).toByteArray()).isEqualTo(new byte[]{2, 3, 0}); + assertThat(bytes.slice(5, 2).toByteArray()).isEqualTo(new byte[]{0, 0}); + assertThat(bytes.slice(3, 2).toByteArray()).isEqualTo(new byte[]{0, 0}); + assertThat(bytes.slice(2).toByteArray()).isEqualTo(new byte[]{3}); assertThat(bytes.slice(5).isEmpty()).isTrue(); } @@ -54,15 +53,15 @@ public void split() ImmutableList pieces = bytes.split(0); assertThat(pieces).hasSize(3); - assertThat(pieces.get(0).toByteArray()).isEqualTo(new byte[] {1, 2}); - assertThat(pieces.get(1).toByteArray()).isEqualTo(new byte[] {3, 4}); - assertThat(pieces.get(2).toByteArray()).isEqualTo(new byte[] {5}); + assertThat(pieces.get(0).toByteArray()).isEqualTo(new byte[]{1, 2}); + assertThat(pieces.get(1).toByteArray()).isEqualTo(new byte[]{3, 4}); + assertThat(pieces.get(2).toByteArray()).isEqualTo(new byte[]{5}); /* Consecutive separators and a trailing separator yield empty pieces. */ ImmutableList empties = Bytes.of(0, 1, 0, 0).split(0); assertThat(empties).hasSize(4); assertThat(empties.get(0).isEmpty()).isTrue(); - assertThat(empties.get(1).toByteArray()).isEqualTo(new byte[] {1}); + assertThat(empties.get(1).toByteArray()).isEqualTo(new byte[]{1}); assertThat(empties.get(2).isEmpty()).isTrue(); assertThat(empties.get(3).isEmpty()).isTrue(); } @@ -70,12 +69,10 @@ public void split() @Test public void swab() { - assertThat(Bytes.of(1, 2, 3, 4).swab().toByteArray()) - .isEqualTo(new byte[] {2, 1, 4, 3}); + assertThat(Bytes.of(1, 2, 3, 4).swab().toByteArray()).isEqualTo(new byte[]{2, 1, 4, 3}); /* Odd length pads the trailing byte with a zero. */ - assertThat(Bytes.of(1, 2, 3).swab().toByteArray()) - .isEqualTo(new byte[] {2, 1, 0, 3}); + assertThat(Bytes.of(1, 2, 3).swab().toByteArray()).isEqualTo(new byte[]{2, 1, 0, 3}); } @Test @@ -129,16 +126,16 @@ public void listOperations() assertThat(bytes.indexOf(Byte.valueOf((byte) 9))).isEqualTo(-1); bytes.add(Byte.valueOf((byte) 4)); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {1, 2, 3, 4}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{1, 2, 3, 4}); bytes.add(1, Byte.valueOf((byte) 9)); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {1, 9, 2, 3, 4}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{1, 9, 2, 3, 4}); assertThat(bytes.remove(0)).isEqualTo((byte) 1); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {9, 2, 3, 4}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{9, 2, 3, 4}); assertThat(bytes.remove(Byte.valueOf((byte) 3))).isTrue(); - assertThat(bytes.toByteArray()).isEqualTo(new byte[] {9, 2, 4}); + assertThat(bytes.toByteArray()).isEqualTo(new byte[]{9, 2, 4}); ListIterator it = bytes.listIterator(); assertThat(it.next()).isEqualTo((byte) 9); diff --git a/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java index 2aa435dd..66c8fef0 100644 --- a/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java +++ b/javatests/com/cowlark/fluxengine/core/flags/FlagsTest.java @@ -94,8 +94,9 @@ public void duplicateNamesThrow() public void unknownFlagThrows() { FlagGroup group = new FlagGroup(); - assertThrows(FluxEngineException.class, - () -> Flags.parse(ImmutableList.of("--nope=x"), group)); + assertThrows( + FluxEngineException.class, + () -> Flags.parse(ImmutableList.of("--nope=x"), group)); } @Test @@ -188,10 +189,11 @@ public void noArgFlagDoesNotConsumeFollowingToken() .setHelpText("read only") .build(); - List filenames = Flags.parseWithFilenames( - ImmutableList.of("--read-only", "image.dsk"), - unused -> false, - group); + List filenames = + Flags.parseWithFilenames( + ImmutableList.of("--read-only", "image.dsk"), + unused -> false, + group); assertThat(flag.get()).isTrue(); assertThat(filenames).containsExactly("image.dsk"); diff --git a/javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java b/javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java index 721535dd..c70b4bea 100644 --- a/javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java +++ b/javatests/com/cowlark/fluxengine/data/DiskLayoutTest.java @@ -9,25 +9,54 @@ import com.cowlark.fluxengine.config.DriveProto; import com.cowlark.fluxengine.config.LayoutProto; import com.google.common.collect.ImmutableMap; -import java.util.function.Consumer; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.function.Consumer; @RunWith(JUnit4.class) public class DiskLayoutTest { + private static DiskLayout diskLayout(com.cowlark.fluxengine.external.FormatType formatType, + Consumer layoutData) + { + ConfigProto.Builder config = baseConfig(formatType); + config.getLayoutBuilder().setTracks(78).setSides(2); + layoutData.accept(addLayoutData(config)); + return new DiskLayout(config.build()); + } + + private static LogicalTrackLayout logicalLayoutAt(DiskLayout diskLayout, int cylinder, int head) + { + return diskLayout.layoutByPhysicalLocation.get(new CylinderHead( + cylinder, + head)).logicalTrackLayout; + } + + private static ConfigProto.Builder baseConfig(com.cowlark.fluxengine.external.FormatType formatType) + { + return ConfigProto.newBuilder() + .setDrive(DriveProto.newBuilder().setDriveType(DRIVETYPE_80TRACK).build()) + .setLayout(LayoutProto.newBuilder().setFormatType(formatType).build()); + } + + private static LayoutProto.LayoutdataProto.Builder addLayoutData(ConfigProto.Builder config) + { + return config.getLayoutBuilder().addLayoutdataBuilder(); + } + @Test public void testPhysicalSectors() { - DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { - track.setSectorSize(256); - track.getPhysicalBuilder().addSector(0).addSector(2).addSector(1).addSector(3); - }); + DiskLayout diskLayout = diskLayout( + FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().addSector(0).addSector(2).addSector(1).addSector(3); + }); LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); - assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))) - .isSameInstanceAs(layout); + assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))).isSameInstanceAs( + layout); assertThat(layout.naturalSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); assertThat(layout.diskSectorOrder).containsExactly(0, 2, 1, 3).inOrder(); assertThat(layout.filesystemSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); @@ -36,15 +65,20 @@ public void testPhysicalSectors() @Test public void testLogicalSectors() { - DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { - track.setSectorSize(256); - track.getPhysicalBuilder().addSector(0).addSector(1).addSector(2).addSector(3); - track.getFilesystemBuilder().addSector(0).addSector(2).addSector(1).addSector(3); - }); + DiskLayout diskLayout = diskLayout( + FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().addSector(0).addSector(1).addSector(2).addSector(3); + track.getFilesystemBuilder() + .addSector(0) + .addSector(2) + .addSector(1) + .addSector(3); + }); LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); - assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))) - .isSameInstanceAs(layout); + assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))).isSameInstanceAs( + layout); assertThat(layout.naturalSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); assertThat(layout.diskSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); assertThat(layout.filesystemSectorOrder).containsExactly(0, 2, 1, 3).inOrder(); @@ -53,15 +87,20 @@ public void testLogicalSectors() @Test public void test_bothSectors() { - DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { - track.setSectorSize(256); - track.getPhysicalBuilder().addSector(3).addSector(2).addSector(1).addSector(0); - track.getFilesystemBuilder().addSector(0).addSector(2).addSector(1).addSector(3); - }); + DiskLayout diskLayout = diskLayout( + FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().addSector(3).addSector(2).addSector(1).addSector(0); + track.getFilesystemBuilder() + .addSector(0) + .addSector(2) + .addSector(1) + .addSector(3); + }); LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); - assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))) - .isSameInstanceAs(layout); + assertThat(diskLayout.layoutByLogicalLocation.get(new CylinderHead(0, 0))).isSameInstanceAs( + layout); assertThat(layout.naturalSectorOrder).containsExactly(0, 1, 2, 3).inOrder(); assertThat(layout.diskSectorOrder).containsExactly(3, 2, 1, 0).inOrder(); assertThat(layout.filesystemSectorOrder).containsExactly(0, 2, 1, 3).inOrder(); @@ -70,17 +109,16 @@ public void test_bothSectors() @Test public void test_skew() { - DiskLayout diskLayout = diskLayout(FORMATTYPE_80TRACK, (track) -> { - track.setSectorSize(256); - track.getPhysicalBuilder().setStartSector(0).setCount(12).setSkew(6); - }); + DiskLayout diskLayout = diskLayout( + FORMATTYPE_80TRACK, (track) -> { + track.setSectorSize(256); + track.getPhysicalBuilder().setStartSector(0).setCount(12).setSkew(6); + }); LogicalTrackLayout layout = logicalLayoutAt(diskLayout, 0, 0); - assertThat(layout.naturalSectorOrder) - .containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) + assertThat(layout.naturalSectorOrder).containsExactly(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11) .inOrder(); - assertThat(layout.diskSectorOrder) - .containsExactly(0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11) + assertThat(layout.diskSectorOrder).containsExactly(0, 6, 1, 7, 2, 8, 3, 9, 4, 10, 5, 11) .inOrder(); } @@ -90,14 +128,23 @@ public void test_bounds() ConfigProto.Builder config = baseConfig(FORMATTYPE_40TRACK); config.getLayoutBuilder().setTracks(2).setSides(2); addLayoutData(config).setSectorSize(256) - .getPhysicalBuilder().setStartSector(0).setCount(12).setSkew(6); + .getPhysicalBuilder() + .setStartSector(0) + .setCount(12) + .setSkew(6); DiskLayout diskLayout = new DiskLayout(config.build()); assertThat(diskLayout.groupSize).isEqualTo(2); - assertThat(diskLayout.getLogicalBounds()) - .isEqualTo(new DiskLayout.LayoutBounds(0, 1, 0, 1)); - assertThat(diskLayout.getPhysicalBounds()) - .isEqualTo(new DiskLayout.LayoutBounds(0, 3, 0, 1)); + assertThat(diskLayout.getLogicalBounds()).isEqualTo(new DiskLayout.LayoutBounds( + 0, + 1, + 0, + 1)); + assertThat(diskLayout.getPhysicalBounds()).isEqualTo(new DiskLayout.LayoutBounds( + 0, + 3, + 0, + 1)); } @Test @@ -112,72 +159,42 @@ public void test_sectoroffsets() DiskLayout diskLayout = new DiskLayout(config.build()); assertThat(diskLayout.groupSize).isEqualTo(1); - assertThat(diskLayout.logicalSectorLocationBySectorOffset) - .isEqualTo(ImmutableMap.builder() - .put(0L, new LogicalLocation(0, 0, 0)) - .put(256L, new LogicalLocation(0, 0, 2)) - .put(512L, new LogicalLocation(0, 0, 1)) - .put(768L, new LogicalLocation(0, 0, 3)) - .put(1024L, new LogicalLocation(0, 1, 0)) - .put(1280L, new LogicalLocation(0, 1, 2)) - .put(1536L, new LogicalLocation(0, 1, 1)) - .put(1792L, new LogicalLocation(0, 1, 3)) - .put(2048L, new LogicalLocation(1, 0, 0)) - .put(2304L, new LogicalLocation(1, 0, 2)) - .put(2560L, new LogicalLocation(1, 0, 1)) - .put(2816L, new LogicalLocation(1, 0, 3)) - .put(3072L, new LogicalLocation(1, 1, 0)) - .put(3328L, new LogicalLocation(1, 1, 2)) - .put(3584L, new LogicalLocation(1, 1, 1)) - .put(3840L, new LogicalLocation(1, 1, 3)) - .build()); - assertThat(diskLayout.sectorOffsetByLogicalSectorLocation) - .isEqualTo(ImmutableMap.builder() - .put(new LogicalLocation(0, 0, 0), 0L) - .put(new LogicalLocation(0, 0, 1), 512L) - .put(new LogicalLocation(0, 0, 2), 256L) - .put(new LogicalLocation(0, 0, 3), 768L) - .put(new LogicalLocation(0, 1, 0), 1024L) - .put(new LogicalLocation(0, 1, 1), 1536L) - .put(new LogicalLocation(0, 1, 2), 1280L) - .put(new LogicalLocation(0, 1, 3), 1792L) - .put(new LogicalLocation(1, 0, 0), 2048L) - .put(new LogicalLocation(1, 0, 1), 2560L) - .put(new LogicalLocation(1, 0, 2), 2304L) - .put(new LogicalLocation(1, 0, 3), 2816L) - .put(new LogicalLocation(1, 1, 0), 3072L) - .put(new LogicalLocation(1, 1, 1), 3584L) - .put(new LogicalLocation(1, 1, 2), 3328L) - .put(new LogicalLocation(1, 1, 3), 3840L) - .build()); - } - - private static DiskLayout diskLayout( - com.cowlark.fluxengine.external.FormatType formatType, - Consumer layoutData) - { - ConfigProto.Builder config = baseConfig(formatType); - config.getLayoutBuilder().setTracks(78).setSides(2); - layoutData.accept(addLayoutData(config)); - return new DiskLayout(config.build()); - } - - private static LogicalTrackLayout logicalLayoutAt(DiskLayout diskLayout, int cylinder, int head) - { - return diskLayout.layoutByPhysicalLocation - .get(new CylinderHead(cylinder, head)).logicalTrackLayout; - } - - private static ConfigProto.Builder baseConfig( - com.cowlark.fluxengine.external.FormatType formatType) - { - return ConfigProto.newBuilder() - .setDrive(DriveProto.newBuilder().setDriveType(DRIVETYPE_80TRACK).build()) - .setLayout(LayoutProto.newBuilder().setFormatType(formatType).build()); - } - - private static LayoutProto.LayoutdataProto.Builder addLayoutData(ConfigProto.Builder config) - { - return config.getLayoutBuilder().addLayoutdataBuilder(); + assertThat(diskLayout.logicalSectorLocationBySectorOffset).isEqualTo(ImmutableMap.builder() + .put(0L, new LogicalLocation(0, 0, 0)) + .put(256L, new LogicalLocation(0, 0, 2)) + .put(512L, new LogicalLocation(0, 0, 1)) + .put(768L, new LogicalLocation(0, 0, 3)) + .put(1024L, new LogicalLocation(0, 1, 0)) + .put(1280L, new LogicalLocation(0, 1, 2)) + .put(1536L, new LogicalLocation(0, 1, 1)) + .put(1792L, new LogicalLocation(0, 1, 3)) + .put(2048L, new LogicalLocation(1, 0, 0)) + .put(2304L, new LogicalLocation(1, 0, 2)) + .put(2560L, new LogicalLocation(1, 0, 1)) + .put(2816L, new LogicalLocation(1, 0, 3)) + .put(3072L, new LogicalLocation(1, 1, 0)) + .put(3328L, new LogicalLocation(1, 1, 2)) + .put(3584L, new LogicalLocation(1, 1, 1)) + .put(3840L, new LogicalLocation(1, 1, 3)) + .build()); + assertThat(diskLayout.sectorOffsetByLogicalSectorLocation).isEqualTo(ImmutableMap.builder() + .put(new LogicalLocation(0, 0, 0), 0L) + .put(new LogicalLocation(0, 0, 1), 512L) + .put(new LogicalLocation(0, 0, 2), 256L) + .put(new LogicalLocation(0, 0, 3), 768L) + .put(new LogicalLocation(0, 1, 0), 1024L) + .put(new LogicalLocation(0, 1, 1), 1536L) + .put(new LogicalLocation(0, 1, 2), 1280L) + .put(new LogicalLocation(0, 1, 3), 1792L) + .put(new LogicalLocation(1, 0, 0), 2048L) + .put(new LogicalLocation(1, 0, 1), 2560L) + .put(new LogicalLocation(1, 0, 2), 2304L) + .put(new LogicalLocation(1, 0, 3), 2816L) + .put(new LogicalLocation(1, 1, 0), 3072L) + .put(new LogicalLocation(1, 1, 1), 3584L) + .put(new LogicalLocation(1, 1, 2), 3328L) + .put(new LogicalLocation(1, 1, 3), 3840L) + .build()); } } \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java index 211b4fcd..a8741aaa 100644 --- a/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java +++ b/javatests/com/cowlark/fluxengine/data/FluxmapReaderTest.java @@ -18,22 +18,22 @@ public class FluxmapReaderTest public void readsEvents() { Fluxmap map = new Fluxmap(Bytes.of( - FluxEngine.F_DESYNC, - FluxEngine.F_BIT_PULSE | 0x30, - FluxEngine.F_BIT_INDEX | 0x30, - FluxEngine.F_BIT_PULSE | FluxEngine.F_BIT_INDEX | 0x30, - FluxEngine.F_DESYNC, - FluxEngine.F_BIT_PULSE | 0x30, - FluxEngine.F_DESYNC, - FluxEngine.F_BIT_PULSE | 0x30)); + FluxEngine.F_DESYNC, + FluxEngine.F_BIT_PULSE | 0x30, + FluxEngine.F_BIT_INDEX | 0x30, + FluxEngine.F_BIT_PULSE | FluxEngine.F_BIT_INDEX | 0x30, + FluxEngine.F_DESYNC, + FluxEngine.F_BIT_PULSE | 0x30, + FluxEngine.F_DESYNC, + FluxEngine.F_BIT_PULSE | 0x30)); FluxmapReader r = new FluxmapReader(map, DECODER); assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_DESYNC); assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_PULSE); assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_INDEX); - assertThat(r.getNextEvent().event()) - .isEqualTo(FluxEngine.F_BIT_PULSE | FluxEngine.F_BIT_INDEX); + assertThat(r.getNextEvent().event()).isEqualTo( + FluxEngine.F_BIT_PULSE | FluxEngine.F_BIT_INDEX); assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_DESYNC); assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_BIT_PULSE); assertThat(r.getNextEvent().event()).isEqualTo(FluxEngine.F_DESYNC); @@ -45,9 +45,8 @@ public void readsEvents() @Test public void ticksAccumulate() { - Fluxmap map = new Fluxmap(Bytes.of( - FluxEngine.F_BIT_PULSE | 0x30, - FluxEngine.F_BIT_PULSE | 0x30)); + Fluxmap map = + new Fluxmap(Bytes.of(FluxEngine.F_BIT_PULSE | 0x30, FluxEngine.F_BIT_PULSE | 0x30)); FluxmapReader r = new FluxmapReader(map, DECODER); @@ -59,9 +58,8 @@ public void ticksAccumulate() @Test public void findEvent() { - Fluxmap map = new Fluxmap(Bytes.of( - FluxEngine.F_BIT_PULSE | 0x30, - FluxEngine.F_BIT_INDEX | 0x30)); + Fluxmap map = + new Fluxmap(Bytes.of(FluxEngine.F_BIT_PULSE | 0x30, FluxEngine.F_BIT_INDEX | 0x30)); FluxmapReader r = new FluxmapReader(map, DECODER); @@ -74,9 +72,8 @@ public void findEvent() @Test public void findEventNotFound() { - Fluxmap map = new Fluxmap(Bytes.of( - FluxEngine.F_BIT_PULSE | 0x30, - FluxEngine.F_BIT_PULSE | 0x30)); + Fluxmap map = + new Fluxmap(Bytes.of(FluxEngine.F_BIT_PULSE | 0x30, FluxEngine.F_BIT_PULSE | 0x30)); FluxmapReader r = new FluxmapReader(map, DECODER); diff --git a/javatests/com/cowlark/fluxengine/data/FluxmapTest.java b/javatests/com/cowlark/fluxengine/data/FluxmapTest.java index 9623538c..5afd1512 100644 --- a/javatests/com/cowlark/fluxengine/data/FluxmapTest.java +++ b/javatests/com/cowlark/fluxengine/data/FluxmapTest.java @@ -3,10 +3,10 @@ import static com.google.common.truth.Truth.assertThat; import com.cowlark.fluxengine.core.Bytes; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.List; @RunWith(JUnit4.class) public class FluxmapTest diff --git a/javatests/com/cowlark/fluxengine/data/KryofluxTest.java b/javatests/com/cowlark/fluxengine/data/KryofluxTest.java index 31f3ac1a..5c9e8edc 100644 --- a/javatests/com/cowlark/fluxengine/data/KryofluxTest.java +++ b/javatests/com/cowlark/fluxengine/data/KryofluxTest.java @@ -3,14 +3,27 @@ import static com.google.common.truth.Truth.assertThat; import com.cowlark.fluxengine.core.Bytes; -import java.util.Arrays; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.Arrays; @RunWith(JUnit4.class) public class KryofluxTest { + private static void testConvert(Bytes kyrofluxBytes, Bytes expectedFluxmapBytes) + { + Fluxmap fluxmap = Kryoflux.readStream(kyrofluxBytes); + assertThat(fluxmap.rawBytes().toByteArray()).isEqualTo(expectedFluxmapBytes.toByteArray()); + } + + private static Bytes unsignedBytes(int count) + { + byte[] data = new byte[count]; + Arrays.fill(data, (byte) 0x3f); + return new Bytes(data); + } + @Test public void test_stream_reader() { @@ -21,18 +34,18 @@ public void test_stream_reader() /* One-and-a-half-byte intervals */ testConvert( - Bytes.of(0x20, 0x00, 0x10, 0x20, 0x01, 0x10, 0x20), - Bytes.of(0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f)); + Bytes.of(0x20, 0x00, 0x10, 0x20, 0x01, 0x10, 0x20), + Bytes.of(0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f)); /* Two-byte intervals */ testConvert( - Bytes.of(0x20, 0x0c, 0x00, 0x10, 0x20, 0x0c, 0x01, 0x10, 0x20), - Bytes.of(0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f)); + Bytes.of(0x20, 0x0c, 0x00, 0x10, 0x20, 0x0c, 0x01, 0x10, 0x20), + Bytes.of(0x8f, 0x87, 0x8f, 0x3f, 0x3f, 0x89, 0x8f)); /* Overflow */ testConvert( - Bytes.of(0x20, 0x0b, 0x10, 0x20), - Bytes.of(0x8f).concat(unsignedBytes(0x207)).concat(Bytes.of(0xa9, 0x8f))); + Bytes.of(0x20, 0x0b, 0x10, 0x20), + Bytes.of(0x8f).concat(unsignedBytes(0x207)).concat(Bytes.of(0xa9, 0x8f))); /* Single-byte nop */ testConvert(Bytes.of(0x20, 0x08, 0x20), Bytes.of(0x8f, 0x8f)); @@ -45,29 +58,12 @@ public void test_stream_reader() /* OOB block */ testConvert( - Bytes.of( - 0x20, /* data before */ - 0x0d, /* OOB */ - 0xaa, /* type byte */ - 0x01, - 0x00, /* size of payload, little-endian */ - 0x55, /* payload */ - 0x20 /* data continues */ - ), - Bytes.of(0x8f, 0x8f)); - } - - private static void testConvert(Bytes kyrofluxBytes, Bytes expectedFluxmapBytes) - { - Fluxmap fluxmap = Kryoflux.readStream(kyrofluxBytes); - assertThat(fluxmap.rawBytes().toByteArray()) - .isEqualTo(expectedFluxmapBytes.toByteArray()); - } - - private static Bytes unsignedBytes(int count) - { - byte[] data = new byte[count]; - Arrays.fill(data, (byte) 0x3f); - return new Bytes(data); + Bytes.of( + 0x20, /* data before */ + 0x0d, /* OOB */ + 0xaa, /* type byte */ + 0x01, 0x00, /* size of payload, little-endian */ + 0x55, /* payload */ + 0x20 /* data continues */), Bytes.of(0x8f, 0x8f)); } } \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/data/LocationsTest.java b/javatests/com/cowlark/fluxengine/data/LocationsTest.java index 33b90838..09db4af6 100644 --- a/javatests/com/cowlark/fluxengine/data/LocationsTest.java +++ b/javatests/com/cowlark/fluxengine/data/LocationsTest.java @@ -1,14 +1,13 @@ package com.cowlark.fluxengine.data; import static com.google.common.truth.Truth.assertThat; - import static org.junit.Assert.assertThrows; import com.cowlark.fluxengine.core.FluxEngineException; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.util.List; @RunWith(JUnit4.class) public class LocationsTest @@ -16,15 +15,15 @@ public class LocationsTest @Test public void parseSingle() { - assertThat(Locations.parseCylinderHeadsString("c0h0")) - .containsExactly(new CylinderHead(0, 0)); + assertThat(Locations.parseCylinderHeadsString("c0h0")).containsExactly(new CylinderHead( + 0, + 0)); } @Test public void parseRangeAndStep() { - assertThat(Locations.parseCylinderHeadsString("c0-2h0-2x2")) - .containsExactly( + assertThat(Locations.parseCylinderHeadsString("c0-2h0-2x2")).containsExactly( new CylinderHead(0, 0), new CylinderHead(0, 2), new CylinderHead(1, 0), @@ -36,8 +35,9 @@ public void parseRangeAndStep() @Test public void parseMultipleGroups() { - assertThat(Locations.parseCylinderHeadsString("c1h1 c0h0")) - .containsExactly(new CylinderHead(0, 0), new CylinderHead(1, 1)); + assertThat(Locations.parseCylinderHeadsString("c1h1 c0h0")).containsExactly( + new CylinderHead(0, + 0), new CylinderHead(1, 1)); } @Test @@ -51,11 +51,10 @@ public void convertRoundTrip() @Test public void parseMalformedThrows() { - assertThrows(FluxEngineException.class, - () -> Locations.parseCylinderHeadsString("c0")); - assertThrows(FluxEngineException.class, - () -> Locations.parseCylinderHeadsString("garbage")); - assertThrows(FluxEngineException.class, - () -> Locations.parseCylinderHeadsString("c0h2x0")); + assertThrows(FluxEngineException.class, () -> Locations.parseCylinderHeadsString("c0")); + assertThrows( + FluxEngineException.class, + () -> Locations.parseCylinderHeadsString("garbage")); + assertThrows(FluxEngineException.class, () -> Locations.parseCylinderHeadsString("c0h2x0")); } } diff --git a/javatests/com/cowlark/fluxengine/decoders/BUILD.bazel b/javatests/com/cowlark/fluxengine/decoders/BUILD.bazel index 5ec663bf..c590f75f 100644 --- a/javatests/com/cowlark/fluxengine/decoders/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/decoders/BUILD.bazel @@ -15,4 +15,4 @@ java_test( "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], -) \ No newline at end of file +) diff --git a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java index 9c70e27b..909f2ac4 100644 --- a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java +++ b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java @@ -7,18 +7,17 @@ import com.cowlark.fluxengine.data.Fluxmap; import com.cowlark.fluxengine.data.FluxmapReader; import com.cowlark.fluxengine.external.FmMfm; -import java.time.Duration; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.time.Duration; @RunWith(JUnit4.class) public class FluxDecoderTest { private static final int CLOCK_TICKS = 1000; private static final Duration CLOCK = - Duration.ofNanos((long) (CLOCK_TICKS * 1000000000.0 / 12000000.0)); + Duration.ofNanos((long) (CLOCK_TICKS * 1000000000.0 / 12000000.0)); private static Bytes roundTrip(Bytes data) { @@ -28,10 +27,8 @@ private static Bytes roundTrip(Bytes data) /* ...write it out as flux... */ Fluxmap map = new Fluxmap(); map.appendBits(encoded, CLOCK_TICKS); - FluxmapReader reader = new FluxmapReader( - map, DecoderProto.getDefaultInstance()); - FluxDecoder decoder = new FluxDecoder( - reader, CLOCK, DecoderProto.getDefaultInstance()); + FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder(reader, CLOCK, DecoderProto.getDefaultInstance()); /* ...and read the raw bits back, skipping the PLL init pulse. */ Bits decoded = new Bits(); @@ -55,12 +52,11 @@ public void emitsAClockForEveryFluxTransition() /* A pulse at every cell boundary reads back as an unbroken run of * trues. */ Fluxmap map = new Fluxmap(); - map.appendBits(java.util.Arrays.asList( - true, true, true, true, true, true, true, true), CLOCK_TICKS); - FluxmapReader reader = new FluxmapReader( - map, DecoderProto.getDefaultInstance()); - FluxDecoder decoder = new FluxDecoder( - reader, CLOCK, DecoderProto.getDefaultInstance()); + map.appendBits( + java.util.Arrays.asList(true, true, true, true, true, true, true, true), + CLOCK_TICKS); + FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder(reader, CLOCK, DecoderProto.getDefaultInstance()); Bits bits = new Bits(); while (!reader.eof()) @@ -78,10 +74,8 @@ public void firstBitIsAlwaysTrue() * first readBit return true. */ Fluxmap map = new Fluxmap(); map.appendBits(java.util.Arrays.asList(true), CLOCK_TICKS); - FluxmapReader reader = new FluxmapReader( - map, DecoderProto.getDefaultInstance()); - FluxDecoder decoder = new FluxDecoder( - reader, CLOCK, DecoderProto.getDefaultInstance()); + FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder(reader, CLOCK, DecoderProto.getDefaultInstance()); assertThat(decoder.readBit()).isTrue(); } diff --git a/javatests/com/cowlark/fluxengine/external/FmMfmTest.java b/javatests/com/cowlark/fluxengine/external/FmMfmTest.java index 348a3038..e10001fe 100644 --- a/javatests/com/cowlark/fluxengine/external/FmMfmTest.java +++ b/javatests/com/cowlark/fluxengine/external/FmMfmTest.java @@ -4,7 +4,6 @@ import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.core.Bytes; -import com.cowlark.fluxengine.external.FmMfm; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -12,55 +11,6 @@ @RunWith(JUnit4.class) public class FmMfmTest { - @Test - public void decode() - { - assertThat(FmMfm.decodeFmMfm(bits(true, - false, true, false, true, false, true, false, - true, false, true, false, true, false, true, false))) - .isEqualTo(Bytes.of(0x00)); - - assertThat(FmMfm.decodeFmMfm(bits(true, - true, true, false, true, false, true, false, - true, false, true, false, true, false, true, true))) - .isEqualTo(Bytes.of(0x81)); - - assertThat(FmMfm.decodeFmMfm(bits(true, true, true, false))) - .isEqualTo(Bytes.of(0x80)); - } - - @Test - public void encodeMfm() - { - assertThat(wrapEncodeMfm(Bytes.of(0xa1))) - .isEqualTo(bits(false, true, false, false, false, true, false, - false, true, false, true, false, true, false, false, true)); - - assertThat(wrapEncodeMfm(Bytes.of(0xc2))) - .isEqualTo(bits(false, true, false, true, false, false, true, - false, true, false, true, false, false, true, false, - false)); - - assertThat(wrapEncodeMfm(Bytes.of(0xb0))) - .isEqualTo(bits(false, true, false, false, false, true, false, - true, false, false, true, false, true, false, true, - false)); - } - - @Test - public void encodeFm() - { - assertThat(wrapEncodeFm(Bytes.of(0x00))) - .isEqualTo(bits(true, false, true, false, true, false, true, - false, true, false, true, false, true, false, true, - false)); - - assertThat(wrapEncodeFm(Bytes.of(0x81))) - .isEqualTo(bits(true, true, true, false, true, false, true, - false, true, false, true, false, true, false, true, - true)); - } - private static Bits wrapEncodeMfm(Bytes bytes) { Bits bits = new Bits(16); @@ -85,4 +35,144 @@ private static Bits bits(boolean... values) bits.setBit(i, values[i]); return bits; } + + @Test + public void decode() + { + assertThat(FmMfm.decodeFmMfm(bits( + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false))).isEqualTo(Bytes.of(0x00)); + + assertThat(FmMfm.decodeFmMfm(bits( + true, + true, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + true))).isEqualTo(Bytes.of(0x81)); + + assertThat(FmMfm.decodeFmMfm(bits(true, true, true, false))).isEqualTo(Bytes.of(0x80)); + } + + @Test + public void encodeMfm() + { + assertThat(wrapEncodeMfm(Bytes.of(0xa1))).isEqualTo(bits( + false, + true, + false, + false, + false, + true, + false, + false, + true, + false, + true, + false, + true, + false, + false, + true)); + + assertThat(wrapEncodeMfm(Bytes.of(0xc2))).isEqualTo(bits( + false, + true, + false, + true, + false, + false, + true, + false, + true, + false, + true, + false, + false, + true, + false, + false)); + + assertThat(wrapEncodeMfm(Bytes.of(0xb0))).isEqualTo(bits( + false, + true, + false, + false, + false, + true, + false, + true, + false, + false, + true, + false, + true, + false, + true, + false)); + } + + @Test + public void encodeFm() + { + assertThat(wrapEncodeFm(Bytes.of(0x00))).isEqualTo(bits( + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false)); + + assertThat(wrapEncodeFm(Bytes.of(0x81))).isEqualTo(bits( + true, + true, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + false, + true, + true)); + } } \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java b/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java index ca2e5d95..39c554de 100644 --- a/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java +++ b/javatests/com/cowlark/fluxengine/external/GreaseweazleUtilsTest.java @@ -15,47 +15,39 @@ public class GreaseweazleUtilsTest private static void testConvert(Bytes gwBytes, Bytes flBytes) { - assertThat(GreaseweazleUtils.greaseweazleToFluxEngine(gwBytes, CLOCK)) - .isEqualTo(flBytes); - assertThat(GreaseweazleUtils.fluxEngineToGreaseweazle(flBytes, CLOCK)) - .isEqualTo(gwBytes); + assertThat(GreaseweazleUtils.greaseweazleToFluxEngine(gwBytes, CLOCK)).isEqualTo(flBytes); + assertThat(GreaseweazleUtils.fluxEngineToGreaseweazle(flBytes, CLOCK)).isEqualTo(gwBytes); } private static Bytes encode28(int val) { - return Bytes.of(1 | (val << 1) & 0xff, - 1 | (val >> 6) & 0xff, - 1 | (val >> 13) & 0xff, - 1 | (val >> 20) & 0xff); + return Bytes.of( + 1 | (val << 1) & 0xff, + 1 | (val >> 6) & 0xff, + 1 | (val >> 13) & 0xff, + 1 | (val >> 20) & 0xff); } @Test public void conversions() { /* Simple one-byte intervals. */ - testConvert(Bytes.of(1, 1, 1, 1, 0), - Bytes.of(0x82, 0x82, 0x82, 0x82)); + testConvert(Bytes.of(1, 1, 1, 1, 0), Bytes.of(0x82, 0x82, 0x82, 0x82)); /* Larger one-byte intervals. */ - testConvert(Bytes.of(32, 0), - Bytes.of(0x3f, 0x81)); - testConvert(Bytes.of(64, 0), - Bytes.of(0x3f, 0x3f, 0x82)); - testConvert(Bytes.of(128, 0), - Bytes.of(0x3f, 0x3f, 0x3f, 0x3f, 0x84)); + testConvert(Bytes.of(32, 0), Bytes.of(0x3f, 0x81)); + testConvert(Bytes.of(64, 0), Bytes.of(0x3f, 0x3f, 0x82)); + testConvert(Bytes.of(128, 0), Bytes.of(0x3f, 0x3f, 0x3f, 0x3f, 0x84)); /* Two-byte intervals. */ - testConvert(Bytes.of(250, 1, 0), - Bytes.of(0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xbb)); + testConvert(Bytes.of(250, 1, 0), Bytes.of(0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0xbb)); /* Very long intervals. */ Bytes gw = new Bytes(0); - new ByteWriter(gw) - .write8(255) - .write8(2) /* FLUXOP_SPACE */ - .write(encode28(2048 - 249)) - .write8(249) - .write8(0); + new ByteWriter(gw).write8(255) + .write8(2) /* FLUXOP_SPACE */.write(encode28(2048 - 249)) + .write8(249) + .write8(0); Bytes fl = new Bytes(0); ByteWriter bw = new ByteWriter(fl); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java index a2f5c291..b023014f 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java @@ -4,42 +4,19 @@ import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.external.DriveType; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; @RunWith(JUnit4.class) public class A2RFluxSourceTest { - @Test - public void readsTracks() throws IOException - { - Path path = writeTempFile(); - - A2RFluxSource source = new A2RFluxSource(A2rFluxSourceProto.newBuilder() - .setFilename(path.toString()) - .build()); - - FluxSourceIterator iterator = source.readFlux(0, 0); - assertThat(iterator.hasNext()).isTrue(); - Bytes expected = Bytes.of(0x40, 0xad, 0xad, 0xad); - assertThat(iterator.next().rawBytes().toByteArray()).isEqualTo(expected.toByteArray()); - assertThat(iterator.hasNext()).isFalse(); - assertThat(source.readFlux(1, 0)).isInstanceOf(EmptyFluxSourceIterator.class); - - ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); - source.adjustConfig(configBuilder); - ConfigProto config = configBuilder.build(); - assertThat(config.getDrive().getTracks()).isEqualTo("c0h0"); - assertThat(config.getDrive().getDriveType()).isEqualTo(DriveType.DRIVETYPE_80TRACK); - } - /* Builds an A2R file containing a single track 0/0, encoded as a 3.5" * disk with two short intervals. */ private static Path writeTempFile() throws IOException @@ -95,4 +72,27 @@ private static void writeChunk(ByteWriter bw, String id) for (int i = 0; i < 4; i++) bw.write8(id.charAt(i)); } + + @Test + public void readsTracks() throws IOException + { + Path path = writeTempFile(); + + A2RFluxSource source = new A2RFluxSource(A2rFluxSourceProto.newBuilder() + .setFilename(path.toString()) + .build()); + + FluxSourceIterator iterator = source.readFlux(0, 0); + assertThat(iterator.hasNext()).isTrue(); + Bytes expected = Bytes.of(0x40, 0xad, 0xad, 0xad); + assertThat(iterator.next().rawBytes().toByteArray()).isEqualTo(expected.toByteArray()); + assertThat(iterator.hasNext()).isFalse(); + assertThat(source.readFlux(1, 0)).isInstanceOf(EmptyFluxSourceIterator.class); + + ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); + source.adjustConfig(configBuilder); + ConfigProto config = configBuilder.build(); + assertThat(config.getDrive().getTracks()).isEqualTo("c0h0"); + assertThat(config.getDrive().getDriveType()).isEqualTo(DriveType.DRIVETYPE_80TRACK); + } } \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java index d5f3f1a5..bb270e56 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java @@ -8,16 +8,23 @@ import com.cowlark.fluxengine.external.FluxFileVersion; import com.cowlark.fluxengine.external.TrackFluxProto; import com.google.protobuf.ByteString; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; @RunWith(JUnit4.class) public class Fl2FluxSourceTest { + private static Path writeTemp(FluxFileProto file) throws IOException + { + Path path = Files.createTempFile("flux", ".fl2"); + Files.write(path, file.toByteArray()); + return path; + } + @Test public void readsTracks() throws IOException { @@ -75,11 +82,4 @@ public void upgradesVersion1() throws IOException iterator.next(); assertThat(iterator.hasNext()).isFalse(); } - - private static Path writeTemp(FluxFileProto file) throws IOException - { - Path path = Files.createTempFile("flux", ".fl2"); - Files.write(path, file.toByteArray()); - return path; - } } diff --git a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java index 456177e8..35b0272f 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java @@ -18,9 +18,8 @@ public class FluxSourceTest @Test public void createUnknownTypeReturnsNull() { - FluxSourceProto config = FluxSourceProto.newBuilder() - .setType(FluxSourceSinkType.FLUXTYPE_NOT_SET) - .build(); + FluxSourceProto config = + FluxSourceProto.newBuilder().setType(FluxSourceSinkType.FLUXTYPE_NOT_SET).build(); assertThat(FluxSource.create(config)).isNull(); } @@ -28,9 +27,8 @@ public void createUnknownTypeReturnsNull() @Test public void createUnportedTypeThrows() { - FluxSourceProto config = FluxSourceProto.newBuilder() - .setType(FluxSourceSinkType.FLUXTYPE_DRIVE) - .build(); + FluxSourceProto config = + FluxSourceProto.newBuilder().setType(FluxSourceSinkType.FLUXTYPE_DRIVE).build(); assertThrows(FluxEngineException.class, () -> FluxSource.create(config)); } @@ -38,9 +36,8 @@ public void createUnportedTypeThrows() @Test public void createEraseFluxSource() { - FluxSourceProto config = FluxSourceProto.newBuilder() - .setType(FluxSourceSinkType.FLUXTYPE_ERASE) - .build(); + FluxSourceProto config = + FluxSourceProto.newBuilder().setType(FluxSourceSinkType.FLUXTYPE_ERASE).build(); FluxSource source = FluxSource.create(config); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java index 875820bf..b3240c73 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java @@ -3,43 +3,40 @@ import static com.google.common.truth.Truth.assertThat; import com.cowlark.fluxengine.config.ConfigBuilder; -import com.cowlark.fluxengine.data.Fluxmap; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.stream.Collectors; import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import org.junit.rules.TemporaryFolder; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.stream.Collectors; @RunWith(JUnit4.class) public class KryofluxFluxSourceTest { - @Rule - public TemporaryFolder folder = new TemporaryFolder(); + @Rule public TemporaryFolder folder = new TemporaryFolder(); @Test public void readsSingleFluxFromDirectory() throws Exception { Path dir = folder.getRoot().toPath(); - Files.write(dir.resolve("track80.0.raw"), new byte[] {0x20}); - Files.write(dir.resolve("track81.1.raw"), new byte[] {0x20}); + Files.write(dir.resolve("track80.0.raw"), new byte[]{0x20}); + Files.write(dir.resolve("track81.1.raw"), new byte[]{0x20}); - KryofluxFluxSourceProto config = KryofluxFluxSourceProto.newBuilder() - .setDirectory(dir.toString()) - .build(); + KryofluxFluxSourceProto config = + KryofluxFluxSourceProto.newBuilder().setDirectory(dir.toString()).build(); KryofluxFluxSource source = new KryofluxFluxSource(config); - assertThat(source.readSingleFlux(80, 0).rawBytes().toByteArray()) - .isEqualTo(new byte[]{(byte) 0x8f}); + assertThat(source.readSingleFlux(80, 0) + .rawBytes() + .toByteArray()).isEqualTo(new byte[]{(byte) 0x8f}); ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); source.adjustConfig(configBuilder); String tracks = configBuilder.build().getDrive().getTracks(); - String sorted = Arrays.stream(tracks.split(" ")).sorted() - .collect(Collectors.joining(" ")); + String sorted = Arrays.stream(tracks.split(" ")).sorted().collect(Collectors.joining(" ")); assertThat(sorted).isEqualTo("c80h0 c81h1"); } } \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/testing/TestHelpers.java b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java index ace75878..d663b85b 100644 --- a/javatests/com/cowlark/fluxengine/testing/TestHelpers.java +++ b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java @@ -1,7 +1,5 @@ package com.cowlark.fluxengine.testing; -import com.cowlark.fluxengine.core.Bytes; - public class TestHelpers { } From d39cb1522fb704096e88da17d336abc749419858 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 12:11:29 +0200 Subject: [PATCH 104/192] Port Record and Sector. --- java/com/cowlark/fluxengine/data/Record.java | 21 +++++ java/com/cowlark/fluxengine/data/Sector.java | 94 +++++++++++++++++++ .../com/cowlark/fluxengine/data/BUILD.bazel | 22 +++++ .../cowlark/fluxengine/data/RecordTest.java | 41 ++++++++ .../cowlark/fluxengine/data/SectorTest.java | 68 ++++++++++++++ lib/data/sector.cc | 75 --------------- lib/data/sector.h | 70 -------------- 7 files changed, 246 insertions(+), 145 deletions(-) create mode 100644 java/com/cowlark/fluxengine/data/Record.java create mode 100644 java/com/cowlark/fluxengine/data/Sector.java create mode 100644 javatests/com/cowlark/fluxengine/data/RecordTest.java create mode 100644 javatests/com/cowlark/fluxengine/data/SectorTest.java delete mode 100644 lib/data/sector.cc delete mode 100644 lib/data/sector.h diff --git a/java/com/cowlark/fluxengine/data/Record.java b/java/com/cowlark/fluxengine/data/Record.java new file mode 100644 index 00000000..e9dd42b5 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Record.java @@ -0,0 +1,21 @@ +package com.cowlark.fluxengine.data; + +import com.cowlark.fluxengine.core.Bytes; +import java.util.ArrayList; +import java.util.List; + +/** + * A single record on a track, ported from lib/data/disk.h. + */ +public class Record +{ + public double clock = 0.0; + public double startTime = 0.0; + public double endTime = 0.0; + public int position = 0; + public Bytes rawData = new Bytes(); + + public Record() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/Sector.java b/java/com/cowlark/fluxengine/data/Sector.java new file mode 100644 index 00000000..8d339de9 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Sector.java @@ -0,0 +1,94 @@ +package com.cowlark.fluxengine.data; + +import com.cowlark.fluxengine.core.Bytes; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; + +/** + * A sector, ported from lib/data/sector.h. + */ +public class Sector +{ + public enum Status + { + OK, + BAD_CHECKSUM, + MISSING, + DATA_MISSING, + CONFLICT, + INTERNAL_ERROR + } + + /* The logical location of this sector. */ + + public LogicalLocation location; + + public Status status = Status.INTERNAL_ERROR; + public int position = 0; + public Duration clock = Duration.ZERO; + public Duration headerStartTime = Duration.ZERO; + public Duration headerEndTime = Duration.ZERO; + public Duration dataStartTime = Duration.ZERO; + public Duration dataEndTime = Duration.ZERO; + public CylinderHead physicalLocation = null; + public Bytes data = new Bytes(); + public List records = new ArrayList<>(); + + public Sector(LogicalLocation location) + { + this.location = location; + } + + public static String statusToString(Status status) + { + switch (status) + { + case OK: + return "OK"; + case BAD_CHECKSUM: + return "bad checksum"; + case MISSING: + return "sector not found"; + case DATA_MISSING: + return "present but no data found"; + case CONFLICT: + return "conflicting data"; + default: + return String.format("unknown error %d", status.ordinal()); + } + } + + public static String statusToChar(Status status) + { + switch (status) + { + case OK: + return ""; + case MISSING: + return "?"; + case BAD_CHECKSUM: + case DATA_MISSING: + return "!"; + case CONFLICT: + return "*"; + default: + return "?"; + } + } + + public static Status stringToStatus(String value) + { + if (value.equals("OK")) + return Status.OK; + if (value.equals("bad checksum")) + return Status.BAD_CHECKSUM; + if (value.equals("sector not found") || value.equals("MISSING")) + return Status.MISSING; + if (value.equals("present but no data found")) + return Status.DATA_MISSING; + if (value.equals("conflicting data")) + return Status.CONFLICT; + return Status.INTERNAL_ERROR; + } +} diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index 9db0ebe4..af1ffa24 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -63,3 +63,25 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "SectorTest", + srcs = ["SectorTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "RecordTest", + srcs = ["RecordTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/data/RecordTest.java b/javatests/com/cowlark/fluxengine/data/RecordTest.java new file mode 100644 index 00000000..9d8e7f7e --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/RecordTest.java @@ -0,0 +1,41 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class RecordTest +{ + @Test + public void defaultsAreEmptyRecord() + { + Record record = new Record(); + + assertThat(record.clock).isEqualTo(0.0); + assertThat(record.startTime).isEqualTo(0.0); + assertThat(record.endTime).isEqualTo(0.0); + assertThat(record.position).isEqualTo(0); + assertThat(record.rawData.isEmpty()).isTrue(); + } + + @Test + public void holdsFields() + { + Record record = new Record(); + record.clock = 123.0; + record.startTime = 456.0; + record.endTime = 789.0; + record.position = 42; + record.rawData = Bytes.of(0x11, 0x22); + + assertThat(record.clock).isEqualTo(123.0); + assertThat(record.startTime).isEqualTo(456.0); + assertThat(record.endTime).isEqualTo(789.0); + assertThat(record.position).isEqualTo(42); + assertThat(record.rawData).isEqualTo(Bytes.of(0x11, 0x22)); + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/data/SectorTest.java b/javatests/com/cowlark/fluxengine/data/SectorTest.java new file mode 100644 index 00000000..cc15ead3 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/SectorTest.java @@ -0,0 +1,68 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import java.time.Duration; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SectorTest +{ + @Test + public void defaultsAreEmptySector() + { + Sector sector = new Sector(new LogicalLocation(0, 0, 0)); + + assertThat(sector.location).isEqualTo(new LogicalLocation(0, 0, 0)); + assertThat(sector.status).isEqualTo(Sector.Status.INTERNAL_ERROR); + assertThat(sector.position).isEqualTo(0); + assertThat(sector.clock).isEqualTo(Duration.ZERO); + assertThat(sector.headerStartTime).isEqualTo(Duration.ZERO); + assertThat(sector.headerEndTime).isEqualTo(Duration.ZERO); + assertThat(sector.dataStartTime).isEqualTo(Duration.ZERO); + assertThat(sector.dataEndTime).isEqualTo(Duration.ZERO); + assertThat(sector.physicalLocation).isNull(); + assertThat(sector.data.isEmpty()).isTrue(); + assertThat(sector.records).isEmpty(); + } + + @Test + public void holdsLogicalLocation() + { + LogicalLocation location = new LogicalLocation(1, 2, 3); + Sector sector = new Sector(location); + + assertThat(sector.location).isSameInstanceAs(location); + assertThat(sector.location.trackLocation()).isEqualTo(new CylinderHead(1, 2)); + } + + @Test + public void statusStringRoundTrips() + { + for (Sector.Status status : Sector.Status.values()) + { + assertThat(Sector.stringToStatus(Sector.statusToString(status))) + .isEqualTo(status); + } + } + + @Test + public void statusToStringIsReadable() + { + assertThat(Sector.statusToString(Sector.Status.OK)).isEqualTo("OK"); + assertThat(Sector.statusToString(Sector.Status.MISSING)).isEqualTo("sector not found"); + assertThat(Sector.statusToString(Sector.Status.DATA_MISSING)) + .isEqualTo("present but no data found"); + } + + @Test + public void stringToStatusAcceptsChars() + { + assertThat(Sector.stringToStatus("OK")).isEqualTo(Sector.Status.OK); + assertThat(Sector.stringToStatus("MISSING")).isEqualTo(Sector.Status.MISSING); + assertThat(Sector.stringToStatus("bad checksum")).isEqualTo(Sector.Status.BAD_CHECKSUM); + assertThat(Sector.stringToStatus("garbage")).isEqualTo(Sector.Status.INTERNAL_ERROR); + } +} diff --git a/lib/data/sector.cc b/lib/data/sector.cc deleted file mode 100644 index cefe78eb..00000000 --- a/lib/data/sector.cc +++ /dev/null @@ -1,75 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/data/disk.h" -#include "lib/data/sector.h" -#include "lib/data/layout.h" - -Sector::Sector(const LogicalLocation& location): LogicalLocation(location) {} - -std::string Sector::statusToString(Status status) -{ - switch (status) - { - case Status::OK: - return "OK"; - case Status::BAD_CHECKSUM: - return "bad checksum"; - case Status::MISSING: - return "sector not found"; - case Status::DATA_MISSING: - return "present but no data found"; - case Status::CONFLICT: - return "conflicting data"; - default: - return fmt::format("unknown error {}", (int)status); - } -} - -std::string Sector::statusToChar(Status status) -{ - switch (status) - { - case Status::OK: - return ""; - case Status::MISSING: - return "?"; - case Status::BAD_CHECKSUM: - return "!"; - case Status::DATA_MISSING: - return "!"; - case Status::CONFLICT: - return "*"; - default: - return "?"; - } -} - -Sector::Status Sector::stringToStatus(const std::string& value) -{ - if (value == "OK") - return Status::OK; - if (value == "bad checksum") - return Status::BAD_CHECKSUM; - if ((value == "sector not found") || (value == "MISSING")) - return Status::MISSING; - if (value == "present but no data found") - return Status::DATA_MISSING; - if (value == "conflicting data") - return Status::CONFLICT; - return Status::INTERNAL_ERROR; -} - -bool sectorPointerSortPredicate(const std::shared_ptr& lhs, - const std::shared_ptr& rhs) -{ - return *lhs < *rhs; -} - -bool sectorPointerEqualsPredicate(const std::shared_ptr& lhs, - const std::shared_ptr& rhs) -{ - if (!lhs && !rhs) - return true; - if (!lhs || !rhs) - return false; - return *lhs == *rhs; -} diff --git a/lib/data/sector.h b/lib/data/sector.h deleted file mode 100644 index d198ff9a..00000000 --- a/lib/data/sector.h +++ /dev/null @@ -1,70 +0,0 @@ -#ifndef SECTOR_H -#define SECTOR_H - -#include "lib/core/bytes.h" -#include "lib/data/fluxmap.h" -#include "lib/data/locations.h" - -class Record; -class LogicalTrackLayout; - -struct Sector : public LogicalLocation -{ - enum Status - { - OK, - BAD_CHECKSUM, - MISSING, - DATA_MISSING, - CONFLICT, - INTERNAL_ERROR, - }; - - static std::string statusToString(Status status); - static std::string statusToChar(Status status); - static Status stringToStatus(const std::string& value); - - Status status = Status::INTERNAL_ERROR; - uint32_t position = 0; - nanoseconds_t clock = 0; - nanoseconds_t headerStartTime = 0; - nanoseconds_t headerEndTime = 0; - nanoseconds_t dataStartTime = 0; - nanoseconds_t dataEndTime = 0; - std::optional physicalLocation = {}; - Bytes data; - std::vector> records; - - Sector(const Sector& other) = default; - Sector& operator=(const Sector& other) = default; - - Sector(const LogicalLocation& location); - - std::tuple key() const - { - return std::make_tuple( - logicalCylinder, logicalHead, logicalSector, status); - } - - std::strong_ordering operator<=>(const Sector& rhs) const - { - return key() <=> rhs.key(); - } -}; - -template <> -struct fmt::formatter : formatter -{ - auto format(Sector::Status status, format_context& ctx) const - { - return fmt::format_to(ctx.out(), "{}", Sector::statusToString(status)); - } -}; - -extern bool sectorPointerSortPredicate(const std::shared_ptr& lhs, - const std::shared_ptr& rhs); -extern bool sectorPointerEqualsPredicate( - const std::shared_ptr& lhs, - const std::shared_ptr& rhs); - -#endif From 912bca5ded190949da97b4d9bb076711ae694c44 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 12:36:48 +0200 Subject: [PATCH 105/192] Actually, Duration just makes things annoying; use doubles instead. --- .../cowlark/fluxengine/cli/RpmCommand.java | 11 +++++----- .../cowlark/fluxengine/data/FluxPosition.java | 6 ++---- .../fluxengine/data/FluxmapReader.java | 5 ++--- java/com/cowlark/fluxengine/data/Record.java | 6 +++--- java/com/cowlark/fluxengine/data/Sector.java | 11 +++++----- .../fluxengine/decoders/FluxDecoder.java | 4 +--- .../fluxengine/usb/GreaseweazleUsbDevice.java | 20 +++++++++---------- .../com/cowlark/fluxengine/usb/UsbDevice.java | 11 +++++----- .../cowlark/fluxengine/data/RecordTest.java | 18 ++++++++--------- .../cowlark/fluxengine/data/SectorTest.java | 11 +++++----- .../fluxengine/decoders/FluxDecoderTest.java | 11 +++++----- 11 files changed, 52 insertions(+), 62 deletions(-) diff --git a/java/com/cowlark/fluxengine/cli/RpmCommand.java b/java/com/cowlark/fluxengine/cli/RpmCommand.java index 8c37ae84..b57ce736 100644 --- a/java/com/cowlark/fluxengine/cli/RpmCommand.java +++ b/java/com/cowlark/fluxengine/cli/RpmCommand.java @@ -8,7 +8,6 @@ import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; import com.google.common.collect.ImmutableList; -import java.time.Duration; /** * Measure the disk rotational speed, modelled after src/fe-rpm.cc. @@ -31,12 +30,12 @@ public void run(ImmutableList args) UsbDevice device = UsbFactory.connect(config); - Duration period = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); - if (!period.isZero()) + double periodNs = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); + if (periodNs != 0.0) System.out.printf( - "Rotational period is %d ms (%.0f rpm)\n", - period.toMillis(), - 60e9 / period.toNanos()); + "Rotational period is %.0f ms (%.0f rpm)\n", + periodNs / 1e6, + 60e9 / periodNs); else System.out.println(""" No index pulses detected from the disk. Common causes of this are: diff --git a/java/com/cowlark/fluxengine/data/FluxPosition.java b/java/com/cowlark/fluxengine/data/FluxPosition.java index 0f912fbe..71a1effa 100644 --- a/java/com/cowlark/fluxengine/data/FluxPosition.java +++ b/java/com/cowlark/fluxengine/data/FluxPosition.java @@ -2,13 +2,11 @@ import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; -import java.time.Duration; - public record FluxPosition(int bytes, int ticks, int zeroes) { - public Duration getDuration() + public double getDurationNs() { - return Duration.ofNanos((long) (ticks * NS_PER_TICK)); + return ticks * NS_PER_TICK; } @Override diff --git a/java/com/cowlark/fluxengine/data/FluxmapReader.java b/java/com/cowlark/fluxengine/data/FluxmapReader.java index ec8d49e7..7ef5ea41 100644 --- a/java/com/cowlark/fluxengine/data/FluxmapReader.java +++ b/java/com/cowlark/fluxengine/data/FluxmapReader.java @@ -7,7 +7,6 @@ import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.decoders.DecoderProto; -import java.time.Duration; /** * A cursor over a Fluxmap's raw bytes. @@ -55,9 +54,9 @@ public void seek(FluxPosition pos) posZeroes = pos.zeroes(); } - public Duration getDuration() + public double getDurationNs() { - return Duration.ofNanos((long) (fluxmap.ticks() * NS_PER_TICK)); + return fluxmap.ticks() * NS_PER_TICK; } public int getCurrentEvent() diff --git a/java/com/cowlark/fluxengine/data/Record.java b/java/com/cowlark/fluxengine/data/Record.java index e9dd42b5..714ee27f 100644 --- a/java/com/cowlark/fluxengine/data/Record.java +++ b/java/com/cowlark/fluxengine/data/Record.java @@ -9,9 +9,9 @@ */ public class Record { - public double clock = 0.0; - public double startTime = 0.0; - public double endTime = 0.0; + public double clockNs = 0.0; + public double startTimeNs = 0.0; + public double endTimeNs = 0.0; public int position = 0; public Bytes rawData = new Bytes(); diff --git a/java/com/cowlark/fluxengine/data/Sector.java b/java/com/cowlark/fluxengine/data/Sector.java index 8d339de9..f31b5b26 100644 --- a/java/com/cowlark/fluxengine/data/Sector.java +++ b/java/com/cowlark/fluxengine/data/Sector.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.data; import com.cowlark.fluxengine.core.Bytes; -import java.time.Duration; import java.util.ArrayList; import java.util.List; @@ -26,11 +25,11 @@ public enum Status public Status status = Status.INTERNAL_ERROR; public int position = 0; - public Duration clock = Duration.ZERO; - public Duration headerStartTime = Duration.ZERO; - public Duration headerEndTime = Duration.ZERO; - public Duration dataStartTime = Duration.ZERO; - public Duration dataEndTime = Duration.ZERO; + public double clockNs = 0.0; + public double headerStartTimeNs = 0.0; + public double headerEndTimeNs = 0.0; + public double dataStartTimeNs = 0.0; + public double dataEndTimeNs = 0.0; public CylinderHead physicalLocation = null; public Bytes data = new Bytes(); public List records = new ArrayList<>(); diff --git a/java/com/cowlark/fluxengine/decoders/FluxDecoder.java b/java/com/cowlark/fluxengine/decoders/FluxDecoder.java index 05741d95..22bde01c 100644 --- a/java/com/cowlark/fluxengine/decoders/FluxDecoder.java +++ b/java/com/cowlark/fluxengine/decoders/FluxDecoder.java @@ -5,7 +5,6 @@ import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.data.FluxPosition; import com.cowlark.fluxengine.data.FluxmapReader; -import java.time.Duration; /* This is a port of the samdisk code: * @@ -31,13 +30,12 @@ public class FluxDecoder private boolean syncLost = false; private int leadingZeroes; - public FluxDecoder(FluxmapReader fmr, Duration bitcell, DecoderProto config) + public FluxDecoder(FluxmapReader fmr, double bitcellNs, DecoderProto config) { this.fmr = fmr; pllPhase = config.getPllPhase(); pllAdjust = config.getPllAdjust(); fluxScale = config.getFluxScale(); - double bitcellNs = bitcell.toNanos(); clockNs = bitcellNs; clockCentreNs = bitcellNs; clockMinNs = bitcellNs * (1.0 - pllAdjust); diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index 7f173bd8..97ed3384 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -184,7 +184,7 @@ public void seek(int track) } @Override - public Duration getRotationalPeriod(int hardSectorCount) + public double getRotationalPeriod(int hardSectorCount) { if (hardSectorCount != 0) throw new FluxEngineException( @@ -259,7 +259,7 @@ else if (secondIndex == -1) doCommand(CMD_GET_FLUX_STATUS); revolutions = (secondIndex - firstIndex) * clock; - return Duration.ofNanos(revolutions); + return revolutions; } @Override @@ -350,9 +350,9 @@ public void testBulkRead() } @Override - public Bytes read(int side, boolean synced, Duration readTime, Duration hardSectorThreshold) + public Bytes read(int side, boolean synced, double readTimeNs, double hardSectorThresholdNs) { - if (!hardSectorThreshold.isZero()) + if (hardSectorThresholdNs != 0.0) throw new FluxEngineException( "hard sectors are currently unsupported on the " + "Greaseweazle"); @@ -362,7 +362,7 @@ public Bytes read(int side, boolean synced, Duration readTime, Duration hardSect { case V22: { - long revs = (readTime.toNanos() + revolutions - 1) / revolutions; + long revs = (long) ((readTimeNs + revolutions - 1) / revolutions); Bytes cmd = new Bytes(0); ByteWriter bw = new ByteWriter(cmd); bw.write8(CMD_READ_FLUX); @@ -379,7 +379,7 @@ public Bytes read(int side, boolean synced, Duration readTime, Duration hardSect ByteWriter bw = new ByteWriter(cmd); bw.write8(CMD_READ_FLUX); bw.write8(8); - bw.writeLe32((int) ((readTime.toNanos() + (synced ? revolutions : 0)) / clock)); + bw.writeLe32((int) ((readTimeNs + (synced ? revolutions : 0)) / clock)); bw.writeLe16(0); doCommand(cmd); } @@ -404,9 +404,9 @@ public Bytes read(int side, boolean synced, Duration readTime, Duration hardSect } @Override - public void write(int side, Bytes fldata, Duration hardSectorThreshold) + public void write(int side, Bytes fldata, double hardSectorThresholdNs) { - if (!hardSectorThreshold.isZero()) + if (hardSectorThresholdNs != 0.0) throw new FluxEngineException( "hard sectors are currently unsupported on the " + "Greaseweazle"); @@ -430,9 +430,9 @@ public void write(int side, Bytes fldata, Duration hardSectorThreshold) } @Override - public void erase(int side, Duration hardSectorThreshold) + public void erase(int side, double hardSectorThresholdNs) { - if (!hardSectorThreshold.isZero()) + if (hardSectorThresholdNs != 0.0) throw new FluxEngineException( "hard sectors are currently unsupported on the " + "Greaseweazle"); diff --git a/java/com/cowlark/fluxengine/usb/UsbDevice.java b/java/com/cowlark/fluxengine/usb/UsbDevice.java index 281ab55f..76307363 100644 --- a/java/com/cowlark/fluxengine/usb/UsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/UsbDevice.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.usb; import com.cowlark.fluxengine.core.Bytes; -import java.time.Duration; /** * Base class for USB floppy drive devices, ported from lib/usb/usb.h. @@ -15,7 +14,7 @@ public void recalibrate() public abstract void seek(int track); - public abstract Duration getRotationalPeriod(int hardSectorCount); + public abstract double getRotationalPeriod(int hardSectorCount); public abstract void testBulkWrite(); @@ -23,12 +22,12 @@ public void recalibrate() public abstract Bytes read(int side, boolean synced, - Duration readTime, - Duration hardSectorThreshold); + double readTimeNs, + double hardSectorThresholdNs); - public abstract void write(int side, Bytes bytes, Duration hardSectorThreshold); + public abstract void write(int side, Bytes bytes, double hardSectorThresholdNs); - public abstract void erase(int side, Duration hardSectorThreshold); + public abstract void erase(int side, double hardSectorThresholdNs); public abstract void setDrive(int drive, boolean highDensity, int indexMode); diff --git a/javatests/com/cowlark/fluxengine/data/RecordTest.java b/javatests/com/cowlark/fluxengine/data/RecordTest.java index 9d8e7f7e..4f9fb2f0 100644 --- a/javatests/com/cowlark/fluxengine/data/RecordTest.java +++ b/javatests/com/cowlark/fluxengine/data/RecordTest.java @@ -15,9 +15,9 @@ public void defaultsAreEmptyRecord() { Record record = new Record(); - assertThat(record.clock).isEqualTo(0.0); - assertThat(record.startTime).isEqualTo(0.0); - assertThat(record.endTime).isEqualTo(0.0); + assertThat(record.clockNs).isEqualTo(0.0); + assertThat(record.startTimeNs).isEqualTo(0.0); + assertThat(record.endTimeNs).isEqualTo(0.0); assertThat(record.position).isEqualTo(0); assertThat(record.rawData.isEmpty()).isTrue(); } @@ -26,15 +26,15 @@ public void defaultsAreEmptyRecord() public void holdsFields() { Record record = new Record(); - record.clock = 123.0; - record.startTime = 456.0; - record.endTime = 789.0; + record.clockNs = 123.0; + record.startTimeNs = 456.0; + record.endTimeNs = 789.0; record.position = 42; record.rawData = Bytes.of(0x11, 0x22); - assertThat(record.clock).isEqualTo(123.0); - assertThat(record.startTime).isEqualTo(456.0); - assertThat(record.endTime).isEqualTo(789.0); + assertThat(record.clockNs).isEqualTo(123.0); + assertThat(record.startTimeNs).isEqualTo(456.0); + assertThat(record.endTimeNs).isEqualTo(789.0); assertThat(record.position).isEqualTo(42); assertThat(record.rawData).isEqualTo(Bytes.of(0x11, 0x22)); } diff --git a/javatests/com/cowlark/fluxengine/data/SectorTest.java b/javatests/com/cowlark/fluxengine/data/SectorTest.java index cc15ead3..e0124793 100644 --- a/javatests/com/cowlark/fluxengine/data/SectorTest.java +++ b/javatests/com/cowlark/fluxengine/data/SectorTest.java @@ -2,7 +2,6 @@ import static com.google.common.truth.Truth.assertThat; -import java.time.Duration; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -18,11 +17,11 @@ public void defaultsAreEmptySector() assertThat(sector.location).isEqualTo(new LogicalLocation(0, 0, 0)); assertThat(sector.status).isEqualTo(Sector.Status.INTERNAL_ERROR); assertThat(sector.position).isEqualTo(0); - assertThat(sector.clock).isEqualTo(Duration.ZERO); - assertThat(sector.headerStartTime).isEqualTo(Duration.ZERO); - assertThat(sector.headerEndTime).isEqualTo(Duration.ZERO); - assertThat(sector.dataStartTime).isEqualTo(Duration.ZERO); - assertThat(sector.dataEndTime).isEqualTo(Duration.ZERO); + assertThat(sector.clockNs).isEqualTo(0.0); + assertThat(sector.headerStartTimeNs).isEqualTo(0.0); + assertThat(sector.headerEndTimeNs).isEqualTo(0.0); + assertThat(sector.dataStartTimeNs).isEqualTo(0.0); + assertThat(sector.dataEndTimeNs).isEqualTo(0.0); assertThat(sector.physicalLocation).isNull(); assertThat(sector.data.isEmpty()).isTrue(); assertThat(sector.records).isEmpty(); diff --git a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java index 909f2ac4..7404bcc7 100644 --- a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java +++ b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java @@ -10,14 +10,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.time.Duration; @RunWith(JUnit4.class) public class FluxDecoderTest { private static final int CLOCK_TICKS = 1000; - private static final Duration CLOCK = - Duration.ofNanos((long) (CLOCK_TICKS * 1000000000.0 / 12000000.0)); + private static final double CLOCK_NS = + CLOCK_TICKS * 1000000000.0 / 12000000.0; private static Bytes roundTrip(Bytes data) { @@ -28,7 +27,7 @@ private static Bytes roundTrip(Bytes data) Fluxmap map = new Fluxmap(); map.appendBits(encoded, CLOCK_TICKS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); - FluxDecoder decoder = new FluxDecoder(reader, CLOCK, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); /* ...and read the raw bits back, skipping the PLL init pulse. */ Bits decoded = new Bits(); @@ -56,7 +55,7 @@ public void emitsAClockForEveryFluxTransition() java.util.Arrays.asList(true, true, true, true, true, true, true, true), CLOCK_TICKS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); - FluxDecoder decoder = new FluxDecoder(reader, CLOCK, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); Bits bits = new Bits(); while (!reader.eof()) @@ -75,7 +74,7 @@ public void firstBitIsAlwaysTrue() Fluxmap map = new Fluxmap(); map.appendBits(java.util.Arrays.asList(true), CLOCK_TICKS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); - FluxDecoder decoder = new FluxDecoder(reader, CLOCK, DecoderProto.getDefaultInstance()); + FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); assertThat(decoder.readBit()).isTrue(); } From 22c0f6c7999c4e2d3f15a523fb5515b4c4d0cf95 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 12:56:22 +0200 Subject: [PATCH 106/192] Port FluxPattern and friends. --- java/com/cowlark/fluxengine/data/BUILD.bazel | 11 +- .../cowlark/fluxengine/data/FluxMatch.java | 13 ++ .../cowlark/fluxengine/data/FluxMatcher.java | 20 +++ .../cowlark/fluxengine/data/FluxMatchers.java | 46 ++++++ .../cowlark/fluxengine/data/FluxPattern.java | 140 ++++++++++++++++++ java/com/cowlark/fluxengine/data/Track.java | 17 +++ .../cowlark/fluxengine/data/TrackInfo.java | 30 ++-- .../com/cowlark/fluxengine/data/BUILD.bazel | 11 ++ .../fluxengine/data/FluxPatternTest.java | 86 +++++++++++ 9 files changed, 359 insertions(+), 15 deletions(-) create mode 100644 java/com/cowlark/fluxengine/data/FluxMatch.java create mode 100644 java/com/cowlark/fluxengine/data/FluxMatcher.java create mode 100644 java/com/cowlark/fluxengine/data/FluxMatchers.java create mode 100644 java/com/cowlark/fluxengine/data/FluxPattern.java create mode 100644 java/com/cowlark/fluxengine/data/Track.java create mode 100644 javatests/com/cowlark/fluxengine/data/FluxPatternTest.java diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel index e9f9f1d8..d6a38053 100644 --- a/java/com/cowlark/fluxengine/data/BUILD.bazel +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -1,10 +1,18 @@ -load("@rules_java//java:defs.bzl", "java_library") +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") package(default_visibility = ["//visibility:public"]) +java_plugin( + name = "lombok_plugin", + generates_api = True, + processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + deps = ["@maven//:org_projectlombok_lombok"], +) + java_library( name = "data", srcs = glob(["*.java"]), + plugins = [":lombok_plugin"], deps = [ "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/config:layout_java_proto", @@ -14,5 +22,6 @@ java_library( "//java/com/cowlark/fluxengine/external:fl2_java_proto", "@com_google_protobuf//java/core", "@maven//:com_google_guava_guava", + "@maven//:org_projectlombok_lombok", ], ) diff --git a/java/com/cowlark/fluxengine/data/FluxMatch.java b/java/com/cowlark/fluxengine/data/FluxMatch.java new file mode 100644 index 00000000..f114e940 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/FluxMatch.java @@ -0,0 +1,13 @@ +package com.cowlark.fluxengine.data; + +/** + * The result of matching a pattern against a run of flux intervals, ported + * from lib/data/fluxpattern.h. + */ +public class FluxMatch +{ + public FluxMatcher matcher = null; + public int intervals = 0; + public double clock = 0.0; + public int zeroes = 0; +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/FluxMatcher.java b/java/com/cowlark/fluxengine/data/FluxMatcher.java new file mode 100644 index 00000000..6d2acafa --- /dev/null +++ b/java/com/cowlark/fluxengine/data/FluxMatcher.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.data; + +/* A special-casing: the matcher walks a sliding window of the last + * `intervals()` intervals and checks whether they match the pattern. */ + +/** + * A matcher over a run of flux intervals, ported from lib/data/fluxpattern.h. + */ +public interface FluxMatcher +{ + /* Intervals is the window of candidate intervals, with `endIndex` one + * past the newest (and most recently found) interval. The matcher + * examines the last `intervals().size()` entries (i.e. from + * `endIndex - intervals()` to `endIndex`); `match` receives the result. + */ + + boolean matches(long[] intervals, int endIndex, double clockDecodeThreshold, FluxMatch match); + + int intervals(); +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/FluxMatchers.java b/java/com/cowlark/fluxengine/data/FluxMatchers.java new file mode 100644 index 00000000..bbcb483c --- /dev/null +++ b/java/com/cowlark/fluxengine/data/FluxMatchers.java @@ -0,0 +1,46 @@ +package com.cowlark.fluxengine.data; + +import java.util.Arrays; +import java.util.List; + +/** + * A compound flux matcher that tries several matchers in turn, ported from + * lib/data/fluxpattern.{h,cc}. + */ +public class FluxMatchers implements FluxMatcher +{ + private final List matchers; + private final int intervalCount; + + public FluxMatchers(List matchers) + { + this.matchers = matchers; + intervalCount = matchers.stream() + .mapToInt(FluxMatcher::intervals) + .max() + .orElse(0); + } + + public static FluxMatchers of(FluxMatcher... matchers) + { + return new FluxMatchers(Arrays.asList(matchers)); + } + + @Override + public boolean matches(long[] candidates, int endIndex, double clockDecodeThreshold, + FluxMatch match) + { + for (FluxMatcher matcher : matchers) + { + if (matcher.matches(candidates, endIndex, clockDecodeThreshold, match)) + return true; + } + return false; + } + + @Override + public int intervals() + { + return intervalCount; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/FluxPattern.java b/java/com/cowlark/fluxengine/data/FluxPattern.java new file mode 100644 index 00000000..0f125f97 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/FluxPattern.java @@ -0,0 +1,140 @@ +package com.cowlark.fluxengine.data; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A single flux pattern, ported from lib/data/fluxpattern.{h,cc}. + */ +public class FluxPattern implements FluxMatcher +{ + private static final long TOPBIT = 1L << 63; + + private final int bitCount; + private final int highZeroes; + private final List intervals = new ArrayList<>(); + private final int length; + private boolean lowZero = false; + + public FluxPattern(int bits, long pattern) + { + bitCount = bits; + if (pattern == 0) + throw new IllegalArgumentException("flux pattern may not be zero"); + if (bits < 1 || bits > 64) + throw new IllegalArgumentException("flux pattern bit count must be 1..64"); + + int lowBit = findLowestSetBit(pattern) - 1; + + pattern <<= (64 - bits); + int highZeroesLocal = 0; + while ((pattern & TOPBIT) == 0) + { + pattern <<= 1; + highZeroesLocal++; + } + highZeroes = highZeroesLocal; + + int lengthLocal = 0; + while (pattern != TOPBIT) + { + int interval = 0; + do + { + pattern <<= 1; + interval++; + } while ((pattern & TOPBIT) == 0); + intervals.add(interval); + lengthLocal += interval; + } + length = lengthLocal; + + if (lowBit != 0) + { + lowZero = true; + intervals.add(lowBit + 1); + } + } + + /* Returns the index (1-based) of the lowest set bit, or 0 if none. */ + private static int findLowestSetBit(long value) + { + if (value == 0) + return 0; + int bit = 1; + while ((value & 1) == 0) + { + value >>= 1; + bit++; + } + return bit; + } + + @Override + /* The `endIndex` is one past the newest candidate interval, mirroring the + * C++ pointer passed as `&*candidates.end()`. */ + public boolean matches(long[] candidates, int endIndex, double clockDecodeThreshold, + FluxMatch match) + { + int start = endIndex - intervals.size(); + + int candidateLength = 0; + for (int i = start; i < endIndex - (lowZero ? 1 : 0); i++) + candidateLength += candidates[i]; + + if (candidateLength == 0) + return false; + match.clock = (double) candidateLength / (double) length; + + int exactIntervals = intervals.size() - (lowZero ? 1 : 0); + for (int i = 0; i < exactIntervals; i++) + { + double ii = match.clock * intervals.get(i); + double ci = candidates[start + i]; + + double error = Math.abs((ii - ci) / match.clock); + if (error > clockDecodeThreshold) + return false; + } + + if (lowZero) + { + double ii = match.clock * intervals.get(exactIntervals); + double ci = candidates[start + exactIntervals]; + + double error = (ii - ci) / match.clock; + if (error > clockDecodeThreshold) + return false; + } + + match.matcher = this; + match.intervals = intervals.size(); + match.zeroes = highZeroes; + return true; + } + + @Override + public int intervals() + { + return intervals.size(); + } + + /* Package-private accessors for the tests (mirrors the C++ `friend` + * test_patternconstruction/test_patternmatching). */ + + int getBitCount() + { + return bitCount; + } + + List getIntervals() + { + return Collections.unmodifiableList(intervals); + } + + int getHighZeroes() + { + return highZeroes; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/Track.java b/java/com/cowlark/fluxengine/data/Track.java new file mode 100644 index 00000000..cd2cc26d --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Track.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.data; + +import java.util.ArrayList; +import java.util.List; + +/** + * A decoded track, ported from lib/data/disk.h. + */ +public class Track +{ + public LogicalTrackLayout ltl; + public PhysicalTrackLayout ptl; + public Fluxmap fluxmap; + public List records = new ArrayList<>(); + public List allSectors = new ArrayList<>(); + public List normalisedSectors = new ArrayList<>(); +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/TrackInfo.java b/java/com/cowlark/fluxengine/data/TrackInfo.java index f2fd067d..cfe2625b 100644 --- a/java/com/cowlark/fluxengine/data/TrackInfo.java +++ b/java/com/cowlark/fluxengine/data/TrackInfo.java @@ -2,10 +2,12 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import lombok.Builder; /** * Summary information about a track, ported from lib/data/layout.h. */ +@Builder(setterPrefix = "set") public class TrackInfo { public final int numCylinders; @@ -48,20 +50,20 @@ public class TrackInfo /* Mapping of natural order to filesystem order. */ public final ImmutableMap naturalToFilesystemSectorMap; - public TrackInfo(int numCylinders, - int numHeads, - int numSectors, - int physicalCylinder, - int physicalHead, - int logicalCylinder, - int logicalHead, - int groupSize, - int sectorSize, - ImmutableList naturalSectorOrder, - ImmutableList diskSectorOrder, - ImmutableList filesystemSectorOrder, - ImmutableMap filesystemToNaturalSectorMap, - ImmutableMap naturalToFilesystemSectorMap) + private TrackInfo(int numCylinders, + int numHeads, + int numSectors, + int physicalCylinder, + int physicalHead, + int logicalCylinder, + int logicalHead, + int groupSize, + int sectorSize, + ImmutableList naturalSectorOrder, + ImmutableList diskSectorOrder, + ImmutableList filesystemSectorOrder, + ImmutableMap filesystemToNaturalSectorMap, + ImmutableMap naturalToFilesystemSectorMap) { this.numCylinders = numCylinders; this.numHeads = numHeads; diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index af1ffa24..41916f48 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -85,3 +85,14 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "FluxPatternTest", + srcs = ["FluxPatternTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/data/FluxPatternTest.java b/javatests/com/cowlark/fluxengine/data/FluxPatternTest.java new file mode 100644 index 00000000..4667a6ef --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/FluxPatternTest.java @@ -0,0 +1,86 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import com.google.common.collect.ImmutableList; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FluxPatternTest +{ + /* Ported from tests/fluxpattern.cc. */ + + @Test + public void testPatternConstruction() + { + FluxPattern fp1 = new FluxPattern(16, 0x0003); + assertThat(fp1.getBitCount()).isEqualTo(16); + assertThat(fp1.getIntervals()).containsExactlyElementsIn(ImmutableList.of(1)); + + FluxPattern fp2 = new FluxPattern(16, 0xc000); + assertThat(fp2.getBitCount()).isEqualTo(16); + assertThat(fp2.getIntervals()).containsExactlyElementsIn(ImmutableList.of(1, 15)); + + FluxPattern fp3 = new FluxPattern(16, 0x0050); + assertThat(fp3.getBitCount()).isEqualTo(16); + assertThat(fp3.getIntervals()).containsExactlyElementsIn(ImmutableList.of(2, 5)); + + FluxPattern fp4 = new FluxPattern(16, 0x0070); + assertThat(fp4.getBitCount()).isEqualTo(16); + assertThat(fp4.getIntervals()).containsExactlyElementsIn(ImmutableList.of(1, 1, 5)); + + FluxPattern fp5 = new FluxPattern(16, 0x0070); + assertThat(fp5.getBitCount()).isEqualTo(16); + assertThat(fp5.getIntervals()).containsExactlyElementsIn(ImmutableList.of(1, 1, 5)); + + FluxPattern fp6 = new FluxPattern(16, 0x0110); + assertThat(fp6.getBitCount()).isEqualTo(16); + assertThat(fp6.getIntervals()).containsExactlyElementsIn(ImmutableList.of(4, 5)); + } + + @Test + public void testPatternMatchingWithoutTrailingZeroes() + { + FluxPattern fp = new FluxPattern(16, 0x000b); + final long[] matching = {100, 100, 200, 100}; + final long[] notMatching = {100, 200, 100, 100}; + final long[] closeMatch1 = {90, 90, 180, 90}; + final long[] closeMatch2 = {110, 110, 220, 110}; + + FluxMatch match = new FluxMatch(); + assertThat(fp.matches(matching, 4, 0.40, match)).isTrue(); + assertThat(match.intervals).isEqualTo(2); + + assertThat(fp.matches(notMatching, 4, 0.40, match)).isFalse(); + + assertThat(fp.matches(closeMatch1, 4, 0.40, match)).isTrue(); + assertThat(match.intervals).isEqualTo(2); + + assertThat(fp.matches(closeMatch2, 4, 0.40, match)).isTrue(); + assertThat(match.intervals).isEqualTo(2); + } + + @Test + public void testPatternMatchingWithTrailingZeroes() + { + FluxPattern fp = new FluxPattern(16, 0x0016); + final long[] matching = {100, 100, 200, 100, 200}; + final long[] notMatching = {100, 200, 100, 100, 100}; + final long[] closeMatch1 = {90, 90, 180, 90, 300}; + final long[] closeMatch2 = {110, 110, 220, 110, 220}; + + FluxMatch match = new FluxMatch(); + assertThat(fp.matches(matching, 5, 0.40, match)).isTrue(); + assertThat(match.intervals).isEqualTo(3); + + assertThat(fp.matches(notMatching, 5, 0.40, match)).isFalse(); + + assertThat(fp.matches(closeMatch1, 5, 0.40, match)).isTrue(); + assertThat(match.intervals).isEqualTo(3); + + assertThat(fp.matches(closeMatch2, 5, 0.40, match)).isTrue(); + assertThat(match.intervals).isEqualTo(3); + } +} \ No newline at end of file From 01538a9008d0704d9db695f37ab02906f7136adb Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 13:29:39 +0200 Subject: [PATCH 107/192] Port Decoder. --- .../fluxengine/data/FluxmapReader.java | 49 ++++ .../cowlark/fluxengine/decoders/Decoder.java | 239 ++++++++++++++++++ lib/decoders/build.py | 29 --- lib/decoders/decoders.cc | 179 ------------- lib/decoders/decoders.h | 118 --------- 5 files changed, 288 insertions(+), 326 deletions(-) create mode 100644 java/com/cowlark/fluxengine/decoders/Decoder.java delete mode 100644 lib/decoders/build.py delete mode 100644 lib/decoders/decoders.cc delete mode 100644 lib/decoders/decoders.h diff --git a/java/com/cowlark/fluxengine/data/FluxmapReader.java b/java/com/cowlark/fluxengine/data/FluxmapReader.java index 7ef5ea41..c4d406ee 100644 --- a/java/com/cowlark/fluxengine/data/FluxmapReader.java +++ b/java/com/cowlark/fluxengine/data/FluxmapReader.java @@ -147,6 +147,55 @@ public void seekToIndexMark() posZeroes = 0; } + /* Ported from lib/data/fluxmapreader.cc FluxmapReader::seekToPattern. */ + + public double seekToPattern(FluxMatcher pattern) + { + return seekToPattern(pattern, null); + } + + public double seekToPattern(FluxMatcher pattern, FluxMatcher[] matching) + { + int intervalCount = pattern.intervals(); + long[] candidates = new long[intervalCount + 1]; + FluxPosition[] positions = new FluxPosition[intervalCount + 1]; + + for (int i = 0; i <= intervalCount; i++) + { + positions[i] = tell(); + candidates[i] = 0; + } + + double clockDecodeThreshold = decoder.getBitErrorThreshold(); + while (!eof()) + { + FluxMatch match = new FluxMatch(); + if (pattern.matches(candidates, intervalCount + 1, clockDecodeThreshold, match)) + { + seek(positions[intervalCount - match.intervals]); + posZeroes = match.zeroes; + if (matching != null) + matching[0] = match.matcher; + double detectedClock = match.clock * NS_PER_TICK; + if (detectedClock > decoder.getMinimumClockUs() * 1000) + return match.clock * NS_PER_TICK; + } + + for (int i = 0; i < intervalCount; i++) + { + positions[i] = positions[i + 1]; + candidates[i] = candidates[i + 1]; + } + EventResult r = findEvent(F_BIT_PULSE); + candidates[intervalCount] = r.ticks(); + positions[intervalCount] = tell(); + } + + if (matching != null) + matching[0] = null; + return 0; + } + public ClockData guessClock() { return guessClock(0.01, 0.05); diff --git a/java/com/cowlark/fluxengine/decoders/Decoder.java b/java/com/cowlark/fluxengine/decoders/Decoder.java new file mode 100644 index 00000000..0ba8d5e8 --- /dev/null +++ b/java/com/cowlark/fluxengine/decoders/Decoder.java @@ -0,0 +1,239 @@ +package com.cowlark.fluxengine.decoders; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.FluxMatcher; +import com.cowlark.fluxengine.data.FluxPosition; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.PhysicalTrackLayout; +import com.cowlark.fluxengine.data.Record; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.data.Track; + +/** + * The base class for track decoders, ported from lib/decoders/decoders.{h,cc}. + */ +public abstract class Decoder +{ + public enum RecordType + { + SECTOR_RECORD, DATA_RECORD, UNKNOWN_RECORD + } + + protected final DecoderProto config; + protected LogicalTrackLayout ltl; + protected Track trackdata; + protected Sector sector; + protected FluxDecoder decoder; + protected Bits recordBits = new Bits(); + + private FluxmapReader fmr; + + public Decoder(DecoderProto config) + { + this.config = config; + } + + public Track decodeToSectors(Fluxmap fluxmap, PhysicalTrackLayout ptl) + { + ltl = ptl.logicalTrackLayout; + + trackdata = new Track(); + trackdata.fluxmap = fluxmap; + trackdata.ptl = ptl; + trackdata.ltl = ptl.logicalTrackLayout; + + FluxmapReader fmrLocal = new FluxmapReader(fluxmap, config); + fmr = fmrLocal; + + newSector(); + beginTrack(); + for (; ; ) + { + newSector(); + + FluxPosition recordStart = fmr.tell(); + sector.clockNs = advanceToNextRecord(); + if (fmr.eof() || sector.clockNs == 0) + break; + + /* Read the sector record. */ + + FluxPosition before = fmr.tell(); + decodeSectorRecord(); + FluxPosition after = fmr.tell(); + pushRecord(before, after); + + if (sector.status != Sector.Status.DATA_MISSING) + { + sector.position = before.bytes(); + sector.dataStartTimeNs = before.getDurationNs(); + sector.dataEndTimeNs = after.getDurationNs(); + } else + { + /* The data is in a separate record. */ + + sector.headerStartTimeNs = before.getDurationNs(); + sector.headerEndTimeNs = after.getDurationNs(); + + sector.clockNs = advanceToNextRecord(); + if (fmr.eof() || sector.clockNs == 0) + break; + + before = fmr.tell(); + decodeDataRecord(); + sector.data = sector.data.slice(0, ltl.sectorSize); + after = fmr.tell(); + + if (sector.status != Sector.Status.DATA_MISSING) + { + sector.position = before.bytes(); + sector.dataStartTimeNs = before.getDurationNs(); + sector.dataEndTimeNs = after.getDurationNs(); + pushRecord(before, after); + } else + { + fmr.skipToEvent(F_BIT_PULSE); + resetFluxDecoder(); + } + } + + if (sector.status != Sector.Status.MISSING) + trackdata.allSectors.add(sector); + } + + return trackdata; + } + + private void newSector() + { + sector = new Sector(new LogicalLocation(0, 0, 0)); + sector.physicalLocation = + new CylinderHead(trackdata.ptl.physicalCylinder, trackdata.ptl.physicalHead); + sector.status = Sector.Status.MISSING; + } + + protected void pushRecord(FluxPosition start, FluxPosition end) + { + Record record = new Record(); + trackdata.records.add(record); + sector.records.add(record); + + record.position = start.bytes(); + record.startTimeNs = start.getDurationNs(); + record.endTimeNs = end.getDurationNs(); + record.clockNs = sector.clockNs; + + record.rawData = recordBits.toBytes(); + recordBits = new Bits(); + } + + protected void resetFluxDecoder() + { + decoder = new FluxDecoder(fmr, sector.clockNs, config); + } + + public double seekToPattern(FluxMatcher pattern) + { + double clockNs = fmr.seekToPattern(pattern); + decoder = new FluxDecoder(fmr, clockNs, config); + return clockNs; + } + + public void seekToIndexMark() + { + fmr.skipToEvent(F_BIT_PULSE); + fmr.seekToIndexMark(); + } + + public Bits readRawBits(int count) + { + Bits bits = decoder.readBits(count); + for (int i = 0; i < bits.size(); i++) + recordBits.add(bits.getBit(i)); + return bits; + } + + public int readRaw8() + { + return readRawBits(8).toBytes().iterator().read8(); + } + + public int readRaw16() + { + return readRawBits(16).toBytes().iterator().readBe16(); + } + + public int readRaw20() + { + Bits bits = new Bits(); + for (int i = 0; i < 4; i++) + bits.add(false); + Bits raw = readRawBits(20); + for (int i = 0; i < raw.size(); i++) + bits.add(raw.getBit(i)); + return bits.toBytes().iterator().readBe24(); + } + + public int readRaw24() + { + return readRawBits(24).toBytes().iterator().readBe24(); + } + + public int readRaw32() + { + return readRawBits(32).toBytes().iterator().readBe32(); + } + + public long readRaw48() + { + return readRawBits(48).toBytes().iterator().readBe48(); + } + + public long readRaw64() + { + return readRawBits(64).toBytes().iterator().readBe64(); + } + + public FluxPosition tell() + { + return fmr.tell(); + } + + public void rewind() + { + fmr.rewind(); + } + + public void seek(FluxPosition pos) + { + fmr.seek(pos); + } + + public boolean eof() + { + return fmr.eof(); + } + + public double getFluxmapDuration() + { + return fmr.getDurationNs(); + } + + protected void beginTrack() + { + } + + protected abstract double advanceToNextRecord(); + + protected abstract void decodeSectorRecord(); + + protected void decodeDataRecord() + { + } +} \ No newline at end of file diff --git a/lib/decoders/build.py b/lib/decoders/build.py deleted file mode 100644 index fb4cf836..00000000 --- a/lib/decoders/build.py +++ /dev/null @@ -1,29 +0,0 @@ -from build.protobuf import proto, protocc -from build.c import cxxlibrary - -proto( - name="proto", - srcs=["./decoders.proto"], - deps=["lib/config+common_proto", "arch+proto", "lib/fluxsink+proto"], -) - -protocc( - name="proto_lib", - srcs=[".+proto"], - deps=[ - "lib/config+common_proto_lib", - "arch+proto_lib", - "lib/fluxsink+proto_lib", - ], -) - -cxxlibrary( - name="decoders", - srcs=["./decoders.cc", "./fluxdecoder.cc", "./fmmfm.cc"], - hdrs={ - "lib/decoders/decoders.h": "./decoders.h", - "lib/decoders/fluxdecoder.h": "./fluxdecoder.h", - "lib/decoders/rawbits.h": "./rawbits.h", - }, - deps=["lib/core", "lib/config", "lib/data", ".+proto_lib"], -) diff --git a/lib/decoders/decoders.cc b/lib/decoders/decoders.cc deleted file mode 100644 index 443b9658..00000000 --- a/lib/decoders/decoders.cc +++ /dev/null @@ -1,179 +0,0 @@ -#include "lib/core/globals.h" -#include "lib/config/flags.h" -#include "lib/data/fluxmap.h" -#include "lib/config/config.h" -#include "lib/decoders/decoders.h" -#include "lib/data/fluxmapreader.h" -#include "lib/data/disk.h" -#include "protocol.h" -#include "lib/decoders/rawbits.h" -#include "lib/data/sector.h" -#include "lib/data/image.h" -#include "lib/decoders/decoders.pb.h" -#include "lib/data/layout.h" -#include - -std::shared_ptr Decoder::decodeToSectors( - std::shared_ptr fluxmap, - const std::shared_ptr& ptl) -{ - _ltl = ptl->logicalTrackLayout; - - _trackdata = std::make_shared(); - _trackdata->fluxmap = fluxmap; - _trackdata->ptl = ptl; - _trackdata->ltl = ptl->logicalTrackLayout; - - FluxmapReader fmr(*fluxmap); - _fmr = &fmr; - - auto newSector = [&] - { - _sector = std::make_shared(LogicalLocation{0, 0, 0}); - _sector->physicalLocation = std::make_optional( - ptl->physicalCylinder, ptl->physicalHead); - _sector->status = Sector::MISSING; - }; - - newSector(); - beginTrack(); - for (;;) - { - newSector(); - - Fluxmap::Position recordStart = fmr.tell(); - _sector->clock = advanceToNextRecord(); - if (fmr.eof() || !_sector->clock) - break; - - /* Read the sector record. */ - - Fluxmap::Position before = fmr.tell(); - decodeSectorRecord(); - Fluxmap::Position after = fmr.tell(); - pushRecord(before, after); - - if (_sector->status != Sector::DATA_MISSING) - { - _sector->position = before.bytes; - _sector->dataStartTime = before.ns(); - _sector->dataEndTime = after.ns(); - } - else - { - /* The data is in a separate record. */ - - _sector->headerStartTime = before.ns(); - _sector->headerEndTime = after.ns(); - - _sector->clock = advanceToNextRecord(); - if (fmr.eof() || !_sector->clock) - break; - - before = fmr.tell(); - decodeDataRecord(); - _sector->data = _sector->data.slice(0, _ltl->sectorSize); - after = fmr.tell(); - - if (_sector->status != Sector::DATA_MISSING) - { - _sector->position = before.bytes; - _sector->dataStartTime = before.ns(); - _sector->dataEndTime = after.ns(); - pushRecord(before, after); - } - else - { - fmr.skipToEvent(F_BIT_PULSE); - resetFluxDecoder(); - } - } - - if (_sector->status != Sector::MISSING) - _trackdata->allSectors.push_back(_sector); - } - - return _trackdata; -} - -void Decoder::pushRecord( - const Fluxmap::Position& start, const Fluxmap::Position& end) -{ - Fluxmap::Position here = _fmr->tell(); - - auto record = std::make_shared(); - _trackdata->records.push_back(record); - _sector->records.push_back(record); - - record->position = start.bytes; - record->startTime = start.ns(); - record->endTime = end.ns(); - record->clock = _sector->clock; - - record->rawData = toBytes(_recordBits); - _recordBits.clear(); -} - -void Decoder::resetFluxDecoder() -{ - _decoder.reset(new FluxDecoder(_fmr, _sector->clock, _config)); -} - -nanoseconds_t Decoder::seekToPattern(const FluxMatcher& pattern) -{ - nanoseconds_t clock = _fmr->seekToPattern(pattern); - _decoder.reset(new FluxDecoder(_fmr, clock, _config)); - return clock; -} - -void Decoder::seekToIndexMark() -{ - _fmr->skipToEvent(F_BIT_PULSE); - _fmr->seekToIndexMark(); -} - -std::vector Decoder::readRawBits(unsigned count) -{ - auto bits = _decoder->readBits(count); - _recordBits.insert(_recordBits.end(), bits.begin(), bits.end()); - return bits; -} - -uint8_t Decoder::readRaw8() -{ - return toBytes(readRawBits(8)).reader().read_8(); -} - -uint16_t Decoder::readRaw16() -{ - return toBytes(readRawBits(16)).reader().read_be16(); -} - -uint32_t Decoder::readRaw20() -{ - std::vector bits(4); - for (bool b : readRawBits(20)) - bits.push_back(b); - - return toBytes(bits).reader().read_be24(); -} - -uint32_t Decoder::readRaw24() -{ - return toBytes(readRawBits(24)).reader().read_be24(); -} - -uint32_t Decoder::readRaw32() -{ - return toBytes(readRawBits(32)).reader().read_be32(); -} - -uint64_t Decoder::readRaw48() -{ - return toBytes(readRawBits(48)).reader().read_be48(); -} - -uint64_t Decoder::readRaw64() -{ - return toBytes(readRawBits(64)).reader().read_be64(); -} diff --git a/lib/decoders/decoders.h b/lib/decoders/decoders.h deleted file mode 100644 index 83ef883b..00000000 --- a/lib/decoders/decoders.h +++ /dev/null @@ -1,118 +0,0 @@ -#ifndef DECODERS_H -#define DECODERS_H - -#include "lib/core/bytes.h" -#include "lib/data/sector.h" -#include "lib/data/fluxmapreader.h" -#include "lib/decoders/fluxdecoder.h" - -class Config; -class DecoderProto; -class FluxMatcher; -class Fluxmap; -class FluxmapReader; -class PhysicalTrackLayout; -class RawBits; -class Sector; - -#include "lib/data/disk.h" - -extern void setDecoderManualClockRate(double clockrate_us); - -extern Bytes decodeFmMfm(std::vector::const_iterator start, - std::vector::const_iterator end); -extern void encodeMfm(std::vector& bits, - unsigned& cursor, - const Bytes& input, - bool& lastBit); -extern void encodeFm( - std::vector& bits, unsigned& cursor, const Bytes& input); -extern Bytes encodeMfm(const Bytes& input, bool& lastBit); - -static inline Bytes decodeFmMfm(const std::vector bits) -{ - return decodeFmMfm(bits.begin(), bits.end()); -} - -class Decoder -{ -public: - Decoder(const DecoderProto& config): _config(config) {} - - virtual ~Decoder() {} - - static std::unique_ptr create(Config& config); - static std::unique_ptr create(const DecoderProto& config); - -public: - enum RecordType - { - SECTOR_RECORD, - DATA_RECORD, - UNKNOWN_RECORD - }; - -public: - std::shared_ptr decodeToSectors( - std::shared_ptr fluxmap, - const std::shared_ptr& ptl); - - void pushRecord( - const Fluxmap::Position& start, const Fluxmap::Position& end); - - void resetFluxDecoder(); - std::vector readRawBits(unsigned count); - uint8_t readRaw8(); - uint16_t readRaw16(); - uint32_t readRaw20(); - uint32_t readRaw24(); - uint32_t readRaw32(); - uint64_t readRaw48(); - uint64_t readRaw64(); - - Fluxmap::Position tell() - { - return _fmr->tell(); - } - - void rewind() - { - _fmr->rewind(); - } - - void seek(const Fluxmap::Position& pos) - { - return _fmr->seek(pos); - } - - nanoseconds_t seekToPattern(const FluxMatcher& pattern); - void seekToIndexMark(); - - bool eof() const - { - return _fmr->eof(); - } - - nanoseconds_t getFluxmapDuration() const - { - return _fmr->getDuration(); - } - -protected: - virtual void beginTrack() {}; - virtual nanoseconds_t advanceToNextRecord() = 0; - virtual void decodeSectorRecord() = 0; - virtual void decodeDataRecord() {}; - - const DecoderProto& _config; - std::shared_ptr _ltl; - std::shared_ptr _trackdata; - std::shared_ptr _sector; - std::unique_ptr _decoder; - std::vector _recordBits; - -private: - FluxmapReader* _fmr = nullptr; -}; - -#endif From 78179c14483b660fe7f01939a736da77bede3825 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 13:55:14 +0200 Subject: [PATCH 108/192] Ported the aeslanier decoder. --- java/com/cowlark/fluxengine/arch/BUILD.bazel | 13 ++++ .../arch/aeslanier/AesLanierDecoder.java | 70 +++++++++++++++++ java/com/cowlark/fluxengine/core/Bytes.java | 17 ++++ java/com/cowlark/fluxengine/external/Crc.java | 77 +++++++++++++++++++ 4 files changed, 177 insertions(+) create mode 100644 java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java create mode 100644 java/com/cowlark/fluxengine/external/Crc.java diff --git a/java/com/cowlark/fluxengine/arch/BUILD.bazel b/java/com/cowlark/fluxengine/arch/BUILD.bazel index 29216cae..8ab34cb9 100644 --- a/java/com/cowlark/fluxengine/arch/BUILD.bazel +++ b/java/com/cowlark/fluxengine/arch/BUILD.bazel @@ -1,4 +1,5 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) @@ -14,3 +15,15 @@ java_proto_library( name = "arch_java_proto", deps = [":arch_proto"], ) + +java_library( + name = "aeslanier", + srcs = ["aeslanier/AesLanierDecoder.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/external", + ], +) diff --git a/java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java b/java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java new file mode 100644 index 00000000..18237cff --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java @@ -0,0 +1,70 @@ +package com.cowlark.fluxengine.arch.aeslanier; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The AES Lanier decoder, ported from arch/aeslanier/decoder.cc. + */ +public class AesLanierDecoder extends Decoder +{ + public static final int AESLANIER_RECORD_SEPARATOR = 0x55555122; + public static final int AESLANIER_SECTOR_LENGTH = 256; + public static final int AESLANIER_RECORD_SIZE = AESLANIER_SECTOR_LENGTH + 5; + + private static final FluxPattern SECTOR_PATTERN = + new FluxPattern(32, AESLANIER_RECORD_SEPARATOR); + + public AesLanierDecoder(DecoderProto config) + { + super(config); + } + + /* This is actually M2FM, rather than MFM, but our MFM/FM decoder copes fine + * with it. */ + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(SECTOR_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + /* Skip ID mark (we know it's a AESLANIER_RECORD_SEPARATOR). */ + + readRawBits(16); + + Bits rawbits = readRawBits(AESLANIER_RECORD_SIZE * 16); + Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, AESLANIER_RECORD_SIZE); + Bytes reversed = bytes.reverseBits(); + + sector.location = + new LogicalLocation(reversed.getByte(1) & 0xff, 0, reversed.getByte(2) & 0xff); + + /* Check header 'checksum' (which seems far too simple to mean much). */ + + { + int wanted = reversed.getByte(3) & 0xff; + int got = ((reversed.getByte(1) & 0xff) + (reversed.getByte(2) & 0xff)) & 0xff; + if (wanted != got) + return; + } + + /* Check data checksum, which also includes the header and is + * significantly better. */ + + sector.data = reversed.slice(1, AESLANIER_SECTOR_LENGTH); + int wanted = reversed.iterator().seek(0x101).readLe16(); + int got = Crc.crc16ref(Crc.MODBUS_POLY_REF, sector.data); + sector.status = (wanted == got) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 323ff8a6..f8ab7675 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -200,6 +200,23 @@ public Bytes swab() return output; } + /* Reverses the bits within each byte, keeping the byte order. */ + public Bytes reverseBits() + { + Bytes output = new Bytes(0); + for (int i = 0; i < size(); i++) + output.add((byte) reverseBits(getByte(i))); + return output; + } + + private static int reverseBits(int b) + { + b = ((b & 0xF0) >> 4) | ((b & 0x0F) << 4); + b = ((b & 0xCC) >> 2) | ((b & 0x33) << 2); + b = ((b & 0xAA) >> 1) | ((b & 0x55) << 1); + return b; + } + /* Extracts the bytes as bits, MSB-first within each byte. */ public Bits toBits() { diff --git a/java/com/cowlark/fluxengine/external/Crc.java b/java/com/cowlark/fluxengine/external/Crc.java new file mode 100644 index 00000000..edd23731 --- /dev/null +++ b/java/com/cowlark/fluxengine/external/Crc.java @@ -0,0 +1,77 @@ +package com.cowlark.fluxengine.external; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; + +/** + * CRC helpers, ported from lib/core/crc.{h,cc}. + */ +public final class Crc +{ + public static final int CCITT_POLY = 0x1021; + public static final int MODBUS_POLY = 0x8005; + public static final int MODBUS_POLY_REF = 0xa001; + public static final int BROTHER_POLY = 0x000201; + + private Crc() + { + } + + public static int crc16(int poly, int init, Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + + int crc = init; + while (!br.eof()) + { + crc ^= br.read8() << 8; + for (int i = 0; i < 8; i++) + crc = (crc & 0x8000) != 0 ? ((crc << 1) ^ poly) : (crc << 1); + } + + return crc; + } + + public static int crc16(int poly, Bytes bytes) + { + return crc16(poly, 0xffff, bytes); + } + + public static int crc16ref(int poly, int init, Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + + int crc = init; + while (!br.eof()) + { + crc ^= br.read8(); + for (int i = 0; i < 8; i++) + crc = (crc & 0x0001) != 0 ? ((crc >> 1) ^ poly) : (crc >> 1); + } + + return crc; + } + + public static int crc16ref(int poly, Bytes bytes) + { + return crc16ref(poly, 0xffff, bytes); + } + + public static int sumBytes(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + int sum = 0; + while (!br.eof()) + sum += br.read8(); + return sum; + } + + public static int xorBytes(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + int result = 0; + while (!br.eof()) + result ^= br.read8(); + return result; + } +} \ No newline at end of file From 8accabf34b7be485ce09b961333862a6d3258ef3 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 14:04:11 +0200 Subject: [PATCH 109/192] Implement tests for Crc. --- java/com/cowlark/fluxengine/external/Crc.java | 1 + .../cowlark/fluxengine/external/BUILD.bazel | 11 +++ .../cowlark/fluxengine/external/CrcTest.java | 74 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 javatests/com/cowlark/fluxengine/external/CrcTest.java diff --git a/java/com/cowlark/fluxengine/external/Crc.java b/java/com/cowlark/fluxengine/external/Crc.java index edd23731..1c5b204d 100644 --- a/java/com/cowlark/fluxengine/external/Crc.java +++ b/java/com/cowlark/fluxengine/external/Crc.java @@ -27,6 +27,7 @@ public static int crc16(int poly, int init, Bytes bytes) crc ^= br.read8() << 8; for (int i = 0; i < 8; i++) crc = (crc & 0x8000) != 0 ? ((crc << 1) ^ poly) : (crc << 1); + crc &= 0xffff; } return crc; diff --git a/javatests/com/cowlark/fluxengine/external/BUILD.bazel b/javatests/com/cowlark/fluxengine/external/BUILD.bazel index 25d5866a..3de1d6be 100644 --- a/javatests/com/cowlark/fluxengine/external/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/external/BUILD.bazel @@ -13,6 +13,17 @@ java_test( ], ) +java_test( + name = "CrcTest", + srcs = ["CrcTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/external", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "GreaseweazleUtilsTest", srcs = ["GreaseweazleUtilsTest.java"], diff --git a/javatests/com/cowlark/fluxengine/external/CrcTest.java b/javatests/com/cowlark/fluxengine/external/CrcTest.java new file mode 100644 index 00000000..925ac604 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/external/CrcTest.java @@ -0,0 +1,74 @@ +package com.cowlark.fluxengine.external; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CrcTest +{ + /* The standard CRC check value: the result over the ASCII string + * "123456789". */ + private static final Bytes CHECK = Bytes.of(0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39); + + @Test + public void crc16() + { + /* CRC-16/CCITT-FALSE. */ + assertThat(Crc.crc16(Crc.CCITT_POLY, CHECK)).isEqualTo(0x29b1); + + /* CRC-16/XMODEM. */ + assertThat(Crc.crc16(Crc.CCITT_POLY, 0x0000, CHECK)).isEqualTo(0x31c3); + + /* TD0 imagereader polynomial. */ + assertThat(Crc.crc16(0xa097, 0x0000, CHECK)).isEqualTo(0x0fb3); + + /* The F85 decoder uses CCITT with non-standard init values. */ + assertThat(Crc.crc16(Crc.CCITT_POLY, 0xef21, CHECK)).isEqualTo(0xd2bb); + assertThat(Crc.crc16(Crc.CCITT_POLY, 0xbf84, CHECK)).isEqualTo(0x10cb); + } + + @Test + public void crc16ref() + { + /* CRC-16/MODBUS. */ + assertThat(Crc.crc16ref(Crc.MODBUS_POLY_REF, CHECK)).isEqualTo(0x4b37); + assertThat(Crc.crc16ref(Crc.MODBUS_POLY_REF, 0x0000, CHECK)).isEqualTo(0xbb3d); + } + + @Test + public void crc16Empty() + { + /* An empty input leaves the CRC at its init value. */ + assertThat(Crc.crc16(Crc.CCITT_POLY, Bytes.of())).isEqualTo(0xffff); + assertThat(Crc.crc16ref(Crc.MODBUS_POLY_REF, Bytes.of())).isEqualTo(0xffff); + } + + @Test + public void crc16SingleByte() + { + assertThat(Crc.crc16(Crc.CCITT_POLY, Bytes.of(0x00))).isEqualTo(0xe1f0); + assertThat(Crc.crc16(Crc.CCITT_POLY, Bytes.of(0x01))).isEqualTo(0xf1d1); + assertThat(Crc.crc16ref(Crc.MODBUS_POLY_REF, Bytes.of(0x00))).isEqualTo(0x40bf); + assertThat(Crc.crc16ref(Crc.MODBUS_POLY_REF, Bytes.of(0xff))).isEqualTo(0xff); + } + + @Test + public void sumBytes() + { + assertThat(Crc.sumBytes(Bytes.of(1, 2, 3, 4))).isEqualTo(10); + assertThat(Crc.sumBytes(Bytes.of())).isEqualTo(0); + assertThat(Crc.sumBytes(Bytes.of(0xff, 0x01))).isEqualTo(0x100); + } + + @Test + public void xorBytes() + { + assertThat(Crc.xorBytes(Bytes.of(1, 2, 3, 4))).isEqualTo(4); + assertThat(Crc.xorBytes(Bytes.of())).isEqualTo(0); + assertThat(Crc.xorBytes(Bytes.of(0xff, 0xff))).isEqualTo(0); + } +} From ea82b35a1c2d21a96870142dc7b3b042cd00927a Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 14:14:47 +0200 Subject: [PATCH 110/192] Refactor AesLanier. --- java/com/cowlark/fluxengine/arch/BUILD.bazel | 2 +- .../fluxengine/arch/aeslanier/AesLanier.java | 15 +++++++++++++++ .../arch/aeslanier/AesLanierDecoder.java | 13 +++++++------ 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 java/com/cowlark/fluxengine/arch/aeslanier/AesLanier.java diff --git a/java/com/cowlark/fluxengine/arch/BUILD.bazel b/java/com/cowlark/fluxengine/arch/BUILD.bazel index 8ab34cb9..da4d497d 100644 --- a/java/com/cowlark/fluxengine/arch/BUILD.bazel +++ b/java/com/cowlark/fluxengine/arch/BUILD.bazel @@ -18,7 +18,7 @@ java_proto_library( java_library( name = "aeslanier", - srcs = ["aeslanier/AesLanierDecoder.java"], + srcs = glob(["aeslanier/*.java"]), deps = [ "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", diff --git a/java/com/cowlark/fluxengine/arch/aeslanier/AesLanier.java b/java/com/cowlark/fluxengine/arch/aeslanier/AesLanier.java new file mode 100644 index 00000000..df3a13bf --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/aeslanier/AesLanier.java @@ -0,0 +1,15 @@ +package com.cowlark.fluxengine.arch.aeslanier; + +/** + * Constants for the AES Lanier format, ported from arch/aeslanier/aeslanier.h. + */ +public final class AesLanier +{ + public static final int AESLANIER_RECORD_SEPARATOR = 0x55555122; + public static final int AESLANIER_SECTOR_LENGTH = 256; + public static final int AESLANIER_RECORD_SIZE = AESLANIER_SECTOR_LENGTH + 5; + + private AesLanier() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java b/java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java index 18237cff..c2df766a 100644 --- a/java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java +++ b/java/com/cowlark/fluxengine/arch/aeslanier/AesLanierDecoder.java @@ -1,13 +1,18 @@ package com.cowlark.fluxengine.arch.aeslanier; +import static com.cowlark.fluxengine.arch.aeslanier.AesLanier.AESLANIER_RECORD_SEPARATOR; +import static com.cowlark.fluxengine.arch.aeslanier.AesLanier.AESLANIER_RECORD_SIZE; +import static com.cowlark.fluxengine.arch.aeslanier.AesLanier.AESLANIER_SECTOR_LENGTH; +import static com.cowlark.fluxengine.external.Crc.MODBUS_POLY_REF; + import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.core.Bytes; -import com.cowlark.fluxengine.external.Crc; import com.cowlark.fluxengine.data.FluxPattern; import com.cowlark.fluxengine.data.LogicalLocation; import com.cowlark.fluxengine.data.Sector; import com.cowlark.fluxengine.decoders.Decoder; import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; import com.cowlark.fluxengine.external.FmMfm; /** @@ -15,10 +20,6 @@ */ public class AesLanierDecoder extends Decoder { - public static final int AESLANIER_RECORD_SEPARATOR = 0x55555122; - public static final int AESLANIER_SECTOR_LENGTH = 256; - public static final int AESLANIER_RECORD_SIZE = AESLANIER_SECTOR_LENGTH + 5; - private static final FluxPattern SECTOR_PATTERN = new FluxPattern(32, AESLANIER_RECORD_SEPARATOR); @@ -64,7 +65,7 @@ protected void decodeSectorRecord() sector.data = reversed.slice(1, AESLANIER_SECTOR_LENGTH); int wanted = reversed.iterator().seek(0x101).readLe16(); - int got = Crc.crc16ref(Crc.MODBUS_POLY_REF, sector.data); + int got = Crc.crc16ref(MODBUS_POLY_REF, sector.data); sector.status = (wanted == got) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file From 12d4111c4a5ab1b829f48204c4b24001f8328953 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 14:47:20 +0200 Subject: [PATCH 111/192] Ported the decoders. Maybe they even work! --- java/com/cowlark/fluxengine/arch/Arch.java | 89 ++++++ java/com/cowlark/fluxengine/arch/BUILD.bazel | 6 +- .../cowlark/fluxengine/arch/agat/Agat.java | 37 +++ .../fluxengine/arch/agat/AgatDecoder.java | 92 ++++++ .../cowlark/fluxengine/arch/amiga/Amiga.java | 122 ++++++++ .../fluxengine/arch/amiga/AmigaDecoder.java | 76 +++++ .../fluxengine/arch/apple2/Apple2.java | 19 ++ .../fluxengine/arch/apple2/Apple2Decoder.java | 233 +++++++++++++++ .../fluxengine/arch/brother/Brother.java | 22 ++ .../arch/brother/BrotherDecoder.java | 226 +++++++++++++++ java/com/cowlark/fluxengine/arch/c64/C64.java | 34 +++ .../arch/c64/Commodore64Decoder.java | 116 ++++++++ .../arch/f85/DurangoF85Decoder.java | 125 ++++++++ java/com/cowlark/fluxengine/arch/f85/F85.java | 15 + .../cowlark/fluxengine/arch/fb100/Fb100.java | 15 + .../fluxengine/arch/fb100/Fb100Decoder.java | 145 ++++++++++ java/com/cowlark/fluxengine/arch/ibm/Ibm.java | 23 ++ .../fluxengine/arch/ibm/IbmDecoder.java | 240 ++++++++++++++++ .../fluxengine/arch/macintosh/Macintosh.java | 20 ++ .../arch/macintosh/MacintoshDecoder.java | 259 +++++++++++++++++ .../arch/micropolis/Micropolis.java | 16 ++ .../arch/micropolis/MicropolisDecoder.java | 266 ++++++++++++++++++ java/com/cowlark/fluxengine/arch/mx/Mx.java | 13 + .../cowlark/fluxengine/arch/mx/MxDecoder.java | 87 ++++++ .../fluxengine/arch/northstar/Northstar.java | 33 +++ .../arch/northstar/NorthstarDecoder.java | 166 +++++++++++ .../arch/rolandd20/RolandD20Decoder.java | 81 ++++++ .../fluxengine/arch/smaky6/Smaky6.java | 14 + .../fluxengine/arch/smaky6/Smaky6Decoder.java | 153 ++++++++++ .../cowlark/fluxengine/arch/tartu/Tartu.java | 14 + .../fluxengine/arch/tartu/TartuDecoder.java | 75 +++++ .../fluxengine/arch/tids990/Tids990.java | 15 + .../arch/tids990/Tids990Decoder.java | 106 +++++++ .../fluxengine/arch/victor9k/Victor9k.java | 23 ++ .../arch/victor9k/Victor9kDecoder.java | 132 +++++++++ .../arch/zilogmcz/ZilogMczDecoder.java | 57 ++++ java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + .../cowlark/fluxengine/cli/ReadCommand.java | 3 +- java/com/cowlark/fluxengine/core/Bytes.java | 7 + java/com/cowlark/fluxengine/external/Crc.java | 17 ++ .../fluxengine/arch/amiga/AmigaTest.java | 38 +++ .../cowlark/fluxengine/arch/amiga/BUILD.bazel | 14 + 42 files changed, 3242 insertions(+), 3 deletions(-) create mode 100644 java/com/cowlark/fluxengine/arch/Arch.java create mode 100644 java/com/cowlark/fluxengine/arch/agat/Agat.java create mode 100644 java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/amiga/Amiga.java create mode 100644 java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/apple2/Apple2.java create mode 100644 java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java create mode 100644 java/com/cowlark/fluxengine/arch/brother/Brother.java create mode 100644 java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/c64/C64.java create mode 100644 java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java create mode 100644 java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java create mode 100644 java/com/cowlark/fluxengine/arch/f85/F85.java create mode 100644 java/com/cowlark/fluxengine/arch/fb100/Fb100.java create mode 100644 java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java create mode 100644 java/com/cowlark/fluxengine/arch/ibm/Ibm.java create mode 100644 java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/macintosh/Macintosh.java create mode 100644 java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/micropolis/Micropolis.java create mode 100644 java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/mx/Mx.java create mode 100644 java/com/cowlark/fluxengine/arch/mx/MxDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/northstar/Northstar.java create mode 100644 java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java create mode 100644 java/com/cowlark/fluxengine/arch/smaky6/Smaky6.java create mode 100644 java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java create mode 100644 java/com/cowlark/fluxengine/arch/tartu/Tartu.java create mode 100644 java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/tids990/Tids990.java create mode 100644 java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java create mode 100644 java/com/cowlark/fluxengine/arch/victor9k/Victor9k.java create mode 100644 java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java create mode 100644 java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java create mode 100644 javatests/com/cowlark/fluxengine/arch/amiga/AmigaTest.java create mode 100644 javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel diff --git a/java/com/cowlark/fluxengine/arch/Arch.java b/java/com/cowlark/fluxengine/arch/Arch.java new file mode 100644 index 00000000..6b9323c4 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/Arch.java @@ -0,0 +1,89 @@ +package com.cowlark.fluxengine.arch; + +import com.cowlark.fluxengine.arch.agat.AgatDecoder; +import com.cowlark.fluxengine.arch.aeslanier.AesLanierDecoder; +import com.cowlark.fluxengine.arch.amiga.AmigaDecoder; +import com.cowlark.fluxengine.arch.apple2.Apple2Decoder; +import com.cowlark.fluxengine.arch.brother.BrotherDecoder; +import com.cowlark.fluxengine.arch.c64.Commodore64Decoder; +import com.cowlark.fluxengine.arch.f85.DurangoF85Decoder; +import com.cowlark.fluxengine.arch.fb100.Fb100Decoder; +import com.cowlark.fluxengine.arch.ibm.IbmDecoder; +import com.cowlark.fluxengine.arch.macintosh.MacintoshDecoder; +import com.cowlark.fluxengine.arch.micropolis.MicropolisDecoder; +import com.cowlark.fluxengine.arch.mx.MxDecoder; +import com.cowlark.fluxengine.arch.northstar.NorthstarDecoder; +import com.cowlark.fluxengine.arch.rolandd20.RolandD20Decoder; +import com.cowlark.fluxengine.arch.smaky6.Smaky6Decoder; +import com.cowlark.fluxengine.arch.tartu.TartuDecoder; +import com.cowlark.fluxengine.arch.tids990.Tids990Decoder; +import com.cowlark.fluxengine.arch.victor9k.Victor9kDecoder; +import com.cowlark.fluxengine.arch.zilogmcz.ZilogMczDecoder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; + +/** + * The Arch class, ported from arch/arch.{h,cc}. + */ +public final class Arch +{ + private Arch() + { + } + + public static Decoder createDecoder(ConfigProto config) + { + if (!config.hasDecoder()) + throw new FluxEngineException("no decoder configured"); + return createDecoder(config.getDecoder()); + } + + public static Decoder createDecoder(DecoderProto config) + { + switch (config.getFormatCase()) + { + case AGAT: + return new AgatDecoder(config); + case AESLANIER: + return new AesLanierDecoder(config); + case AMIGA: + return new AmigaDecoder(config); + case APPLE2: + return new Apple2Decoder(config); + case BROTHER: + return new BrotherDecoder(config); + case C64: + return new Commodore64Decoder(config); + case F85: + return new DurangoF85Decoder(config); + case FB100: + return new Fb100Decoder(config); + case IBM: + return new IbmDecoder(config); + case MACINTOSH: + return new MacintoshDecoder(config); + case MICROPOLIS: + return new MicropolisDecoder(config); + case MX: + return new MxDecoder(config); + case NORTHSTAR: + return new NorthstarDecoder(config); + case ROLANDD20: + return new RolandD20Decoder(config); + case SMAKY6: + return new Smaky6Decoder(config); + case TARTU: + return new TartuDecoder(config); + case TIDS990: + return new Tids990Decoder(config); + case VICTOR9K: + return new Victor9kDecoder(config); + case ZILOGMCZ: + return new ZilogMczDecoder(config); + default: + throw new FluxEngineException("no decoder specified"); + } + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/BUILD.bazel b/java/com/cowlark/fluxengine/arch/BUILD.bazel index da4d497d..cb890a81 100644 --- a/java/com/cowlark/fluxengine/arch/BUILD.bazel +++ b/java/com/cowlark/fluxengine/arch/BUILD.bazel @@ -17,9 +17,11 @@ java_proto_library( ) java_library( - name = "aeslanier", - srcs = glob(["aeslanier/*.java"]), + name = "arch", + srcs = glob(["*.java", "*/*.java"]), deps = [ + ":arch_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/decoders", diff --git a/java/com/cowlark/fluxengine/arch/agat/Agat.java b/java/com/cowlark/fluxengine/arch/agat/Agat.java new file mode 100644 index 00000000..8744af3f --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/agat/Agat.java @@ -0,0 +1,37 @@ +package com.cowlark.fluxengine.arch.agat; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; + +/** + * Constants and helpers for the Agat format, ported from + * arch/agat/agat.h and arch/agat/agat.cc. + */ +public final class Agat +{ + public static final int AGAT_SECTOR_SIZE = 256; + + public static final long SECTOR_ID = 0x8924555549111444L; + public static final long DATA_ID = 0x8924555514444911L; + + private Agat() + { + } + + public static int agatChecksum(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + int checksum = 0; + + while (!br.eof()) + { + int b = br.read8(); + if (checksum > 0xff) + checksum = (checksum + 1) & 0xff; + + checksum += b; + } + + return checksum & 0xff; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java b/java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java new file mode 100644 index 00000000..47e8d0e3 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java @@ -0,0 +1,92 @@ +package com.cowlark.fluxengine.arch.agat; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The Agat decoder, ported from arch/agat/decoder.cc. + */ +public class AgatDecoder extends Decoder +{ + /* + * data: X X X X X X X X X - - X - X - X - X X - X - X - = 0xff956a + * flux: 01 01 01 01 01 01 01 01 01 00 10 01 00 01 00 01 00 01 01 00 01 00 01 00 = 0x555549111444 + * + * data: X X X X X X X X - X X - X - X - X - - X - X - X = 0xff6a95 + * flux: 01 01 01 01 01 01 01 01 00 01 01 00 01 00 01 00 01 00 10 01 00 01 00 01 = 0x555514444911 + * + * Each pattern is prefixed with this one: + * + * data: - - - X - - X - = 0x12 + * flux: (10) 10 10 10 01 00 10 01 00 = 0xa924 + * magic: (10) 10 00 10 01 00 10 01 00 = 0x8924 + * ^ + * + * This seems to be generated by emitting A4 in MFM and then a single 0 bit + * to shift it out of phase, so the data bits become clock bits and vice + * versa. + * + * X - X - - X - - = 0xA4 + * 0100010010010010 = MFM encoded + * 1000100100100100 = with trailing zero + * - - - X - - X - = effective bitstream = 0x12 + */ + private static final FluxPattern SECTOR_PATTERN = new FluxPattern(64, Agat.SECTOR_ID); + private static final FluxPattern DATA_PATTERN = new FluxPattern(64, Agat.DATA_ID); + + private static final FluxMatchers ALL_PATTERNS = FluxMatchers.of(SECTOR_PATTERN, DATA_PATTERN); + + public AgatDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ALL_PATTERNS); + } + + @Override + protected void decodeSectorRecord() + { + if (readRaw64() != Agat.SECTOR_ID) + return; + + Bytes bytes = FmMfm.decodeFmMfm(readRawBits(64)).slice(0, 4); + if (bytes.getByte(3) != 0x5a) + return; + + int logicalCylinder = (bytes.getByte(1) & 0xff) >> 1; + int logicalSector = bytes.getByte(2) & 0xff; + int logicalHead = bytes.getByte(1) & 1; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + sector.status = Sector.Status.DATA_MISSING; + } + + @Override + protected void decodeDataRecord() + { + if (readRaw64() != Agat.DATA_ID) + return; + + Bytes bytes = FmMfm.decodeFmMfm(readRawBits((Agat.AGAT_SECTOR_SIZE + 2) * 16)) + .slice(0, Agat.AGAT_SECTOR_SIZE + 2); + + if (bytes.getByte(Agat.AGAT_SECTOR_SIZE + 1) != 0x5a) + return; + + sector.data = bytes.slice(0, Agat.AGAT_SECTOR_SIZE); + int wantChecksum = bytes.getByte(Agat.AGAT_SECTOR_SIZE) & 0xff; + int gotChecksum = Agat.agatChecksum(sector.data); + sector.status = + (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/amiga/Amiga.java b/java/com/cowlark/fluxengine/arch/amiga/Amiga.java new file mode 100644 index 00000000..d1afcfa4 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/amiga/Amiga.java @@ -0,0 +1,122 @@ +package com.cowlark.fluxengine.arch.amiga; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; + +/** + * Constants and helpers for the Amiga format, ported from arch/amiga/amiga.h + * and arch/amiga/amiga.cc. + */ +public final class Amiga +{ + public static final long AMIGA_SECTOR_RECORD = 0xaaaa44894489L; + + public static final int AMIGA_TRACKS_PER_DISK = 80; + public static final int AMIGA_SECTORS_PER_TRACK = 11; + public static final int AMIGA_RECORD_SIZE = 0x21c; + + private Amiga() + { + } + + public static int amigaChecksum(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + int checksum = 0; + + while (!br.eof()) + checksum ^= br.readBe32(); + + return checksum & 0x55555555; + } + + private static int everyother(int x) + { + /* aabb ccdd eeff gghh */ + x &= 0x6666; /* 0ab0 0cd0 0ef0 0gh0 */ + x >>= 1; /* 00ab 00cd 00ef 00gh */ + x |= x << 2; /* abab cdcd efef ghgh */ + x &= 0x3c3c; /* 00ab cd00 00ef gh00 */ + x >>= 2; /* 0000 abcd 0000 efgh */ + x |= x >> 4; /* 0000 abcd abcd efgh */ + return x; + } + + public static Bytes amigaInterleave(Bytes input) + { + Bytes output = new Bytes(); + ByteWriter bw = new ByteWriter(output); + + /* Write all odd bits. (Numbering starts at 0...) */ + + { + ByteReader br = new ByteReader(input); + while (!br.eof()) + { + int x = br.readBe16(); + x &= 0xaaaa; /* a0b0 c0d0 e0f0 g0h0 */ + x |= x >> 1; /* aabb ccdd eeff gghh */ + x = everyother(x); /* 0000 0000 abcd efgh */ + bw.write8(x); + } + } + + /* Write all even bits. */ + + { + ByteReader br = new ByteReader(input); + while (!br.eof()) + { + int x = br.readBe16(); + x &= 0x5555; /* 0a0b 0c0d 0e0f 0g0h */ + x |= x << 1; /* aabb ccdd eeff gghh */ + x = everyother(x); /* 0000 0000 abcd efgh */ + bw.write8(x); + } + } + + return output; + } + + /* Deinterleaves `len` bytes starting at `index[0]` within `input`, + * advancing `index[0]` by `len`. Mirrors the pointer-advancing C++ + * amigaDeinterleave(). */ + public static Bytes amigaDeinterleave(Bytes input, int[] index, int len) + { + int start = index[0]; + int odds = start; + int evens = start + len / 2; + Bytes output = new Bytes(); + ByteWriter bw = new ByteWriter(output); + + for (int i = 0; i < len / 2; i++) + { + int o = input.getByte(odds++) & 0xff; + int e = input.getByte(evens++) & 0xff; + + /* This is the 'Interleave bits with 64-bit multiply' technique + * from + * http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN + */ + long result = + ((((e * 0x0101010101010101L) & 0x8040201008040201L) * + 0x0102040810204081L >>> 49) & + 0x5555) | + ((((o * 0x0101010101010101L) & 0x8040201008040201L) * + 0x0102040810204081L >>> 48) & + 0xAAAA); + + bw.writeBe16((int) result); + } + + index[0] += len; + return output; + } + + public static Bytes amigaDeinterleave(Bytes input) + { + int[] index = {0}; + return amigaDeinterleave(input, index, input.size()); + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java b/java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java new file mode 100644 index 00000000..0a513bd6 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java @@ -0,0 +1,76 @@ +package com.cowlark.fluxengine.arch.amiga; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The Amiga decoder, ported from arch/amiga/decoder.cc. + */ +public class AmigaDecoder extends Decoder +{ + /* + * Amiga disks use MFM but it's not quite the same as IBM MFM. They only + * use a single type of record with a different marker byte. + * + * See the big comment in the IBM MFM decoder for the gruesome details of + * how MFM works. + */ + private static final FluxPattern SECTOR_PATTERN = + new FluxPattern(48, Amiga.AMIGA_SECTOR_RECORD); + + public AmigaDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(SECTOR_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + if (readRaw48() != Amiga.AMIGA_SECTOR_RECORD) + return; + + Bits rawbits = readRawBits(Amiga.AMIGA_RECORD_SIZE * 16); + if (rawbits.size() < (Amiga.AMIGA_RECORD_SIZE * 16)) + return; + Bytes rawbytes = rawbits.toBytes().slice(0, Amiga.AMIGA_RECORD_SIZE * 2); + Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, Amiga.AMIGA_RECORD_SIZE); + + int[] index = {0}; + + Bytes header = Amiga.amigaDeinterleave(bytes, index, 4); + Bytes recoveryinfo = Amiga.amigaDeinterleave(bytes, index, 16); + + int logicalCylinder = (header.getByte(1) & 0xff) >> 1; + int logicalHead = header.getByte(1) & 1; + int logicalSector = header.getByte(2) & 0xff; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + int wantedheaderchecksum = Amiga.amigaDeinterleave(bytes, index, 4).iterator().readBe32(); + int gotheaderchecksum = Amiga.amigaChecksum(rawbytes.slice(0, 40)); + if (gotheaderchecksum != wantedheaderchecksum) + return; + + int wanteddatachecksum = Amiga.amigaDeinterleave(bytes, index, 4).iterator().readBe32(); + int gotdatachecksum = Amiga.amigaChecksum(rawbytes.slice(56, 1024)); + + Bytes data = new Bytes(); + data.writer() + .write(Amiga.amigaDeinterleave(bytes, index, 512)) + .write(recoveryinfo); + sector.data = data; + sector.status = + (gotdatachecksum == wanteddatachecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/apple2/Apple2.java b/java/com/cowlark/fluxengine/arch/apple2/Apple2.java new file mode 100644 index 00000000..1d001290 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/apple2/Apple2.java @@ -0,0 +1,19 @@ +package com.cowlark.fluxengine.arch.apple2; + +/** + * Constants for the Apple II format, ported from arch/apple2/apple2.h. + */ +public final class Apple2 +{ + public static final int APPLE2_SECTOR_RECORD = 0xd5aa96; + public static final int APPLE2_DATA_RECORD = 0xd5aaad; + + public static final int APPLE2_SECTOR_LENGTH = 256; + public static final int APPLE2_ENCODED_SECTOR_LENGTH = 342; + + public static final int APPLE2_SECTORS = 16; + + private Apple2() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java b/java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java new file mode 100644 index 00000000..131ec0d2 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java @@ -0,0 +1,233 @@ +package com.cowlark.fluxengine.arch.apple2; + +import com.cowlark.fluxengine.apple2.Apple2DecoderProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; + +/** + * The Apple II decoder, ported from arch/apple2/decoder.cc. + */ +public class Apple2Decoder extends Decoder +{ + private static final FluxPattern SECTOR_RECORD_PATTERN = + new FluxPattern(24, Apple2.APPLE2_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = + new FluxPattern(24, Apple2.APPLE2_DATA_RECORD); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + + private static int decodeDataGcr(int gcr) + { + switch (gcr) + { + case 0x96: return 0x00; + case 0x97: return 0x01; + case 0x9a: return 0x02; + case 0x9b: return 0x03; + case 0x9d: return 0x04; + case 0x9e: return 0x05; + case 0x9f: return 0x06; + case 0xa6: return 0x07; + case 0xa7: return 0x08; + case 0xab: return 0x09; + case 0xac: return 0x0a; + case 0xad: return 0x0b; + case 0xae: return 0x0c; + case 0xaf: return 0x0d; + case 0xb2: return 0x0e; + case 0xb3: return 0x0f; + case 0xb4: return 0x10; + case 0xb5: return 0x11; + case 0xb6: return 0x12; + case 0xb7: return 0x13; + case 0xb9: return 0x14; + case 0xba: return 0x15; + case 0xbb: return 0x16; + case 0xbc: return 0x17; + case 0xbd: return 0x18; + case 0xbe: return 0x19; + case 0xbf: return 0x1a; + case 0xcb: return 0x1b; + case 0xcd: return 0x1c; + case 0xce: return 0x1d; + case 0xcf: return 0x1e; + case 0xd3: return 0x1f; + case 0xd6: return 0x20; + case 0xd7: return 0x21; + case 0xd9: return 0x22; + case 0xda: return 0x23; + case 0xdb: return 0x24; + case 0xdc: return 0x25; + case 0xdd: return 0x26; + case 0xde: return 0x27; + case 0xdf: return 0x28; + case 0xe5: return 0x29; + case 0xe6: return 0x2a; + case 0xe7: return 0x2b; + case 0xe9: return 0x2c; + case 0xea: return 0x2d; + case 0xeb: return 0x2e; + case 0xec: return 0x2f; + case 0xed: return 0x30; + case 0xee: return 0x31; + case 0xef: return 0x32; + case 0xf2: return 0x33; + case 0xf3: return 0x34; + case 0xf4: return 0x35; + case 0xf5: return 0x36; + case 0xf6: return 0x37; + case 0xf7: return 0x38; + case 0xf9: return 0x39; + case 0xfa: return 0x3a; + case 0xfb: return 0x3b; + case 0xfc: return 0x3c; + case 0xfd: return 0x3d; + case 0xfe: return 0x3e; + case 0xff: return 0x3f; + default: return -1; + } + } + + private static int combine(int word) + { + return (word & (word >> 7)) & 0xff; + } + + /* This is extremely inspired by the MESS implementation, written by Nathan + * Woods and R. Belmont: + * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp + */ + private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) + { + Bytes output = new Bytes(Apple2.APPLE2_SECTOR_LENGTH); + + int checksum = 0; + for (int i = 0; i < Apple2.APPLE2_ENCODED_SECTOR_LENGTH; i++) + { + checksum ^= decodeDataGcr(input.getByte(i) & 0xff); + + if (i >= 86) + { + /* 6 bit */ + output.setByte(i - 86, (byte) (output.getByte(i - 86) | (checksum << 2))); + } else + { + /* 3 * 2 bit */ + output.setByte(i, (byte) (((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02))); + output.setByte(i + 86, (byte) (((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02))); + if ((i + 172) < Apple2.APPLE2_SECTOR_LENGTH) + output.setByte(i + 172, (byte) (((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02))); + } + } + + checksum &= 0x3f; + int wantedchecksum = decodeDataGcr(input.getByte(Apple2.APPLE2_ENCODED_SECTOR_LENGTH) & 0xff); + status[0] = (checksum == wantedchecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + return output; + } + + private final Apple2DecoderProto config; + + public Apple2Decoder(DecoderProto config) + { + super(config); + this.config = config.getApple2(); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + if (readRaw24() != Apple2.APPLE2_SECTOR_RECORD) + return; + + /* Read header. */ + + Bytes header = readRawBits(8 * 8).toBytes().slice(0, 8); + ByteReader br = header.iterator(); + + int volume = combine(br.readBe16()); + int logicalCylinder = combine(br.readBe16()); + int logicalHead = ltl.logicalHead; + int logicalSector = combine(br.readBe16()); + int checksum = combine(br.readBe16()); + + /* If the checksum is correct, upgrade the sector from MISSING to + * DATA_MISSING in anticipation of its data record. */ + if (checksum == (volume ^ logicalCylinder ^ logicalSector)) + sector.status = Sector.Status.DATA_MISSING; + + if (logicalHead == 1) + logicalCylinder -= config.getSideOneTrackOffset(); + + /* Sanity check. */ + + if (logicalCylinder > 100) + { + sector.status = Sector.Status.MISSING; + return; + } + + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + } + + @Override + protected void decodeDataRecord() + { + /* Check ID. */ + + if (readRaw24() != Apple2.APPLE2_DATA_RECORD) + return; + + /* Read and decode data. */ + + /* Sometimes there's a 1-bit gap between APPLE2_DATA_RECORD and the + * data itself. This has been seen on real world disks such as the + * Apple II Operating System Kit from Apple2Online. However, I haven't + * seen it described in any of the various references. + * + * This extra '0' bit would not affect the real disk interface, as it + * was a '1' reaching the top bit of a shift register that triggered a + * byte to be available, but it affects the way the data is read here. + * + * While the floppies tested only seemed to need this applied to the + * first byte of the data record, applying it consistently to all of + * them doesn't seem to hurt, and simplifies the code. + */ + + int recordLength = Apple2.APPLE2_ENCODED_SECTOR_LENGTH + 2; + Bytes bytes = new Bytes(recordLength); + for (int i = 0; i < recordLength; i++) + { + int result = 0; + while ((result & 0x80) == 0) + { + Bits b = readRawBits(1); + if (b.size() == 0) + break; + result = (result << 1) | (b.getBit(0) ? 1 : 0); + } + bytes.setByte(i, (byte) result); + } + + /* Upgrade the sector from MISSING to BAD_CHECKSUM. If + * decodeCrazyData succeeds, it upgrades the sector to OK. */ + + sector.status = Sector.Status.BAD_CHECKSUM; + Sector.Status[] status = {sector.status}; + sector.data = decodeCrazyData(bytes, status); + sector.status = status[0]; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/brother/Brother.java b/java/com/cowlark/fluxengine/arch/brother/Brother.java new file mode 100644 index 00000000..42ffb717 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/brother/Brother.java @@ -0,0 +1,22 @@ +package com.cowlark.fluxengine.arch.brother; + +/** + * Constants for the Brother word processor format (or at least, one of them), + * ported from arch/brother/brother.h. + */ +public final class Brother +{ + public static final int BROTHER_SECTOR_RECORD = 0xFFFFFD57; + public static final int BROTHER_DATA_RECORD = 0xFFFFFDDB; + public static final int BROTHER_DATA_RECORD_PAYLOAD = 256; + public static final int BROTHER_DATA_RECORD_CHECKSUM = 3; + public static final int BROTHER_DATA_RECORD_ENCODED_SIZE = 415; + + public static final int BROTHER_TRACKS_PER_240KB_DISK = 78; + public static final int BROTHER_TRACKS_PER_120KB_DISK = 39; + public static final int BROTHER_SECTORS_PER_TRACK = 12; + + private Brother() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java b/java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java new file mode 100644 index 00000000..6a465075 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java @@ -0,0 +1,226 @@ +package com.cowlark.fluxengine.arch.brother; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.BitWriter; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; + +/** + * The Brother word processor decoder, ported from arch/brother/decoder.cc. + */ +public class BrotherDecoder extends Decoder +{ + private static final FluxPattern SECTOR_RECORD_PATTERN = + new FluxPattern(32, Brother.BROTHER_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = + new FluxPattern(32, Brother.BROTHER_DATA_RECORD); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + + /* + * Brother disks have this very very non-IBM system where sector header + * records and data records use two different kinds of GCR: sector headers + * are 8-in-16 (but the encodable values range from 0 to 77ish only) and + * data headers are 5-in-8. In addition, there's a non-encoded 10-bit ID + * word at the beginning of each record, as well as a string of 53 1s + * introducing them. That does at least make them easy to find. + * + * Disk formats vary from machine to machine, but mine uses 78 tracks. + * Track 0 is erased but not formatted. Track alignment is extremely + * dubious and Brother track 0 shows up on my machine at track 2. + */ + + private static int decodeDataGcr(int gcr) + { + switch (gcr) + { + case 0x55: return 0; + case 0x57: return 1; + case 0x5b: return 2; + case 0x5d: return 3; + case 0x5f: return 4; + case 0x6b: return 5; + case 0x6d: return 6; + case 0x6f: return 7; + case 0x75: return 8; + case 0x77: return 9; + case 0x7b: return 10; + case 0x7d: return 11; + case 0x7f: return 12; + case 0xab: return 13; + case 0xad: return 14; + case 0xaf: return 15; + case 0xb5: return 16; + case 0xb7: return 17; + case 0xbb: return 18; + case 0xbd: return 19; + case 0xbf: return 20; + case 0xd5: return 21; + case 0xd7: return 22; + case 0xdb: return 23; + case 0xdd: return 24; + case 0xdf: return 25; + case 0xeb: return 26; + case 0xed: return 27; + case 0xef: return 28; + case 0xf5: return 29; + case 0xf7: return 30; + case 0xfb: return 31; + default: return -1; + } + } + + private static int decodeHeaderGcr(int word) + { + switch (word) + { + case 0xDFB5: return 0; + case 0x5B6F: return 1; + case 0x7DF7: return 2; + case 0xBFD5: return 3; + case 0xF57F: return 4; + case 0x6D5D: return 5; + case 0xAFEB: return 6; + case 0xDDB7: return 7; + case 0x5775: return 8; + case 0x7BFB: return 9; + case 0xBDD7: return 10; + case 0xEFAB: return 11; + case 0x6B5F: return 12; + case 0xADED: return 13; + case 0xDBBB: return 14; + case 0x5577: return 15; + case 0x77DB: return 16; + case 0xBBAD: return 17; + case 0xED6B: return 18; + case 0x5FEF: return 19; + case 0xABBD: return 20; + case 0xD77B: return 21; + case 0xFB57: return 22; + case 0x75DD: return 23; + case 0xB7AF: return 24; + case 0xEB6D: return 25; + case 0x5DF5: return 26; + case 0x7FBF: return 27; + case 0xD57D: return 28; + case 0xF75B: return 29; + case 0x6FDF: return 30; + case 0xB5B5: return 31; + case 0xDF6F: return 32; + case 0x5BF7: return 33; + case 0x7DD5: return 34; + case 0xBF7F: return 35; + case 0xF55D: return 36; + case 0x6DEB: return 37; + case 0xAFB7: return 38; + case 0xDD75: return 39; + case 0x57FB: return 40; + case 0x7BD7: return 41; + case 0xBDAB: return 42; + case 0xEF5F: return 43; + case 0x6BED: return 44; + case 0xADBB: return 45; + case 0xDB77: return 46; + case 0xBB55: return 47; + case 0xEDDB: return 48; + case 0x5FAD: return 49; + case 0xAB6B: return 50; + case 0xD7EF: return 51; + case 0xFBBD: return 52; + case 0x757B: return 53; + case 0xB757: return 54; + case 0xEBDD: return 55; + case 0x5DAF: return 56; + case 0x7F6D: return 57; + case 0xD5F5: return 58; + case 0xF7BF: return 59; + case 0x6F7D: return 60; + case 0xB55B: return 61; + case 0xDFDF: return 62; + case 0x5BB5: return 63; + case 0x7D6F: return 64; + case 0xBFF7: return 65; + case 0xF5D5: return 66; + case 0x6D7F: return 67; + case 0xAF5D: return 68; + case 0xDDEB: return 69; + case 0x57B7: return 70; + case 0x7B75: return 71; + case 0xBDFB: return 72; + case 0xEFD7: return 73; + case 0x6BAB: return 74; + case 0xAD5F: return 75; + case 0xDBED: return 76; + case 0x55BB: return 77; + default: return -1; + } + } + + public BrotherDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + if (readRaw32() != Brother.BROTHER_SECTOR_RECORD) + return; + + Bits rawbits = readRawBits(32); + Bytes bytes = rawbits.toBytes().slice(0, 4); + + ByteReader br = bytes.iterator(); + int logicalCylinder = decodeHeaderGcr(br.readBe16()); + int logicalSector = decodeHeaderGcr(br.readBe16()); + + /* Sanity check the values read; there's no header checksum and + * occasionally we get garbage due to bit errors. */ + if (logicalSector > 11) + return; + if (logicalCylinder > 79) + return; + + sector.location = new LogicalLocation(logicalCylinder, 0, logicalSector); + sector.status = Sector.Status.DATA_MISSING; + } + + @Override + protected void decodeDataRecord() + { + if (readRaw32() != Brother.BROTHER_DATA_RECORD) + return; + + Bits rawbits = readRawBits(Brother.BROTHER_DATA_RECORD_ENCODED_SIZE * 8); + Bytes rawbytes = rawbits.toBytes().slice(0, Brother.BROTHER_DATA_RECORD_ENCODED_SIZE); + + Bytes bytes = new Bytes(); + ByteWriter bw = new ByteWriter(bytes); + BitWriter bitw = new BitWriter(bw); + for (int i = 0; i < rawbytes.size(); i++) + { + int nibble = decodeDataGcr(rawbytes.getByte(i) & 0xff); + bitw.push(nibble, 5); + } + bitw.flush(); + + sector.data = bytes.slice(0, Brother.BROTHER_DATA_RECORD_PAYLOAD); + int realCrc = Crc.crcbrother(sector.data); + int wantCrc = bytes.iterator().seek(Brother.BROTHER_DATA_RECORD_PAYLOAD).readBe24(); + sector.status = (realCrc == wantCrc) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/c64/C64.java b/java/com/cowlark/fluxengine/arch/c64/C64.java new file mode 100644 index 00000000..c7049510 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/c64/C64.java @@ -0,0 +1,34 @@ +package com.cowlark.fluxengine.arch.c64; + +/** + * Constants for the Commodore 64 format, ported from arch/c64/c64.h. + * + * Source: http://www.unusedino.de/ec64/technical/formats/g64.html + * 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR) + * 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) + * 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read) + * 4. Data sync FF FF FF FF FF (40 'on' bits, not GCR) + * 5. Data block 55...4A (325 GCR bytes) + * 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read) + * 1. Header sync (SYNC for the next sector) + */ +public final class C64 +{ + public static final int C64_SECTOR_RECORD = 0xffd49; + public static final int C64_DATA_RECORD = 0xffd57; + public static final int C64_SECTOR_LENGTH = 256; + + public static final int C64_HEADER_DATA_SYNC = 0xFF; + public static final int C64_HEADER_BLOCK_ID = 0x08; + public static final int C64_DATA_BLOCK_ID = 0x07; + public static final int C64_HEADER_GAP = 0x55; + public static final int C64_INTER_SECTOR_GAP = 0x55; + public static final int C64_PADDING = 0x0F; + + public static final int C64_TRACKS_PER_DISK = 40; + public static final int C64_BAM_TRACK = 17; + + private C64() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java b/java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java new file mode 100644 index 00000000..93960381 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java @@ -0,0 +1,116 @@ +package com.cowlark.fluxengine.arch.c64; + +import com.cowlark.fluxengine.core.BitWriter; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; + +/** + * The Commodore 64 decoder, ported from arch/c64/decoder.cc. + */ +public class Commodore64Decoder extends Decoder +{ + private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(20, C64.C64_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(20, C64.C64_DATA_RECORD); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + + private static int decodeDataGcr(int gcr) + { + switch (gcr) + { + case 0x0a: return 0x0; + case 0x0b: return 0x1; + case 0x12: return 0x2; + case 0x13: return 0x3; + case 0x0e: return 0x4; + case 0x0f: return 0x5; + case 0x16: return 0x6; + case 0x17: return 0x7; + case 0x09: return 0x8; + case 0x19: return 0x9; + case 0x1a: return 0xa; + case 0x1b: return 0xb; + case 0x0d: return 0xc; + case 0x1d: return 0xd; + case 0x1e: return 0xe; + case 0x15: return 0xf; + default: return -1; + } + } + + private static Bytes decode(Bits bits) + { + Bytes output = new Bytes(); + ByteWriter bw = new ByteWriter(output); + BitWriter bitw = new BitWriter(bw); + + int ii = 0; + while (ii < bits.size()) + { + int inputfifo = 0; + for (int i = 0; i < 5; i++) + { + if (ii >= bits.size()) + break; + inputfifo = (inputfifo << 1) | (bits.getBit(ii++) ? 1 : 0); + } + + bitw.push(decodeDataGcr(inputfifo), 4); + } + bitw.flush(); + + return output; + } + + public Commodore64Decoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + if (readRaw20() != C64.C64_SECTOR_RECORD) + return; + + Bits bits = readRawBits(5 * 10); + Bytes bytes = decode(bits).slice(0, 5); + + int checksum = bytes.getByte(0) & 0xff; + int logicalSector = bytes.getByte(1) & 0xff; + int logicalHead = 0; + int logicalCylinder = (bytes.getByte(2) & 0xff) - 1; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + if (checksum == Crc.xorBytes(bytes.slice(1, 4))) + sector.status = Sector.Status.DATA_MISSING; /* unintuitive but correct */ + } + + @Override + protected void decodeDataRecord() + { + if (readRaw20() != C64.C64_DATA_RECORD) + return; + + Bits bits = readRawBits(259 * 10); + Bytes bytes = decode(bits).slice(0, 259); + + sector.data = bytes.slice(0, C64.C64_SECTOR_LENGTH); + int gotChecksum = Crc.xorBytes(sector.data); + int wantChecksum = bytes.getByte(256) & 0xff; + sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java b/java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java new file mode 100644 index 00000000..6d751ee9 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java @@ -0,0 +1,125 @@ +package com.cowlark.fluxengine.arch.f85; + +import com.cowlark.fluxengine.core.BitWriter; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; + +/** + * The Durango F85 decoder, ported from arch/f85/decoder.cc. + */ +public class DurangoF85Decoder extends Decoder +{ + private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(24, F85.F85_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(24, F85.F85_DATA_RECORD); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + + private static int decodeDataGcr(int gcr) + { + switch (gcr) + { + case 0x19: return 0x00; + case 0x1b: return 0x01; + case 0x12: return 0x02; + case 0x13: return 0x03; + case 0x1d: return 0x04; + case 0x15: return 0x05; + case 0x16: return 0x06; + case 0x17: return 0x07; + case 0x1a: return 0x08; + case 0x09: return 0x09; + case 0x0a: return 0x0a; + case 0x0b: return 0x0b; + case 0x1e: return 0x0c; + case 0x0d: return 0x0d; + case 0x0e: return 0x0e; + case 0x0f: return 0x0f; + default: return -1; + } + } + + private static Bytes decode(Bits bits) + { + Bytes output = new Bytes(); + ByteWriter bw = new ByteWriter(output); + BitWriter bitw = new BitWriter(bw); + + int ii = 0; + while (ii < bits.size()) + { + int inputfifo = 0; + for (int i = 0; i < 5; i++) + { + if (ii >= bits.size()) + break; + inputfifo = (inputfifo << 1) | (bits.getBit(ii++) ? 1 : 0); + } + + bitw.push(decodeDataGcr(inputfifo), 4); + } + bitw.flush(); + + return output; + } + + public DurangoF85Decoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + /* Skip sync bits and ID byte. */ + + if (readRaw24() != F85.F85_SECTOR_RECORD) + return; + + /* Read header. */ + + Bytes bytes = decode(readRawBits(6 * 10)); + + int logicalSector = bytes.getByte(2) & 0xff; + int logicalHead = 0; + int logicalCylinder = bytes.getByte(0) & 0xff; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + int wantChecksum = bytes.iterator().seek(4).readBe16(); + int gotChecksum = Crc.crc16(Crc.CCITT_POLY, 0xef21, bytes.slice(0, 4)); + if (wantChecksum == gotChecksum) + sector.status = Sector.Status.DATA_MISSING; /* unintuitive but correct */ + } + + @Override + protected void decodeDataRecord() + { + /* Skip sync bits ID byte. */ + + if (readRaw24() != F85.F85_DATA_RECORD) + return; + + Bytes bytes = decode(readRawBits((F85.F85_SECTOR_LENGTH + 3) * 10)) + .slice(0, F85.F85_SECTOR_LENGTH + 3); + ByteReader br = bytes.iterator(); + + sector.data = br.read(F85.F85_SECTOR_LENGTH); + int wantChecksum = br.readBe16(); + int gotChecksum = Crc.crc16(Crc.CCITT_POLY, 0xbf84, sector.data); + sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/f85/F85.java b/java/com/cowlark/fluxengine/arch/f85/F85.java new file mode 100644 index 00000000..3439b387 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/f85/F85.java @@ -0,0 +1,15 @@ +package com.cowlark.fluxengine.arch.f85; + +/** + * Constants for the Durango F85 format, ported from arch/f85/f85.h. + */ +public final class F85 +{ + public static final int F85_SECTOR_RECORD = 0xffffce; /* 1111 1111 1111 1111 1100 1110 */ + public static final int F85_DATA_RECORD = 0xffffcb; /* 1111 1111 1111 1111 1100 1101 */ + public static final int F85_SECTOR_LENGTH = 512; + + private F85() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/fb100/Fb100.java b/java/com/cowlark/fluxengine/arch/fb100/Fb100.java new file mode 100644 index 00000000..025a601b --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/fb100/Fb100.java @@ -0,0 +1,15 @@ +package com.cowlark.fluxengine.arch.fb100; + +/** + * Constants for the FB100 format, ported from arch/fb100/fb100.h. + */ +public final class Fb100 +{ + public static final int FB100_RECORD_SIZE = 0x516; /* bytes */ + public static final int FB100_ID_SIZE = 17; + public static final int FB100_PAYLOAD_SIZE = 0x500; + + private Fb100() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java b/java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java new file mode 100644 index 00000000..e5ce2e18 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java @@ -0,0 +1,145 @@ +package com.cowlark.fluxengine.arch.fb100; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The FB100 decoder, ported from arch/fb100/decoder.cc. + */ +public class Fb100Decoder extends Decoder +{ + private static final FluxPattern SECTOR_ID_PATTERN = new FluxPattern(16, 0xabaa); + + /* + * Reverse engineered from a dump of the floppy drive's ROM. I have no idea + * how it works. + * + * LF8BA: + * clra + * staa X00B0 + * staa X00B1 + * ldx #$8000 + * LF8C2: ldaa $00,x + * inx + * bsr LF8CF + * cpx #$8011 + * bne LF8C2 + * ldd X00B0 + * rts + * LF8CF: + * eora X00B0 + * staa X00CF + * asla + * asla + * asla + * asla + * eora X00CF + * staa X00CF + * rola + * rola + * rola + * tab + * anda #$F8 + * eora X00B1 + * staa X00B0 + * rolb + * rolb + * andb #$0F + * eorb X00B0 + * stab X00B0 + * rolb + * eorb X00CF + * stab X00B1 + * rts + */ + private static void rol(int[] b, boolean[] c) + { + boolean newc = (b[0] & 0x80) != 0; + b[0] = ((b[0] << 1) | (c[0] ? 1 : 0)) & 0xff; + c[0] = newc; + } + + private static int checksum(Bytes bytes) + { + int crclo = 0; + int crchi = 0; + for (int i = 0; i < bytes.size(); i++) + { + int a = bytes.getByte(i) & 0xff; + a ^= crchi; + int t1 = a; + a <<= 4; + boolean[] c = {((a & 0x10) != 0)}; + a ^= t1; + t1 = a; + int[] b = {a}; + rol(b, c); + rol(b, c); + rol(b, c); + a = b[0]; + a &= 0xf8; + a ^= crclo; + crchi = a; + rol(b, c); + rol(b, c); + b[0] &= 0x0f; + b[0] ^= crchi; + crchi = b[0]; + rol(b, c); + b[0] ^= t1; + crclo = b[0]; + } + + return (crchi << 8) | crclo; + } + + public Fb100Decoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(SECTOR_ID_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + Bits rawbits = readRawBits(Fb100.FB100_RECORD_SIZE * 16); + + Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, Fb100.FB100_RECORD_SIZE); + ByteReader br = bytes.iterator(); + br.seek(1); + Bytes id = br.read(Fb100.FB100_ID_SIZE); + int wantIdCrc = br.readBe16(); + int gotIdCrc = checksum(id); + Bytes payload = br.read(Fb100.FB100_PAYLOAD_SIZE); + int wantPayloadCrc = br.readBe16(); + int gotPayloadCrc = checksum(payload); + + if (wantIdCrc != gotIdCrc) + return; + + int abssector = id.getByte(2) & 0xff; + int logicalCylinder = abssector >> 1; + int logicalHead = 0; + int logicalSector = abssector & 1; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + Bytes data = new Bytes(); + data.writer().write(id.slice(5, 12)).write(payload); + sector.data = data; + + sector.status = + (wantPayloadCrc == gotPayloadCrc) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/ibm/Ibm.java b/java/com/cowlark/fluxengine/arch/ibm/Ibm.java new file mode 100644 index 00000000..4e68d954 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/ibm/Ibm.java @@ -0,0 +1,23 @@ +package com.cowlark.fluxengine.arch.ibm; + +/** + * Constants for the IBM format (i.e. ordinary PC floppies), ported from + * arch/ibm/ibm.h. + */ +public final class Ibm +{ + public static final int IBM_MFM_SYNC = 0xA1; /* sync byte for MFM */ + public static final int IBM_IAM = 0xFC; /* start-of-track record */ + public static final int IBM_IAM_LEN = 1; /* plus prologue */ + public static final int IBM_IDAM = 0xFE; /* sector header */ + public static final int IBM_IDAM_LEN = 7; /* plus prologue */ + public static final int IBM_DAM1 = 0xF8; /* sector data (type 1) */ + public static final int IBM_DAM2 = 0xFB; /* sector data (type 2) */ + public static final int IBM_TRS80DAM1 = 0xF9; /* sector data (TRS-80 directory) */ + public static final int IBM_TRS80DAM2 = 0xFA; /* sector data (TRS-80 directory) */ + public static final int IBM_DAM_LEN = 1; /* plus prologue and user data */ + + private Ibm() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java b/java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java new file mode 100644 index 00000000..aac7684b --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java @@ -0,0 +1,240 @@ +package com.cowlark.fluxengine.arch.ibm; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.ibm.IbmDecoderProto; + +/** + * The IBM decoder, ported from arch/ibm/decoder.cc. + */ +public class IbmDecoder extends Decoder +{ + /* + * The markers at the beginning of records are special, and have + * missing clock pulses, allowing them to be found by the logic. + * + * IAM record: + * flux: XXXX-XXX-XXXX-X- = 0xf77a + * clock: X X - X - X X X = 0xd7 + * data: X X X X X X - - = 0xfc + * + * (We just ignore this one --- it's useless and optional.) + */ + + /* + * IDAM record: + * flux: XXXX-X-X-XXXXXX- = 0xf57e + * clock: X X - - - X X X = 0xc7 + * data: X X X X X X X - = 0xfe + */ + private static final FluxPattern FM_IDAM_PATTERN = new FluxPattern(16, 0xf57e); + + /* + * DAM1 record: + * flux: XXXX-X-X-XX-X-X- = 0xf56a + * clock: X X - - - X X X = 0xc7 + * data: X X X X X - - - = 0xf8 + */ + private static final FluxPattern FM_DAM1_PATTERN = new FluxPattern(16, 0xf56a); + + /* + * DAM2 record: + * flux: XXXX-X-X-XX-XXXX = 0xf56f + * clock: X X - - - X X X = 0xc7 + * data: X X X X X - X X = 0xfb + */ + private static final FluxPattern FM_DAM2_PATTERN = new FluxPattern(16, 0xf56f); + + /* + * TRS80DAM1 record: + * flux: XXXX-X-X-XX-X-XX = 0xf56b + * clock: X X - - - X X X = 0xc7 + * data: X X X X X - - X = 0xf9 + */ + private static final FluxPattern FM_TRS80DAM1_PATTERN = new FluxPattern(16, 0xf56b); + + /* + * TRS80DAM2 record: + * flux: XXXX-X-X-XX-XXX- = 0xf56e + * clock: X X - - - X X X = 0xc7 + * data: X X X X X - X - = 0xfa + */ + private static final FluxPattern FM_TRS80DAM2_PATTERN = new FluxPattern(16, 0xf56e); + + /* MFM record separator: + * 0xA1 is: + * data: 1 0 1 0 0 0 0 1 = 0xa1 + * mfm: 01 00 01 00 10 10 10 01 = 0x44a9 + * special: 01 00 01 00 10 00 10 01 = 0x4489 + * ^^^^^ + * When shifted out of phase, the special 0xa1 byte becomes an illegal + * encoding (you can't do 10 00). So this can't be spoofed by user data. + * + * shifted: 10 00 10 01 00 01 00 1 + * + * It's repeated three times. + */ + private static final FluxPattern MFM_PATTERN = new FluxPattern(48, 0x448944894489L); + + private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of( + MFM_PATTERN, + FM_IDAM_PATTERN, + FM_DAM1_PATTERN, + FM_DAM2_PATTERN, + FM_TRS80DAM1_PATTERN, + FM_TRS80DAM2_PATTERN); + + private final IbmDecoderProto config; + private int currentSectorSize; + + public IbmDecoder(DecoderProto config) + { + super(config); + this.config = config.getIbm(); + } + + private IbmDecoderProto.TrackdataProto getTrackFormat(int track, int head) + { + IbmDecoderProto.TrackdataProto.Builder builder = IbmDecoderProto.TrackdataProto.newBuilder(); + for (IbmDecoderProto.TrackdataProto f : config.getTrackdataList()) + { + if (f.hasTrack() && (f.getTrack() != track)) + continue; + if (f.hasHead() && (f.getHead() != head)) + continue; + + builder.mergeFrom(f); + } + return builder.build(); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + /* This is really annoying because the IBM record scheme has a + * variable-sized header _and_ the checksum covers this header too. So + * we have to read and decode a byte at a time until we know where the + * record itself starts, saving the bytes for the checksumming later. + */ + + Bytes bytes = new Bytes(); + ByteWriter bw = bytes.writer(); + + int id = readByte(bw); + if (id == 0xa1) + { + readByte(bw); + readByte(bw); + id = readByte(bw); + } + if (id != Ibm.IBM_IDAM) + return; + + ByteReader br = bytes.iterator(); + br.seek(bw.pos()); + + Bits bits = readRawBits(Ibm.IBM_IDAM_LEN * 16); + bw.write(FmMfm.decodeFmMfm(bits).slice(0, Ibm.IBM_IDAM_LEN)); + + IbmDecoderProto.TrackdataProto trackdata = + getTrackFormat(ltl.logicalCylinder, ltl.logicalHead); + + int logicalCylinder = br.read8(); + int logicalHead = br.read8(); + int logicalSector = br.read8(); + currentSectorSize = 1 << (br.read8() + 7); + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + int gotCrc = Crc.crc16(Crc.CCITT_POLY, bytes.slice(0, br.pos())); + int wantCrc = br.readBe16(); + if (wantCrc == gotCrc) + sector.status = Sector.Status.DATA_MISSING; + + if (trackdata.getIgnoreSideByte()) + sector.location = new LogicalLocation( + sector.location.logicalCylinder(), + ltl.logicalHead, + sector.location.logicalSector()); + sector.location = new LogicalLocation( + sector.location.logicalCylinder(), + sector.location.logicalHead() ^ (trackdata.getInvertSideByte() ? 1 : 0), + sector.location.logicalSector()); + if (trackdata.getIgnoreTrackByte()) + sector.location = new LogicalLocation( + ltl.logicalCylinder, + sector.location.logicalHead(), + sector.location.logicalSector()); + + for (int s : trackdata.getIgnoreSectorList()) + if (sector.location.logicalSector() == s) + { + sector.status = Sector.Status.MISSING; + break; + } + } + + @Override + protected void decodeDataRecord() + { + /* This is the same deal as the sector record. */ + + Bytes bytes = new Bytes(); + ByteWriter bw = bytes.writer(); + + int id = readByte(bw); + if (id == 0xa1) + { + readByte(bw); + readByte(bw); + id = readByte(bw); + } + if ((id != Ibm.IBM_DAM1) && (id != Ibm.IBM_DAM2) && + (id != Ibm.IBM_TRS80DAM1) && (id != Ibm.IBM_TRS80DAM2)) + return; + + ByteReader br = bytes.iterator(); + br.seek(bw.pos()); + + Bits bits = readRawBits((currentSectorSize + 2) * 16); + bw.write(FmMfm.decodeFmMfm(bits).slice(0, currentSectorSize + 2)); + + sector.data = br.read(currentSectorSize); + int gotCrc = Crc.crc16(Crc.CCITT_POLY, bytes.slice(0, br.pos())); + int wantCrc = br.readBe16(); + sector.status = (wantCrc == gotCrc) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + + if (currentSectorSize != ltl.sectorSize) + System.err.printf( + "Warning: configured sector size for t%d.h%d.s%d is %d bytes but that seen on disk is %d bytes%n", + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector(), + ltl.sectorSize, + currentSectorSize); + } + + private int readByte(ByteWriter bw) + { + Bits bits = readRawBits(16); + Bytes bytes = FmMfm.decodeFmMfm(bits).slice(0, 1); + int byte0 = bytes.getByte(0) & 0xff; + bw.write8(byte0); + return byte0; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/macintosh/Macintosh.java b/java/com/cowlark/fluxengine/arch/macintosh/Macintosh.java new file mode 100644 index 00000000..6abd03ac --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/macintosh/Macintosh.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.arch.macintosh; + +/** + * Constants for the Macintosh format, ported from arch/macintosh/macintosh.h. + */ +public final class Macintosh +{ + public static final int MAC_SECTOR_RECORD = 0xd5aa96; /* 1101 0101 1010 1010 1001 0110 */ + public static final int MAC_DATA_RECORD = 0xd5aaad; /* 1101 0101 1010 1010 1010 1101 */ + + public static final int MAC_SECTOR_LENGTH = 524; /* yes, really */ + public static final int MAC_ENCODED_SECTOR_LENGTH = 703; + public static final int MAC_FORMAT_BYTE = 0x22; + + public static final int MAC_TRACKS_PER_DISK = 80; + + private Macintosh() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java new file mode 100644 index 00000000..8a9219f0 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java @@ -0,0 +1,259 @@ +package com.cowlark.fluxengine.arch.macintosh; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; + +/** + * The Macintosh decoder, ported from arch/macintosh/decoder.cc. + */ +public class MacintoshDecoder extends Decoder +{ + private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(24, Macintosh.MAC_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(24, Macintosh.MAC_DATA_RECORD); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + + private static int decodeDataGcr(int gcr) + { + switch (gcr) + { + case 0x96: return 0x00; + case 0x97: return 0x01; + case 0x9a: return 0x02; + case 0x9b: return 0x03; + case 0x9d: return 0x04; + case 0x9e: return 0x05; + case 0x9f: return 0x06; + case 0xa6: return 0x07; + case 0xa7: return 0x08; + case 0xab: return 0x09; + case 0xac: return 0x0a; + case 0xad: return 0x0b; + case 0xae: return 0x0c; + case 0xaf: return 0x0d; + case 0xb2: return 0x0e; + case 0xb3: return 0x0f; + case 0xb4: return 0x10; + case 0xb5: return 0x11; + case 0xb6: return 0x12; + case 0xb7: return 0x13; + case 0xb9: return 0x14; + case 0xba: return 0x15; + case 0xbb: return 0x16; + case 0xbc: return 0x17; + case 0xbd: return 0x18; + case 0xbe: return 0x19; + case 0xbf: return 0x1a; + case 0xcb: return 0x1b; + case 0xcd: return 0x1c; + case 0xce: return 0x1d; + case 0xcf: return 0x1e; + case 0xd3: return 0x1f; + case 0xd6: return 0x20; + case 0xd7: return 0x21; + case 0xd9: return 0x22; + case 0xda: return 0x23; + case 0xdb: return 0x24; + case 0xdc: return 0x25; + case 0xdd: return 0x26; + case 0xde: return 0x27; + case 0xdf: return 0x28; + case 0xe5: return 0x29; + case 0xe6: return 0x2a; + case 0xe7: return 0x2b; + case 0xe9: return 0x2c; + case 0xea: return 0x2d; + case 0xeb: return 0x2e; + case 0xec: return 0x2f; + case 0xed: return 0x30; + case 0xee: return 0x31; + case 0xef: return 0x32; + case 0xf2: return 0x33; + case 0xf3: return 0x34; + case 0xf4: return 0x35; + case 0xf5: return 0x36; + case 0xf6: return 0x37; + case 0xf7: return 0x38; + case 0xf9: return 0x39; + case 0xfa: return 0x3a; + case 0xfb: return 0x3b; + case 0xfc: return 0x3c; + case 0xfd: return 0x3d; + case 0xfe: return 0x3e; + case 0xff: return 0x3f; + default: return -1; + } + } + + /* This is extremely inspired by the MESS implementation, written by Nathan + * Woods and R. Belmont: + * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp + */ + private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) + { + Bytes output = new Bytes(); + ByteWriter bw = new ByteWriter(output); + ByteReader br = input.iterator(); + + int lookupLen = Macintosh.MAC_SECTOR_LENGTH / 3; + + int[] b1 = new int[lookupLen + 1]; + int[] b2 = new int[lookupLen + 1]; + int[] b3 = new int[lookupLen + 1]; + + for (int i = 0; i <= lookupLen; i++) + { + int w4 = br.read8(); + int w1 = br.read8(); + int w2 = br.read8(); + int w3 = (i != 174) ? br.read8() : 0; + + b1[i] = (w1 & 0x3F) | ((w4 << 2) & 0xC0); + b2[i] = (w2 & 0x3F) | ((w4 << 4) & 0xC0); + b3[i] = (w3 & 0x3F) | ((w4 << 6) & 0xC0); + } + + /* Copy from the user's buffer to our buffer, while computing the + * three-byte data checksum. */ + + int c1 = 0; + int c2 = 0; + int c3 = 0; + int count = 0; + for (; ; ) + { + c1 = (c1 & 0xFF) << 1; + if ((c1 & 0x0100) != 0) + c1++; + + int val = b1[count] ^ c1; + c3 += val; + if ((c1 & 0x0100) != 0) + { + c3++; + c1 &= 0xFF; + } + bw.write8(val); + + val = b2[count] ^ c3; + c2 += val; + if (c3 > 0xFF) + { + c2++; + c3 &= 0xFF; + } + bw.write8(val); + + if (output.size() == 524) + break; + + val = b3[count] ^ c2; + c1 += val; + if (c2 > 0xFF) + { + c1++; + c2 &= 0xFF; + } + bw.write8(val); + count++; + } + + int c4 = ((c1 & 0xC0) >> 6) | ((c2 & 0xC0) >> 4) | ((c3 & 0xC0) >> 2); + c1 &= 0x3f; + c2 &= 0x3f; + c3 &= 0x3f; + c4 &= 0x3f; + int g4 = br.read8(); + int g3 = br.read8(); + int g2 = br.read8(); + int g1 = br.read8(); + if ((g4 == c4) && (g3 == c3) && (g2 == c2) && (g1 == c1)) + status[0] = Sector.Status.OK; + + return output; + } + + private static int decodeSide(int side) + { + /* Mac disks, being weird, use the side byte to encode both the side + * (in bit 5) and also whether we're above track 0x3f (in bit 0). */ + + return (side & 0x20) != 0 ? 1 : 0; + } + + public MacintoshDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + if (readRaw24() != Macintosh.MAC_SECTOR_RECORD) + return; + + /* Read header. */ + + Bytes header = readRawBits(7 * 8).toBytes().slice(0, 7); + + int encodedTrack = decodeDataGcr(header.getByte(0) & 0xff); + if (encodedTrack != (ltl.logicalCylinder & 0x3f)) + return; + + int encodedSector = decodeDataGcr(header.getByte(1) & 0xff); + int encodedSide = decodeDataGcr(header.getByte(2) & 0xff); + int formatByte = decodeDataGcr(header.getByte(3) & 0xff); + int wantedsum = decodeDataGcr(header.getByte(4) & 0xff); + + if (encodedSector > 11) + return; + + int logicalCylinder = ltl.logicalCylinder; + int logicalHead = decodeSide(encodedSide); + int logicalSector = encodedSector; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + int gotsum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; + if (wantedsum == gotsum) + sector.status = Sector.Status.DATA_MISSING; + } + + @Override + protected void decodeDataRecord() + { + if (readRaw24() != Macintosh.MAC_DATA_RECORD) + return; + + /* Read data. */ + + readRawBits(8); /* skip spare byte */ + Bytes inputbuffer = readRawBits(Macintosh.MAC_ENCODED_SECTOR_LENGTH * 8) + .toBytes() + .slice(0, Macintosh.MAC_ENCODED_SECTOR_LENGTH); + + for (int i = 0; i < inputbuffer.size(); i++) + inputbuffer.setByte(i, (byte) decodeDataGcr(inputbuffer.getByte(i) & 0xff)); + + Sector.Status[] status = {Sector.Status.BAD_CHECKSUM}; + sector.status = status[0]; + Bytes userData = decodeCrazyData(inputbuffer, status); + sector.status = status[0]; + sector.data = new Bytes(); + sector.data.writer() + .write(userData.slice(12, 512)) + .write(userData.slice(0, 12)); + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/micropolis/Micropolis.java b/java/com/cowlark/fluxengine/arch/micropolis/Micropolis.java new file mode 100644 index 00000000..18fb291b --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/micropolis/Micropolis.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.arch.micropolis; + +/** + * Constants for the Micropolis format, ported from arch/micropolis/micropolis.h. + */ +public final class Micropolis +{ + public static final int MICROPOLIS_PAYLOAD_SIZE = (256); + public static final int MICROPOLIS_HEADER_SIZE = (1 + 2 + 10); + public static final int MICROPOLIS_ENCODED_SECTOR_SIZE = + (MICROPOLIS_HEADER_SIZE + MICROPOLIS_PAYLOAD_SIZE + 6); + + private Micropolis() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java new file mode 100644 index 00000000..4395502e --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java @@ -0,0 +1,266 @@ +package com.cowlark.fluxengine.arch.micropolis; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.micropolis.MicropolisDecoderProto; + +/** + * The Micropolis decoder, ported from arch/micropolis/decoder.cc. + */ +public class MicropolisDecoder extends Decoder +{ + /* The sector has a preamble of MFM 0x00s and uses 0xFF as a sync pattern. + * + * 00 00 00 F F + * 0000 0000 0000 0000 0000 0000 0101 0101 0101 0101 + * A A A A A A 5 5 5 5 + */ + private static final FluxPattern SECTOR_SYNC_PATTERN = new FluxPattern(64, 0xAAAAAAAAAAAA5555L); + + /* Pattern to skip past current SYNC. */ + private static final FluxPattern SECTOR_ADVANCE_PATTERN = new FluxPattern(64, 0xAAAAAAAAAAAAAAAAL); + + /* Standard Micropolis checksum. Adds all bytes, with carry. */ + public static int micropolisChecksum(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + int sum = 0; + while (!br.eof()) + { + if (sum > 0xFF) + { + sum -= 0x100 - 1; + } + sum += br.read8(); + } + /* The last carry is ignored. */ + return sum & 0xFF; + } + + /* Vector MZOS does not use the standard Micropolis checksum. */ + public static int mzosChecksum(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + int checksum = 0; + + while (!br.eof()) + { + int databyte = br.read8(); + checksum ^= ((databyte << 1) | (databyte >>> 7)) & 0xff; + } + + return checksum; + } + + private static int b(int field, int pos) + { + return (field >>> pos) & 1; + } + + private static int eccNextBit(int ecc, int dataBit) + { + /* This is 0x81932080 which is 0x0104C981 with reversed bits. */ + return b(ecc, 7) ^ b(ecc, 13) ^ b(ecc, 16) ^ b(ecc, 17) ^ b(ecc, 20) ^ b(ecc, 23) ^ + b(ecc, 24) ^ b(ecc, 31) ^ dataBit; + } + + public static int vectorGraphicEcc(Bytes bytes) + { + int e = 0; + Bytes payloadBytes = bytes.slice(0, bytes.size() - 4); + ByteReader payload = new ByteReader(payloadBytes); + while (!payload.eof()) + { + int byte0 = payload.read8(); + for (int i = 0; i < 8; i++) + { + e = (e << 1) | eccNextBit(e, byte0 >>> 7); + byte0 <<= 1; + } + } + Bytes trailerBytes = bytes.slice(bytes.size() - 4); + ByteReader trailer = new ByteReader(trailerBytes); + int res = e; + while (!trailer.eof()) + { + int byte0 = trailer.read8(); + for (int i = 0; i < 8; i++) + { + res = (res << 1) | eccNextBit(e, byte0 >>> 7); + e <<= 1; + byte0 <<= 1; + } + } + return res; + } + + /* Fixes bytes when possible, returning true if changed. */ + private static boolean vectorGraphicEccFix(Bytes bytes, int syndrome) + { + int ecc = syndrome; + int pos = (Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE - 5) * 8 + 7; + boolean aligned = false; + while ((ecc & 0xff000000) == 0) + { + pos += 8; + ecc <<= 8; + } + for (; pos >= 0; pos--) + { + boolean bit = (ecc & 1) != 0; + ecc >>>= 1; + if (bit) + ecc ^= 0x808264c0; + if ((ecc & 0xff07ffff) == 0) + aligned = true; + if (aligned && pos % 8 == 0) + break; + } + if (pos < 0) + return false; + bytes.setByte(pos / 8, (byte) (bytes.getByte(pos / 8) ^ (ecc >>> 16))); + return true; + } + + private final MicropolisDecoderProto config; + private MicropolisDecoderProto.ChecksumType checksumType; + + public MicropolisDecoder(DecoderProto config) + { + super(config); + this.config = config.getMicropolis(); + checksumType = this.config.getChecksumType(); + } + + @Override + protected double advanceToNextRecord() + { + double now = tell().getDurationNs(); + + /* For all but the first sector, seek to the next sector pulse. The + * first sector does not contain the sector pulse in the fluxmap. */ + if (now != 0) + { + seekToIndexMark(); + now = tell().getDurationNs(); + } + + /* Discard a possible partial sector at the end of the track. */ + if (now > (getFluxmapDuration() - 12.0e6)) + { + seekToIndexMark(); + return 0; + } + + double clock = seekToPattern(SECTOR_SYNC_PATTERN); + + double syncDelta = tell().getDurationNs() - now; + /* Due to the weak nature of the Micropolis SYNC pattern, it's possible + * to detect a false SYNC during the gap between the sector pulse and + * the write gate. */ + if ((syncDelta > 0) && (syncDelta < 100e3)) + { + seekToPattern(SECTOR_ADVANCE_PATTERN); + clock = seekToPattern(SECTOR_SYNC_PATTERN); + } + + sector.headerStartTimeNs = tell().getDurationNs(); + + /* seekToPattern() can skip past the index hole, if this happens too + * close to the end of the Fluxmap, discard the sector. */ + if (sector.headerStartTimeNs > (getFluxmapDuration() - 11.3e6)) + { + return 0; + } + + return clock; + } + + @Override + protected void decodeSectorRecord() + { + readRawBits(48); + com.cowlark.fluxengine.core.Bits rawbits = readRawBits(Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE * 16); + Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE); + + boolean eccPresent = (bytes.getByte(274) & 0xff) == 0xaa; + int ecc = 0; + if (config.getEccType() == MicropolisDecoderProto.EccType.VECTOR && eccPresent) + { + ecc = vectorGraphicEcc(bytes.slice(0, 274)); + if (ecc != 0) + { + vectorGraphicEccFix(bytes, ecc); + ecc = vectorGraphicEcc(bytes.slice(0, 274)); + } + } + + ByteReader br = bytes.iterator(); + + int syncByte = br.read8(); /* sync */ + if (syncByte != 0xFF) + return; + + int logicalCylinder = br.read8(); + int logicalHead = ltl.logicalHead; + int logicalSector = br.read8(); + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + if (logicalSector > 15) + return; + if (logicalCylinder > 76) + return; + if (logicalCylinder != ltl.logicalCylinder) + return; + + br.read(10); /* OS data or padding */ + Bytes data = br.read(Micropolis.MICROPOLIS_PAYLOAD_SIZE); + int wantChecksum = br.read8(); + + /* If not specified, automatically determine the checksum type. */ + if (checksumType == MicropolisDecoderProto.ChecksumType.AUTO) + { + /* Calculate both standard Micropolis (MDOS, CP/M, OASIS) and MZOS + * checksums. */ + if (wantChecksum == micropolisChecksum(bytes.slice(1, 2 + 266))) + { + checksumType = MicropolisDecoderProto.ChecksumType.MICROPOLIS; + } else if (wantChecksum == mzosChecksum(bytes.slice( + Micropolis.MICROPOLIS_HEADER_SIZE, Micropolis.MICROPOLIS_PAYLOAD_SIZE))) + { + checksumType = MicropolisDecoderProto.ChecksumType.MZOS; + System.out.println("Note: MZOS checksum detected."); + } + } + + int gotChecksum; + + if (checksumType == MicropolisDecoderProto.ChecksumType.MZOS) + { + gotChecksum = mzosChecksum(bytes.slice( + Micropolis.MICROPOLIS_HEADER_SIZE, Micropolis.MICROPOLIS_PAYLOAD_SIZE)); + } else + { + gotChecksum = micropolisChecksum(bytes.slice(1, 2 + 266)); + } + + br.read(5); /* 4 byte ECC and ECC-present flag */ + + if (config.getSectorOutputSize() == Micropolis.MICROPOLIS_PAYLOAD_SIZE) + sector.data = data; + else if (config.getSectorOutputSize() == Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE) + sector.data = bytes; + else + throw new FluxEngineException("Sector output size may only be 256 or 275"); + if (wantChecksum == gotChecksum && (!eccPresent || ecc == 0)) + sector.status = Sector.Status.OK; + else + sector.status = Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/mx/Mx.java b/java/com/cowlark/fluxengine/arch/mx/Mx.java new file mode 100644 index 00000000..bc25f7e9 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/mx/Mx.java @@ -0,0 +1,13 @@ +package com.cowlark.fluxengine.arch.mx; + +/** + * Constants for the MX format, ported from arch/mx/mx.h. + */ +public final class Mx +{ + public static final int SECTOR_SIZE = 256; + + private Mx() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/mx/MxDecoder.java b/java/com/cowlark/fluxengine/arch/mx/MxDecoder.java new file mode 100644 index 00000000..afcd3699 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/mx/MxDecoder.java @@ -0,0 +1,87 @@ +package com.cowlark.fluxengine.arch.mx; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The MX decoder, ported from arch/mx/decoder.cc. + */ +public class MxDecoder extends Decoder +{ + /* + * MX disks are a bunch of sectors glued together with no gaps or sync + * markers, following a single beginning-of-track synchronisation and + * identification sequence. + */ + + /* FM beginning of track marker: + * 0 0 f 3 decoded nibbles + * 0 0 0 0 0 0 0 0 1 1 1 1 0 0 1 1 + * 1010 1010 1010 1010 1111 1111 1010 1111 + * a a a a f f a f encoded nibbles + */ + private static final FluxPattern ID_PATTERN = new FluxPattern(32, 0xaaaaffaf); + + private double clock; + private int currentSector; + + public MxDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected void beginTrack() + { + clock = sector.clockNs = seekToPattern(ID_PATTERN); + currentSector = 0; + } + + @Override + protected double advanceToNextRecord() + { + if (currentSector == 11) + { + /* That was the last sector on the disk. */ + return 0; + } else + { + return clock; + } + } + + @Override + protected void decodeSectorRecord() + { + /* Skip the ID pattern and track word, which is only present on the + * first sector. We don't trust the track word because some drivers + * don't write it correctly. */ + + if (currentSector == 0) + readRawBits(64); + + Bits bits = readRawBits((Mx.SECTOR_SIZE + 2) * 16); + Bytes bytes = FmMfm.decodeFmMfm(bits).slice(0, Mx.SECTOR_SIZE + 2); + + int gotChecksum = 0; + ByteReader br = bytes.iterator(); + for (int i = 0; i < (Mx.SECTOR_SIZE / 2); i++) + gotChecksum += br.readBe16(); + int wantChecksum = br.readBe16(); + + int logicalCylinder = ltl.logicalCylinder; + int logicalHead = ltl.logicalHead; + int logicalSector = currentSector; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + sector.data = bytes.slice(0, Mx.SECTOR_SIZE).swab(); + sector.status = (gotChecksum == wantChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + currentSector++; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/northstar/Northstar.java b/java/com/cowlark/fluxengine/arch/northstar/Northstar.java new file mode 100644 index 00000000..9325ac6c --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/northstar/Northstar.java @@ -0,0 +1,33 @@ +package com.cowlark.fluxengine.arch.northstar; + +/** + * Constants for the North Star format, ported from arch/northstar/northstar.h. + * + * Northstar floppies are 10-hard sectored disks with a sector format as + * follows: + * + * |----------------------------------| + * | SYNC Byte | Payload | Checksum | + * |------------+----------+----------| + * | 1 (0xFB) | 256 (SD) | 1 | + * | 2 (0xFBFB) | 512 (DD) | | + * |----------------------------------| + */ +public final class Northstar +{ + public static final int NORTHSTAR_PREAMBLE_SIZE_SD = (16); + public static final int NORTHSTAR_PREAMBLE_SIZE_DD = (32); + public static final int NORTHSTAR_HEADER_SIZE_SD = (1); + public static final int NORTHSTAR_HEADER_SIZE_DD = (2); + public static final int NORTHSTAR_PAYLOAD_SIZE_SD = (256); + public static final int NORTHSTAR_PAYLOAD_SIZE_DD = (512); + public static final int NORTHSTAR_CHECKSUM_SIZE = (1); + public static final int NORTHSTAR_ENCODED_SECTOR_SIZE_SD = + (NORTHSTAR_HEADER_SIZE_SD + NORTHSTAR_PAYLOAD_SIZE_SD + NORTHSTAR_CHECKSUM_SIZE); + public static final int NORTHSTAR_ENCODED_SECTOR_SIZE_DD = + (NORTHSTAR_HEADER_SIZE_DD + NORTHSTAR_PAYLOAD_SIZE_DD + NORTHSTAR_CHECKSUM_SIZE); + + private Northstar() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java b/java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java new file mode 100644 index 00000000..599bccce --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java @@ -0,0 +1,166 @@ +package com.cowlark.fluxengine.arch.northstar; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * Decoder for North Star 10-sector hard-sectored disks, ported from + * arch/northstar/decoder.cc. + * + * Supports both single- and double-density. For the sector format and + * checksum algorithm, see pp. 33 of the North Star Double Density Controller + * manual: + * + * http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf + * + * North Star disks do not contain any track/head/sector information encoded in + * the sector record. For this reason, we have to be absolutely sure that the + * hardSectorId is correct. + */ +public class NorthstarDecoder extends Decoder +{ + private static final long MFM_ID = 0xaaaaaaaaaaaa5545L; + private static final long FM_ID = 0xaaaaaaaaaaaaffefL; + + /* + * MFM sectors have 32 bytes of 00's followed by two sync characters, + * specified in the North Star MDS manual as 0xFBFB. + * + * This is true for most disks; however, I found a few disks, including an + * original North Star DOS/BASIC v2.2.1 DQ disk) that uses 0xFBnn, where + * nn is an incrementing pattern. + * + * 00 00 00 F B + * 0000 0000 0000 0000 0000 0000 0101 0101 0100 0101 + * A A A A A A 5 5 4 5 + */ + private static final FluxPattern MFM_PATTERN = new FluxPattern(64, MFM_ID); + + /* FM sectors have 16 bytes of 00's followed by 0xFB. + * 00 FB + * 0000 0000 1111 1111 1110 1111 + * A A F F E F + */ + private static final FluxPattern FM_PATTERN = new FluxPattern(64, FM_ID); + + private static final FluxMatchers ANY_SECTOR_PATTERN = FluxMatchers.of(MFM_PATTERN, FM_PATTERN); + + /* Checksum is initially 0. For each data byte, XOR with the current + * checksum. Rotate checksum left, carrying bit 7 to bit 0. */ + public static int northstarChecksum(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + int checksum = 0; + + while (!br.eof()) + { + checksum ^= br.read8(); + checksum = ((checksum << 1) | (checksum >>> 7)) & 0xff; + } + + return checksum; + } + + private int hardSectorId; + + public NorthstarDecoder(DecoderProto config) + { + super(config); + } + + /* Search for FM or MFM sector record. */ + @Override + protected double advanceToNextRecord() + { + double now = tell().getDurationNs(); + + /* For all but the first sector, seek to the next sector pulse. The + * first sector does not contain the sector pulse in the fluxmap. */ + if (now != 0) + { + seekToIndexMark(); + now = tell().getDurationNs(); + } + + /* Discard a possible partial sector at the end of the track. */ + if (now > (getFluxmapDuration() - 21e6)) + { + seekToIndexMark(); + return 0; + } + + double clock = seekToPattern(ANY_SECTOR_PATTERN); + sector.headerStartTimeNs = tell().getDurationNs(); + + /* Discard a possible partial sector. */ + if (sector.headerStartTimeNs > (getFluxmapDuration() - 21e6)) + { + return 0; + } + + double sectorFoundTimeRaw = Math.round(sector.headerStartTimeNs / 1e6); + double sectorFoundTime; + + /* Round time to the nearest 20ms. */ + if ((sectorFoundTimeRaw % 20) < 10) + { + sectorFoundTime = (sectorFoundTimeRaw / 20) * 20; + } else + { + sectorFoundTime = ((sectorFoundTimeRaw + 20) / 20) * 20; + } + + /* Calculate the sector ID based on time since the index. */ + hardSectorId = (int) ((sectorFoundTime / 20) % 10); + + return clock; + } + + @Override + protected void decodeSectorRecord() + { + long id = readRawBits(64).toBytes().iterator().readBe64(); + int recordSize; + int payloadSize; + int headerSize; + + if (id == MFM_ID) + { + recordSize = Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_DD; + payloadSize = Northstar.NORTHSTAR_PAYLOAD_SIZE_DD; + headerSize = Northstar.NORTHSTAR_HEADER_SIZE_DD; + } else + { + recordSize = Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_SD; + payloadSize = Northstar.NORTHSTAR_PAYLOAD_SIZE_SD; + headerSize = Northstar.NORTHSTAR_HEADER_SIZE_SD; + } + + Bits rawbits = readRawBits(recordSize * 16); + Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, recordSize); + ByteReader br = bytes.iterator(); + + int logicalHead = ltl.logicalHead; + int logicalSector = hardSectorId; + int logicalCylinder = ltl.logicalCylinder; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + if (headerSize == Northstar.NORTHSTAR_HEADER_SIZE_DD) + { + br.read8(); /* MFM second Sync char, usually 0xFB */ + } + + sector.data = br.read(payloadSize); + int wantChecksum = br.read8(); + int gotChecksum = northstarChecksum(bytes.slice(headerSize - 1, payloadSize)); + sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java b/java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java new file mode 100644 index 00000000..203d8149 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java @@ -0,0 +1,81 @@ +package com.cowlark.fluxengine.arch.rolandd20; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The Roland D20 decoder, ported from arch/rolandd20/decoder.cc. + */ +public class RolandD20Decoder extends Decoder +{ + /* Sector header record: + * + * BF FF FF FF FF FF FE AB + * + * This encodes to: + * + * e d 5 5 5 5 5 5 + * 1110 1101 0101 0101 0101 0101 0101 0101 + * 5 5 5 5 5 5 5 5 + * 0101 0101 0101 0101 0101 0101 0101 0101 + * 5 5 5 5 5 5 5 5 + * 0101 0101 0101 0101 0101 0101 0101 0101 + * 5 5 5 4 4 4 4 5 + * 0101 0101 0101 0100 0100 0100 0100 0101 + */ + private static final FluxPattern SECTOR_PATTERN = new FluxPattern(64, 0xed55555555555555L); + + public RolandD20Decoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(SECTOR_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + Bytes bytes = FmMfm.decodeFmMfm(readRawBits(256)); + System.out.printf("%.3f ", sector.clockNs); + hexdump(bytes); + } + + private static void hexdump(Bytes buffer) + { + int pos = 0; + + while (pos < buffer.size()) + { + System.out.printf("%05x : ", pos); + for (int i = 0; i < 16; i++) + { + if ((pos + i) < buffer.size()) + System.out.printf("%02x ", buffer.getByte(pos + i)); + else + System.out.print("-- "); + } + System.out.print(" : "); + for (int i = 0; i < 16; i++) + { + if ((pos + i) >= buffer.size()) + break; + + int c = buffer.getByte(pos + i) & 0xff; + if ((c >= 32) && (c <= 126)) + System.out.print((char) c); + else + System.out.print('.'); + } + System.out.println(); + + pos += 16; + } + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/smaky6/Smaky6.java b/java/com/cowlark/fluxengine/arch/smaky6/Smaky6.java new file mode 100644 index 00000000..d6bfb626 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/smaky6/Smaky6.java @@ -0,0 +1,14 @@ +package com.cowlark.fluxengine.arch.smaky6; + +/** + * Constants for the Smaky6 format, ported from arch/smaky6/smaky6.h. + */ +public final class Smaky6 +{ + public static final int SMAKY6_SECTOR_SIZE = 256; + public static final int SMAKY6_RECORD_SIZE = (1 + SMAKY6_SECTOR_SIZE + 1); + + private Smaky6() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java b/java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java new file mode 100644 index 00000000..27b474e9 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java @@ -0,0 +1,153 @@ +package com.cowlark.fluxengine.arch.smaky6; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.FluxPosition; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; +import java.util.ArrayList; +import java.util.List; + +/** + * The Smaky6 decoder, ported from arch/smaky6/decoder.cc. + */ +public class Smaky6Decoder extends Decoder +{ + private static final FluxPattern SECTOR_PATTERN = new FluxPattern(32, 0x54892aaa); + + private record SectorStart(int id, FluxPosition pos) + { + } + + private int sectorId; + private int sectorIndex; + private final List sectorStarts = new ArrayList<>(); + + public Smaky6Decoder(DecoderProto config) + { + super(config); + } + + /* Returns the sector ID of the _current_ sector. */ + private int advanceToNextSector() + { + FluxPosition previous = tell(); + seekToIndexMark(); + FluxPosition now = tell(); + if ((now.getDurationNs() - previous.getDurationNs()) < 9e6) + { + seekToIndexMark(); + FluxPosition next = tell(); + if ((next.getDurationNs() - now.getDurationNs()) < 9e6) + { + /* We just found sector 0. */ + + sectorId = 0; + } else + { + /* Spurious... */ + + seek(now); + } + } + + return sectorId++; + } + + @Override + protected void beginTrack() + { + /* Find the start-of-track index marks, which will be an interval of + * about 6ms. */ + + seekToIndexMark(); + sectorId = 99; + for (; ; ) + { + FluxPosition pos = tell(); + advanceToNextSector(); + if (sectorId < 99) + { + seek(pos); + break; + } + + if (eof()) + return; + } + + /* Now we know where to start counting, start finding sectors. */ + + sectorStarts.clear(); + for (; ; ) + { + FluxPosition now = tell(); + if (eof()) + break; + + int id = advanceToNextSector(); + if (id < 16) + sectorStarts.add(new SectorStart(id, now)); + } + + sectorIndex = 0; + } + + @Override + protected double advanceToNextRecord() + { + if (sectorIndex == sectorStarts.size()) + { + seekToIndexMark(); + return 0; + } + + SectorStart p = sectorStarts.get(sectorIndex++); + sectorId = p.id(); + seek(p.pos()); + + double clock = seekToPattern(SECTOR_PATTERN); + sector.headerStartTimeNs = tell().getDurationNs(); + + return clock; + } + + @Override + protected void decodeSectorRecord() + { + readRawBits(33); + Bits rawbits = readRawBits(Smaky6.SMAKY6_RECORD_SIZE * 16); + if (rawbits.size() < Smaky6.SMAKY6_SECTOR_SIZE) + return; + + /* The Smaky bytes are stored backwards! Backwards! */ + + Bytes bytes = FmMfm.decodeFmMfm(rawbits) + .slice(0, Smaky6.SMAKY6_RECORD_SIZE) + .reverseBits(); + ByteReader br = bytes.iterator(); + + int track = br.read8(); + Bytes data = br.read(Smaky6.SMAKY6_SECTOR_SIZE); + int wantedChecksum = br.read8(); + int gotChecksum = Crc.sumBytes(data) & 0xff; + + if (track != ltl.logicalCylinder) + return; + + int logicalCylinder = ltl.physicalCylinder; + int logicalHead = ltl.logicalHead; + int logicalSector = sectorId; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + sector.data = data; + sector.status = + (wantedChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/tartu/Tartu.java b/java/com/cowlark/fluxengine/arch/tartu/Tartu.java new file mode 100644 index 00000000..376212cd --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/tartu/Tartu.java @@ -0,0 +1,14 @@ +package com.cowlark.fluxengine.arch.tartu; + +/** + * Constants for the Tartu format, ported from arch/tartu/tartu.h. + */ +public final class Tartu +{ + public static final long HEADER_BITS = 0xaaaaaaaa44895554L; + public static final long DATA_BITS = 0xaaaaaaaa44895545L; + + private Tartu() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java b/java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java new file mode 100644 index 00000000..dcc3769f --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java @@ -0,0 +1,75 @@ +package com.cowlark.fluxengine.arch.tartu; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The Tartu decoder, ported from arch/tartu/decoder.cc. + */ +public class TartuDecoder extends Decoder +{ + private static final FluxPattern HEADER_PATTERN = new FluxPattern(64, Tartu.HEADER_BITS); + private static final FluxPattern DATA_PATTERN = new FluxPattern(64, Tartu.DATA_BITS); + + private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(HEADER_PATTERN, DATA_PATTERN); + + public TartuDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + if (readRaw64() != Tartu.HEADER_BITS) + return; + + Bits bits = readRawBits(16 * 4); + Bytes bytes = FmMfm.decodeFmMfm(bits).slice(0, 4); + + ByteReader br = bytes.iterator(); + int track = br.read8(); + int logicalCylinder = track >> 1; + int logicalHead = track & 1; + br.skip(1); /* seems always to be 1 */ + int logicalSector = br.read8(); + int wantChecksum = br.read8(); + int gotChecksum = ~Crc.sumBytes(bytes.slice(0, 3)) & 0xff; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + if (wantChecksum == gotChecksum) + sector.status = Sector.Status.DATA_MISSING; + + sector.status = Sector.Status.DATA_MISSING; + } + + @Override + protected void decodeDataRecord() + { + if (readRaw64() != Tartu.DATA_BITS) + return; + + Bits bits = readRawBits(129 * 16); + Bytes bytes = FmMfm.decodeFmMfm(bits).slice(0, 129); + sector.data = bytes.slice(0, 128); + + int wantChecksum = bytes.iterator().seek(128).read8(); + int gotChecksum = ~Crc.sumBytes(sector.data) & 0xff; + sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/tids990/Tids990.java b/java/com/cowlark/fluxengine/arch/tids990/Tids990.java new file mode 100644 index 00000000..ce54f419 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/tids990/Tids990.java @@ -0,0 +1,15 @@ +package com.cowlark.fluxengine.arch.tids990; + +/** + * Constants for the TI DS990 format, ported from arch/tids990/tids990.h. + */ +public final class Tids990 +{ + public static final int TIDS990_PAYLOAD_SIZE = 288; /* bytes */ + public static final int TIDS990_SECTOR_RECORD_SIZE = 10; /* bytes */ + public static final int TIDS990_DATA_RECORD_SIZE = (TIDS990_PAYLOAD_SIZE + 4); /* bytes */ + + private Tids990() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java b/java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java new file mode 100644 index 00000000..efb3e38d --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java @@ -0,0 +1,106 @@ +package com.cowlark.fluxengine.arch.tids990; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The Texas Instruments DS990 decoder, ported from arch/tids990/decoder.cc. + */ +public class Tids990Decoder extends Decoder +{ + /* + * The Texas Instruments DS990 uses MFM with a scheme similar to a + * simplified version of the IBM record scheme (it's actually easier to + * parse than IBM). There are 26 sectors per track, each holding a rather + * weird 288 bytes. + */ + + /* + * Sector record: + * data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 0 = 0x550a + * mfm: 00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 00 = 0x11112a44 + * special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 00 = 0x11112244 + * ^^ + * When shifted out of phase, the special 0xa1 byte becomes an illegal + * encoding (you can't do 10 00). So this can't be spoofed by user data. + */ + private static final int SECTOR_ID = 0x550a; + private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(32, 0x11112244); + + /* + * Data record: + * data: 0 1 0 1 0 1 0 1 .0 0 0 0 1 0 1 1 = 0x550b + * mfm: 00 01 00 01 00 01 00 01.00 10 10 10 01 00 01 01 = 0x11112a45 + * special: 00 01 00 01 00 01 00 01.00 10 00 10 01 00 01 01 = 0x11112245 + * ^^ + * When shifted out of phase, the special 0xa1 byte becomes an illegal + * encoding (you can't do 10 00). So this can't be spoofed by user data. + */ + private static final int DATA_ID = 0x550b; + private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(32, 0x11112245); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + + public Tids990Decoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + Bits bits = readRawBits(Tids990.TIDS990_SECTOR_RECORD_SIZE * 16); + Bytes bytes = FmMfm.decodeFmMfm(bits).slice(0, Tids990.TIDS990_SECTOR_RECORD_SIZE); + + ByteReader br = bytes.iterator(); + if (br.readBe16() != SECTOR_ID) + return; + + int gotChecksum = + Crc.crc16(Crc.CCITT_POLY, bytes.slice(1, Tids990.TIDS990_SECTOR_RECORD_SIZE - 3)); + + int logicalHead = br.read8() >> 3; + int logicalCylinder = br.read8(); + br.read8(); /* number of sectors per track */ + int logicalSector = br.read8(); + br.readBe16(); /* sector size */ + int wantChecksum = br.readBe16(); + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + + if (wantChecksum == gotChecksum) + sector.status = Sector.Status.DATA_MISSING; + } + + @Override + protected void decodeDataRecord() + { + Bits bits = readRawBits(Tids990.TIDS990_DATA_RECORD_SIZE * 16); + Bytes bytes = FmMfm.decodeFmMfm(bits).slice(0, Tids990.TIDS990_DATA_RECORD_SIZE); + + ByteReader br = bytes.iterator(); + if (br.readBe16() != DATA_ID) + return; + + int gotChecksum = + Crc.crc16(Crc.CCITT_POLY, bytes.slice(1, Tids990.TIDS990_DATA_RECORD_SIZE - 3)); + + sector.data = br.read(Tids990.TIDS990_PAYLOAD_SIZE); + int wantChecksum = br.readBe16(); + sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/victor9k/Victor9k.java b/java/com/cowlark/fluxengine/arch/victor9k/Victor9k.java new file mode 100644 index 00000000..80864551 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/victor9k/Victor9k.java @@ -0,0 +1,23 @@ +package com.cowlark.fluxengine.arch.victor9k; + +/** + * Constants for the Victor 9k format, ported from arch/victor9k/victor9k.h. + */ +public final class Victor9k +{ + /* ... 1101 0101 0111 + * ^^ ^^^^ ^^^^ ten bit IO byte */ + public static final int VICTOR9K_SECTOR_RECORD = 0xfffffd57; + public static final int VICTOR9K_HEADER_ID = 0x7; + + /* ... 1101 0100 1001 + * ^^ ^^^^ ^^^^ ten bit IO byte */ + public static final int VICTOR9K_DATA_RECORD = 0xfffffd49; + public static final int VICTOR9K_DATA_ID = 0x8; + + public static final int VICTOR9K_SECTOR_LENGTH = 512; + + private Victor9k() + { + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java new file mode 100644 index 00000000..a91f2da5 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java @@ -0,0 +1,132 @@ +package com.cowlark.fluxengine.arch.victor9k; + +import com.cowlark.fluxengine.core.BitWriter; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxMatchers; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; + +/** + * The Victor 9k decoder, ported from arch/victor9k/decoder.cc. + */ +public class Victor9kDecoder extends Decoder +{ + private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(32, Victor9k.VICTOR9K_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(32, Victor9k.VICTOR9K_DATA_RECORD); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + + private static int decodeDataGcr(int gcr) + { + switch (gcr) + { + case 0x0a: return 0x0; + case 0x0b: return 0x1; + case 0x12: return 0x2; + case 0x13: return 0x3; + case 0x0e: return 0x4; + case 0x0f: return 0x5; + case 0x16: return 0x6; + case 0x17: return 0x7; + case 0x09: return 0x8; + case 0x19: return 0x9; + case 0x1a: return 0xa; + case 0x1b: return 0xb; + case 0x0d: return 0xc; + case 0x1d: return 0xd; + case 0x1e: return 0xe; + case 0x15: return 0xf; + default: return -1; + } + } + + private static Bytes decode(Bits bits) + { + Bytes output = new Bytes(); + ByteWriter bw = new ByteWriter(output); + BitWriter bitw = new BitWriter(bw); + + int ii = 0; + while (ii < bits.size()) + { + int inputfifo = 0; + for (int i = 0; i < 5; i++) + { + if (ii >= bits.size()) + break; + inputfifo = (inputfifo << 1) | (bits.getBit(ii++) ? 1 : 0); + } + + int decoded = decodeDataGcr(inputfifo); + bitw.push(decoded, 4); + } + bitw.flush(); + + return output; + } + + public Victor9kDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(ANY_RECORD_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + /* Check the ID. */ + + if (readRaw32() != Victor9k.VICTOR9K_SECTOR_RECORD) + return; + + /* Read header. */ + + Bytes bytes = decode(readRawBits(3 * 10)).slice(0, 3); + + int rawTrack = bytes.getByte(0) & 0xff; + int logicalSector = bytes.getByte(1) & 0xff; + int gotChecksum = bytes.getByte(2) & 0xff; + + int logicalCylinder = rawTrack & 0x7f; + int logicalHead = rawTrack >> 7; + int wantChecksum = (bytes.getByte(0) & 0xff) + (bytes.getByte(1) & 0xff); + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + if ((logicalSector > 20) || (logicalCylinder > 85) || (logicalHead > 1)) + return; + + if (wantChecksum == gotChecksum) + sector.status = Sector.Status.DATA_MISSING; /* unintuitive but correct */ + } + + @Override + protected void decodeDataRecord() + { + /* Check the ID. */ + + if (readRaw32() != Victor9k.VICTOR9K_DATA_RECORD) + return; + + /* Read data. */ + + Bytes bytes = decode(readRawBits((Victor9k.VICTOR9K_SECTOR_LENGTH + 4) * 10)) + .slice(0, Victor9k.VICTOR9K_SECTOR_LENGTH + 4); + ByteReader br = bytes.iterator(); + + sector.data = br.read(Victor9k.VICTOR9K_SECTOR_LENGTH); + int gotChecksum = Crc.sumBytes(sector.data); + int wantChecksum = br.readLe16(); + sector.status = (gotChecksum == wantChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java b/java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java new file mode 100644 index 00000000..4fcb26bb --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java @@ -0,0 +1,57 @@ +package com.cowlark.fluxengine.arch.zilogmcz; + +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.FluxPattern; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; + +/** + * The Zilog MCZ decoder, ported from arch/zilogmcz/decoder.cc. + */ +public class ZilogMczDecoder extends Decoder +{ + private static final FluxPattern SECTOR_START_PATTERN = new FluxPattern(16, 0xaaab); + + public ZilogMczDecoder(DecoderProto config) + { + super(config); + } + + @Override + protected double advanceToNextRecord() + { + seekToIndexMark(); + return seekToPattern(SECTOR_START_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + readRawBits(14); + + Bits rawbits = readRawBits(140 * 16); + Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, 140); + ByteReader br = bytes.iterator(); + + int logicalSector = br.read8() & 0x1f; + int logicalHead = 0; + int logicalCylinder = br.read8() & 0x7f; + sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); + if (logicalSector > 31) + return; + if (logicalCylinder > 80) + return; + + sector.data = br.read(132); + int wantChecksum = br.readBe16(); + int gotChecksum = Crc.crc16(Crc.MODBUS_POLY, 0x0000, bytes.slice(0, 134)); + + sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index f7276857..78728a8f 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -6,6 +6,7 @@ java_library( name = "cli", srcs = glob(["*.java"]), deps = [ + "//java/com/cowlark/fluxengine/arch", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index e064bccc..a6fdecef 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -2,6 +2,7 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; +import com.cowlark.fluxengine.arch.Arch; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; @@ -60,7 +61,7 @@ public void run(ImmutableList args) // Unsupported: DiskLayout diskLayout = new DiskLayout(config); FluxSource fluxSource = FluxSource.create(config); - // var decoder = Arch.createDecoder(config); + var decoder = Arch.createDecoder(config); // var writer = ImageWriter.create(config); // readDiskCommand(diskLayout, fluxSource, decoder, writer); } diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index f8ab7675..e6f8f3fe 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -317,6 +317,13 @@ public ByteReader iterator() return new ByteReader(this); } + /* Returns a cursor for writing into this Bytes, ported from + * lib/core/bytes.h Bytes::writer(). */ + public ByteWriter writer() + { + return new ByteWriter(this); + } + @Override public boolean add(Byte value) { diff --git a/java/com/cowlark/fluxengine/external/Crc.java b/java/com/cowlark/fluxengine/external/Crc.java index 1c5b204d..0034dbb9 100644 --- a/java/com/cowlark/fluxengine/external/Crc.java +++ b/java/com/cowlark/fluxengine/external/Crc.java @@ -75,4 +75,21 @@ public static int xorBytes(Bytes bytes) result ^= br.read8(); return result; } + + /* Thanks to user202729 on StackOverflow for miraculously reverse + * engineering this. */ + public static int crcbrother(Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + + int crc = br.read8(); + while (!br.eof()) + { + for (int i = 0; i < 8; i++) + crc = (crc & 0x800000) != 0 ? ((crc << 1) ^ BROTHER_POLY) : (crc << 1); + crc ^= br.read8(); + } + + return crc & 0xFFFFFF; + } } \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/arch/amiga/AmigaTest.java b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaTest.java new file mode 100644 index 00000000..bf9d0827 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaTest.java @@ -0,0 +1,38 @@ +package com.cowlark.fluxengine.arch.amiga; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AmigaTest +{ + private static final Bytes TEST_DATA = Bytes.of( + 0x52, /* 0101 0010 */ + 0xff, /* 1111 1111 */ + 0x4a, /* 0100 1010 */ + 0x22 /* 0010 0010 */ + ); + + private static final Bytes TEST_DATA_INTERLEAVED = Bytes.of( + 0x1f, /* 0001 1111 */ + 0x35, /* 0011 0101 */ + 0xcf, /* 1100 1111 */ + 0x80 /* 1000 0000 */ + ); + + @Test + public void interleave() + { + assertThat(Amiga.amigaInterleave(TEST_DATA)).isEqualTo(TEST_DATA_INTERLEAVED); + } + + @Test + public void deinterleave() + { + assertThat(Amiga.amigaDeinterleave(TEST_DATA_INTERLEAVED)).isEqualTo(TEST_DATA); + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel b/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel new file mode 100644 index 00000000..033cfc33 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel @@ -0,0 +1,14 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "AmigaTest", + srcs = ["AmigaTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/arch", + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) From 7fb5efdf70d1591cadbaec69cc5a6957929920dc Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 16:06:35 +0200 Subject: [PATCH 112/192] Add ProtoEncode build tooling. --- .../cowlark/fluxengine/buildtools/BUILD.bazel | 19 +++ .../fluxengine/buildtools/ProtoEncode.java | 136 ++++++++++++++++++ java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + .../cowlark/fluxengine/cli/ReadCommand.java | 3 +- .../cowlark/fluxengine/buildtools/BUILD.bazel | 15 ++ .../buildtools/ProtoEncodeTest.java | 87 +++++++++++ 6 files changed, 260 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/buildtools/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/buildtools/ProtoEncode.java create mode 100644 javatests/com/cowlark/fluxengine/buildtools/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/buildtools/ProtoEncodeTest.java diff --git a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel new file mode 100644 index 00000000..95aa4081 --- /dev/null +++ b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel @@ -0,0 +1,19 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "buildtools", + srcs = glob(["*.java"]), + deps = [ + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "@com_google_protobuf//java/core", + ], +) + +java_binary( + name = "protoencode", + main_class = "com.cowlark.fluxengine.buildtools.ProtoEncode", + runtime_deps = [":buildtools"], +) diff --git a/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java new file mode 100644 index 00000000..1ff05e88 --- /dev/null +++ b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java @@ -0,0 +1,136 @@ +package com.cowlark.fluxengine.buildtools; + +import com.cowlark.fluxengine.core.FluxEngineException; +import com.google.protobuf.Message; +import com.google.protobuf.TextFormat; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/** + * Reads a textpb file (with the {@code <<<}...{@code >>>} multiline string + * extension) and writes out the binary representation of the encoded protobuf, + * ported from scripts/protoencode.cc. + * + * Usage: ProtoEncode <input.textpb> <output.bin> + * [<proto-class-fqn>] + */ +public final class ProtoEncode +{ + private ProtoEncode() + { + } + + public static void main(String[] args) + { + if (args.length < 2) + { + System.err.println( + "Usage: ProtoEncode []"); + System.exit(1); + } + + String protoClass = args.length > 2 + ? args[2] + : "com.cowlark.fluxengine.config.ConfigProto"; + + try + { + byte[] data = encodeToBytes(readFile(args[0]), protoClass); + Files.write(Path.of(args[1]), data); + } catch (IOException e) + { + System.err.println("couldn't open file: " + e.getMessage()); + System.exit(1); + } catch (FluxEngineException e) + { + System.err.println(e.getMessage()); + System.exit(1); + } + } + + /* Reads the textpb file, handling the multiline string extension, and + * returns the serialized protobuf bytes. */ + public static byte[] encodeToBytes(String contents, String protoClass) + { + String processed = processMultilineStrings(contents); + Message.Builder builder = newBuilder(protoClass); + try + { + TextFormat.merge(processed, builder); + } catch (TextFormat.ParseException e) + { + throw new FluxEngineException("cannot parse text proto: " + e.getMessage()); + } + return builder.build().toByteArray(); + } + + /* Encodes the textpb and writes the serialized protobuf bytes to a file. */ + public static void encodeToFile(String contents, String output, String protoClass) + throws IOException + { + Files.write(Path.of(output), encodeToBytes(contents, protoClass)); + } + + private static String readFile(String filename) throws IOException + { + return Files.readString(Path.of(filename), StandardCharsets.UTF_8); + } + + private static String processMultilineStrings(String contents) + { + StringBuilder result = new StringBuilder(); + List lines = contents.lines().toList(); + int i = 0; + while (i < lines.size()) + { + String line = lines.get(i); + if (line.equals("<<<")) + { + i++; + while (i < lines.size()) + { + String s = lines.get(i++); + if (s.equals(">>>")) + break; + + result.append('"'); + int offset = 0; + while (offset < s.length()) + { + int codePoint = s.codePointAt(offset); + offset += Character.charCount(codePoint); + if (codePoint <= 0xffff) + result.append(String.format("\\u%04x", codePoint)); + else + result.append(String.format("\\U%08x", codePoint)); + } + result.append("\\n\"\n"); + } + } else + { + result.append(line).append('\n'); + i++; + } + } + return result.toString(); + } + + private static Message.Builder newBuilder(String protoClass) + { + try + { + Class clazz = Class.forName(protoClass); + Method method = clazz.getMethod("newBuilder"); + return (Message.Builder) method.invoke(null); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | + InvocationTargetException | ClassCastException e) + { + throw new FluxEngineException("cannot create builder for " + protoClass + ": " + e); + } + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 78728a8f..298fed04 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -13,6 +13,7 @@ java_library( "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index a6fdecef..105bd041 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -10,6 +10,7 @@ import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.decoders.Decoder; import com.cowlark.fluxengine.fluxsource.FluxSource; import com.google.common.collect.ImmutableList; @@ -61,7 +62,7 @@ public void run(ImmutableList args) // Unsupported: DiskLayout diskLayout = new DiskLayout(config); FluxSource fluxSource = FluxSource.create(config); - var decoder = Arch.createDecoder(config); + Decoder decoder = Arch.createDecoder(config); // var writer = ImageWriter.create(config); // readDiskCommand(diskLayout, fluxSource, decoder, writer); } diff --git a/javatests/com/cowlark/fluxengine/buildtools/BUILD.bazel b/javatests/com/cowlark/fluxengine/buildtools/BUILD.bazel new file mode 100644 index 00000000..3b66d4c3 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/buildtools/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "ProtoEncodeTest", + srcs = ["ProtoEncodeTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/buildtools", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/buildtools/ProtoEncodeTest.java b/javatests/com/cowlark/fluxengine/buildtools/ProtoEncodeTest.java new file mode 100644 index 00000000..4ba88016 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/buildtools/ProtoEncodeTest.java @@ -0,0 +1,87 @@ +package com.cowlark.fluxengine.buildtools; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.google.protobuf.TextFormat; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ProtoEncodeTest +{ + @Rule + public TemporaryFolder tmp = new TemporaryFolder(); + + private static final String PROTO_CLASS = "com.cowlark.fluxengine.config.ConfigProto"; + + private static ConfigProto parse(String textproto) throws TextFormat.ParseException + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + TextFormat.merge(textproto, builder); + return builder.build(); + } + + @Test + public void encodesPlainTextproto() throws Exception + { + String textpb = "shortname: 'test'\ncomment: 'a comment'\n"; + ConfigProto expected = parse(textpb); + + byte[] data = ProtoEncode.encodeToBytes(textpb, PROTO_CLASS); + assertThat(ConfigProto.parseFrom(data)).isEqualTo(expected); + } + + @Test + public void encodesMultilineStrings() throws Exception + { + String textpb = + "shortname: 'test'\n" + + "documentation:\n" + + "<<<\n" + + "The first line\n" + + "The second line\n" + + ">>>\n"; + ConfigProto expected = parse( + "shortname: 'test'\n" + + "documentation: \"The first line\\nThe second line\\n\"\n"); + + byte[] data = ProtoEncode.encodeToBytes(textpb, PROTO_CLASS); + assertThat(ConfigProto.parseFrom(data)).isEqualTo(expected); + } + + @Test + public void encodesMultilineStringsWithUnicode() throws Exception + { + String textpb = + "shortname: 'test'\n" + + "documentation:\n" + + "<<<\n" + + "Агат is Russian\n" + + ">>>\n"; + ConfigProto expected = parse( + "shortname: 'test'\n" + + "documentation: \"Агат is Russian\\n\"\n"); + + byte[] data = ProtoEncode.encodeToBytes(textpb, PROTO_CLASS); + assertThat(ConfigProto.parseFrom(data)).isEqualTo(expected); + } + + @Test + public void writesBinaryFileThatRoundTrips() throws Exception + { + String textpb = "shortname: 'agat'\ncomment: 'a format'\n"; + ConfigProto expected = parse(textpb); + + Path output = tmp.newFile("agat.bin").toPath(); + ProtoEncode.encodeToFile(textpb, output.toString(), PROTO_CLASS); + + byte[] data = Files.readAllBytes(output); + assertThat(ConfigProto.parseFrom(data)).isEqualTo(expected); + } +} From 69b0c349667e14f8b2a4e0ade49925d0d4f5b591 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 16:37:06 +0200 Subject: [PATCH 113/192] You can now look up built-in formats. --- BUILD.bazel | 2 + .../fluxengine/buildtools/ProtoEncode.java | 7 +- .../com/cowlark/fluxengine/config/BUILD.bazel | 1 + .../fluxengine/config/ConfigBuilder.java | 10 ++ java/com/cowlark/fluxengine/data/BUILD.bazel | 52 +++++++ java/com/cowlark/fluxengine/data/Formats.java | 139 ++++++++++++++++++ .../fluxengine/config/ConfigBuilderTest.java | 18 +++ .../com/cowlark/fluxengine/data/BUILD.bazel | 11 ++ .../cowlark/fluxengine/data/FormatsTest.java | 42 ++++++ 9 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/data/Formats.java create mode 100644 javatests/com/cowlark/fluxengine/data/FormatsTest.java diff --git a/BUILD.bazel b/BUILD.bazel index f9c721c5..92daabad 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,5 +1,7 @@ package(default_visibility = ["//visibility:public"]) +exports_files(glob(["src/formats/*.textpb"])) + # Root aliases for running/building the application alias( name = "fluxengine", diff --git a/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java index 1ff05e88..1dd99f2b 100644 --- a/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java +++ b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java @@ -9,6 +9,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -84,7 +86,10 @@ private static String readFile(String filename) throws IOException private static String processMultilineStrings(String contents) { StringBuilder result = new StringBuilder(); - List lines = contents.lines().toList(); + List lines = new ArrayList<>(); + Iterator it = contents.lines().iterator(); + while (it.hasNext()) + lines.add(it.next()); int i = 0; while (i < lines.size()) { diff --git a/java/com/cowlark/fluxengine/config/BUILD.bazel b/java/com/cowlark/fluxengine/config/BUILD.bazel index 7cd2bcec..9f2688dd 100644 --- a/java/com/cowlark/fluxengine/config/BUILD.bazel +++ b/java/com/cowlark/fluxengine/config/BUILD.bazel @@ -78,6 +78,7 @@ java_library( ":config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", + "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/fluxsink:fluxsink_java_proto", "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", "//java/com/cowlark/fluxengine/imagewriter:imagewriter_java_proto", diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index ca7eec25..acadc68b 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -26,6 +26,7 @@ import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.Flags; +import com.cowlark.fluxengine.data.Formats; import com.cowlark.fluxengine.fluxsink.FluxSinkProto; import com.cowlark.fluxengine.fluxsource.FluxSourceProto; import com.google.common.collect.ImmutableList; @@ -96,6 +97,15 @@ public ConfigBuilder fromFlags(ImmutableList args, FlagGroup... group) public ConfigBuilder loadConfigFile(String name) { + /* Try to load the config from the built-in formats first. */ + + ConfigProto config = Formats.get(name); + if (config != null) + { + proto.mergeFrom(config); + return this; + } + String contents; try { diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel index d6a38053..915c8a88 100644 --- a/java/com/cowlark/fluxengine/data/BUILD.bazel +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -2,6 +2,57 @@ load("@rules_java//java:defs.bzl", "java_library", "java_plugin") package(default_visibility = ["//visibility:public"]) +FORMATS = [ + "acornadfs", + "acorndfs", + "aeslanier", + "agat", + "amiga", + "ampro", + "apple2", + "atarist", + "bk", + "brother", + "commodore", + "eco1", + "epsonpf10", + "f85", + "fb100", + "_global_options", + "hplif", + "ibm", + "icl30", + "juku", + "mac", + "micropolis", + "ms2000", + "mx", + "n88basic", + "northstar", + "psos", + "rolandd20", + "rx50", + "smaky6", + "tartu", + "ti99", + "tids990", + "tiki", + "victor9k", + "zilogmcz", +] + +genrule( + name = "formats", + srcs = ["//:src/formats/%s.textpb" % f for f in FORMATS], + outs = ["formats/%s.bin" % f for f in FORMATS], + tools = ["//java/com/cowlark/fluxengine/buildtools:protoencode"], + cmd = " && ".join([ + "$(location //java/com/cowlark/fluxengine/buildtools:protoencode) " + + "$(location //:src/formats/%s.textpb) $(location formats/%s.bin)" % (f, f) + for f in FORMATS + ]), +) + java_plugin( name = "lombok_plugin", generates_api = True, @@ -12,6 +63,7 @@ java_plugin( java_library( name = "data", srcs = glob(["*.java"]), + resources = ["formats/%s.bin" % f for f in FORMATS], plugins = [":lombok_plugin"], deps = [ "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/java/com/cowlark/fluxengine/data/Formats.java b/java/com/cowlark/fluxengine/data/Formats.java new file mode 100644 index 00000000..ded932ea --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Formats.java @@ -0,0 +1,139 @@ +package com.cowlark.fluxengine.data; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.google.common.collect.ImmutableList; +import com.google.protobuf.InvalidProtocolBufferException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * The built-in format configurations, loaded on demand from the classpath + * resources generated from the textpb files in src/formats, ported from the + * C++ `formats` map in lib/config. + */ +public final class Formats +{ + private static final String RESOURCE_DIR = "formats"; + private static final String RESOURCE_PREFIX = "com/cowlark/fluxengine/data/" + RESOURCE_DIR + "/"; + private static final String RESOURCE_SUFFIX = ".bin"; + + private static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); + + private Formats() + { + } + + /* Returns the config with the given name, loading it on demand, or null + * if it doesn't exist. */ + public static ConfigProto get(String name) + { + ConfigProto config = cache.get(name); + if (config == null) + { + config = load(name); + if (config != null) + cache.putIfAbsent(name, config); + } + return config; + } + + /* Returns the names of all the available configs. */ + public static ImmutableList all() + { + return ImmutableList.copyOf(scanNames()); + } + + private static ConfigProto load(String name) + { + String resource = RESOURCE_DIR + "/" + name + RESOURCE_SUFFIX; + byte[] data; + try (InputStream stream = Formats.class.getResourceAsStream(resource)) + { + if (stream == null) + return null; + data = stream.readAllBytes(); + } catch (IOException e) + { + throw new FluxEngineException("cannot read format resource " + resource + ": " + e); + } + + try + { + return ConfigProto.parseFrom(data); + } catch (InvalidProtocolBufferException e) + { + throw new FluxEngineException("invalid format data in " + resource + ": " + e); + } + } + + /* Scans the resources directory for the format files, returning their + * names in sorted order. */ + private static List scanNames() + { + URL location = Formats.class.getProtectionDomain().getCodeSource().getLocation(); + if (location == null) + throw new FluxEngineException("cannot determine the location of the format resources"); + + List names = new ArrayList<>(); + try + { + if (location.getProtocol().equals("file")) + { + Path path = Path.of(location.toURI()); + if (Files.isDirectory(path)) + scanDirectory(path.resolve(RESOURCE_PREFIX), names); + else + scanJar(path, names); + } else + { + throw new FluxEngineException( + "unsupported resource protocol: " + location.getProtocol()); + } + } catch (Exception e) + { + throw new FluxEngineException("cannot scan format resources: " + e); + } + + Collections.sort(names); + return names; + } + + private static void scanDirectory(Path dir, List names) throws IOException + { + try (DirectoryStream stream = Files.newDirectoryStream(dir, "*" + RESOURCE_SUFFIX)) + { + for (Path entry : stream) + { + String filename = entry.getFileName().toString(); + names.add(filename.substring(0, filename.length() - RESOURCE_SUFFIX.length())); + } + } + } + + private static void scanJar(Path jar, List names) throws IOException + { + try (JarFile jarFile = new JarFile(jar.toFile())) + { + Enumeration entries = jarFile.entries(); + while (entries.hasMoreElements()) + { + String name = entries.nextElement().getName(); + if (name.startsWith(RESOURCE_PREFIX) && name.endsWith(RESOURCE_SUFFIX)) + names.add(name.substring( + RESOURCE_PREFIX.length(), name.length() - RESOURCE_SUFFIX.length())); + } + } + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index a0497b4b..975a55ae 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -58,6 +58,24 @@ public void loadConfigFileMissingFileThrows() () -> new ConfigBuilder().loadConfigFile("/nonexistent/config")); } + @Test + public void loadConfigFileLoadsBuiltInFormatByName() + { + ConfigProto proto = builder().loadConfigFile("amiga").build(); + + assertThat(proto.getShortname()).isEqualTo("Amiga"); + } + + @Test + public void loadConfigFileLoadsBuiltInFormatBeforeFile() + { + /* A file named "amiga" may exist, but the built-in format must take + * precedence. */ + ConfigProto proto = builder().loadConfigFile("amiga").build(); + + assertThat(proto.getShortname()).isEqualTo("Amiga"); + } + @Test public void loadConfigFileBadTextprotoThrows() throws IOException { diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index 41916f48..9e96194e 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -2,6 +2,17 @@ load("@rules_java//java:defs.bzl", "java_test") package(default_visibility = ["//visibility:public"]) +java_test( + name = "FormatsTest", + srcs = ["FormatsTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "FluxmapTest", srcs = ["FluxmapTest.java"], diff --git a/javatests/com/cowlark/fluxengine/data/FormatsTest.java b/javatests/com/cowlark/fluxengine/data/FormatsTest.java new file mode 100644 index 00000000..3cd340d3 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/FormatsTest.java @@ -0,0 +1,42 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigProto; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FormatsTest +{ + @Test + public void looksUpConfigByName() + { + ConfigProto config = Formats.get("amiga"); + assertThat(config).isNotNull(); + assertThat(config.getShortname()).isEqualTo("Amiga"); + } + + @Test + public void looksUpGlobalOptions() + { + ConfigProto config = Formats.get("_global_options"); + assertThat(config).isNotNull(); + assertThat(config.getIsExtension()).isTrue(); + } + + @Test + public void returnsNullForUnknownName() + { + assertThat(Formats.get("not a real format")).isNull(); + } + + @Test + public void returnsAllConfigNames() + { + assertThat(Formats.all()).hasSize(36); + assertThat(Formats.all()).contains("ibm"); + assertThat(Formats.all()).contains("_global_options"); + } +} From de717471f08cb323cb6e1555a569d2d03f8f9b5a Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 17:02:10 +0200 Subject: [PATCH 114/192] Rearrange the way the formats are handled. --- BUILD.bazel | 2 - java/com/cowlark/fluxengine/data/BUILD.bazel | 54 +------------ java/com/cowlark/fluxengine/data/Formats.java | 75 +++++-------------- .../cowlark/fluxengine/resource-config.json | 3 + src/formats/BUILD.bazel | 57 ++++++++++++++ 5 files changed, 79 insertions(+), 112 deletions(-) create mode 100644 src/formats/BUILD.bazel diff --git a/BUILD.bazel b/BUILD.bazel index 92daabad..f9c721c5 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,7 +1,5 @@ package(default_visibility = ["//visibility:public"]) -exports_files(glob(["src/formats/*.textpb"])) - # Root aliases for running/building the application alias( name = "fluxengine", diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel index 915c8a88..2e67b666 100644 --- a/java/com/cowlark/fluxengine/data/BUILD.bazel +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -2,57 +2,6 @@ load("@rules_java//java:defs.bzl", "java_library", "java_plugin") package(default_visibility = ["//visibility:public"]) -FORMATS = [ - "acornadfs", - "acorndfs", - "aeslanier", - "agat", - "amiga", - "ampro", - "apple2", - "atarist", - "bk", - "brother", - "commodore", - "eco1", - "epsonpf10", - "f85", - "fb100", - "_global_options", - "hplif", - "ibm", - "icl30", - "juku", - "mac", - "micropolis", - "ms2000", - "mx", - "n88basic", - "northstar", - "psos", - "rolandd20", - "rx50", - "smaky6", - "tartu", - "ti99", - "tids990", - "tiki", - "victor9k", - "zilogmcz", -] - -genrule( - name = "formats", - srcs = ["//:src/formats/%s.textpb" % f for f in FORMATS], - outs = ["formats/%s.bin" % f for f in FORMATS], - tools = ["//java/com/cowlark/fluxengine/buildtools:protoencode"], - cmd = " && ".join([ - "$(location //java/com/cowlark/fluxengine/buildtools:protoencode) " + - "$(location //:src/formats/%s.textpb) $(location formats/%s.bin)" % (f, f) - for f in FORMATS - ]), -) - java_plugin( name = "lombok_plugin", generates_api = True, @@ -63,7 +12,8 @@ java_plugin( java_library( name = "data", srcs = glob(["*.java"]), - resources = ["formats/%s.bin" % f for f in FORMATS], + resources = ["//src/formats:formats_files"], + resource_strip_prefix = "src/formats", plugins = [":lombok_plugin"], deps = [ "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/java/com/cowlark/fluxengine/data/Formats.java b/java/com/cowlark/fluxengine/data/Formats.java index ded932ea..ac865633 100644 --- a/java/com/cowlark/fluxengine/data/Formats.java +++ b/java/com/cowlark/fluxengine/data/Formats.java @@ -6,17 +6,11 @@ import com.google.protobuf.InvalidProtocolBufferException; import java.io.IOException; import java.io.InputStream; -import java.net.URL; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; -import java.util.Enumeration; import java.util.List; import java.util.concurrent.ConcurrentHashMap; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; /** * The built-in format configurations, loaded on demand from the classpath @@ -26,8 +20,8 @@ public final class Formats { private static final String RESOURCE_DIR = "formats"; - private static final String RESOURCE_PREFIX = "com/cowlark/fluxengine/data/" + RESOURCE_DIR + "/"; private static final String RESOURCE_SUFFIX = ".bin"; + private static final String NAMES_RESOURCE = RESOURCE_DIR + "/names.txt"; private static final ConcurrentHashMap cache = new ConcurrentHashMap<>(); @@ -57,7 +51,7 @@ public static ImmutableList all() private static ConfigProto load(String name) { - String resource = RESOURCE_DIR + "/" + name + RESOURCE_SUFFIX; + String resource = "/" + RESOURCE_DIR + "/" + name + RESOURCE_SUFFIX; byte[] data; try (InputStream stream = Formats.class.getResourceAsStream(resource)) { @@ -78,62 +72,27 @@ private static ConfigProto load(String name) } } - /* Scans the resources directory for the format files, returning their - * names in sorted order. */ + /* Returns the list of format names from the generated names index. */ private static List scanNames() { - URL location = Formats.class.getProtectionDomain().getCodeSource().getLocation(); - if (location == null) - throw new FluxEngineException("cannot determine the location of the format resources"); - - List names = new ArrayList<>(); - try + String contents; + try (InputStream stream = Formats.class.getResourceAsStream("/" + NAMES_RESOURCE)) { - if (location.getProtocol().equals("file")) - { - Path path = Path.of(location.toURI()); - if (Files.isDirectory(path)) - scanDirectory(path.resolve(RESOURCE_PREFIX), names); - else - scanJar(path, names); - } else - { - throw new FluxEngineException( - "unsupported resource protocol: " + location.getProtocol()); - } - } catch (Exception e) - { - throw new FluxEngineException("cannot scan format resources: " + e); - } - - Collections.sort(names); - return names; - } - - private static void scanDirectory(Path dir, List names) throws IOException - { - try (DirectoryStream stream = Files.newDirectoryStream(dir, "*" + RESOURCE_SUFFIX)) + if (stream == null) + throw new FluxEngineException("format resource not found: " + NAMES_RESOURCE); + contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + } catch (IOException e) { - for (Path entry : stream) - { - String filename = entry.getFileName().toString(); - names.add(filename.substring(0, filename.length() - RESOURCE_SUFFIX.length())); - } + throw new FluxEngineException("cannot read format resource " + NAMES_RESOURCE + ": " + e); } - } - private static void scanJar(Path jar, List names) throws IOException - { - try (JarFile jarFile = new JarFile(jar.toFile())) + List names = new ArrayList<>(); + for (String line : contents.split("\n")) { - Enumeration entries = jarFile.entries(); - while (entries.hasMoreElements()) - { - String name = entries.nextElement().getName(); - if (name.startsWith(RESOURCE_PREFIX) && name.endsWith(RESOURCE_SUFFIX)) - names.add(name.substring( - RESOURCE_PREFIX.length(), name.length() - RESOURCE_SUFFIX.length())); - } + if (!line.isEmpty()) + names.add(line); } + Collections.sort(names); + return names; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/resource-config.json b/java/com/cowlark/fluxengine/resource-config.json index 5f5eabb9..7fb9fbea 100644 --- a/java/com/cowlark/fluxengine/resource-config.json +++ b/java/com/cowlark/fluxengine/resource-config.json @@ -9,6 +9,9 @@ }, { "pattern": "\\Qorg/usb4java/linux-x86-64/libusb4java.so\\E" + }, + { + "pattern": "\\Qformats/\\E.*" } ] }, diff --git a/src/formats/BUILD.bazel b/src/formats/BUILD.bazel new file mode 100644 index 00000000..1c006045 --- /dev/null +++ b/src/formats/BUILD.bazel @@ -0,0 +1,57 @@ +package(default_visibility = ["//visibility:public"]) + +FORMATS = [ + "acornadfs", + "acorndfs", + "aeslanier", + "agat", + "amiga", + "ampro", + "apple2", + "atarist", + "bk", + "brother", + "commodore", + "eco1", + "epsonpf10", + "f85", + "fb100", + "_global_options", + "hplif", + "ibm", + "icl30", + "juku", + "mac", + "micropolis", + "ms2000", + "mx", + "n88basic", + "northstar", + "psos", + "rolandd20", + "rx50", + "smaky6", + "tartu", + "ti99", + "tids990", + "tiki", + "victor9k", + "zilogmcz", +] + +genrule( + name = "formats", + srcs = ["%s.textpb" % f for f in FORMATS], + outs = ["formats/%s.bin" % f for f in FORMATS] + ["formats/names.txt"], + tools = ["//java/com/cowlark/fluxengine/buildtools:protoencode"], + cmd = " && ".join([ + "$(location //java/com/cowlark/fluxengine/buildtools:protoencode) " + + "$(location %s.textpb) $(location formats/%s.bin)" % (f, f) + for f in FORMATS + ]) + " && printf '%%s\\n' %s > $(location formats/names.txt)" % " ".join(FORMATS), +) + +filegroup( + name = "formats_files", + srcs = ["formats/%s.bin" % f for f in FORMATS] + ["formats/names.txt"], +) From 60587a1cbb7ef69926ff28b109b075c53bbe15da Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 17:19:15 +0200 Subject: [PATCH 115/192] Add HardwareFluxSource. --- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 1 + .../fluxengine/fluxsource/FluxSource.java | 3 + .../fluxsource/HardwareFluxSource.java | 76 +++++++++ .../cowlark/fluxengine/fluxsource/BUILD.bazel | 17 ++ .../fluxsource/HardwareFluxSourceTest.java | 150 ++++++++++++++++++ 5 files changed, 247 insertions(+) create mode 100644 java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 26700508..bb6fc984 100644 --- a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -28,6 +28,7 @@ java_library( "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/external", "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "//java/com/cowlark/fluxengine/usb", "@com_google_protobuf//java/core", ], ) diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index 102f479c..02248935 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -2,6 +2,7 @@ import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.FluxSourceSinkType; import com.cowlark.fluxengine.core.FluxEngineException; /** @@ -11,6 +12,8 @@ public abstract class FluxSource { public static FluxSource create(ConfigProto config) { + if (config.getFluxSource().getType() == FluxSourceSinkType.FLUXTYPE_DRIVE) + return new HardwareFluxSource(config); return create(config.getFluxSource()); } diff --git a/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java new file mode 100644 index 00000000..506cf790 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java @@ -0,0 +1,76 @@ +package com.cowlark.fluxengine.fluxsource; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; + +/** + * A flux source which reads from real hardware, ported from + * lib/fluxsource/hardwarefluxsource.cc. + */ +public class HardwareFluxSource extends FluxSource +{ + private final ConfigProto config; + private final UsbDevice device; + + public HardwareFluxSource(ConfigProto config) + { + this(config, UsbFactory.connect(config)); + } + + /* Package-private for testing. */ + HardwareFluxSource(ConfigProto config, UsbDevice device) + { + this.config = config; + this.device = device; + } + + @Override + public FluxSourceIterator readFlux(int track, int head) + { + return new FluxSourceIterator() + { + @Override + public boolean hasNext() + { + return true; + } + + @Override + public Fluxmap next() + { + device.seek(track); + + Bytes data = device.read( + head, + config.getDrive().getSyncWithIndex(), + config.getDrive().getRevolutions() * + config.getDrive().getRotationalPeriodMs() * 1e6, + config.getDrive().getHardSectorThresholdNs()); + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBytes(data); + return fluxmap; + } + }; + } + + @Override + public void recalibrate() + { + device.recalibrate(); + } + + @Override + public void seek(int track) + { + device.seek(track); + } + + @Override + public boolean isHardware() + { + return true; + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 52bbf3b2..9d4da107 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -36,6 +36,23 @@ java_test( ], ) +java_test( + name = "HardwareFluxSourceTest", + srcs = ["HardwareFluxSourceTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "//java/com/cowlark/fluxengine/usb", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "KryofluxFluxSourceTest", srcs = ["KryofluxFluxSourceTest.java"], diff --git a/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java new file mode 100644 index 00000000..017d485e --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java @@ -0,0 +1,150 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.VoltageMeasurements; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class HardwareFluxSourceTest +{ + private static class FakeUsbDevice extends UsbDevice + { + int seekedTo = -1; + int recalibrated = 0; + Integer readSide; + Boolean readSynced; + Double readTimeNs; + Double readThresholdNs; + Bytes readResult = new Bytes(); + + @Override + public void seek(int track) + { + seekedTo = track; + } + + @Override + public void recalibrate() + { + recalibrated++; + seek(0); + } + + @Override + public double getRotationalPeriod(int hardSectorCount) + { + return 0; + } + + @Override + public void testBulkWrite() + { + } + + @Override + public void testBulkRead() + { + } + + @Override + public Bytes read(int side, boolean synced, double readTimeNs, double hardSectorThresholdNs) + { + readSide = side; + readSynced = synced; + this.readTimeNs = readTimeNs; + readThresholdNs = hardSectorThresholdNs; + return readResult; + } + + @Override + public void write(int side, Bytes bytes, double hardSectorThresholdNs) + { + } + + @Override + public void erase(int side, double hardSectorThresholdNs) + { + } + + @Override + public void setDrive(int drive, boolean highDensity, int indexMode) + { + } + + @Override + public VoltageMeasurements measureVoltages() + { + return null; + } + } + + private static ConfigProto config() + { + return new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.sync_with_index", "true") + .set("drive.revolutions", "3") + .set("drive.rotational_period_ms", "200") + .set("drive.hard_sector_threshold_ns", "1000") + .build(); + } + + @Test + public void isHardware() + { + HardwareFluxSource source = new HardwareFluxSource(config(), new FakeUsbDevice()); + + assertThat(source.isHardware()).isTrue(); + } + + @Test + public void seekDelegatesToDevice() + { + FakeUsbDevice device = new FakeUsbDevice(); + HardwareFluxSource source = new HardwareFluxSource(config(), device); + + source.seek(42); + + assertThat(device.seekedTo).isEqualTo(42); + } + + @Test + public void recalibrateDelegatesToDevice() + { + FakeUsbDevice device = new FakeUsbDevice(); + HardwareFluxSource source = new HardwareFluxSource(config(), device); + + source.recalibrate(); + + assertThat(device.recalibrated).isEqualTo(1); + } + + @Test + public void readFluxReadsAndWrapsFluxmap() + { + FakeUsbDevice device = new FakeUsbDevice(); + device.readResult = Bytes.of(0x01, 0x02, 0x03, 0x04); + HardwareFluxSource source = new HardwareFluxSource(config(), device); + + FluxSourceIterator iterator = source.readFlux(17, 1); + + assertThat(iterator.hasNext()).isTrue(); + Fluxmap fluxmap = iterator.next(); + + assertThat(device.seekedTo).isEqualTo(17); + assertThat(device.readSide).isEqualTo(1); + assertThat(device.readSynced).isTrue(); + assertThat(device.readTimeNs).isEqualTo(3 * 200 * 1e6); + assertThat(device.readThresholdNs).isEqualTo(1000); + assertThat(fluxmap.rawBytes()).isEqualTo(device.readResult); + assertThat(iterator.hasNext()).isTrue(); + } +} From 975b31f569f91d801bfc6e6bf2205df6af3e8c2d Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 17:46:17 +0200 Subject: [PATCH 116/192] Port Geometry and Image. --- .../cowlark/fluxengine/algorithms/BUILD.bazel | 0 .../com/cowlark/fluxengine/data/Geometry.java | 15 ++ java/com/cowlark/fluxengine/data/Image.java | 170 ++++++++++++++++++ .../com/cowlark/fluxengine/data/BUILD.bazel | 12 ++ .../cowlark/fluxengine/data/ImageTest.java | 113 ++++++++++++ 5 files changed, 310 insertions(+) create mode 100644 java/com/cowlark/fluxengine/algorithms/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/data/Geometry.java create mode 100644 java/com/cowlark/fluxengine/data/Image.java create mode 100644 javatests/com/cowlark/fluxengine/data/ImageTest.java diff --git a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel new file mode 100644 index 00000000..e69de29b diff --git a/java/com/cowlark/fluxengine/data/Geometry.java b/java/com/cowlark/fluxengine/data/Geometry.java new file mode 100644 index 00000000..b7427076 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Geometry.java @@ -0,0 +1,15 @@ +package com.cowlark.fluxengine.data; + +/** + * The geometry of a disk image, ported from lib/data/image.h. + */ +public class Geometry +{ + public int numCylinders = 0; + public int numHeads = 0; + public int firstSector = Integer.MAX_VALUE; + public int numSectors = 0; + public int sectorSize = 0; + public boolean irregular = false; + public int totalBytes = 0; +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/Image.java b/java/com/cowlark/fluxengine/data/Image.java new file mode 100644 index 00000000..f160c506 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Image.java @@ -0,0 +1,170 @@ +package com.cowlark.fluxengine.data; + +import com.cowlark.fluxengine.core.Bytes; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A disk image, a collection of sectors indexed by logical location, ported + * from lib/data/image.h. + */ +public class Image implements Iterable +{ + private final Map sectors = new LinkedHashMap<>(); + private Geometry geometry = new Geometry(); + + public Image() + { + } + + public Image(Collection sectors) + { + for (Sector sector : sectors) + this.sectors.put(sector.location, sector); + calculateSize(); + } + + public void calculateSize() + { + geometry = new Geometry(); + int maxSector = 0; + for (Map.Entry entry : sectors.entrySet()) + { + Sector sector = entry.getValue(); + if (sector != null) + { + geometry.numCylinders = Math.max( + geometry.numCylinders, sector.location.logicalCylinder() + 1); + geometry.numHeads = + Math.max(geometry.numHeads, sector.location.logicalHead() + 1); + geometry.firstSector = + Math.min(geometry.firstSector, sector.location.logicalSector()); + maxSector = Math.max(maxSector, sector.location.logicalSector()); + geometry.sectorSize = Math.max(geometry.sectorSize, sector.data.size()); + geometry.totalBytes += geometry.sectorSize; + } + } + geometry.numSectors = maxSector - geometry.firstSector + 1; + } + + public void clear() + { + sectors.clear(); + geometry = new Geometry(); + } + + public boolean empty() + { + return sectors.isEmpty(); + } + + public boolean contains(LogicalLocation location) + { + return sectors.containsKey(location); + } + + public boolean contains(int cylinder, int head, int sector) + { + return contains(new LogicalLocation(cylinder, head, sector)); + } + + public Sector get(LogicalLocation location) + { + return sectors.get(location); + } + + public Sector get(int cylinder, int head, int sector) + { + return get(new LogicalLocation(cylinder, head, sector)); + } + + public Sector put(LogicalLocation location) + { + Sector sector = new Sector(location); + sectors.put(location, sector); + return sector; + } + + public Sector put(int cylinder, int head, int sector) + { + return put(new LogicalLocation(cylinder, head, sector)); + } + + public void erase(LogicalLocation location) + { + sectors.remove(location); + } + + public void erase(int cylinder, int head, int sector) + { + erase(new LogicalLocation(cylinder, head, sector)); + } + + public void addMissingSectors(DiskLayout layout, boolean populated) + { + for (LogicalLocation location : layout.logicalSectorLocationsInFilesystemOrder) + { + if (!sectors.containsKey(location)) + { + LogicalTrackLayout ltl = + layout.layoutByLogicalLocation.get(location.trackLocation()); + Sector sector = new Sector(location); + + if (populated) + sector.data = new Bytes(ltl.sectorSize); + else + sector.status = Sector.Status.MISSING; + + sectors.put(location, sector); + } + } + calculateSize(); + } + + public void populateSectorPhysicalLocationsFromLogicalLocations(DiskLayout diskLayout) + { + Image tempImage = new Image(); + for (Sector sector : this) + { + LogicalTrackLayout ltl = + diskLayout.layoutByLogicalLocation.get(sector.location.trackLocation()); + Sector newSector = tempImage.put( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()); + newSector.location = sector.location; + newSector.status = sector.status; + newSector.position = sector.position; + newSector.clockNs = sector.clockNs; + newSector.headerStartTimeNs = sector.headerStartTimeNs; + newSector.headerEndTimeNs = sector.headerEndTimeNs; + newSector.dataStartTimeNs = sector.dataStartTimeNs; + newSector.dataEndTimeNs = sector.dataEndTimeNs; + newSector.data = sector.data; + newSector.records = sector.records; + newSector.physicalLocation = + new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); + } + + for (Sector sector : tempImage) + sectors.put(sector.location, sector); + } + + public void setGeometry(Geometry geometry) + { + this.geometry = geometry; + } + + public Geometry getGeometry() + { + return geometry; + } + + @Override + public Iterator iterator() + { + return sectors.values().iterator(); + } +} \ No newline at end of file diff --git a/javatests/com/cowlark/fluxengine/data/BUILD.bazel b/javatests/com/cowlark/fluxengine/data/BUILD.bazel index 9e96194e..e02c01ee 100644 --- a/javatests/com/cowlark/fluxengine/data/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/data/BUILD.bazel @@ -13,6 +13,18 @@ java_test( ], ) +java_test( + name = "ImageTest", + srcs = ["ImageTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "FluxmapTest", srcs = ["FluxmapTest.java"], diff --git a/javatests/com/cowlark/fluxengine/data/ImageTest.java b/javatests/com/cowlark/fluxengine/data/ImageTest.java new file mode 100644 index 00000000..46d6da90 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/data/ImageTest.java @@ -0,0 +1,113 @@ +package com.cowlark.fluxengine.data; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImageTest +{ + @Test + public void emptyImageHasNoSectors() + { + Image image = new Image(); + + assertThat(image.empty()).isTrue(); + assertThat(image.iterator().hasNext()).isFalse(); + } + + @Test + public void putAndGetSectors() + { + Image image = new Image(); + + Sector sector = image.put(0, 0, 3); + assertThat(image.contains(0, 0, 3)).isTrue(); + assertThat(image.contains(new LogicalLocation(0, 0, 3))).isTrue(); + assertThat(image.get(0, 0, 3)).isSameInstanceAs(sector); + assertThat(image.get(new LogicalLocation(0, 0, 3))).isSameInstanceAs(sector); + + image.erase(0, 0, 3); + assertThat(image.contains(0, 0, 3)).isFalse(); + assertThat(image.get(0, 0, 3)).isNull(); + } + + @Test + public void calculatesGeometry() + { + Image image = new Image(); + image.put(0, 0, 1).data = new Bytes(128); + image.put(2, 1, 5).data = new Bytes(256); + image.put(2, 1, 8).data = new Bytes(512); + + image.calculateSize(); + + Geometry geometry = image.getGeometry(); + assertThat(geometry.numCylinders).isEqualTo(3); + assertThat(geometry.numHeads).isEqualTo(2); + assertThat(geometry.firstSector).isEqualTo(1); + assertThat(geometry.numSectors).isEqualTo(8); + assertThat(geometry.sectorSize).isEqualTo(512); + assertThat(geometry.totalBytes).isEqualTo(896); + } + + @Test + public void constructorCalculatesGeometry() + { + java.util.List sectors = java.util.List.of( + makeSector(0, 0, 0, 256), + makeSector(1, 1, 3, 256)); + + Image image = new Image(sectors); + + assertThat(image.getGeometry().numCylinders).isEqualTo(2); + assertThat(image.getGeometry().numHeads).isEqualTo(2); + assertThat(image.getGeometry().firstSector).isEqualTo(0); + assertThat(image.getGeometry().numSectors).isEqualTo(4); + } + + @Test + public void addMissingSectorsPopulatesMissing() + { + Image image = new Image(); + image.put(0, 0, 0); + + /* A disk with sectors 0 and 1; sector 1 is missing. */ + DiskLayout layout = new DiskLayout(1, 1, 2, 256); + image.addMissingSectors(layout, false); + + assertThat(image.contains(0, 0, 0)).isTrue(); + assertThat(image.contains(0, 0, 1)).isTrue(); + assertThat(image.get(0, 0, 1).status).isEqualTo(Sector.Status.MISSING); + } + + @Test + public void populateSectorPhysicalLocations() + { + Image image = new Image(); + image.put(0, 0, 0); + image.put(0, 0, 1); + + DiskLayout layout = new DiskLayout(1, 1, 2, 256); + image.populateSectorPhysicalLocationsFromLogicalLocations(layout); + + for (Sector sector : image) + { + assertThat(sector.physicalLocation).isNotNull(); + assertThat(sector.physicalLocation.cylinder()).isEqualTo( + sector.location.logicalCylinder()); + assertThat(sector.physicalLocation.head()).isEqualTo( + sector.location.logicalHead()); + } + } + + private static Sector makeSector(int cylinder, int head, int sector, int size) + { + Sector s = new Sector(new LogicalLocation(cylinder, head, sector)); + s.data = new Bytes(size); + return s; + } +} From cbf03212cf27fa9e79d68f0554a929822b2061a1 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 17:51:49 +0200 Subject: [PATCH 117/192] Port ImageWriter. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + .../cowlark/fluxengine/cli/ReadCommand.java | 3 +- .../fluxengine/imagewriter/BUILD.bazel | 13 ++ .../fluxengine/imagewriter/ImageWriter.java | 191 ++++++++++++++++++ .../fluxengine/imagewriter/BUILD.bazel | 20 ++ .../imagewriter/ImageWriterTest.java | 83 ++++++++ 6 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/imagewriter/ImageWriter.java create mode 100644 javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 298fed04..cfd9d5a2 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -15,6 +15,7 @@ java_library( "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/decoders", "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/imagewriter", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", ], diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index 105bd041..b97d2e20 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -12,6 +12,7 @@ import com.cowlark.fluxengine.data.DiskLayout; import com.cowlark.fluxengine.decoders.Decoder; import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.imagewriter.ImageWriter; import com.google.common.collect.ImmutableList; /** @@ -63,7 +64,7 @@ public void run(ImmutableList args) DiskLayout diskLayout = new DiskLayout(config); FluxSource fluxSource = FluxSource.create(config); Decoder decoder = Arch.createDecoder(config); - // var writer = ImageWriter.create(config); + ImageWriter writer = ImageWriter.create(config); // readDiskCommand(diskLayout, fluxSource, decoder, writer); } } diff --git a/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel index 7b30e6e5..14b5e04a 100644 --- a/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel +++ b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel @@ -1,4 +1,5 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) @@ -17,3 +18,15 @@ java_proto_library( name = "imagewriter_java_proto", deps = [":imagewriter_proto"], ) + +java_library( + name = "imagewriter", + srcs = glob(["*.java"]), + deps = [ + ":imagewriter_java_proto", + "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + ], +) diff --git a/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java new file mode 100644 index 00000000..0fd2e3bd --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java @@ -0,0 +1,191 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.ImageReaderWriterType; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Writes sector images to disk, ported from + * lib/imagewriter/imagewriter.{h,cc}. + */ +public abstract class ImageWriter +{ + protected final ImageWriterProto config; + + public ImageWriter(ImageWriterProto config) + { + this.config = config; + } + + public static ImageWriter create(ConfigProto config) + { + if (!config.hasImageWriter()) + throw new FluxEngineException("no image writer configured"); + return create(config.getImageWriter()); + } + + public static ImageWriter create(ImageWriterProto config) + { + switch (config.getType()) + { + case IMAGETYPE_IMG: + return notImplemented("img"); + case IMAGETYPE_D64: + return notImplemented("d64"); + case IMAGETYPE_LDBS: + return notImplemented("ldbs"); + case IMAGETYPE_DISKCOPY: + return notImplemented("diskcopy"); + case IMAGETYPE_NSI: + return notImplemented("nsi"); + case IMAGETYPE_RAW: + return notImplemented("raw"); + case IMAGETYPE_D88: + return notImplemented("d88"); + case IMAGETYPE_IMD: + return notImplemented("imd"); + default: + throw new FluxEngineException("bad output image config"); + } + } + + private static ImageWriter notImplemented(String name) + { + throw new FluxEngineException(name + " image writer is not implemented yet"); + } + + public void writeCsv(Image image, String filename) + { + StringBuilder f = new StringBuilder(); + f.append("\"Physical track\",") + .append("\"Physical side\",") + .append("\"Logical sector\",") + .append("\"Logical track\",") + .append("\"Logical side\",") + .append("\"Clock (ns)\",") + .append("\"Header start (ns)\",") + .append("\"Header end (ns)\",") + .append("\"Data start (ns)\",") + .append("\"Data end (ns)\",") + .append("\"Raw data address (bytes)\",") + .append("\"User payload length (bytes)\",") + .append("\"Status\"") + .append("\n"); + + for (Sector sector : image) + { + f.append(sector.physicalLocation != null + ? sector.physicalLocation.cylinder() + : -1).append(','); + f.append(sector.physicalLocation != null + ? sector.physicalLocation.head() + : -1).append(','); + f.append(sector.location.logicalSector()).append(','); + f.append(sector.location.logicalCylinder()).append(','); + f.append(sector.location.logicalHead()).append(','); + f.append(sector.clockNs).append(','); + f.append(sector.headerStartTimeNs).append(','); + f.append(sector.headerEndTimeNs).append(','); + f.append(sector.dataStartTimeNs).append(','); + f.append(sector.dataEndTimeNs).append(','); + f.append(sector.position).append(','); + f.append(sector.data.size()).append(','); + f.append(Sector.statusToString(sector.status)); + f.append("\n"); + } + + try + { + Files.writeString(Path.of(filename), f.toString(), StandardCharsets.UTF_8); + } catch (IOException e) + { + throw new FluxEngineException("cannot open CSV report file"); + } + } + + public void printMap(Image image) + { + Geometry geometry = image.getGeometry(); + + int badSectors = 0; + int missingSectors = 0; + int totalSectors = 0; + + System.out.print(" Tracks -> "); + for (int i = 10; i < geometry.numCylinders; i += 10) + System.out.printf("%-10d", i / 10); + System.out.println(); + System.out.print("H.SS "); + for (int i = 0; i < geometry.numCylinders; i++) + System.out.print(i % 10); + System.out.println(); + + for (int side = 0; side < geometry.numHeads; side++) + { + int maxSector = geometry.firstSector + geometry.numSectors - 1; + for (int sectorId = 0; sectorId <= maxSector; sectorId++) + { + if (sectorId < geometry.firstSector) + continue; + + System.out.printf("%d.%2d ", side, sectorId); + for (int track = 0; track < geometry.numCylinders; track++) + { + Sector sector = image.get(track, side, sectorId); + if (sector == null) + { + System.out.print('X'); + missingSectors++; + } else + { + switch (sector.status) + { + case OK: + System.out.print('.'); + break; + + case BAD_CHECKSUM: + System.out.print('B'); + badSectors++; + break; + + case CONFLICT: + System.out.print('C'); + badSectors++; + break; + + default: + System.out.print(sector.status.ordinal()); + break; + } + } + totalSectors++; + } + System.out.println(); + } + } + int goodSectors = totalSectors - missingSectors - badSectors; + if (totalSectors == 0) + System.out.println("No sectors in output; skipping analysis"); + else + { + System.out.printf("Good sectors: %d/%d (%d%%)%n", + goodSectors, totalSectors, 100 * goodSectors / totalSectors); + System.out.printf("Missing sectors: %d/%d (%d%%)%n", + missingSectors, totalSectors, 100 * missingSectors / totalSectors); + System.out.printf("Bad sectors: %d/%d (%d%%)%n", + badSectors, totalSectors, 100 * badSectors / totalSectors); + } + } + + /* Writes a raw image. */ + + public abstract void writeImage(Image image); +} diff --git a/javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel b/javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel new file mode 100644 index 00000000..fbc4b879 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "ImageWriterTest", + srcs = ["ImageWriterTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/imagewriter", + "//java/com/cowlark/fluxengine/imagewriter:imagewriter_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java b/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java new file mode 100644 index 00000000..e4b45704 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java @@ -0,0 +1,83 @@ +package com.cowlark.fluxengine.imagewriter; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.ImageReaderWriterType; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.Sector; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImageWriterTest +{ + @Test + public void createUnportedTypeThrows() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_IMG) + .build(); + + assertThrows(FluxEngineException.class, () -> ImageWriter.create(config)); + } + + @Test + public void createBadTypeThrows() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_NOT_SET) + .build(); + + assertThrows(FluxEngineException.class, () -> ImageWriter.create(config)); + } + + @Test + public void createNoWriterConfiguredThrows() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .build(); + + assertThrows(FluxEngineException.class, () -> ImageWriter.create(config)); + } + + @Test + public void writeCsv() throws Exception + { + Image image = new Image(); + Sector sector = image.put(2, 1, 5); + sector.status = Sector.Status.OK; + sector.position = 1234; + sector.clockNs = 2000.0; + sector.headerStartTimeNs = 1.0; + sector.headerEndTimeNs = 2.0; + sector.dataStartTimeNs = 3.0; + sector.dataEndTimeNs = 4.0; + + Path file = Files.createTempFile("image", ".csv"); + ImageWriter writer = new ImageWriter(ImageWriterProto.getDefaultInstance()) + { + @Override + public void writeImage(Image image) + { + } + }; + + writer.writeCsv(image, file.toString()); + + String contents = Files.readString(file); + assertThat(contents).contains("\"Physical track\",\"Physical side\""); + assertThat(contents).contains("\"Status\""); + assertThat(contents).contains( + "-1,-1,5,2,1,2000.0,1.0,2.0,3.0,4.0,1234,0,OK\n"); + } +} From 603d5bc0fa6c22d76bdcbbe8a70a50e0c61f19fc Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 18:13:01 +0200 Subject: [PATCH 118/192] Port the image writers. --- java/com/cowlark/fluxengine/core/Bytes.java | 16 + .../fluxengine/imagewriter/BUILD.bazel | 1 + .../imagewriter/D64ImageWriter.java | 57 +++ .../imagewriter/D88ImageWriter.java | 119 ++++++ .../imagewriter/DiskCopyImageWriter.java | 175 +++++++++ .../fluxengine/imagewriter/ImageWriter.java | 19 +- .../imagewriter/ImdImageWriter.java | 365 ++++++++++++++++++ .../imagewriter/ImgImageWriter.java | 72 ++++ .../imagewriter/NsiImageWriter.java | 104 +++++ .../imagewriter/RawImageWriter.java | 66 ++++ .../imagewriter/ImageWriterTest.java | 113 +++++- 11 files changed, 1100 insertions(+), 7 deletions(-) create mode 100644 java/com/cowlark/fluxengine/imagewriter/D64ImageWriter.java create mode 100644 java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java create mode 100644 java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java create mode 100644 java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java create mode 100644 java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java create mode 100644 java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java create mode 100644 java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index e6f8f3fe..fa69b09c 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; @@ -110,6 +111,21 @@ public byte[] toByteArray() return result; } + /* Writes the contents to a file, ported from lib/core/bytes.h + * Bytes::writeToFile(). */ + public void writeToFile(String filename) + { + try + { + java.nio.file.Files.write( + java.nio.file.Path.of(filename), toByteArray()); + } catch (IOException e) + { + throw new FluxEngineException( + "cannot write to file " + filename + ": " + e.getMessage()); + } + } + @Override public Object[] toArray() { diff --git a/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel index 14b5e04a..52905e15 100644 --- a/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel +++ b/java/com/cowlark/fluxengine/imagewriter/BUILD.bazel @@ -28,5 +28,6 @@ java_library( "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/imagereader:imagereader_java_proto", ], ) diff --git a/java/com/cowlark/fluxengine/imagewriter/D64ImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/D64ImageWriter.java new file mode 100644 index 00000000..93a3f1aa --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/D64ImageWriter.java @@ -0,0 +1,57 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; + +/** + * Writes a D64 (Commodore 1541) sector image, ported from + * lib/imagewriter/d64imagewriter.cc. + */ +public class D64ImageWriter extends ImageWriter +{ + public D64ImageWriter(ImageWriterProto config) + { + super(config); + } + + private static int sectorsPerTrack(int track) + { + if (track < 17) + return 21; + if (track < 24) + return 19; + if (track < 30) + return 18; + return 17; + } + + @Override + public void writeImage(Image image) + { + System.out.println("D64: writing triangular image"); + + Bytes output = new Bytes(); + ByteWriter bw = output.writer(); + + int offset = 0; + for (int track = 0; track < 40; track++) + { + int sectorCount = sectorsPerTrack(track); + for (int sectorId = 0; sectorId < sectorCount; sectorId++) + { + Sector sector = image.get(track, 0, sectorId); + if (sector != null) + { + bw.seek(offset); + bw.write(sector.data); + } + + offset += 256; + } + } + + output.writeToFile(config.getFilename()); + } +} diff --git a/java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java new file mode 100644 index 00000000..9b1737fa --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java @@ -0,0 +1,119 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * Writes a D88 sector image, ported from lib/imagewriter/d88imagewriter.cc. + */ +public class D88ImageWriter extends ImageWriter +{ + public D88ImageWriter(ImageWriterProto config) + { + super(config); + } + + private static int countlZero(int value) + { + int count = 0; + while ((value & 0x80000000) == 0) + { + value <<= 1; + count++; + } + return count; + } + + @Override + public void writeImage(Image image) + { + Geometry geometry = image.getGeometry(); + + int tracks = geometry.numCylinders; + int sides = geometry.numHeads; + + Bytes header = new Bytes(); + ByteWriter headerWriter = header.writer(); + for (int i = 0; i < 26; i++) + { + headerWriter.write8(0x0); /* image name + reserved bytes */ + } + headerWriter.write8(0x00); /* not write protected */ + if (geometry.numCylinders > 42) + { + headerWriter.write8(0x20); /* 2HD */ + } else + { + headerWriter.write8(0x00); /* 2D */ + } + headerWriter.writeLe32(0); /* disk size (overridden at the end) */ + for (int i = 0; i < 164; i++) + { + headerWriter.writeLe32(0); /* track pointer (overridden in loop) */ + } + + Bytes output = header; + ByteWriter bw = output.writer(); + + int trackOffset = 688; + + for (int track = 0; track < geometry.numCylinders * geometry.numHeads; track++) + { + headerWriter.seek(0x20 + 4 * track); + headerWriter.writeLe32(trackOffset); + int side = track & 1; + List sectors = new ArrayList<>(); + for (int sectorId = geometry.firstSector; sectorId <= geometry.numSectors; + sectorId++) + { + Sector sector = image.get(track >> 1, side, sectorId); + if (sector != null) + sectors.add(sector); + } + sectors.sort(Comparator.comparingInt(s -> s.position)); + for (Sector sector : sectors) + { + Bytes sectorBytes = new Bytes(); + ByteWriter sectorWriter = sectorBytes.writer(); + sectorWriter.write8(sector.location.logicalCylinder()); + sectorWriter.write8(sector.location.logicalHead()); + sectorWriter.write8(sector.location.logicalSector()); + sectorWriter.write8(24 - countlZero(sector.data.size())); + sectorWriter.writeLe16(sectors.size()); + sectorWriter.write8(0x00); /* always write mfm */ + sectorWriter.write8(0x00); /* always write not deleted data */ + if (sector.status == Sector.Status.BAD_CHECKSUM) + { + sectorWriter.write8(0xB0); + } else + { + sectorWriter.write8(0x00); + } + sectorWriter.write8(0x00); /* reserved */ + sectorWriter.write8(0x00); + sectorWriter.write8(0x00); + sectorWriter.write8(0x00); + sectorWriter.write8(0x00); + sectorWriter.writeLe16(sector.data.size()); + output = output.concat(sectorBytes); + output = output.concat(sector.data); + trackOffset += sectorBytes.size(); + trackOffset += sector.data.size(); + } + } + + headerWriter.seek(0x1c); + headerWriter.writeLe32(output.size()); + + output.writeToFile(config.getFilename()); + + System.out.printf("D88: wrote %d tracks, %d sides, %d kB total%n", + tracks, sides, output.size() / 1024); + } +} diff --git a/java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java new file mode 100644 index 00000000..177e99c7 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java @@ -0,0 +1,175 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.nio.charset.StandardCharsets; + +/** + * Writes a DiskCopy 4.2 sector image, ported from + * lib/imagewriter/diskcopyimagewriter.cc. + */ +public class DiskCopyImageWriter extends ImageWriter +{ + private static final String LABEL = "FluxEngine image"; + + public DiskCopyImageWriter(ImageWriterProto config) + { + super(config); + } + + private static void writeAndUpdateChecksum( + ByteWriter bw, int[] checksum, Bytes data) + { + ByteReader br = data.iterator(); + while (!br.eof()) + { + int i = br.readBe16(); + checksum[0] += i; + checksum[0] = (checksum[0] >>> 1) | (checksum[0] << 31); + bw.writeBe16(i); + } + } + + @Override + public void writeImage(Image image) + { + Geometry geometry = image.getGeometry(); + + boolean mfm = false; + + switch (geometry.sectorSize) + { + case 524: + /* GCR disk */ + break; + + case 512: + /* MFM disk */ + mfm = true; + break; + + default: + throw new FluxEngineException( + "this image is not compatible with the DiskCopy 4.2 format"); + } + final boolean isMfm = mfm; + + System.out.println("DC42: writing DiskCopy 4.2 image"); + System.out.printf( + "DC42: %d tracks, %d sides, %d sectors, %d bytes per sector; %s%n", + geometry.numCylinders, + geometry.numHeads, + geometry.numSectors, + geometry.sectorSize, + isMfm ? "MFM" : "GCR"); + + java.util.function.IntUnaryOperator sectorsPerTrack = track -> + { + if (isMfm) + return geometry.numSectors; + + if (track < 16) + return 12; + if (track < 32) + return 11; + if (track < 48) + return 10; + if (track < 64) + return 9; + return 8; + }; + + Bytes data = new Bytes(); + ByteWriter bw = data.writer(); + + /* Write the actual sector data. */ + + int[] dataChecksum = {0}; + int[] tagChecksum = {0}; + int offset = 0x54; + int sectorDataStart = offset; + for (int track = 0; track < geometry.numCylinders; track++) + { + for (int side = 0; side < geometry.numHeads; side++) + { + int sectorCount = sectorsPerTrack.applyAsInt(track); + for (int sectorId = 0; sectorId < sectorCount; sectorId++) + { + Sector sector = image.get(track, side, sectorId); + if (sector != null) + { + bw.seek(offset); + writeAndUpdateChecksum(bw, dataChecksum, sector.data.slice(0, 512)); + } + offset += 512; + } + } + } + int sectorDataEnd = offset; + if (!mfm) + { + for (int track = 0; track < geometry.numCylinders; track++) + { + for (int side = 0; side < geometry.numHeads; side++) + { + int sectorCount = sectorsPerTrack.applyAsInt(track); + for (int sectorId = 0; sectorId < sectorCount; sectorId++) + { + Sector sector = image.get(track, side, sectorId); + if (sector != null) + { + bw.seek(offset); + writeAndUpdateChecksum( + bw, tagChecksum, sector.data.slice(512, 12)); + } + offset += 12; + } + } + } + } + int tagDataEnd = offset; + + /* Write the header. */ + + int encoding; + int format; + if (isMfm) + { + format = 0x22; + if (geometry.numSectors == 18) + encoding = 3; + else + encoding = 2; + } else + { + if (geometry.numHeads == 2) + { + encoding = 1; + format = 0x22; + } else + { + encoding = 0; + format = 0x02; + } + } + + bw.seek(0); + bw.write8(LABEL.getBytes(StandardCharsets.US_ASCII).length); + bw.write(LABEL.getBytes(StandardCharsets.US_ASCII)); + bw.seek(0x40); + bw.writeBe32(sectorDataEnd - sectorDataStart); /* data size */ + bw.writeBe32(tagDataEnd - sectorDataEnd); /* tag size */ + bw.writeBe32(dataChecksum[0]); /* data checksum */ + bw.writeBe32(tagChecksum[0]); /* tag checksum */ + bw.write8(encoding); /* encoding */ + bw.write8(format); /* format byte */ + bw.writeBe16(0x0100); /* magic number */ + + data.writeToFile(config.getFilename()); + } +} diff --git a/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java index 0fd2e3bd..db3270cc 100644 --- a/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java @@ -24,10 +24,17 @@ public ImageWriter(ImageWriterProto config) this.config = config; } + protected ImageWriterProto getWriterConfig() + { + return config; + } + public static ImageWriter create(ConfigProto config) { if (!config.hasImageWriter()) throw new FluxEngineException("no image writer configured"); + if (config.getImageWriter().getType() == ImageReaderWriterType.IMAGETYPE_IMG) + return new ImgImageWriter(config.getImageWriter(), config); return create(config.getImageWriter()); } @@ -38,19 +45,19 @@ public static ImageWriter create(ImageWriterProto config) case IMAGETYPE_IMG: return notImplemented("img"); case IMAGETYPE_D64: - return notImplemented("d64"); + return new D64ImageWriter(config); case IMAGETYPE_LDBS: return notImplemented("ldbs"); case IMAGETYPE_DISKCOPY: - return notImplemented("diskcopy"); + return new DiskCopyImageWriter(config); case IMAGETYPE_NSI: - return notImplemented("nsi"); + return new NsiImageWriter(config); case IMAGETYPE_RAW: - return notImplemented("raw"); + return new RawImageWriter(config); case IMAGETYPE_D88: - return notImplemented("d88"); + return new D88ImageWriter(config); case IMAGETYPE_IMD: - return notImplemented("imd"); + return new ImdImageWriter(config); default: throw new FluxEngineException("bad output image config"); } diff --git a/java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java new file mode 100644 index 00000000..67761c05 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java @@ -0,0 +1,365 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * Writes an IMD (ImageDisk) sector image, ported from + * lib/imagewriter/imdimagewriter.cc. + */ +public class ImdImageWriter extends ImageWriter +{ + private static final String LABEL = "IMD archive by fluxengine on"; + private static final int SEC_CYL_MAP_FLAG = 0x80; + private static final int SEC_HEAD_MAP_FLAG = 0x40; + private static final int END_OF_FILE = 0x1A; + + public ImdImageWriter(ImageWriterProto config) + { + super(config); + } + + private static int getModulationAndSpeed(int flags, ImdOutputProto.RecordingMode mode) + { + if (flags == 0) + { + throw new FluxEngineException( + "Can't write IMD files with this speed " + flags + + ", and modulation " + mode + + ". Did you read a real disk?"); + } else + { + flags = (int) (1000000.0 / flags); + } + + if ((flags > 950) && (flags < 1050)) /* HD disk */ + { + /* 500 kbps */ + if (mode == ImdOutputProto.RecordingMode.RECMODE_FM) + { + return 0; + } else + { + return 3; + } + } else if ((flags > 1475) && (flags < 1575)) /* SD disk */ + { + /* 300 kbps */ + if (mode == ImdOutputProto.RecordingMode.RECMODE_FM) + { + return 1; + } else + { + return 4; + } + } else if ((flags > 1900) && (flags < 2100)) /* DD disk */ + { + /* 250 kbps */ + if (mode == ImdOutputProto.RecordingMode.RECMODE_FM) + { + return 2; + } else + { + return 5; + } + } else + { + throw new FluxEngineException( + "IMD: Can't write IMD files with this speed " + flags + + ", and modulation " + mode + ". Try another format."); + } + } + + private static int setSectorSize(int flags) + { + switch (flags) + { + case 128: + return 0; + case 256: + return 1; + case 512: + return 2; + case 1024: + return 3; + case 2048: + return 4; + case 4096: + return 5; + case 8192: + return 6; + } + throw new FluxEngineException( + "IMD: Sector size " + flags + + " not in standard range (128, 256, 512, 1024, 2048, 4096, 8192)."); + } + + @Override + public void writeImage(Image image) + { + Geometry geometry = image.getGeometry(); + int numHeads; + int numSectors; + int numBytes; + int numSectorsInTrack = 0; + + numHeads = geometry.numHeads; + numSectors = geometry.numSectors; + numBytes = geometry.sectorSize; + + Bytes imagenew = new Bytes(); + ByteWriter bw = imagenew.writer(); + + ImdOutputProto.DataRate dataRate = config.getImd().getDataRate(); + if (dataRate == ImdOutputProto.DataRate.RATE_GUESS) + { + dataRate = (geometry.numSectors > 10) + ? ImdOutputProto.DataRate.RATE_HD + : ImdOutputProto.DataRate.RATE_DD; + if (geometry.sectorSize <= 256) + dataRate = ImdOutputProto.DataRate.RATE_SD; + System.out.println("IMD: guessing data rate as " + dataRate); + } + + ImdOutputProto.RecordingMode recordingMode = config.getImd().getRecordingMode(); + if (recordingMode == ImdOutputProto.RecordingMode.RECMODE_GUESS) + { + recordingMode = ImdOutputProto.RecordingMode.RECMODE_MFM; + System.out.println("IMD: guessing recording mode as " + recordingMode); + } + + String comment = config.getImd().getComment(); + if (comment.length() == 0) + { + comment = LABEL; + comment = comment + " date: " + + LocalDateTime.now().format(DateTimeFormatter.ofPattern( + "E MMM d HH:mm:ss yyyy")); + } else + { + comment = "IMD " + comment; + } + bw.seek(0); + + bw.write(comment.getBytes(java.nio.charset.StandardCharsets.US_ASCII)); + bw.write8(END_OF_FILE); + String sectorSkew = ""; + int statusSector = 1; + boolean blnOptionalCylinderMap = false; + boolean blnOptionalHeadMap = false; + + /* Write the actual sector data. */ + for (int track = 0; track < geometry.numCylinders; track++) + { + for (int head = 0; head < numHeads; head++) + { + int sectorIdBase = 1; /* IMD starts sector numbering with 1 */ + int sectorId = 0; + int modeValue = 0; + int headerTrack = 0; + int headerHead = 0; + int headerNumSectors = 0; + int headerSectorSize = 0; + Sector sector = image.get(track, head, sectorId + 1); + if (sector == null) + { + /* sector 0 doesnt exist exit with error */ + statusSector = 0; + System.out.printf("IMD: sector %d not found on track %d, head %d%n", + sectorId + 1, track, head); + break; + } else + { + /* Get the header information */ + numBytes = sector.data.size(); + headerTrack = track; + headerHead = head; + headerSectorSize = setSectorSize(numBytes); + sectorSkew = ""; + numSectorsInTrack = 0; + double RATE = 0; + if (sector.clockNs > 0) + { + RATE = 1000000.0 / sector.clockNs; + } else + { + switch (dataRate) + { + case RATE_HD: + RATE = 1000; + break; + case RATE_SD: + RATE = 1500; + break; + case RATE_DD: + RATE = 2000; + break; + case RATE_GUESS: + break; + } + } + modeValue = getModulationAndSpeed((int) RATE, recordingMode); + } + /* determine number of sectors in track */ + for (int i = 0; i < numSectors; i++) + { + Sector s = image.get(track, head, i + 1); + if (s == null) + { + break; + } else + { + numSectorsInTrack++; + } + } + /* determine sector skew and if there are optional cylinder maps + * or head maps */ + for (int i = 0; i < numSectorsInTrack; i++) + { + Sector s = image.get(track, head, i + 1); + if (s == null) + { + break; + } else + { + sectorSkew = sectorSkew + (char) ((i + sectorIdBase) + '0'); + if (s.physicalLocation != null && + ((s.physicalLocation.cylinder() != + s.location.logicalCylinder()) || + (s.physicalLocation.head() != + s.location.logicalHead()))) + blnOptionalHeadMap = true; + } + } + bw.write8(modeValue); /* 1 byte ModeValue */ + bw.write8(track); /* 1 byte Cylinder */ + /* are there optional cylinder or head maps? */ + if (blnOptionalCylinderMap) + { + headerHead = headerHead ^ SEC_CYL_MAP_FLAG; + } + if (blnOptionalHeadMap) + { + headerHead = headerHead ^ SEC_HEAD_MAP_FLAG; + } + bw.write8(head); /* 1 byte Head */ + bw.write8(numSectorsInTrack); /* 1 byte number of sectors */ + bw.write8(headerSectorSize); /* 1 byte sector size */ + for (int i = 0; i < numSectorsInTrack; i++) + { + bw.write8((i + sectorIdBase)); /* sector numbering map */ + } + /* Write optional cylinder map */ + if (blnOptionalCylinderMap) + { + for (int i = 0; i < numSectorsInTrack; i++) + { + Sector s = image.get(track, head, i + 1); + bw.write8(s.location.logicalCylinder()); + } + } + + /* Write optional sector head map */ + if (blnOptionalHeadMap) + { + for (int i = 0; i < numSectorsInTrack; i++) + { + Sector s = image.get(track, head, i + 1); + bw.write8(s.location.logicalHead()); + } + } + /* Now read data and write to file */ + for (int i = 0; i < numSectorsInTrack; i++) + { + Sector s = image.get(track, head, i + 1); + boolean blnCompressable = false; + Bytes sectordata = new Bytes(numBytes); + Bytes compressed = new Bytes(1); + int byte0 = 0; + int bytePrevious = 0; + if (s == null) + { + statusSector = 0; + break; + } else + { + ByteReader br = s.data.iterator(); + int j; + /* determine if all bytes are the same -> compress */ + for (j = 0; j < numBytes; j++) + { + byte0 = br.read8(); + if (j == 0) + { + bytePrevious = byte0; + } + if (bytePrevious == byte0) + { + blnCompressable = true; + } else + { + blnCompressable = false; + break; + } + } + switch (s.status) + { + case MISSING: + statusSector = 0; + break; + + case OK: + if (blnCompressable) + { + statusSector = 2; + } else + { + statusSector = 1; + } + break; + case DATA_MISSING: + statusSector = 3; + break; + case BAD_CHECKSUM: + statusSector = 5; + break; + + default: + throw new FluxEngineException( + "IMD: Don't understand IMD files with sector status " + + statusSector); + } + bw.write8(statusSector); /* 1 byte status sector */ + if (blnCompressable) + { + bw.write8(byte0); + blnCompressable = false; + } else + { + bw.write(s.data); + } + numSectors = numSectorsInTrack; + } + blnOptionalCylinderMap = false; + blnOptionalHeadMap = false; + } + } + } + imagenew.writeToFile(config.getFilename()); + System.out.printf( + "IMD: Written %d tracks, %d heads, %d sectors, %d bytes per " + + "sector, %d kB total%n", + geometry.numCylinders, + numHeads, + numSectors, + numBytes, + imagenew.size() / 1024); + } +} diff --git a/java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java new file mode 100644 index 00000000..1f6c0003 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java @@ -0,0 +1,72 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; + +/** + * Writes a raw (non-interleaved) sector image, ported from + * lib/imagewriter/imgimagewriter.cc. + */ +public class ImgImageWriter extends ImageWriter +{ + private final ConfigProto config; + + /* The img writer needs the full config to determine the layout; created + * via ImageWriter.create(ConfigProto). */ + public ImgImageWriter(ImageWriterProto writerConfig, ConfigProto config) + { + super(writerConfig); + this.config = config; + } + + @Override + public void writeImage(Image image) + { + Geometry geometry = image.getGeometry(); + + int tracks = + config.getLayout().hasTracks() ? config.getLayout().getTracks() : geometry.numCylinders; + int sides = + config.getLayout().hasSides() ? config.getLayout().getSides() : geometry.numHeads; + + DiskLayout diskLayout = new DiskLayout(config); + boolean inFilesystemOrder = getWriterConfig().getImg().getFilesystemSectorOrder(); + + Bytes output = new Bytes(); + ByteWriter bw = output.writer(); + + Iterable locations = inFilesystemOrder + ? diskLayout.logicalLocationsInFilesystemOrder + : diskLayout.logicalLocations; + for (CylinderHead logicalLocation : locations) + { + LogicalTrackLayout ltl = + diskLayout.layoutByLogicalLocation.get(logicalLocation); + + Iterable sectorOrder = inFilesystemOrder + ? ltl.filesystemSectorOrder + : ltl.naturalSectorOrder; + for (int sectorId : sectorOrder) + { + Sector sector = image.get( + logicalLocation.cylinder(), logicalLocation.head(), sectorId); + if (sector != null) + bw.write(sector.data.slice(0, ltl.sectorSize)); + else + bw.pad(ltl.sectorSize); + } + } + + output.writeToFile(getWriterConfig().getFilename()); + + System.out.printf("IMG: wrote %d tracks, %d sides, %d kB total to %s%n", + tracks, sides, output.size() / 1024, getWriterConfig().getFilename()); + } +} diff --git a/java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java new file mode 100644 index 00000000..88fe12fa --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java @@ -0,0 +1,104 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; + +/** + * Writes an NSI (North Star) sector image, ported from + * lib/imagewriter/nsiimagewriter.cc. + */ +public class NsiImageWriter extends ImageWriter +{ + public NsiImageWriter(ImageWriterProto config) + { + super(config); + } + + @Override + public void writeImage(Image image) + { + Geometry geometry = image.getGeometry(); + boolean mixedDensity = false; + + int trackSize = geometry.numSectors * geometry.sectorSize; + + if (geometry.numCylinders * trackSize == 0) + { + System.out.println( + "No sectors in output; skipping .nsi image file generation."); + return; + } + + System.out.printf( + "Writing %d tracks, %d sides, %d sectors, %s (%d bytes/sector), " + + "%d kB total%n", + geometry.numCylinders, + geometry.numHeads, + geometry.numSectors, + geometry.sectorSize == 256 ? "SD" : "DD", + geometry.sectorSize, + geometry.numCylinders * geometry.numHeads * geometry.numSectors * + geometry.sectorSize / 1024); + + Bytes output = new Bytes( + geometry.numCylinders * geometry.numHeads * geometry.numSectors * + geometry.sectorSize); + ByteWriter bw = output.writer(); + + int sectorFileOffset; + for (int track = 0; track < geometry.numCylinders * geometry.numHeads; track++) + { + int side = (track < geometry.numCylinders) ? 0 : 1; + for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++) + { + Sector sector = image.get(track % geometry.numCylinders, side, sectorId); + if (sector != null) + { + if (side == 0) + { /* Side 0 is from track 0-34 */ + sectorFileOffset = + track * trackSize + sectorId * geometry.sectorSize; + } else + { /* Side 1 is from track 70-35 */ + sectorFileOffset = + (geometry.sectorSize * geometry.numSectors * + geometry.numCylinders) + /* Skip over side 0 */ + ((geometry.numCylinders - 1) - + (track % geometry.numCylinders)) * + (geometry.sectorSize * geometry.numSectors) + + (sectorId * geometry.sectorSize); + } + bw.seek(sectorFileOffset); + if ((geometry.sectorSize == 512) && (sector.data.size() == 256)) + { + /* North Star DOS provided an upgrade path for disks + * formatted as single-density to hold double-density + * data without reformatting. In this case, the four + * directory blocks will be single-density but other + * areas of the disk are double-density. This cannot be + * accurately represented using a .nsi file, so in these + * cases, we pad the sector to 512-bytes, filling with + * spaces. */ + if (!mixedDensity) + { + System.out.println( + "Warning: Disk contains mixed " + + "single/double-density sectors."); + } + mixedDensity = true; + bw.write(sector.data.slice(0, 256)); + bw.pad(256, ' '); + } else + { + bw.write(sector.data.slice(0, geometry.sectorSize)); + } + } + } + } + + output.writeToFile(config.getFilename()); + } +} diff --git a/java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java new file mode 100644 index 00000000..db30154a --- /dev/null +++ b/java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java @@ -0,0 +1,66 @@ +package com.cowlark.fluxengine.imagewriter; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Record; +import com.cowlark.fluxengine.data.Sector; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +/** + * Writes a raw (flux-level) image, ported from + * lib/imagewriter/rawimagewriter.cc. + */ +public class RawImageWriter extends ImageWriter +{ + public RawImageWriter(ImageWriterProto config) + { + super(config); + } + + @Override + public void writeImage(Image image) + { + Geometry geometry = image.getGeometry(); + + int trackSize = geometry.numSectors * geometry.sectorSize; + + if (geometry.numCylinders * trackSize == 0) + { + System.out.println( + "RAW: no sectors in output; skipping image file generation."); + return; + } + + System.out.printf("RAW: writing %d tracks, %d sides%n", + geometry.numCylinders, geometry.numHeads); + + Bytes output = new Bytes(); + + for (int track = 0; track < geometry.numCylinders * geometry.numHeads; track++) + { + int side = (track < geometry.numCylinders) ? 0 : 1; + + List records = new ArrayList<>(); + for (int sectorId = 0; sectorId < geometry.numSectors; sectorId++) + { + Sector sector = image.get(track % geometry.numCylinders, side, sectorId); + if (sector != null) + records.addAll(sector.records); + } + + records.sort(Comparator.comparingDouble(r -> r.startTimeNs)); + + for (Record record : records) + { + output = output.concat(record.rawData); + output = output.concat(new Bytes(3)); + } + output = output.concat(new Bytes(1)); + } + + output.writeToFile(config.getFilename()); + } +} diff --git a/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java b/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java index e4b45704..01e200e4 100644 --- a/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java +++ b/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java @@ -24,12 +24,83 @@ public class ImageWriterTest public void createUnportedTypeThrows() { ImageWriterProto config = ImageWriterProto.newBuilder() - .setType(ImageReaderWriterType.IMAGETYPE_IMG) + .setType(ImageReaderWriterType.IMAGETYPE_LDBS) .build(); assertThrows(FluxEngineException.class, () -> ImageWriter.create(config)); } + @Test + public void createD64ImageWriter() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_D64) + .build(); + + assertThat(ImageWriter.create(config)).isInstanceOf(D64ImageWriter.class); + } + + @Test + public void createD88ImageWriter() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_D88) + .build(); + + assertThat(ImageWriter.create(config)).isInstanceOf(D88ImageWriter.class); + } + + @Test + public void createDiskCopyImageWriter() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_DISKCOPY) + .build(); + + assertThat(ImageWriter.create(config)).isInstanceOf(DiskCopyImageWriter.class); + } + + @Test + public void createImdImageWriter() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_IMD) + .build(); + + assertThat(ImageWriter.create(config)).isInstanceOf(ImdImageWriter.class); + } + + @Test + public void createNsiImageWriter() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_NSI) + .build(); + + assertThat(ImageWriter.create(config)).isInstanceOf(NsiImageWriter.class); + } + + @Test + public void createRawImageWriter() + { + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_RAW) + .build(); + + assertThat(ImageWriter.create(config)).isInstanceOf(RawImageWriter.class); + } + + @Test + public void createImgImageWriterFromConfig() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .withImageWriter("out.dsk") + .build(); + + assertThat(ImageWriter.create(config)).isInstanceOf(ImgImageWriter.class); + } + @Test public void createBadTypeThrows() { @@ -50,6 +121,46 @@ public void createNoWriterConfiguredThrows() assertThrows(FluxEngineException.class, () -> ImageWriter.create(config)); } + @Test + public void d64WritesSectorData() throws Exception + { + Image image = new Image(); + Sector sector = image.put(0, 0, 0); + sector.data = com.cowlark.fluxengine.core.Bytes.of(1, 2, 3, 4); + + Path file = Files.createTempFile("image", ".d64"); + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_D64) + .setFilename(file.toString()) + .build(); + + new D64ImageWriter(config).writeImage(image); + + byte[] data = Files.readAllBytes(file); + assertThat(data.length).isEqualTo(4); + assertThat(data[0]).isEqualTo((byte) 1); + assertThat(data[1]).isEqualTo((byte) 2); + assertThat(data[2]).isEqualTo((byte) 3); + assertThat(data[3]).isEqualTo((byte) 4); + } + + @Test + public void d64EmptyImageWritesNothing() throws Exception + { + Image image = new Image(); + + Path file = Files.createTempFile("image", ".d64"); + ImageWriterProto config = ImageWriterProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_D64) + .setFilename(file.toString()) + .build(); + + new D64ImageWriter(config).writeImage(image); + + byte[] data = Files.readAllBytes(file); + assertThat(data).isEmpty(); + } + @Test public void writeCsv() throws Exception { From df6fe81fc4cd4ebf27aa7cc19d79688120d0cff8 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 18:37:39 +0200 Subject: [PATCH 119/192] Port the logger (and reformat). --- .bazelrc | 4 + java/com/cowlark/fluxengine/arch/Arch.java | 2 +- java/com/cowlark/fluxengine/arch/BUILD.bazel | 5 +- .../fluxengine/arch/agat/AgatDecoder.java | 13 +- .../cowlark/fluxengine/arch/amiga/Amiga.java | 9 +- .../fluxengine/arch/amiga/AmigaDecoder.java | 9 +- .../fluxengine/arch/apple2/Apple2Decoder.java | 224 +++++++---- .../arch/brother/BrotherDecoder.java | 350 ++++++++++++------ java/com/cowlark/fluxengine/arch/c64/C64.java | 16 +- .../arch/c64/Commodore64Decoder.java | 67 ++-- .../arch/f85/DurangoF85Decoder.java | 72 ++-- .../fluxengine/arch/fb100/Fb100Decoder.java | 10 +- .../fluxengine/arch/ibm/IbmDecoder.java | 12 +- .../arch/macintosh/MacintoshDecoder.java | 222 +++++++---- .../arch/micropolis/MicropolisDecoder.java | 34 +- .../cowlark/fluxengine/arch/mx/MxDecoder.java | 3 +- .../fluxengine/arch/northstar/Northstar.java | 4 +- .../arch/northstar/NorthstarDecoder.java | 22 +- .../arch/rolandd20/RolandD20Decoder.java | 28 +- .../fluxengine/arch/smaky6/Smaky6Decoder.java | 15 +- .../fluxengine/arch/tartu/TartuDecoder.java | 6 +- .../arch/tids990/Tids990Decoder.java | 3 +- .../arch/victor9k/Victor9kDecoder.java | 75 ++-- .../arch/zilogmcz/ZilogMczDecoder.java | 3 +- .../fluxengine/buildtools/ProtoEncode.java | 10 +- java/com/cowlark/fluxengine/core/Bytes.java | 19 +- .../cowlark/fluxengine/core/LogMessage.java | 131 +++++++ .../cowlark/fluxengine/core/LogRenderer.java | 135 +++++++ java/com/cowlark/fluxengine/core/Logger.java | 32 ++ java/com/cowlark/fluxengine/data/BUILD.bazel | 4 +- .../cowlark/fluxengine/data/FluxMatchers.java | 11 +- .../cowlark/fluxengine/data/FluxPattern.java | 7 +- java/com/cowlark/fluxengine/data/Formats.java | 3 +- java/com/cowlark/fluxengine/data/Image.java | 18 +- java/com/cowlark/fluxengine/data/Record.java | 2 - java/com/cowlark/fluxengine/data/Sector.java | 18 +- .../cowlark/fluxengine/decoders/Decoder.java | 11 +- .../imagewriter/D88ImageWriter.java | 10 +- .../imagewriter/DiskCopyImageWriter.java | 9 +- .../fluxengine/imagewriter/ImageWriter.java | 41 +- .../imagewriter/ImdImageWriter.java | 46 ++- .../imagewriter/ImgImageWriter.java | 31 +- .../imagewriter/NsiImageWriter.java | 25 +- .../imagewriter/RawImageWriter.java | 9 +- .../com/cowlark/fluxengine/core/BUILD.bazel | 20 + .../fluxengine/core/LogRendererTest.java | 111 ++++++ .../cowlark/fluxengine/core/LoggerTest.java | 66 ++++ 47 files changed, 1390 insertions(+), 587 deletions(-) create mode 100644 java/com/cowlark/fluxengine/core/LogMessage.java create mode 100644 java/com/cowlark/fluxengine/core/LogRenderer.java create mode 100644 java/com/cowlark/fluxengine/core/Logger.java create mode 100644 javatests/com/cowlark/fluxengine/core/LogRendererTest.java create mode 100644 javatests/com/cowlark/fluxengine/core/LoggerTest.java diff --git a/.bazelrc b/.bazelrc index 23ef3cf7..794186e6 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,5 +1,9 @@ common --java_language_version=21 +# Tools built in the exec configuration also need Java 21 (for records etc.). +common --tool_java_language_version=21 +common --tool_java_runtime_version=remotejdk_21 + # Lombok generates builder classes that are part of the public API; the # annotation-processor output doesn't reach the interface jar used by header # compilation (and Turbine can't run Lombok), so disable header compilation. diff --git a/java/com/cowlark/fluxengine/arch/Arch.java b/java/com/cowlark/fluxengine/arch/Arch.java index 6b9323c4..b4178824 100644 --- a/java/com/cowlark/fluxengine/arch/Arch.java +++ b/java/com/cowlark/fluxengine/arch/Arch.java @@ -1,7 +1,7 @@ package com.cowlark.fluxengine.arch; -import com.cowlark.fluxengine.arch.agat.AgatDecoder; import com.cowlark.fluxengine.arch.aeslanier.AesLanierDecoder; +import com.cowlark.fluxengine.arch.agat.AgatDecoder; import com.cowlark.fluxengine.arch.amiga.AmigaDecoder; import com.cowlark.fluxengine.arch.apple2.Apple2Decoder; import com.cowlark.fluxengine.arch.brother.BrotherDecoder; diff --git a/java/com/cowlark/fluxengine/arch/BUILD.bazel b/java/com/cowlark/fluxengine/arch/BUILD.bazel index cb890a81..fc447377 100644 --- a/java/com/cowlark/fluxengine/arch/BUILD.bazel +++ b/java/com/cowlark/fluxengine/arch/BUILD.bazel @@ -18,7 +18,10 @@ java_proto_library( java_library( name = "arch", - srcs = glob(["*.java", "*/*.java"]), + srcs = glob([ + "*.java", + "*/*.java", + ]), deps = [ ":arch_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java b/java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java index 47e8d0e3..9d3ef99e 100644 --- a/java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java +++ b/java/com/cowlark/fluxengine/arch/agat/AgatDecoder.java @@ -1,6 +1,5 @@ package com.cowlark.fluxengine.arch.agat; -import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.data.FluxMatchers; import com.cowlark.fluxengine.data.FluxPattern; @@ -16,11 +15,15 @@ public class AgatDecoder extends Decoder { /* - * data: X X X X X X X X X - - X - X - X - X X - X - X - = 0xff956a - * flux: 01 01 01 01 01 01 01 01 01 00 10 01 00 01 00 01 00 01 01 00 01 00 01 00 = 0x555549111444 + * data: X X X X X X X X X - - X - X - X - X X - X - X - = + * 0xff956a + * flux: 01 01 01 01 01 01 01 01 01 00 10 01 00 01 00 01 00 01 01 00 01 00 01 00 = + * 0x555549111444 * - * data: X X X X X X X X - X X - X - X - X - - X - X - X = 0xff6a95 - * flux: 01 01 01 01 01 01 01 01 00 01 01 00 01 00 01 00 01 00 10 01 00 01 00 01 = 0x555514444911 + * data: X X X X X X X X - X X - X - X - X - - X - X - X = + * 0xff6a95 + * flux: 01 01 01 01 01 01 01 01 00 01 01 00 01 00 01 00 01 00 10 01 00 01 00 01 = + * 0x555514444911 * * Each pattern is prefixed with this one: * diff --git a/java/com/cowlark/fluxengine/arch/amiga/Amiga.java b/java/com/cowlark/fluxengine/arch/amiga/Amiga.java index d1afcfa4..46bebd3b 100644 --- a/java/com/cowlark/fluxengine/arch/amiga/Amiga.java +++ b/java/com/cowlark/fluxengine/arch/amiga/Amiga.java @@ -100,12 +100,9 @@ public static Bytes amigaDeinterleave(Bytes input, int[] index, int len) * http://graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN */ long result = - ((((e * 0x0101010101010101L) & 0x8040201008040201L) * - 0x0102040810204081L >>> 49) & - 0x5555) | - ((((o * 0x0101010101010101L) & 0x8040201008040201L) * - 0x0102040810204081L >>> 48) & - 0xAAAA); + ((((e * 0x0101010101010101L) & 0x8040201008040201L) * 0x0102040810204081L >>> + 49) & 0x5555) | ((((o * 0x0101010101010101L) & 0x8040201008040201L) * + 0x0102040810204081L >>> 48) & 0xAAAA); bw.writeBe16((int) result); } diff --git a/java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java b/java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java index 0a513bd6..f9250b54 100644 --- a/java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java +++ b/java/com/cowlark/fluxengine/arch/amiga/AmigaDecoder.java @@ -66,11 +66,10 @@ protected void decodeSectorRecord() int gotdatachecksum = Amiga.amigaChecksum(rawbytes.slice(56, 1024)); Bytes data = new Bytes(); - data.writer() - .write(Amiga.amigaDeinterleave(bytes, index, 512)) - .write(recoveryinfo); + data.writer().write(Amiga.amigaDeinterleave(bytes, index, 512)).write(recoveryinfo); sector.data = data; - sector.status = - (gotdatachecksum == wanteddatachecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = (gotdatachecksum == wanteddatachecksum) ? + Sector.Status.OK : + Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java b/java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java index 131ec0d2..4043779c 100644 --- a/java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java +++ b/java/com/cowlark/fluxengine/arch/apple2/Apple2Decoder.java @@ -22,76 +22,148 @@ public class Apple2Decoder extends Decoder new FluxPattern(24, Apple2.APPLE2_DATA_RECORD); private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + private final Apple2DecoderProto config; + + public Apple2Decoder(DecoderProto config) + { + super(config); + this.config = config.getApple2(); + } private static int decodeDataGcr(int gcr) { switch (gcr) { - case 0x96: return 0x00; - case 0x97: return 0x01; - case 0x9a: return 0x02; - case 0x9b: return 0x03; - case 0x9d: return 0x04; - case 0x9e: return 0x05; - case 0x9f: return 0x06; - case 0xa6: return 0x07; - case 0xa7: return 0x08; - case 0xab: return 0x09; - case 0xac: return 0x0a; - case 0xad: return 0x0b; - case 0xae: return 0x0c; - case 0xaf: return 0x0d; - case 0xb2: return 0x0e; - case 0xb3: return 0x0f; - case 0xb4: return 0x10; - case 0xb5: return 0x11; - case 0xb6: return 0x12; - case 0xb7: return 0x13; - case 0xb9: return 0x14; - case 0xba: return 0x15; - case 0xbb: return 0x16; - case 0xbc: return 0x17; - case 0xbd: return 0x18; - case 0xbe: return 0x19; - case 0xbf: return 0x1a; - case 0xcb: return 0x1b; - case 0xcd: return 0x1c; - case 0xce: return 0x1d; - case 0xcf: return 0x1e; - case 0xd3: return 0x1f; - case 0xd6: return 0x20; - case 0xd7: return 0x21; - case 0xd9: return 0x22; - case 0xda: return 0x23; - case 0xdb: return 0x24; - case 0xdc: return 0x25; - case 0xdd: return 0x26; - case 0xde: return 0x27; - case 0xdf: return 0x28; - case 0xe5: return 0x29; - case 0xe6: return 0x2a; - case 0xe7: return 0x2b; - case 0xe9: return 0x2c; - case 0xea: return 0x2d; - case 0xeb: return 0x2e; - case 0xec: return 0x2f; - case 0xed: return 0x30; - case 0xee: return 0x31; - case 0xef: return 0x32; - case 0xf2: return 0x33; - case 0xf3: return 0x34; - case 0xf4: return 0x35; - case 0xf5: return 0x36; - case 0xf6: return 0x37; - case 0xf7: return 0x38; - case 0xf9: return 0x39; - case 0xfa: return 0x3a; - case 0xfb: return 0x3b; - case 0xfc: return 0x3c; - case 0xfd: return 0x3d; - case 0xfe: return 0x3e; - case 0xff: return 0x3f; - default: return -1; + case 0x96: + return 0x00; + case 0x97: + return 0x01; + case 0x9a: + return 0x02; + case 0x9b: + return 0x03; + case 0x9d: + return 0x04; + case 0x9e: + return 0x05; + case 0x9f: + return 0x06; + case 0xa6: + return 0x07; + case 0xa7: + return 0x08; + case 0xab: + return 0x09; + case 0xac: + return 0x0a; + case 0xad: + return 0x0b; + case 0xae: + return 0x0c; + case 0xaf: + return 0x0d; + case 0xb2: + return 0x0e; + case 0xb3: + return 0x0f; + case 0xb4: + return 0x10; + case 0xb5: + return 0x11; + case 0xb6: + return 0x12; + case 0xb7: + return 0x13; + case 0xb9: + return 0x14; + case 0xba: + return 0x15; + case 0xbb: + return 0x16; + case 0xbc: + return 0x17; + case 0xbd: + return 0x18; + case 0xbe: + return 0x19; + case 0xbf: + return 0x1a; + case 0xcb: + return 0x1b; + case 0xcd: + return 0x1c; + case 0xce: + return 0x1d; + case 0xcf: + return 0x1e; + case 0xd3: + return 0x1f; + case 0xd6: + return 0x20; + case 0xd7: + return 0x21; + case 0xd9: + return 0x22; + case 0xda: + return 0x23; + case 0xdb: + return 0x24; + case 0xdc: + return 0x25; + case 0xdd: + return 0x26; + case 0xde: + return 0x27; + case 0xdf: + return 0x28; + case 0xe5: + return 0x29; + case 0xe6: + return 0x2a; + case 0xe7: + return 0x2b; + case 0xe9: + return 0x2c; + case 0xea: + return 0x2d; + case 0xeb: + return 0x2e; + case 0xec: + return 0x2f; + case 0xed: + return 0x30; + case 0xee: + return 0x31; + case 0xef: + return 0x32; + case 0xf2: + return 0x33; + case 0xf3: + return 0x34; + case 0xf4: + return 0x35; + case 0xf5: + return 0x36; + case 0xf6: + return 0x37; + case 0xf7: + return 0x38; + case 0xf9: + return 0x39; + case 0xfa: + return 0x3a; + case 0xfb: + return 0x3b; + case 0xfc: + return 0x3c; + case 0xfd: + return 0x3d; + case 0xfe: + return 0x3e; + case 0xff: + return 0x3f; + default: + return -1; } } @@ -102,7 +174,8 @@ private static int combine(int word) /* This is extremely inspired by the MESS implementation, written by Nathan * Woods and R. Belmont: - * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp + * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib + * /formats/ap2_dsk.cpp */ private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) { @@ -121,26 +194,23 @@ private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) { /* 3 * 2 bit */ output.setByte(i, (byte) (((checksum >> 1) & 0x01) | ((checksum << 1) & 0x02))); - output.setByte(i + 86, (byte) (((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02))); + output.setByte( + i + 86, + (byte) (((checksum >> 3) & 0x01) | ((checksum >> 1) & 0x02))); if ((i + 172) < Apple2.APPLE2_SECTOR_LENGTH) - output.setByte(i + 172, (byte) (((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02))); + output.setByte( + i + 172, + (byte) (((checksum >> 5) & 0x01) | ((checksum >> 3) & 0x02))); } } checksum &= 0x3f; - int wantedchecksum = decodeDataGcr(input.getByte(Apple2.APPLE2_ENCODED_SECTOR_LENGTH) & 0xff); + int wantedchecksum = + decodeDataGcr(input.getByte(Apple2.APPLE2_ENCODED_SECTOR_LENGTH) & 0xff); status[0] = (checksum == wantedchecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; return output; } - private final Apple2DecoderProto config; - - public Apple2Decoder(DecoderProto config) - { - super(config); - this.config = config.getApple2(); - } - @Override protected double advanceToNextRecord() { diff --git a/java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java b/java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java index 6a465075..e81281d8 100644 --- a/java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java +++ b/java/com/cowlark/fluxengine/arch/brother/BrotherDecoder.java @@ -1,9 +1,9 @@ package com.cowlark.fluxengine.arch.brother; -import com.cowlark.fluxengine.core.ByteReader; -import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.BitWriter; import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.data.FluxMatchers; import com.cowlark.fluxengine.data.FluxPattern; @@ -38,43 +38,81 @@ public class BrotherDecoder extends Decoder * dubious and Brother track 0 shows up on my machine at track 2. */ + public BrotherDecoder(DecoderProto config) + { + super(config); + } + private static int decodeDataGcr(int gcr) { switch (gcr) { - case 0x55: return 0; - case 0x57: return 1; - case 0x5b: return 2; - case 0x5d: return 3; - case 0x5f: return 4; - case 0x6b: return 5; - case 0x6d: return 6; - case 0x6f: return 7; - case 0x75: return 8; - case 0x77: return 9; - case 0x7b: return 10; - case 0x7d: return 11; - case 0x7f: return 12; - case 0xab: return 13; - case 0xad: return 14; - case 0xaf: return 15; - case 0xb5: return 16; - case 0xb7: return 17; - case 0xbb: return 18; - case 0xbd: return 19; - case 0xbf: return 20; - case 0xd5: return 21; - case 0xd7: return 22; - case 0xdb: return 23; - case 0xdd: return 24; - case 0xdf: return 25; - case 0xeb: return 26; - case 0xed: return 27; - case 0xef: return 28; - case 0xf5: return 29; - case 0xf7: return 30; - case 0xfb: return 31; - default: return -1; + case 0x55: + return 0; + case 0x57: + return 1; + case 0x5b: + return 2; + case 0x5d: + return 3; + case 0x5f: + return 4; + case 0x6b: + return 5; + case 0x6d: + return 6; + case 0x6f: + return 7; + case 0x75: + return 8; + case 0x77: + return 9; + case 0x7b: + return 10; + case 0x7d: + return 11; + case 0x7f: + return 12; + case 0xab: + return 13; + case 0xad: + return 14; + case 0xaf: + return 15; + case 0xb5: + return 16; + case 0xb7: + return 17; + case 0xbb: + return 18; + case 0xbd: + return 19; + case 0xbf: + return 20; + case 0xd5: + return 21; + case 0xd7: + return 22; + case 0xdb: + return 23; + case 0xdd: + return 24; + case 0xdf: + return 25; + case 0xeb: + return 26; + case 0xed: + return 27; + case 0xef: + return 28; + case 0xf5: + return 29; + case 0xf7: + return 30; + case 0xfb: + return 31; + default: + return -1; } } @@ -82,93 +120,167 @@ private static int decodeHeaderGcr(int word) { switch (word) { - case 0xDFB5: return 0; - case 0x5B6F: return 1; - case 0x7DF7: return 2; - case 0xBFD5: return 3; - case 0xF57F: return 4; - case 0x6D5D: return 5; - case 0xAFEB: return 6; - case 0xDDB7: return 7; - case 0x5775: return 8; - case 0x7BFB: return 9; - case 0xBDD7: return 10; - case 0xEFAB: return 11; - case 0x6B5F: return 12; - case 0xADED: return 13; - case 0xDBBB: return 14; - case 0x5577: return 15; - case 0x77DB: return 16; - case 0xBBAD: return 17; - case 0xED6B: return 18; - case 0x5FEF: return 19; - case 0xABBD: return 20; - case 0xD77B: return 21; - case 0xFB57: return 22; - case 0x75DD: return 23; - case 0xB7AF: return 24; - case 0xEB6D: return 25; - case 0x5DF5: return 26; - case 0x7FBF: return 27; - case 0xD57D: return 28; - case 0xF75B: return 29; - case 0x6FDF: return 30; - case 0xB5B5: return 31; - case 0xDF6F: return 32; - case 0x5BF7: return 33; - case 0x7DD5: return 34; - case 0xBF7F: return 35; - case 0xF55D: return 36; - case 0x6DEB: return 37; - case 0xAFB7: return 38; - case 0xDD75: return 39; - case 0x57FB: return 40; - case 0x7BD7: return 41; - case 0xBDAB: return 42; - case 0xEF5F: return 43; - case 0x6BED: return 44; - case 0xADBB: return 45; - case 0xDB77: return 46; - case 0xBB55: return 47; - case 0xEDDB: return 48; - case 0x5FAD: return 49; - case 0xAB6B: return 50; - case 0xD7EF: return 51; - case 0xFBBD: return 52; - case 0x757B: return 53; - case 0xB757: return 54; - case 0xEBDD: return 55; - case 0x5DAF: return 56; - case 0x7F6D: return 57; - case 0xD5F5: return 58; - case 0xF7BF: return 59; - case 0x6F7D: return 60; - case 0xB55B: return 61; - case 0xDFDF: return 62; - case 0x5BB5: return 63; - case 0x7D6F: return 64; - case 0xBFF7: return 65; - case 0xF5D5: return 66; - case 0x6D7F: return 67; - case 0xAF5D: return 68; - case 0xDDEB: return 69; - case 0x57B7: return 70; - case 0x7B75: return 71; - case 0xBDFB: return 72; - case 0xEFD7: return 73; - case 0x6BAB: return 74; - case 0xAD5F: return 75; - case 0xDBED: return 76; - case 0x55BB: return 77; - default: return -1; + case 0xDFB5: + return 0; + case 0x5B6F: + return 1; + case 0x7DF7: + return 2; + case 0xBFD5: + return 3; + case 0xF57F: + return 4; + case 0x6D5D: + return 5; + case 0xAFEB: + return 6; + case 0xDDB7: + return 7; + case 0x5775: + return 8; + case 0x7BFB: + return 9; + case 0xBDD7: + return 10; + case 0xEFAB: + return 11; + case 0x6B5F: + return 12; + case 0xADED: + return 13; + case 0xDBBB: + return 14; + case 0x5577: + return 15; + case 0x77DB: + return 16; + case 0xBBAD: + return 17; + case 0xED6B: + return 18; + case 0x5FEF: + return 19; + case 0xABBD: + return 20; + case 0xD77B: + return 21; + case 0xFB57: + return 22; + case 0x75DD: + return 23; + case 0xB7AF: + return 24; + case 0xEB6D: + return 25; + case 0x5DF5: + return 26; + case 0x7FBF: + return 27; + case 0xD57D: + return 28; + case 0xF75B: + return 29; + case 0x6FDF: + return 30; + case 0xB5B5: + return 31; + case 0xDF6F: + return 32; + case 0x5BF7: + return 33; + case 0x7DD5: + return 34; + case 0xBF7F: + return 35; + case 0xF55D: + return 36; + case 0x6DEB: + return 37; + case 0xAFB7: + return 38; + case 0xDD75: + return 39; + case 0x57FB: + return 40; + case 0x7BD7: + return 41; + case 0xBDAB: + return 42; + case 0xEF5F: + return 43; + case 0x6BED: + return 44; + case 0xADBB: + return 45; + case 0xDB77: + return 46; + case 0xBB55: + return 47; + case 0xEDDB: + return 48; + case 0x5FAD: + return 49; + case 0xAB6B: + return 50; + case 0xD7EF: + return 51; + case 0xFBBD: + return 52; + case 0x757B: + return 53; + case 0xB757: + return 54; + case 0xEBDD: + return 55; + case 0x5DAF: + return 56; + case 0x7F6D: + return 57; + case 0xD5F5: + return 58; + case 0xF7BF: + return 59; + case 0x6F7D: + return 60; + case 0xB55B: + return 61; + case 0xDFDF: + return 62; + case 0x5BB5: + return 63; + case 0x7D6F: + return 64; + case 0xBFF7: + return 65; + case 0xF5D5: + return 66; + case 0x6D7F: + return 67; + case 0xAF5D: + return 68; + case 0xDDEB: + return 69; + case 0x57B7: + return 70; + case 0x7B75: + return 71; + case 0xBDFB: + return 72; + case 0xEFD7: + return 73; + case 0x6BAB: + return 74; + case 0xAD5F: + return 75; + case 0xDBED: + return 76; + case 0x55BB: + return 77; + default: + return -1; } } - public BrotherDecoder(DecoderProto config) - { - super(config); - } - @Override protected double advanceToNextRecord() { diff --git a/java/com/cowlark/fluxengine/arch/c64/C64.java b/java/com/cowlark/fluxengine/arch/c64/C64.java index c7049510..be7b36b0 100644 --- a/java/com/cowlark/fluxengine/arch/c64/C64.java +++ b/java/com/cowlark/fluxengine/arch/c64/C64.java @@ -2,15 +2,15 @@ /** * Constants for the Commodore 64 format, ported from arch/c64/c64.h. - * + *

* Source: http://www.unusedino.de/ec64/technical/formats/g64.html - * 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR) - * 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) - * 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read) - * 4. Data sync FF FF FF FF FF (40 'on' bits, not GCR) - * 5. Data block 55...4A (325 GCR bytes) - * 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read) - * 1. Header sync (SYNC for the next sector) + * 1. Header sync FF FF FF FF FF (40 'on' bits, not GCR) + * 2. Header info 52 54 B5 29 4B 7A 5E 95 55 55 (10 GCR bytes) + * 3. Header gap 55 55 55 55 55 55 55 55 55 (9 bytes, never read) + * 4. Data sync FF FF FF FF FF (40 'on' bits, not GCR) + * 5. Data block 55...4A (325 GCR bytes) + * 6. Inter-sector gap 55 55 55 55...55 55 (4 to 12 bytes, never read) + * 1. Header sync (SYNC for the next sector) */ public final class C64 { diff --git a/java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java b/java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java index 93960381..6831a2db 100644 --- a/java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java +++ b/java/com/cowlark/fluxengine/arch/c64/Commodore64Decoder.java @@ -17,32 +17,55 @@ */ public class Commodore64Decoder extends Decoder { - private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(20, C64.C64_SECTOR_RECORD); + private static final FluxPattern SECTOR_RECORD_PATTERN = + new FluxPattern(20, C64.C64_SECTOR_RECORD); private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(20, C64.C64_DATA_RECORD); private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + public Commodore64Decoder(DecoderProto config) + { + super(config); + } + private static int decodeDataGcr(int gcr) { switch (gcr) { - case 0x0a: return 0x0; - case 0x0b: return 0x1; - case 0x12: return 0x2; - case 0x13: return 0x3; - case 0x0e: return 0x4; - case 0x0f: return 0x5; - case 0x16: return 0x6; - case 0x17: return 0x7; - case 0x09: return 0x8; - case 0x19: return 0x9; - case 0x1a: return 0xa; - case 0x1b: return 0xb; - case 0x0d: return 0xc; - case 0x1d: return 0xd; - case 0x1e: return 0xe; - case 0x15: return 0xf; - default: return -1; + case 0x0a: + return 0x0; + case 0x0b: + return 0x1; + case 0x12: + return 0x2; + case 0x13: + return 0x3; + case 0x0e: + return 0x4; + case 0x0f: + return 0x5; + case 0x16: + return 0x6; + case 0x17: + return 0x7; + case 0x09: + return 0x8; + case 0x19: + return 0x9; + case 0x1a: + return 0xa; + case 0x1b: + return 0xb; + case 0x0d: + return 0xc; + case 0x1d: + return 0xd; + case 0x1e: + return 0xe; + case 0x15: + return 0xf; + default: + return -1; } } @@ -70,11 +93,6 @@ private static Bytes decode(Bits bits) return output; } - public Commodore64Decoder(DecoderProto config) - { - super(config); - } - @Override protected double advanceToNextRecord() { @@ -111,6 +129,7 @@ protected void decodeDataRecord() sector.data = bytes.slice(0, C64.C64_SECTOR_LENGTH); int gotChecksum = Crc.xorBytes(sector.data); int wantChecksum = bytes.getByte(256) & 0xff; - sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java b/java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java index 6d751ee9..dd0f648d 100644 --- a/java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java +++ b/java/com/cowlark/fluxengine/arch/f85/DurangoF85Decoder.java @@ -18,32 +18,55 @@ */ public class DurangoF85Decoder extends Decoder { - private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(24, F85.F85_SECTOR_RECORD); + private static final FluxPattern SECTOR_RECORD_PATTERN = + new FluxPattern(24, F85.F85_SECTOR_RECORD); private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(24, F85.F85_DATA_RECORD); private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + public DurangoF85Decoder(DecoderProto config) + { + super(config); + } + private static int decodeDataGcr(int gcr) { switch (gcr) { - case 0x19: return 0x00; - case 0x1b: return 0x01; - case 0x12: return 0x02; - case 0x13: return 0x03; - case 0x1d: return 0x04; - case 0x15: return 0x05; - case 0x16: return 0x06; - case 0x17: return 0x07; - case 0x1a: return 0x08; - case 0x09: return 0x09; - case 0x0a: return 0x0a; - case 0x0b: return 0x0b; - case 0x1e: return 0x0c; - case 0x0d: return 0x0d; - case 0x0e: return 0x0e; - case 0x0f: return 0x0f; - default: return -1; + case 0x19: + return 0x00; + case 0x1b: + return 0x01; + case 0x12: + return 0x02; + case 0x13: + return 0x03; + case 0x1d: + return 0x04; + case 0x15: + return 0x05; + case 0x16: + return 0x06; + case 0x17: + return 0x07; + case 0x1a: + return 0x08; + case 0x09: + return 0x09; + case 0x0a: + return 0x0a; + case 0x0b: + return 0x0b; + case 0x1e: + return 0x0c; + case 0x0d: + return 0x0d; + case 0x0e: + return 0x0e; + case 0x0f: + return 0x0f; + default: + return -1; } } @@ -71,11 +94,6 @@ private static Bytes decode(Bits bits) return output; } - public DurangoF85Decoder(DecoderProto config) - { - super(config); - } - @Override protected double advanceToNextRecord() { @@ -113,13 +131,15 @@ protected void decodeDataRecord() if (readRaw24() != F85.F85_DATA_RECORD) return; - Bytes bytes = decode(readRawBits((F85.F85_SECTOR_LENGTH + 3) * 10)) - .slice(0, F85.F85_SECTOR_LENGTH + 3); + Bytes bytes = decode(readRawBits((F85.F85_SECTOR_LENGTH + 3) * 10)).slice( + 0, + F85.F85_SECTOR_LENGTH + 3); ByteReader br = bytes.iterator(); sector.data = br.read(F85.F85_SECTOR_LENGTH); int wantChecksum = br.readBe16(); int gotChecksum = Crc.crc16(Crc.CCITT_POLY, 0xbf84, sector.data); - sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java b/java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java index e5ce2e18..d977fee6 100644 --- a/java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java +++ b/java/com/cowlark/fluxengine/arch/fb100/Fb100Decoder.java @@ -17,6 +17,11 @@ public class Fb100Decoder extends Decoder { private static final FluxPattern SECTOR_ID_PATTERN = new FluxPattern(16, 0xabaa); + public Fb100Decoder(DecoderProto config) + { + super(config); + } + /* * Reverse engineered from a dump of the floppy drive's ROM. I have no idea * how it works. @@ -100,11 +105,6 @@ private static int checksum(Bytes bytes) return (crchi << 8) | crclo; } - public Fb100Decoder(DecoderProto config) - { - super(config); - } - @Override protected double advanceToNextRecord() { diff --git a/java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java b/java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java index aac7684b..b1944f14 100644 --- a/java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java +++ b/java/com/cowlark/fluxengine/arch/ibm/IbmDecoder.java @@ -1,8 +1,8 @@ package com.cowlark.fluxengine.arch.ibm; +import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.core.ByteReader; import com.cowlark.fluxengine.core.ByteWriter; -import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.data.FluxMatchers; import com.cowlark.fluxengine.data.FluxPattern; @@ -105,7 +105,8 @@ public IbmDecoder(DecoderProto config) private IbmDecoderProto.TrackdataProto getTrackFormat(int track, int head) { - IbmDecoderProto.TrackdataProto.Builder builder = IbmDecoderProto.TrackdataProto.newBuilder(); + IbmDecoderProto.TrackdataProto.Builder builder = + IbmDecoderProto.TrackdataProto.newBuilder(); for (IbmDecoderProto.TrackdataProto f : config.getTrackdataList()) { if (f.hasTrack() && (f.getTrack() != track)) @@ -204,8 +205,8 @@ protected void decodeDataRecord() readByte(bw); id = readByte(bw); } - if ((id != Ibm.IBM_DAM1) && (id != Ibm.IBM_DAM2) && - (id != Ibm.IBM_TRS80DAM1) && (id != Ibm.IBM_TRS80DAM2)) + if ((id != Ibm.IBM_DAM1) && (id != Ibm.IBM_DAM2) && (id != Ibm.IBM_TRS80DAM1) && + (id != Ibm.IBM_TRS80DAM2)) return; ByteReader br = bytes.iterator(); @@ -221,7 +222,8 @@ protected void decodeDataRecord() if (currentSectorSize != ltl.sectorSize) System.err.printf( - "Warning: configured sector size for t%d.h%d.s%d is %d bytes but that seen on disk is %d bytes%n", + "Warning: configured sector size for t%d.h%d.s%d is %d bytes but that seen on" + + " disk is %d bytes%n", sector.location.logicalCylinder(), sector.location.logicalHead(), sector.location.logicalSector(), diff --git a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java index 8a9219f0..fe7a99b2 100644 --- a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java +++ b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java @@ -1,6 +1,5 @@ package com.cowlark.fluxengine.arch.macintosh; -import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.core.ByteReader; import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; @@ -16,86 +15,159 @@ */ public class MacintoshDecoder extends Decoder { - private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(24, Macintosh.MAC_SECTOR_RECORD); - private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(24, Macintosh.MAC_DATA_RECORD); + private static final FluxPattern SECTOR_RECORD_PATTERN = + new FluxPattern(24, Macintosh.MAC_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = + new FluxPattern(24, Macintosh.MAC_DATA_RECORD); private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + public MacintoshDecoder(DecoderProto config) + { + super(config); + } + private static int decodeDataGcr(int gcr) { switch (gcr) { - case 0x96: return 0x00; - case 0x97: return 0x01; - case 0x9a: return 0x02; - case 0x9b: return 0x03; - case 0x9d: return 0x04; - case 0x9e: return 0x05; - case 0x9f: return 0x06; - case 0xa6: return 0x07; - case 0xa7: return 0x08; - case 0xab: return 0x09; - case 0xac: return 0x0a; - case 0xad: return 0x0b; - case 0xae: return 0x0c; - case 0xaf: return 0x0d; - case 0xb2: return 0x0e; - case 0xb3: return 0x0f; - case 0xb4: return 0x10; - case 0xb5: return 0x11; - case 0xb6: return 0x12; - case 0xb7: return 0x13; - case 0xb9: return 0x14; - case 0xba: return 0x15; - case 0xbb: return 0x16; - case 0xbc: return 0x17; - case 0xbd: return 0x18; - case 0xbe: return 0x19; - case 0xbf: return 0x1a; - case 0xcb: return 0x1b; - case 0xcd: return 0x1c; - case 0xce: return 0x1d; - case 0xcf: return 0x1e; - case 0xd3: return 0x1f; - case 0xd6: return 0x20; - case 0xd7: return 0x21; - case 0xd9: return 0x22; - case 0xda: return 0x23; - case 0xdb: return 0x24; - case 0xdc: return 0x25; - case 0xdd: return 0x26; - case 0xde: return 0x27; - case 0xdf: return 0x28; - case 0xe5: return 0x29; - case 0xe6: return 0x2a; - case 0xe7: return 0x2b; - case 0xe9: return 0x2c; - case 0xea: return 0x2d; - case 0xeb: return 0x2e; - case 0xec: return 0x2f; - case 0xed: return 0x30; - case 0xee: return 0x31; - case 0xef: return 0x32; - case 0xf2: return 0x33; - case 0xf3: return 0x34; - case 0xf4: return 0x35; - case 0xf5: return 0x36; - case 0xf6: return 0x37; - case 0xf7: return 0x38; - case 0xf9: return 0x39; - case 0xfa: return 0x3a; - case 0xfb: return 0x3b; - case 0xfc: return 0x3c; - case 0xfd: return 0x3d; - case 0xfe: return 0x3e; - case 0xff: return 0x3f; - default: return -1; + case 0x96: + return 0x00; + case 0x97: + return 0x01; + case 0x9a: + return 0x02; + case 0x9b: + return 0x03; + case 0x9d: + return 0x04; + case 0x9e: + return 0x05; + case 0x9f: + return 0x06; + case 0xa6: + return 0x07; + case 0xa7: + return 0x08; + case 0xab: + return 0x09; + case 0xac: + return 0x0a; + case 0xad: + return 0x0b; + case 0xae: + return 0x0c; + case 0xaf: + return 0x0d; + case 0xb2: + return 0x0e; + case 0xb3: + return 0x0f; + case 0xb4: + return 0x10; + case 0xb5: + return 0x11; + case 0xb6: + return 0x12; + case 0xb7: + return 0x13; + case 0xb9: + return 0x14; + case 0xba: + return 0x15; + case 0xbb: + return 0x16; + case 0xbc: + return 0x17; + case 0xbd: + return 0x18; + case 0xbe: + return 0x19; + case 0xbf: + return 0x1a; + case 0xcb: + return 0x1b; + case 0xcd: + return 0x1c; + case 0xce: + return 0x1d; + case 0xcf: + return 0x1e; + case 0xd3: + return 0x1f; + case 0xd6: + return 0x20; + case 0xd7: + return 0x21; + case 0xd9: + return 0x22; + case 0xda: + return 0x23; + case 0xdb: + return 0x24; + case 0xdc: + return 0x25; + case 0xdd: + return 0x26; + case 0xde: + return 0x27; + case 0xdf: + return 0x28; + case 0xe5: + return 0x29; + case 0xe6: + return 0x2a; + case 0xe7: + return 0x2b; + case 0xe9: + return 0x2c; + case 0xea: + return 0x2d; + case 0xeb: + return 0x2e; + case 0xec: + return 0x2f; + case 0xed: + return 0x30; + case 0xee: + return 0x31; + case 0xef: + return 0x32; + case 0xf2: + return 0x33; + case 0xf3: + return 0x34; + case 0xf4: + return 0x35; + case 0xf5: + return 0x36; + case 0xf6: + return 0x37; + case 0xf7: + return 0x38; + case 0xf9: + return 0x39; + case 0xfa: + return 0x3a; + case 0xfb: + return 0x3b; + case 0xfc: + return 0x3c; + case 0xfd: + return 0x3d; + case 0xfe: + return 0x3e; + case 0xff: + return 0x3f; + default: + return -1; } } /* This is extremely inspired by the MESS implementation, written by Nathan * Woods and R. Belmont: - * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp + * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib + * /formats/ap_dsk35.cpp */ private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) { @@ -189,11 +261,6 @@ private static int decodeSide(int side) return (side & 0x20) != 0 ? 1 : 0; } - public MacintoshDecoder(DecoderProto config) - { - super(config); - } - @Override protected double advanceToNextRecord() { @@ -240,8 +307,7 @@ protected void decodeDataRecord() /* Read data. */ readRawBits(8); /* skip spare byte */ - Bytes inputbuffer = readRawBits(Macintosh.MAC_ENCODED_SECTOR_LENGTH * 8) - .toBytes() + Bytes inputbuffer = readRawBits(Macintosh.MAC_ENCODED_SECTOR_LENGTH * 8).toBytes() .slice(0, Macintosh.MAC_ENCODED_SECTOR_LENGTH); for (int i = 0; i < inputbuffer.size(); i++) @@ -252,8 +318,6 @@ protected void decodeDataRecord() Bytes userData = decodeCrazyData(inputbuffer, status); sector.status = status[0]; sector.data = new Bytes(); - sector.data.writer() - .write(userData.slice(12, 512)) - .write(userData.slice(0, 12)); + sector.data.writer().write(userData.slice(12, 512)).write(userData.slice(0, 12)); } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java index 4395502e..e77502d7 100644 --- a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java +++ b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisDecoder.java @@ -25,7 +25,17 @@ public class MicropolisDecoder extends Decoder private static final FluxPattern SECTOR_SYNC_PATTERN = new FluxPattern(64, 0xAAAAAAAAAAAA5555L); /* Pattern to skip past current SYNC. */ - private static final FluxPattern SECTOR_ADVANCE_PATTERN = new FluxPattern(64, 0xAAAAAAAAAAAAAAAAL); + private static final FluxPattern SECTOR_ADVANCE_PATTERN = + new FluxPattern(64, 0xAAAAAAAAAAAAAAAAL); + private final MicropolisDecoderProto config; + private MicropolisDecoderProto.ChecksumType checksumType; + + public MicropolisDecoder(DecoderProto config) + { + super(config); + this.config = config.getMicropolis(); + checksumType = this.config.getChecksumType(); + } /* Standard Micropolis checksum. Adds all bytes, with carry. */ public static int micropolisChecksum(Bytes bytes) @@ -129,16 +139,6 @@ private static boolean vectorGraphicEccFix(Bytes bytes, int syndrome) return true; } - private final MicropolisDecoderProto config; - private MicropolisDecoderProto.ChecksumType checksumType; - - public MicropolisDecoder(DecoderProto config) - { - super(config); - this.config = config.getMicropolis(); - checksumType = this.config.getChecksumType(); - } - @Override protected double advanceToNextRecord() { @@ -187,8 +187,10 @@ protected double advanceToNextRecord() protected void decodeSectorRecord() { readRawBits(48); - com.cowlark.fluxengine.core.Bits rawbits = readRawBits(Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE * 16); - Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE); + com.cowlark.fluxengine.core.Bits rawbits = + readRawBits(Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE * 16); + Bytes bytes = + FmMfm.decodeFmMfm(rawbits).slice(0, Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE); boolean eccPresent = (bytes.getByte(274) & 0xff) == 0xaa; int ecc = 0; @@ -232,7 +234,8 @@ protected void decodeSectorRecord() { checksumType = MicropolisDecoderProto.ChecksumType.MICROPOLIS; } else if (wantChecksum == mzosChecksum(bytes.slice( - Micropolis.MICROPOLIS_HEADER_SIZE, Micropolis.MICROPOLIS_PAYLOAD_SIZE))) + Micropolis.MICROPOLIS_HEADER_SIZE, + Micropolis.MICROPOLIS_PAYLOAD_SIZE))) { checksumType = MicropolisDecoderProto.ChecksumType.MZOS; System.out.println("Note: MZOS checksum detected."); @@ -244,7 +247,8 @@ protected void decodeSectorRecord() if (checksumType == MicropolisDecoderProto.ChecksumType.MZOS) { gotChecksum = mzosChecksum(bytes.slice( - Micropolis.MICROPOLIS_HEADER_SIZE, Micropolis.MICROPOLIS_PAYLOAD_SIZE)); + Micropolis.MICROPOLIS_HEADER_SIZE, + Micropolis.MICROPOLIS_PAYLOAD_SIZE)); } else { gotChecksum = micropolisChecksum(bytes.slice(1, 2 + 266)); diff --git a/java/com/cowlark/fluxengine/arch/mx/MxDecoder.java b/java/com/cowlark/fluxengine/arch/mx/MxDecoder.java index afcd3699..a1d63020 100644 --- a/java/com/cowlark/fluxengine/arch/mx/MxDecoder.java +++ b/java/com/cowlark/fluxengine/arch/mx/MxDecoder.java @@ -81,7 +81,8 @@ protected void decodeSectorRecord() int logicalSector = currentSector; sector.location = new LogicalLocation(logicalCylinder, logicalHead, logicalSector); sector.data = bytes.slice(0, Mx.SECTOR_SIZE).swab(); - sector.status = (gotChecksum == wantChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (gotChecksum == wantChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; currentSector++; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/northstar/Northstar.java b/java/com/cowlark/fluxengine/arch/northstar/Northstar.java index 9325ac6c..777e012d 100644 --- a/java/com/cowlark/fluxengine/arch/northstar/Northstar.java +++ b/java/com/cowlark/fluxengine/arch/northstar/Northstar.java @@ -2,10 +2,10 @@ /** * Constants for the North Star format, ported from arch/northstar/northstar.h. - * + *

* Northstar floppies are 10-hard sectored disks with a sector format as * follows: - * + *

* |----------------------------------| * | SYNC Byte | Payload | Checksum | * |------------+----------+----------| diff --git a/java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java b/java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java index 599bccce..fa20797f 100644 --- a/java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java +++ b/java/com/cowlark/fluxengine/arch/northstar/NorthstarDecoder.java @@ -14,13 +14,13 @@ /** * Decoder for North Star 10-sector hard-sectored disks, ported from * arch/northstar/decoder.cc. - * + *

* Supports both single- and double-density. For the sector format and * checksum algorithm, see pp. 33 of the North Star Double Density Controller * manual: - * + *

* http://bitsavers.org/pdf/northstar/boards/Northstar_MDS-A-D_1978.pdf - * + *

* North Star disks do not contain any track/head/sector information encoded in * the sector record. For this reason, we have to be absolutely sure that the * hardSectorId is correct. @@ -52,6 +52,12 @@ public class NorthstarDecoder extends Decoder private static final FluxPattern FM_PATTERN = new FluxPattern(64, FM_ID); private static final FluxMatchers ANY_SECTOR_PATTERN = FluxMatchers.of(MFM_PATTERN, FM_PATTERN); + private int hardSectorId; + + public NorthstarDecoder(DecoderProto config) + { + super(config); + } /* Checksum is initially 0. For each data byte, XOR with the current * checksum. Rotate checksum left, carrying bit 7 to bit 0. */ @@ -69,13 +75,6 @@ public static int northstarChecksum(Bytes bytes) return checksum; } - private int hardSectorId; - - public NorthstarDecoder(DecoderProto config) - { - super(config); - } - /* Search for FM or MFM sector record. */ @Override protected double advanceToNextRecord() @@ -161,6 +160,7 @@ protected void decodeSectorRecord() sector.data = br.read(payloadSize); int wantChecksum = br.read8(); int gotChecksum = northstarChecksum(bytes.slice(headerSize - 1, payloadSize)); - sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java b/java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java index 203d8149..0e514079 100644 --- a/java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java +++ b/java/com/cowlark/fluxengine/arch/rolandd20/RolandD20Decoder.java @@ -33,20 +33,6 @@ public RolandD20Decoder(DecoderProto config) super(config); } - @Override - protected double advanceToNextRecord() - { - return seekToPattern(SECTOR_PATTERN); - } - - @Override - protected void decodeSectorRecord() - { - Bytes bytes = FmMfm.decodeFmMfm(readRawBits(256)); - System.out.printf("%.3f ", sector.clockNs); - hexdump(bytes); - } - private static void hexdump(Bytes buffer) { int pos = 0; @@ -78,4 +64,18 @@ private static void hexdump(Bytes buffer) pos += 16; } } + + @Override + protected double advanceToNextRecord() + { + return seekToPattern(SECTOR_PATTERN); + } + + @Override + protected void decodeSectorRecord() + { + Bytes bytes = FmMfm.decodeFmMfm(readRawBits(256)); + System.out.printf("%.3f ", sector.clockNs); + hexdump(bytes); + } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java b/java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java index 27b474e9..d511dd49 100644 --- a/java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java +++ b/java/com/cowlark/fluxengine/arch/smaky6/Smaky6Decoder.java @@ -20,14 +20,9 @@ public class Smaky6Decoder extends Decoder { private static final FluxPattern SECTOR_PATTERN = new FluxPattern(32, 0x54892aaa); - - private record SectorStart(int id, FluxPosition pos) - { - } - + private final List sectorStarts = new ArrayList<>(); private int sectorId; private int sectorIndex; - private final List sectorStarts = new ArrayList<>(); public Smaky6Decoder(DecoderProto config) { @@ -128,9 +123,7 @@ protected void decodeSectorRecord() /* The Smaky bytes are stored backwards! Backwards! */ - Bytes bytes = FmMfm.decodeFmMfm(rawbits) - .slice(0, Smaky6.SMAKY6_RECORD_SIZE) - .reverseBits(); + Bytes bytes = FmMfm.decodeFmMfm(rawbits).slice(0, Smaky6.SMAKY6_RECORD_SIZE).reverseBits(); ByteReader br = bytes.iterator(); int track = br.read8(); @@ -150,4 +143,8 @@ protected void decodeSectorRecord() sector.status = (wantedChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } + + private record SectorStart(int id, FluxPosition pos) + { + } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java b/java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java index dcc3769f..f4d9a8ab 100644 --- a/java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java +++ b/java/com/cowlark/fluxengine/arch/tartu/TartuDecoder.java @@ -20,7 +20,8 @@ public class TartuDecoder extends Decoder private static final FluxPattern HEADER_PATTERN = new FluxPattern(64, Tartu.HEADER_BITS); private static final FluxPattern DATA_PATTERN = new FluxPattern(64, Tartu.DATA_BITS); - private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(HEADER_PATTERN, DATA_PATTERN); + private static final FluxMatchers ANY_RECORD_PATTERN = + FluxMatchers.of(HEADER_PATTERN, DATA_PATTERN); public TartuDecoder(DecoderProto config) { @@ -70,6 +71,7 @@ protected void decodeDataRecord() int wantChecksum = bytes.iterator().seek(128).read8(); int gotChecksum = ~Crc.sumBytes(sector.data) & 0xff; - sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java b/java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java index efb3e38d..b55f440c 100644 --- a/java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java +++ b/java/com/cowlark/fluxengine/arch/tids990/Tids990Decoder.java @@ -101,6 +101,7 @@ protected void decodeDataRecord() sector.data = br.read(Tids990.TIDS990_PAYLOAD_SIZE); int wantChecksum = br.readBe16(); - sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java index a91f2da5..d69bc362 100644 --- a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java +++ b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kDecoder.java @@ -18,32 +18,56 @@ */ public class Victor9kDecoder extends Decoder { - private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(32, Victor9k.VICTOR9K_SECTOR_RECORD); - private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(32, Victor9k.VICTOR9K_DATA_RECORD); + private static final FluxPattern SECTOR_RECORD_PATTERN = + new FluxPattern(32, Victor9k.VICTOR9K_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = + new FluxPattern(32, Victor9k.VICTOR9K_DATA_RECORD); private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); + public Victor9kDecoder(DecoderProto config) + { + super(config); + } + private static int decodeDataGcr(int gcr) { switch (gcr) { - case 0x0a: return 0x0; - case 0x0b: return 0x1; - case 0x12: return 0x2; - case 0x13: return 0x3; - case 0x0e: return 0x4; - case 0x0f: return 0x5; - case 0x16: return 0x6; - case 0x17: return 0x7; - case 0x09: return 0x8; - case 0x19: return 0x9; - case 0x1a: return 0xa; - case 0x1b: return 0xb; - case 0x0d: return 0xc; - case 0x1d: return 0xd; - case 0x1e: return 0xe; - case 0x15: return 0xf; - default: return -1; + case 0x0a: + return 0x0; + case 0x0b: + return 0x1; + case 0x12: + return 0x2; + case 0x13: + return 0x3; + case 0x0e: + return 0x4; + case 0x0f: + return 0x5; + case 0x16: + return 0x6; + case 0x17: + return 0x7; + case 0x09: + return 0x8; + case 0x19: + return 0x9; + case 0x1a: + return 0xa; + case 0x1b: + return 0xb; + case 0x0d: + return 0xc; + case 0x1d: + return 0xd; + case 0x1e: + return 0xe; + case 0x15: + return 0xf; + default: + return -1; } } @@ -72,11 +96,6 @@ private static Bytes decode(Bits bits) return output; } - public Victor9kDecoder(DecoderProto config) - { - super(config); - } - @Override protected double advanceToNextRecord() { @@ -120,13 +139,15 @@ protected void decodeDataRecord() /* Read data. */ - Bytes bytes = decode(readRawBits((Victor9k.VICTOR9K_SECTOR_LENGTH + 4) * 10)) - .slice(0, Victor9k.VICTOR9K_SECTOR_LENGTH + 4); + Bytes bytes = decode(readRawBits((Victor9k.VICTOR9K_SECTOR_LENGTH + 4) * 10)).slice( + 0, + Victor9k.VICTOR9K_SECTOR_LENGTH + 4); ByteReader br = bytes.iterator(); sector.data = br.read(Victor9k.VICTOR9K_SECTOR_LENGTH); int gotChecksum = Crc.sumBytes(sector.data); int wantChecksum = br.readLe16(); - sector.status = (gotChecksum == wantChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (gotChecksum == wantChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java b/java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java index 4fcb26bb..6ce873cc 100644 --- a/java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java +++ b/java/com/cowlark/fluxengine/arch/zilogmcz/ZilogMczDecoder.java @@ -52,6 +52,7 @@ protected void decodeSectorRecord() int wantChecksum = br.readBe16(); int gotChecksum = Crc.crc16(Crc.MODBUS_POLY, 0x0000, bytes.slice(0, 134)); - sector.status = (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; + sector.status = + (wantChecksum == gotChecksum) ? Sector.Status.OK : Sector.Status.BAD_CHECKSUM; } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java index 1dd99f2b..be69223e 100644 --- a/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java +++ b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java @@ -17,7 +17,7 @@ * Reads a textpb file (with the {@code <<<}...{@code >>>} multiline string * extension) and writes out the binary representation of the encoded protobuf, * ported from scripts/protoencode.cc. - * + *

* Usage: ProtoEncode <input.textpb> <output.bin> * [<proto-class-fqn>] */ @@ -32,13 +32,11 @@ public static void main(String[] args) if (args.length < 2) { System.err.println( - "Usage: ProtoEncode []"); + "Usage: ProtoEncode " + "[]"); System.exit(1); } - String protoClass = args.length > 2 - ? args[2] - : "com.cowlark.fluxengine.config.ConfigProto"; + String protoClass = args.length > 2 ? args[2] : "com.cowlark.fluxengine.config.ConfigProto"; try { @@ -133,7 +131,7 @@ private static Message.Builder newBuilder(String protoClass) Method method = clazz.getMethod("newBuilder"); return (Message.Builder) method.invoke(null); } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | - InvocationTargetException | ClassCastException e) + InvocationTargetException | ClassCastException e) { throw new FluxEngineException("cannot create builder for " + protoClass + ": " + e); } diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index fa69b09c..148530a2 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -63,6 +63,14 @@ public static Bytes of(int... values) return new Bytes(data); } + private static int reverseBits(int b) + { + b = ((b & 0xF0) >> 4) | ((b & 0x0F) << 4); + b = ((b & 0xCC) >> 2) | ((b & 0x33) << 2); + b = ((b & 0xAA) >> 1) | ((b & 0x55) << 1); + return b; + } + public int size() { return high - low; @@ -117,8 +125,7 @@ public void writeToFile(String filename) { try { - java.nio.file.Files.write( - java.nio.file.Path.of(filename), toByteArray()); + java.nio.file.Files.write(java.nio.file.Path.of(filename), toByteArray()); } catch (IOException e) { throw new FluxEngineException( @@ -225,14 +232,6 @@ public Bytes reverseBits() return output; } - private static int reverseBits(int b) - { - b = ((b & 0xF0) >> 4) | ((b & 0x0F) << 4); - b = ((b & 0xCC) >> 2) | ((b & 0x33) << 2); - b = ((b & 0xAA) >> 1) | ((b & 0x55) << 1); - return b; - } - /* Extracts the bytes as bits, MSB-first within each byte. */ public Bits toBits() { diff --git a/java/com/cowlark/fluxengine/core/LogMessage.java b/java/com/cowlark/fluxengine/core/LogMessage.java new file mode 100644 index 00000000..25feafeb --- /dev/null +++ b/java/com/cowlark/fluxengine/core/LogMessage.java @@ -0,0 +1,131 @@ +package com.cowlark.fluxengine.core; + +/** + * A log message, ported from lib/core/logger.h. This is a marker interface; + * each message type renders itself to a string via {@link #render()}. + */ +public interface LogMessage +{ + /* Fallback rendering of this message to a string. */ + String render(); + + record StringMessage(String message) implements LogMessage + { + @Override + public String render() + { + return message; + } + } + + record ErrorLogMessage(String message) implements LogMessage + { + @Override + public String render() + { + return message; + } + } + + record EmergencyStopMessage() implements LogMessage + { + @Override + public String render() + { + return ""; + } + } + + record BeginSpeedOperationLogMessage() implements LogMessage + { + @Override + public String render() + { + return "Measuring rotational speed..."; + } + } + + record EndSpeedOperationLogMessage(double rotationalPeriodNs) implements LogMessage + { + @Override + public String render() + { + return String.format( + "Rotational period is %.1fms (%.1frpm)", + rotationalPeriodNs / 1e6, + 60e9 / rotationalPeriodNs); + } + } + + record BeginReadOperationLogMessage(int track, int head) implements LogMessage + { + @Override + public String render() + { + return String.format("R%2d.%d", track, head); + } + } + + record EndReadOperationLogMessage() implements LogMessage + { + @Override + public String render() + { + return ""; + } + } + + record BeginWriteOperationLogMessage(int track, int head) implements LogMessage + { + @Override + public String render() + { + return String.format("W%2d.%d", track, head); + } + } + + record EndWriteOperationLogMessage() implements LogMessage + { + @Override + public String render() + { + return ""; + } + } + + record BeginOperationLogMessage(String message) implements LogMessage + { + @Override + public String render() + { + return message; + } + } + + record EndOperationLogMessage(String message) implements LogMessage + { + @Override + public String render() + { + return message; + } + } + + record OperationProgressLogMessage(int progress) implements LogMessage + { + @Override + public String render() + { + return ""; + } + } + + record OptionLogMessage(String message) implements LogMessage + { + @Override + public String render() + { + return message; + } + } +} diff --git a/java/com/cowlark/fluxengine/core/LogRenderer.java b/java/com/cowlark/fluxengine/core/LogRenderer.java new file mode 100644 index 00000000..f506fd68 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/LogRenderer.java @@ -0,0 +1,135 @@ +package com.cowlark.fluxengine.core; + +import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.BeginSpeedOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.BeginWriteOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.EmergencyStopMessage; +import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.ErrorLogMessage; +import com.cowlark.fluxengine.core.LogMessage.OptionLogMessage; +import java.io.PrintStream; + +/** + * Renders log messages to a stream, ported from lib/core/logrenderer.cc. + */ +public abstract class LogRenderer +{ + public static LogRenderer create(PrintStream stream) + { + return new LogRendererImpl(stream); + } + + public LogRenderer add(LogMessage message) + { + return switch (message) + { + case ErrorLogMessage msg -> newline().add("Error:").add(msg.render()).newline(); + case EmergencyStopMessage msg -> newline().add("Stop!").newline(); + case BeginSpeedOperationLogMessage msg -> newline().add(msg.render()).newline(); + case EndSpeedOperationLogMessage msg -> newline().add(msg.render()).newline(); + case BeginReadOperationLogMessage msg -> + header(String.format("R%2d.%d: ", msg.track(), msg.head())); + case BeginWriteOperationLogMessage msg -> + header(String.format("W%2d.%d: ", msg.track(), msg.head())); + case OptionLogMessage msg -> newline().add("OPTION:").add(msg.render()).newline(); + default -> newline().add(message.render()).newline(); + }; + } + + public abstract LogRenderer add(String message); + + public abstract LogRenderer comma(); + + public abstract LogRenderer header(String message); + + public abstract LogRenderer newline(); + + private static class LogRendererImpl extends LogRenderer + { + private final PrintStream stream; + private boolean header = false; + private boolean newline = false; + private boolean space = false; + private int lineLen = 0; + + LogRendererImpl(PrintStream stream) + { + this.stream = stream; + } + + private void indent() + { + stream.print(" "); + lineLen = 7; + space = true; + } + + @Override + public LogRenderer add(String message) + { + if (newline && !header) + indent(); + + if (!space) + { + stream.print(' '); + lineLen++; + } + + newline = false; + header = false; + + lineLen += message.length(); + if (lineLen >= 80) + { + stream.println(); + indent(); + } + stream.print(message); + space = !message.isEmpty() && + Character.isWhitespace(message.charAt(message.length() - 1)); + return this; + } + + @Override + public LogRenderer header(String message) + { + if (!newline) + stream.println(); + stream.print(message); + lineLen = message.length(); + header = true; + newline = true; + space = !message.isEmpty() && + Character.isWhitespace(message.charAt(message.length() - 1)); + return this; + } + + @Override + public LogRenderer comma() + { + if (!newline || header) + { + stream.print(';'); + space = false; + } + return this; + } + + @Override + public LogRenderer newline() + { + if (!header) + { + if (!newline) + stream.println(); + + lineLen = 0; + header = false; + newline = true; + space = true; + } + return this; + } + } +} diff --git a/java/com/cowlark/fluxengine/core/Logger.java b/java/com/cowlark/fluxengine/core/Logger.java new file mode 100644 index 00000000..ee8512fc --- /dev/null +++ b/java/com/cowlark/fluxengine/core/Logger.java @@ -0,0 +1,32 @@ +package com.cowlark.fluxengine.core; + +import com.cowlark.fluxengine.core.LogMessage.StringMessage; +import java.util.function.Consumer; + +/** + * The logger, ported from lib/core/logger.{h,cc}. + */ +public final class Logger +{ + private static Consumer loggerImpl = + message -> LogRenderer.create(System.out).add(message); + + private Logger() + { + } + + public static void log(String message) + { + log(new StringMessage(message)); + } + + public static void log(LogMessage message) + { + loggerImpl.accept(message); + } + + public static void setLogger(Consumer callback) + { + loggerImpl = callback; + } +} diff --git a/java/com/cowlark/fluxengine/data/BUILD.bazel b/java/com/cowlark/fluxengine/data/BUILD.bazel index 2e67b666..3292c9c6 100644 --- a/java/com/cowlark/fluxengine/data/BUILD.bazel +++ b/java/com/cowlark/fluxengine/data/BUILD.bazel @@ -12,9 +12,9 @@ java_plugin( java_library( name = "data", srcs = glob(["*.java"]), - resources = ["//src/formats:formats_files"], - resource_strip_prefix = "src/formats", plugins = [":lombok_plugin"], + resource_strip_prefix = "src/formats", + resources = ["//src/formats:formats_files"], deps = [ "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/config:layout_java_proto", diff --git a/java/com/cowlark/fluxengine/data/FluxMatchers.java b/java/com/cowlark/fluxengine/data/FluxMatchers.java index bbcb483c..1bda250a 100644 --- a/java/com/cowlark/fluxengine/data/FluxMatchers.java +++ b/java/com/cowlark/fluxengine/data/FluxMatchers.java @@ -15,10 +15,7 @@ public class FluxMatchers implements FluxMatcher public FluxMatchers(List matchers) { this.matchers = matchers; - intervalCount = matchers.stream() - .mapToInt(FluxMatcher::intervals) - .max() - .orElse(0); + intervalCount = matchers.stream().mapToInt(FluxMatcher::intervals).max().orElse(0); } public static FluxMatchers of(FluxMatcher... matchers) @@ -27,8 +24,10 @@ public static FluxMatchers of(FluxMatcher... matchers) } @Override - public boolean matches(long[] candidates, int endIndex, double clockDecodeThreshold, - FluxMatch match) + public boolean matches(long[] candidates, + int endIndex, + double clockDecodeThreshold, + FluxMatch match) { for (FluxMatcher matcher : matchers) { diff --git a/java/com/cowlark/fluxengine/data/FluxPattern.java b/java/com/cowlark/fluxengine/data/FluxPattern.java index 0f125f97..d1e2254e 100644 --- a/java/com/cowlark/fluxengine/data/FluxPattern.java +++ b/java/com/cowlark/fluxengine/data/FluxPattern.java @@ -73,9 +73,10 @@ private static int findLowestSetBit(long value) @Override /* The `endIndex` is one past the newest candidate interval, mirroring the - * C++ pointer passed as `&*candidates.end()`. */ - public boolean matches(long[] candidates, int endIndex, double clockDecodeThreshold, - FluxMatch match) + * C++ pointer passed as `&*candidates.end()`. */ public boolean matches(long[] candidates, + int endIndex, + double clockDecodeThreshold, + FluxMatch match) { int start = endIndex - intervals.size(); diff --git a/java/com/cowlark/fluxengine/data/Formats.java b/java/com/cowlark/fluxengine/data/Formats.java index ac865633..bd055902 100644 --- a/java/com/cowlark/fluxengine/data/Formats.java +++ b/java/com/cowlark/fluxengine/data/Formats.java @@ -83,7 +83,8 @@ private static List scanNames() contents = new String(stream.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException e) { - throw new FluxEngineException("cannot read format resource " + NAMES_RESOURCE + ": " + e); + throw new FluxEngineException( + "cannot read format resource " + NAMES_RESOURCE + ": " + e); } List names = new ArrayList<>(); diff --git a/java/com/cowlark/fluxengine/data/Image.java b/java/com/cowlark/fluxengine/data/Image.java index f160c506..5077a8cc 100644 --- a/java/com/cowlark/fluxengine/data/Image.java +++ b/java/com/cowlark/fluxengine/data/Image.java @@ -35,10 +35,9 @@ public void calculateSize() Sector sector = entry.getValue(); if (sector != null) { - geometry.numCylinders = Math.max( - geometry.numCylinders, sector.location.logicalCylinder() + 1); - geometry.numHeads = - Math.max(geometry.numHeads, sector.location.logicalHead() + 1); + geometry.numCylinders = + Math.max(geometry.numCylinders, sector.location.logicalCylinder() + 1); + geometry.numHeads = Math.max(geometry.numHeads, sector.location.logicalHead() + 1); geometry.firstSector = Math.min(geometry.firstSector, sector.location.logicalSector()); maxSector = Math.max(maxSector, sector.location.logicalSector()); @@ -144,22 +143,21 @@ public void populateSectorPhysicalLocationsFromLogicalLocations(DiskLayout diskL newSector.dataEndTimeNs = sector.dataEndTimeNs; newSector.data = sector.data; newSector.records = sector.records; - newSector.physicalLocation = - new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); + newSector.physicalLocation = new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); } for (Sector sector : tempImage) sectors.put(sector.location, sector); } - public void setGeometry(Geometry geometry) + public Geometry getGeometry() { - this.geometry = geometry; + return geometry; } - public Geometry getGeometry() + public void setGeometry(Geometry geometry) { - return geometry; + this.geometry = geometry; } @Override diff --git a/java/com/cowlark/fluxengine/data/Record.java b/java/com/cowlark/fluxengine/data/Record.java index 714ee27f..e00a7f23 100644 --- a/java/com/cowlark/fluxengine/data/Record.java +++ b/java/com/cowlark/fluxengine/data/Record.java @@ -1,8 +1,6 @@ package com.cowlark.fluxengine.data; import com.cowlark.fluxengine.core.Bytes; -import java.util.ArrayList; -import java.util.List; /** * A single record on a track, ported from lib/data/disk.h. diff --git a/java/com/cowlark/fluxengine/data/Sector.java b/java/com/cowlark/fluxengine/data/Sector.java index f31b5b26..2ab03526 100644 --- a/java/com/cowlark/fluxengine/data/Sector.java +++ b/java/com/cowlark/fluxengine/data/Sector.java @@ -9,20 +9,9 @@ */ public class Sector { - public enum Status - { - OK, - BAD_CHECKSUM, - MISSING, - DATA_MISSING, - CONFLICT, - INTERNAL_ERROR - } - - /* The logical location of this sector. */ - public LogicalLocation location; + /* The logical location of this sector. */ public Status status = Status.INTERNAL_ERROR; public int position = 0; public double clockNs = 0.0; @@ -90,4 +79,9 @@ public static Status stringToStatus(String value) return Status.CONFLICT; return Status.INTERNAL_ERROR; } + + public enum Status + { + OK, BAD_CHECKSUM, MISSING, DATA_MISSING, CONFLICT, INTERNAL_ERROR + } } diff --git a/java/com/cowlark/fluxengine/decoders/Decoder.java b/java/com/cowlark/fluxengine/decoders/Decoder.java index 0ba8d5e8..241c1fa4 100644 --- a/java/com/cowlark/fluxengine/decoders/Decoder.java +++ b/java/com/cowlark/fluxengine/decoders/Decoder.java @@ -20,18 +20,12 @@ */ public abstract class Decoder { - public enum RecordType - { - SECTOR_RECORD, DATA_RECORD, UNKNOWN_RECORD - } - protected final DecoderProto config; protected LogicalTrackLayout ltl; protected Track trackdata; protected Sector sector; protected FluxDecoder decoder; protected Bits recordBits = new Bits(); - private FluxmapReader fmr; public Decoder(DecoderProto config) @@ -236,4 +230,9 @@ protected void beginTrack() protected void decodeDataRecord() { } + + public enum RecordType + { + SECTOR_RECORD, DATA_RECORD, UNKNOWN_RECORD + } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java index 9b1737fa..a4690211 100644 --- a/java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/D88ImageWriter.java @@ -69,8 +69,7 @@ public void writeImage(Image image) headerWriter.writeLe32(trackOffset); int side = track & 1; List sectors = new ArrayList<>(); - for (int sectorId = geometry.firstSector; sectorId <= geometry.numSectors; - sectorId++) + for (int sectorId = geometry.firstSector; sectorId <= geometry.numSectors; sectorId++) { Sector sector = image.get(track >> 1, side, sectorId); if (sector != null) @@ -113,7 +112,10 @@ public void writeImage(Image image) output.writeToFile(config.getFilename()); - System.out.printf("D88: wrote %d tracks, %d sides, %d kB total%n", - tracks, sides, output.size() / 1024); + System.out.printf( + "D88: wrote %d tracks, %d sides, %d kB total%n", + tracks, + sides, + output.size() / 1024); } } diff --git a/java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java index 177e99c7..4f184a3b 100644 --- a/java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/DiskCopyImageWriter.java @@ -22,8 +22,7 @@ public DiskCopyImageWriter(ImageWriterProto config) super(config); } - private static void writeAndUpdateChecksum( - ByteWriter bw, int[] checksum, Bytes data) + private static void writeAndUpdateChecksum(ByteWriter bw, int[] checksum, Bytes data) { ByteReader br = data.iterator(); while (!br.eof()) @@ -68,8 +67,7 @@ public void writeImage(Image image) geometry.sectorSize, isMfm ? "MFM" : "GCR"); - java.util.function.IntUnaryOperator sectorsPerTrack = track -> - { + java.util.function.IntUnaryOperator sectorsPerTrack = track -> { if (isMfm) return geometry.numSectors; @@ -124,8 +122,7 @@ public void writeImage(Image image) if (sector != null) { bw.seek(offset); - writeAndUpdateChecksum( - bw, tagChecksum, sector.data.slice(512, 12)); + writeAndUpdateChecksum(bw, tagChecksum, sector.data.slice(512, 12)); } offset += 12; } diff --git a/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java index db3270cc..12d1ecc8 100644 --- a/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java @@ -24,11 +24,6 @@ public ImageWriter(ImageWriterProto config) this.config = config; } - protected ImageWriterProto getWriterConfig() - { - return config; - } - public static ImageWriter create(ConfigProto config) { if (!config.hasImageWriter()) @@ -68,6 +63,11 @@ private static ImageWriter notImplemented(String name) throw new FluxEngineException(name + " image writer is not implemented yet"); } + protected ImageWriterProto getWriterConfig() + { + return config; + } + public void writeCsv(Image image, String filename) { StringBuilder f = new StringBuilder(); @@ -88,12 +88,10 @@ public void writeCsv(Image image, String filename) for (Sector sector : image) { - f.append(sector.physicalLocation != null - ? sector.physicalLocation.cylinder() - : -1).append(','); - f.append(sector.physicalLocation != null - ? sector.physicalLocation.head() - : -1).append(','); + f.append(sector.physicalLocation != null ? sector.physicalLocation.cylinder() : -1) + .append(','); + f.append(sector.physicalLocation != null ? sector.physicalLocation.head() : -1) + .append(','); f.append(sector.location.logicalSector()).append(','); f.append(sector.location.logicalCylinder()).append(','); f.append(sector.location.logicalHead()).append(','); @@ -183,12 +181,21 @@ public void printMap(Image image) System.out.println("No sectors in output; skipping analysis"); else { - System.out.printf("Good sectors: %d/%d (%d%%)%n", - goodSectors, totalSectors, 100 * goodSectors / totalSectors); - System.out.printf("Missing sectors: %d/%d (%d%%)%n", - missingSectors, totalSectors, 100 * missingSectors / totalSectors); - System.out.printf("Bad sectors: %d/%d (%d%%)%n", - badSectors, totalSectors, 100 * badSectors / totalSectors); + System.out.printf( + "Good sectors: %d/%d (%d%%)%n", + goodSectors, + totalSectors, + 100 * goodSectors / totalSectors); + System.out.printf( + "Missing sectors: %d/%d (%d%%)%n", + missingSectors, + totalSectors, + 100 * missingSectors / totalSectors); + System.out.printf( + "Bad sectors: %d/%d (%d%%)%n", + badSectors, + totalSectors, + 100 * badSectors / totalSectors); } } diff --git a/java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java index 67761c05..344bbbb8 100644 --- a/java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/ImdImageWriter.java @@ -31,9 +31,8 @@ private static int getModulationAndSpeed(int flags, ImdOutputProto.RecordingMode if (flags == 0) { throw new FluxEngineException( - "Can't write IMD files with this speed " + flags - + ", and modulation " + mode - + ". Did you read a real disk?"); + "Can't write IMD files with this speed " + flags + ", and modulation " + mode + + ". Did you read a real disk?"); } else { flags = (int) (1000000.0 / flags); @@ -72,8 +71,8 @@ private static int getModulationAndSpeed(int flags, ImdOutputProto.RecordingMode } else { throw new FluxEngineException( - "IMD: Can't write IMD files with this speed " + flags - + ", and modulation " + mode + ". Try another format."); + "IMD: Can't write IMD files with this speed " + flags + ", and modulation " + + mode + ". Try another format."); } } @@ -96,9 +95,8 @@ private static int setSectorSize(int flags) case 8192: return 6; } - throw new FluxEngineException( - "IMD: Sector size " + flags - + " not in standard range (128, 256, 512, 1024, 2048, 4096, 8192)."); + throw new FluxEngineException("IMD: Sector size " + flags + + " not in standard range (128, 256, 512, 1024, 2048, 4096, 8192)."); } @Override @@ -120,9 +118,9 @@ public void writeImage(Image image) ImdOutputProto.DataRate dataRate = config.getImd().getDataRate(); if (dataRate == ImdOutputProto.DataRate.RATE_GUESS) { - dataRate = (geometry.numSectors > 10) - ? ImdOutputProto.DataRate.RATE_HD - : ImdOutputProto.DataRate.RATE_DD; + dataRate = (geometry.numSectors > 10) ? + ImdOutputProto.DataRate.RATE_HD : + ImdOutputProto.DataRate.RATE_DD; if (geometry.sectorSize <= 256) dataRate = ImdOutputProto.DataRate.RATE_SD; System.out.println("IMD: guessing data rate as " + dataRate); @@ -139,9 +137,8 @@ public void writeImage(Image image) if (comment.length() == 0) { comment = LABEL; - comment = comment + " date: " - + LocalDateTime.now().format(DateTimeFormatter.ofPattern( - "E MMM d HH:mm:ss yyyy")); + comment = comment + " date: " + LocalDateTime.now() + .format(DateTimeFormatter.ofPattern("E MMM d HH:mm:ss yyyy")); } else { comment = "IMD " + comment; @@ -172,8 +169,11 @@ public void writeImage(Image image) { /* sector 0 doesnt exist exit with error */ statusSector = 0; - System.out.printf("IMD: sector %d not found on track %d, head %d%n", - sectorId + 1, track, head); + System.out.printf( + "IMD: sector %d not found on track %d, head %d%n", + sectorId + 1, + track, + head); break; } else { @@ -231,10 +231,8 @@ public void writeImage(Image image) { sectorSkew = sectorSkew + (char) ((i + sectorIdBase) + '0'); if (s.physicalLocation != null && - ((s.physicalLocation.cylinder() != - s.location.logicalCylinder()) || - (s.physicalLocation.head() != - s.location.logicalHead()))) + ((s.physicalLocation.cylinder() != s.location.logicalCylinder()) || + (s.physicalLocation.head() != s.location.logicalHead()))) blnOptionalHeadMap = true; } } @@ -333,8 +331,8 @@ public void writeImage(Image image) default: throw new FluxEngineException( - "IMD: Don't understand IMD files with sector status " - + statusSector); + "IMD: Don't understand IMD files with sector status " + + statusSector); } bw.write8(statusSector); /* 1 byte status sector */ if (blnCompressable) @@ -354,8 +352,8 @@ public void writeImage(Image image) } imagenew.writeToFile(config.getFilename()); System.out.printf( - "IMD: Written %d tracks, %d heads, %d sectors, %d bytes per " - + "sector, %d kB total%n", + "IMD: Written %d tracks, %d heads, %d sectors, %d bytes per " + + "sector, %d kB total%n", geometry.numCylinders, numHeads, numSectors, diff --git a/java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java index 1f6c0003..c57ff1a9 100644 --- a/java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/ImgImageWriter.java @@ -31,8 +31,9 @@ public void writeImage(Image image) { Geometry geometry = image.getGeometry(); - int tracks = - config.getLayout().hasTracks() ? config.getLayout().getTracks() : geometry.numCylinders; + int tracks = config.getLayout().hasTracks() ? + config.getLayout().getTracks() : + geometry.numCylinders; int sides = config.getLayout().hasSides() ? config.getLayout().getSides() : geometry.numHeads; @@ -42,21 +43,19 @@ public void writeImage(Image image) Bytes output = new Bytes(); ByteWriter bw = output.writer(); - Iterable locations = inFilesystemOrder - ? diskLayout.logicalLocationsInFilesystemOrder - : diskLayout.logicalLocations; + Iterable locations = inFilesystemOrder ? + diskLayout.logicalLocationsInFilesystemOrder : + diskLayout.logicalLocations; for (CylinderHead logicalLocation : locations) { - LogicalTrackLayout ltl = - diskLayout.layoutByLogicalLocation.get(logicalLocation); + LogicalTrackLayout ltl = diskLayout.layoutByLogicalLocation.get(logicalLocation); - Iterable sectorOrder = inFilesystemOrder - ? ltl.filesystemSectorOrder - : ltl.naturalSectorOrder; + Iterable sectorOrder = + inFilesystemOrder ? ltl.filesystemSectorOrder : ltl.naturalSectorOrder; for (int sectorId : sectorOrder) { - Sector sector = image.get( - logicalLocation.cylinder(), logicalLocation.head(), sectorId); + Sector sector = + image.get(logicalLocation.cylinder(), logicalLocation.head(), sectorId); if (sector != null) bw.write(sector.data.slice(0, ltl.sectorSize)); else @@ -66,7 +65,11 @@ public void writeImage(Image image) output.writeToFile(getWriterConfig().getFilename()); - System.out.printf("IMG: wrote %d tracks, %d sides, %d kB total to %s%n", - tracks, sides, output.size() / 1024, getWriterConfig().getFilename()); + System.out.printf( + "IMG: wrote %d tracks, %d sides, %d kB total to %s%n", + tracks, + sides, + output.size() / 1024, + getWriterConfig().getFilename()); } } diff --git a/java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java index 88fe12fa..49331b3b 100644 --- a/java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/NsiImageWriter.java @@ -27,14 +27,12 @@ public void writeImage(Image image) if (geometry.numCylinders * trackSize == 0) { - System.out.println( - "No sectors in output; skipping .nsi image file generation."); + System.out.println("No sectors in output; skipping .nsi image file generation."); return; } System.out.printf( - "Writing %d tracks, %d sides, %d sectors, %s (%d bytes/sector), " - + "%d kB total%n", + "Writing %d tracks, %d sides, %d sectors, %s (%d bytes/sector), " + "%d kB total%n", geometry.numCylinders, geometry.numHeads, geometry.numSectors, @@ -43,8 +41,7 @@ public void writeImage(Image image) geometry.numCylinders * geometry.numHeads * geometry.numSectors * geometry.sectorSize / 1024); - Bytes output = new Bytes( - geometry.numCylinders * geometry.numHeads * geometry.numSectors * + Bytes output = new Bytes(geometry.numCylinders * geometry.numHeads * geometry.numSectors * geometry.sectorSize); ByteWriter bw = output.writer(); @@ -59,15 +56,12 @@ public void writeImage(Image image) { if (side == 0) { /* Side 0 is from track 0-34 */ - sectorFileOffset = - track * trackSize + sectorId * geometry.sectorSize; + sectorFileOffset = track * trackSize + sectorId * geometry.sectorSize; } else { /* Side 1 is from track 70-35 */ - sectorFileOffset = - (geometry.sectorSize * geometry.numSectors * - geometry.numCylinders) + /* Skip over side 0 */ - ((geometry.numCylinders - 1) - - (track % geometry.numCylinders)) * + sectorFileOffset = (geometry.sectorSize * geometry.numSectors * + geometry.numCylinders) + /* Skip over side 0 */ + ((geometry.numCylinders - 1) - (track % geometry.numCylinders)) * (geometry.sectorSize * geometry.numSectors) + (sectorId * geometry.sectorSize); } @@ -84,9 +78,8 @@ public void writeImage(Image image) * spaces. */ if (!mixedDensity) { - System.out.println( - "Warning: Disk contains mixed " - + "single/double-density sectors."); + System.out.println("Warning: Disk contains mixed " + + "single/double-density sectors."); } mixedDensity = true; bw.write(sector.data.slice(0, 256)); diff --git a/java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java index db30154a..232327a8 100644 --- a/java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/RawImageWriter.java @@ -29,13 +29,14 @@ public void writeImage(Image image) if (geometry.numCylinders * trackSize == 0) { - System.out.println( - "RAW: no sectors in output; skipping image file generation."); + System.out.println("RAW: no sectors in output; skipping image file generation."); return; } - System.out.printf("RAW: writing %d tracks, %d sides%n", - geometry.numCylinders, geometry.numHeads); + System.out.printf( + "RAW: writing %d tracks, %d sides%n", + geometry.numCylinders, + geometry.numHeads); Bytes output = new Bytes(); diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 198af61c..92cf3c99 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -53,6 +53,26 @@ java_test( ], ) +java_test( + name = "LoggerTest", + srcs = ["LoggerTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "LogRendererTest", + srcs = ["LogRendererTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "BitsTest", srcs = ["BitsTest.java"], diff --git a/javatests/com/cowlark/fluxengine/core/LogRendererTest.java b/javatests/com/cowlark/fluxengine/core/LogRendererTest.java new file mode 100644 index 00000000..7b317e43 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/LogRendererTest.java @@ -0,0 +1,111 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.BeginWriteOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.EmergencyStopMessage; +import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.ErrorLogMessage; +import com.cowlark.fluxengine.core.LogMessage.OptionLogMessage; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.function.Consumer; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class LogRendererTest +{ + private static String render(Consumer action) + { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(buffer); + LogRenderer renderer = LogRenderer.create(stream); + action.accept(renderer); + stream.flush(); + return buffer.toString(); + } + + @Test + public void errorMessage() + { + String output = render( + r -> r.add(new ErrorLogMessage("disk failed"))); + + assertThat(output).isEqualTo("\n Error: disk failed\n"); + } + + @Test + public void emergencyStop() + { + String output = render( + r -> r.add(new EmergencyStopMessage())); + + assertThat(output).isEqualTo("\n Stop!\n"); + } + + @Test + public void endSpeedOperation() + { + String output = render( + r -> r.add(new EndSpeedOperationLogMessage(200e6))); + + assertThat(output).isEqualTo( + "\n Rotational period is 200.0ms (300.0rpm)\n"); + } + + @Test + public void readOperationHeader() + { + String output = render( + r -> r.add(new BeginReadOperationLogMessage(3, 1))); + + assertThat(output).isEqualTo("\nR 3.1: "); + } + + @Test + public void writeOperationHeader() + { + String output = render( + r -> r.add(new BeginWriteOperationLogMessage(3, 1))); + + assertThat(output).isEqualTo("\nW 3.1: "); + } + + @Test + public void optionMessage() + { + String output = render( + r -> r.add(new OptionLogMessage("high density"))); + + assertThat(output).isEqualTo("\n OPTION: high density\n"); + } + + @Test + public void commaSeparates() + { + String output = render(r -> + { + r.add("one"); + r.comma(); + r.add("two"); + }); + + assertThat(output).isEqualTo(" one; two"); + } + + @Test + public void addAfterNewlineIndents() + { + String output = render(r -> + { + r.add("one"); + r.newline(); + r.add("two"); + }); + + assertThat(output).isEqualTo(" one\n two"); + } +} diff --git a/javatests/com/cowlark/fluxengine/core/LoggerTest.java b/javatests/com/cowlark/fluxengine/core/LoggerTest.java new file mode 100644 index 00000000..f7213e6c --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/LoggerTest.java @@ -0,0 +1,66 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.ErrorLogMessage; +import com.cowlark.fluxengine.core.LogMessage.StringMessage; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class LoggerTest +{ + @Test + public void logStringWrapsInStringMessage() + { + List messages = new ArrayList<>(); + Logger.setLogger(messages::add); + + Logger.log("hello"); + + assertThat(messages).containsExactly(new StringMessage("hello")); + } + + @Test + public void logMessagePassesThrough() + { + List messages = new ArrayList<>(); + Logger.setLogger(messages::add); + + Logger.log(new ErrorLogMessage("oops")); + + assertThat(messages).containsExactly(new ErrorLogMessage("oops")); + } + + @Test + public void defaultLoggerRendersToStdout() + { + Logger.setLogger(message -> LogRenderer.create(System.out).add(message)); + + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(buffer); + LogRenderer renderer = LogRenderer.create(stream); + + renderer.add(new BeginReadOperationLogMessage(3, 1)); + + assertThat(buffer.toString()).isEqualTo("\nR 3.1: "); + } + + @Test + public void logUsesSetLogger() + { + List messages = new ArrayList<>(); + Logger.setLogger(messages::add); + + Logger.log("one"); + Logger.log(new StringMessage("two")); + + assertThat(messages).hasSize(2); + } +} From 46006cec2355afc44fd4def0cf3055d0942d7418 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 19:04:32 +0200 Subject: [PATCH 120/192] Port the reader code, although it doesn't work yet. --- .../cowlark/fluxengine/algorithms/BUILD.bazel | 21 + .../fluxengine/algorithms/ReaderWriter.java | 565 ++++++++++++++++++ java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + .../cowlark/fluxengine/cli/ReadCommand.java | 3 +- .../core/EmergencyStopException.java | 12 + java/com/cowlark/fluxengine/data/Disk.java | 60 ++ java/com/cowlark/fluxengine/data/Fluxmap.java | 8 + .../cowlark/fluxengine/fluxsink/BUILD.bazel | 13 + .../cowlark/fluxengine/fluxsink/FluxSink.java | 18 + .../fluxengine/fluxsink/FluxSinkFactory.java | 60 ++ 10 files changed, 760 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/algorithms/ReaderWriter.java create mode 100644 java/com/cowlark/fluxengine/core/EmergencyStopException.java create mode 100644 java/com/cowlark/fluxengine/data/Disk.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/FluxSink.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java diff --git a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel index e69de29b..406c53ff 100644 --- a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -0,0 +1,21 @@ +load("@rules_java//java:defs.bzl", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "algorithms", + srcs = glob(["*.java"]), + deps = [ + "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/fluxsink", + "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/imagewriter", + "//java/com/cowlark/fluxengine/usb", + "@com_google_protobuf//java/core", + ], +) diff --git a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java new file mode 100644 index 00000000..671580f4 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java @@ -0,0 +1,565 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogMessage.BeginOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.BeginSpeedOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.EndOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.EndReadOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; +import com.cowlark.fluxengine.core.LogMessage.OperationProgressLogMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Disk; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.PhysicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.data.Track; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import com.cowlark.fluxengine.imagewriter.ImageWriter; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Disk read/write algorithms, ported from lib/algorithms/readerwriter.cc. + */ +public final class ReaderWriter +{ + private static enum ReadResult + { + GOOD_READ, + BAD_AND_CAN_RETRY, + BAD_AND_CAN_NOT_RETRY + } + + private static enum BadSectorsState + { + HAS_NO_BAD_SECTORS, + HAS_BAD_SECTORS + } + + private ReaderWriter() + { + } + + public static void readDiskCommand( + ConfigProto config, DiskLayout diskLayout, FluxSource fluxSource, + Decoder decoder, Disk disk) + { + FluxSinkFactory outputFluxSinkFactory = null; + if (config.getDecoder().hasCopyFluxTo()) + outputFluxSinkFactory = FluxSinkFactory.create( + config.getDecoder().getCopyFluxTo()); + + Map> tracksByLogicalLocation = + new HashMap<>(); + for (Map.Entry entry : disk.tracksByPhysicalLocation.entries()) + { + Track track = entry.getValue(); + tracksByLogicalLocation + .computeIfAbsent(new CylinderHead( + track.ltl.logicalCylinder, track.ltl.logicalHead), + k -> new ArrayList<>()) + .add(track); + } + + Logger.log(new BeginOperationLogMessage("Reading and decoding disk")); + + if (fluxSource.isHardware()) + disk.rotationalPeriod = measureDiskRotation(config); + else + disk.rotationalPeriod = getRotationalPeriodFromConfig(config); + + { + FluxSink outputFluxSink = null; + if (outputFluxSinkFactory != null) + outputFluxSink = outputFluxSinkFactory.create(); + int index = 0; + for (Map.Entry entry : + diskLayout.layoutByLogicalLocation.entrySet()) + { + CylinderHead logicalLocation = entry.getKey(); + LogicalTrackLayout ltl = entry.getValue(); + Logger.log(new OperationProgressLogMessage( + index * 100 / diskLayout.layoutByLogicalLocation.size())); + index++; + + testForEmergencyStop(); + + List trackFluxes = + tracksByLogicalLocation.computeIfAbsent( + logicalLocation, k -> new ArrayList<>()); + List trackSectors = new ArrayList<>(); + readAndDecodeTrack(config, diskLayout, fluxSource, decoder, + ltl, trackFluxes, trackSectors); + + /* Replace all tracks on the disk by the new combined set. */ + + for (Track flux : trackFluxes) + disk.tracksByPhysicalLocation.removeAll(new CylinderHead( + flux.ptl.physicalCylinder, flux.ptl.physicalHead)); + for (Track flux : trackFluxes) + disk.tracksByPhysicalLocation.put(new CylinderHead( + flux.ptl.physicalCylinder, flux.ptl.physicalHead), flux); + + /* Likewise for sectors. */ + + for (Sector sector : trackSectors) + disk.sectorsByPhysicalLocation.removeAll( + sector.physicalLocation); + for (Sector sector : trackSectors) + disk.sectorsByPhysicalLocation.put( + sector.physicalLocation, sector); + + if (outputFluxSink != null) + { + for (Track data : trackFluxes) + outputFluxSink.addFlux( + data.ptl.physicalCylinder, + data.ptl.physicalHead, + data.fluxmap); + } + + if (config.getDecoder().getDumpRecords()) + { + List sortedRecords = + new ArrayList<>(); + for (Track data : trackFluxes) + sortedRecords.addAll(data.records); + sortedRecords.sort(Comparator.comparingDouble( + r -> r.startTimeNs)); + + System.out.println("\nRaw (undecoded) records follow:\n"); + for (com.cowlark.fluxengine.data.Record record : sortedRecords) + { + System.out.printf( + "I+%.2fus with %.2fus clock%n", + record.startTimeNs / 1000.0, + record.clockNs / 1000.0); + hexdump(System.out, record.rawData); + System.out.println(); + } + } + + if (config.getDecoder().getDumpSectors()) + { + List sectors = collectSectors(trackSectors, false); + sectors.sort(Comparator + .comparing((Sector s) -> s.location.logicalCylinder()) + .thenComparing((Sector s) -> s.location.logicalHead()) + .thenComparing((Sector s) -> s.location.logicalSector())); + + System.out.println("\nDecoded sectors follow:\n"); + for (Sector sector : sectors) + { + System.out.printf( + "%d.%02d.%02d: I+%.2fus with %.2fus clock: " + + "status %s%n", + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector(), + sector.headerStartTimeNs / 1000.0, + sector.clockNs / 1000.0, + Sector.statusToString(sector.status)); + hexdump(System.out, sector.data); + System.out.println(); + } + } + + /* track can't be modified below this point. */ + + List allSectors = new ArrayList<>(); + for (Sector sector : disk.sectorsByPhysicalLocation.values()) + allSectors.add(sector); + allSectors = collectSectors(allSectors); + disk.image = new Image(allSectors); + } + } + + if (disk.image == null) + disk.image = new Image(); + + Logger.log(new EndOperationLogMessage("Read complete")); + } + + public static void readDiskCommand(ConfigProto config, DiskLayout diskLayout, + FluxSource fluxSource, Decoder decoder, ImageWriter writer) + { + Disk disk = new Disk(); + readDiskCommand(config, diskLayout, fluxSource, decoder, disk); + + writer.printMap(disk.image); + if (config.getDecoder().hasWriteCsvTo()) + writer.writeCsv(disk.image, config.getDecoder().getWriteCsvTo()); + writer.writeImage(disk.image); + } + + /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a + * good and bad version of the same sector, the bad version is dropped). */ + private static List collectSectors( + List trackSectors, boolean collapseConflicts) + { + Map> sectors = new LinkedHashMap<>(); + for (Sector sector : trackSectors) + sectors.computeIfAbsent(sector.location, k -> new ArrayList<>()) + .add(sector); + + List sectorSet = new ArrayList<>(); + for (Map.Entry> entry : sectors.entrySet()) + { + List bucket = entry.getValue(); + Sector newSector = bucket.get(0); + for (int i = 1; i < bucket.size(); i++) + { + Sector right = bucket.get(i); + if ((newSector.status == Sector.Status.OK) && + (right.status == Sector.Status.OK) && + (!newSector.data.equals(right.data))) + { + if (!collapseConflicts) + { + Sector s = copySector(right); + s.status = Sector.Status.CONFLICT; + sectorSet.add(s); + } + Sector s = copySector(newSector); + s.status = Sector.Status.CONFLICT; + newSector = s; + continue; + } + if (newSector.status == Sector.Status.CONFLICT) + continue; + if (right.status == Sector.Status.CONFLICT) + { + newSector = right; + continue; + } + if (newSector.status == Sector.Status.OK) + continue; + if (right.status == Sector.Status.OK) + newSector = right; + } + sectorSet.add(newSector); + } + + return sectorSet; + } + + private static List collectSectors(List trackSectors) + { + return collectSectors(trackSectors, true); + } + + private static Sector copySector(Sector sector) + { + Sector s = new Sector(sector.location); + s.status = sector.status; + s.position = sector.position; + s.clockNs = sector.clockNs; + s.headerStartTimeNs = sector.headerStartTimeNs; + s.headerEndTimeNs = sector.headerEndTimeNs; + s.dataStartTimeNs = sector.dataStartTimeNs; + s.dataEndTimeNs = sector.dataEndTimeNs; + s.physicalLocation = sector.physicalLocation; + s.data = sector.data; + s.records = sector.records; + return s; + } + + private static class CombinationResult + { + BadSectorsState result; + List sectors; + } + + private static CombinationResult combineRecordAndSectors( + List tracks, Decoder decoder, LogicalTrackLayout ltl) + { + CombinationResult cr = new CombinationResult(); + cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; + List trackSectors = new ArrayList<>(); + + /* Add the sectors which were there. */ + + for (Track track : tracks) + trackSectors.addAll(track.allSectors); + + /* Add the sectors which should be there. */ + + for (int sectorId : ltl.diskSectorOrder) + { + Sector sector = new Sector(new LogicalLocation( + ltl.logicalCylinder, ltl.logicalHead, sectorId)); + + sector.status = Sector.Status.MISSING; + sector.physicalLocation = + new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); + trackSectors.add(sector); + } + + /* Deduplicate. */ + + cr.sectors = collectSectors(trackSectors); + if (cr.sectors.isEmpty()) + cr.result = BadSectorsState.HAS_BAD_SECTORS; + for (Sector sector : cr.sectors) + if (sector.status != Sector.Status.OK) + cr.result = BadSectorsState.HAS_BAD_SECTORS; + + return cr; + } + + private static void adjustTrackOnError( + FluxSource fluxSource, int baseTrack, ConfigProto config) + { + switch (config.getDrive().getErrorBehaviour()) + { + case NOTHING: + break; + + case RECALIBRATE: + fluxSource.recalibrate(); + break; + + case JIGGLE: + if (baseTrack > 0) + fluxSource.seek(baseTrack - 1); + else + fluxSource.seek(baseTrack + 1); + break; + } + } + + private static class ReadGroupResult + { + ReadResult result; + List combinedSectors; + } + + private static class FluxSourceIteratorHolder + { + private final FluxSource fluxSource; + private final Map cache = + new HashMap<>(); + + FluxSourceIteratorHolder(FluxSource fluxSource) + { + this.fluxSource = fluxSource; + } + + FluxSourceIterator getIterator(int physicalCylinder, int head) + { + CylinderHead key = new CylinderHead(physicalCylinder, head); + FluxSourceIterator it = cache.get(key); + if (it == null) + { + it = fluxSource.readFlux(physicalCylinder, head); + cache.put(key, it); + } + return it; + } + } + + private static ReadGroupResult readGroup(DiskLayout diskLayout, + FluxSourceIteratorHolder fluxSourceIteratorHolder, + LogicalTrackLayout ltl, List tracks, Decoder decoder, + ConfigProto config) + { + ReadGroupResult rgr = new ReadGroupResult(); + rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; + + /* Before doing the read, look to see if we already have the necessary + * sectors. */ + + { + CombinationResult cr = + combineRecordAndSectors(tracks, decoder, ltl); + rgr.combinedSectors = cr.sectors; + if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) + { + /* We have all necessary sectors, so can stop here. */ + rgr.result = ReadResult.GOOD_READ; + if (config.getDecoder().getSkipUnnecessaryTracks()) + return rgr; + } + } + + for (int offset = 0; offset < ltl.groupSize; + offset += diskLayout.headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + PhysicalTrackLayout ptl = diskLayout.layoutByPhysicalLocation.get( + new CylinderHead(physicalCylinder, physicalHead)); + + /* Do the physical read. */ + + Logger.log(new BeginReadOperationLogMessage( + physicalCylinder, physicalHead)); + + FluxSourceIterator fluxSourceIterator = + fluxSourceIteratorHolder.getIterator( + physicalCylinder, physicalHead); + if (!fluxSourceIterator.hasNext()) + continue; + + Fluxmap fluxmap = fluxSourceIterator.next(); + Logger.log(new EndReadOperationLogMessage()); + Logger.log(String.format("%d ms in %d bytes", + (int) (fluxmap.duration() / 1e6), fluxmap.bytes())); + + Track flux = decoder.decodeToSectors(fluxmap, ptl); + flux.normalisedSectors = collectSectors(flux.allSectors); + tracks.add(flux); + + /* Decode what we've got so far. */ + + CombinationResult cr = + combineRecordAndSectors(tracks, decoder, ltl); + rgr.combinedSectors = cr.sectors; + if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) + { + /* We have all necessary sectors, so can stop here. */ + rgr.result = ReadResult.GOOD_READ; + if (config.getDecoder().getSkipUnnecessaryTracks()) + break; + } else if (fluxSourceIterator.hasNext()) + { + /* The flux source claims it can do more reads, so mark this + * group as being retryable. */ + rgr.result = ReadResult.BAD_AND_CAN_RETRY; + } + } + + return rgr; + } + + private static void readAndDecodeTrack(ConfigProto config, + DiskLayout diskLayout, FluxSource fluxSource, Decoder decoder, + LogicalTrackLayout ltl, List tracks, + List combinedSectors) + { + if (fluxSource.isHardware()) + measureDiskRotation(config); + + FluxSourceIteratorHolder fluxSourceIteratorHolder = + new FluxSourceIteratorHolder(fluxSource); + int retriesRemaining = config.getDecoder().getRetries(); + for (; ; ) + { + ReadGroupResult rgr = readGroup(diskLayout, + fluxSourceIteratorHolder, ltl, tracks, decoder, config); + combinedSectors.clear(); + combinedSectors.addAll(rgr.combinedSectors); + if (rgr.result == ReadResult.GOOD_READ) + break; + if (rgr.result == ReadResult.BAD_AND_CAN_NOT_RETRY) + { + Logger.log("no more data; giving up"); + break; + } + + if (retriesRemaining == 0) + { + Logger.log("giving up"); + break; + } + + if (fluxSource.isHardware()) + { + adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); + Logger.log(String.format("retrying; %d retries remaining", + retriesRemaining)); + retriesRemaining--; + } + } + } + + private static double getRotationalPeriodFromConfig(ConfigProto config) + { + return config.getDrive().getRotationalPeriodMs() * 1e6; + } + + private static double measureDiskRotation(ConfigProto config) + { + Logger.log(new BeginSpeedOperationLogMessage()); + + double oneRevolution = getRotationalPeriodFromConfig(config); + if (oneRevolution == 0) + { + UsbDevice device = UsbFactory.connect(config); + device.setDrive(config.getDrive().getDrive(), + config.getDrive().getHighDensity(), + config.getDrive().getIndexMode().getNumber()); + + Logger.log(new BeginOperationLogMessage( + "Measuring drive rotational speed")); + int retries = 5; + do + { + oneRevolution = device.getRotationalPeriod( + config.getDrive().getHardSectorCount()); + retries--; + } while ((oneRevolution == 0) && (retries > 0)); + Logger.log(new EndOperationLogMessage("")); + } + + if (oneRevolution == 0) + throw new FluxEngineException("Failed\nIs a disk in the drive?"); + + Logger.log(new EndSpeedOperationLogMessage(oneRevolution)); + return oneRevolution; + } + + private static void testForEmergencyStop() + { + } + + private static void hexdump(java.io.PrintStream stream, Bytes buffer) + { + int pos = 0; + + while (pos < buffer.size()) + { + stream.printf("%05x : ", pos); + for (int i = 0; i < 16; i++) + { + if ((pos + i) < buffer.size()) + stream.printf("%02x ", buffer.getByte(pos + i)); + else + stream.print("-- "); + } + stream.print(" : "); + for (int i = 0; i < 16; i++) + { + if ((pos + i) >= buffer.size()) + break; + + int c = buffer.getByte(pos + i) & 0xff; + if ((c >= 32) && (c <= 126)) + stream.print((char) c); + else + stream.print('.'); + } + stream.println(); + + pos += 16; + } + } +} diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index cfd9d5a2..77d5649a 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -6,6 +6,7 @@ java_library( name = "cli", srcs = glob(["*.java"]), deps = [ + "//java/com/cowlark/fluxengine/algorithms", "//java/com/cowlark/fluxengine/arch", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index b97d2e20..ca99b067 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -3,6 +3,7 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; import com.cowlark.fluxengine.arch.Arch; +import com.cowlark.fluxengine.algorithms.ReaderWriter; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; @@ -65,6 +66,6 @@ public void run(ImmutableList args) FluxSource fluxSource = FluxSource.create(config); Decoder decoder = Arch.createDecoder(config); ImageWriter writer = ImageWriter.create(config); - // readDiskCommand(diskLayout, fluxSource, decoder, writer); + ReaderWriter.readDiskCommand(config, diskLayout, fluxSource, decoder, writer); } } diff --git a/java/com/cowlark/fluxengine/core/EmergencyStopException.java b/java/com/cowlark/fluxengine/core/EmergencyStopException.java new file mode 100644 index 00000000..5aa31053 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/EmergencyStopException.java @@ -0,0 +1,12 @@ +package com.cowlark.fluxengine.core; + +/** + * Thrown to abort a running operation, ported from lib/core/utils.h. + */ +public class EmergencyStopException extends RuntimeException +{ + public EmergencyStopException() + { + super(); + } +} diff --git a/java/com/cowlark/fluxengine/data/Disk.java b/java/com/cowlark/fluxengine/data/Disk.java new file mode 100644 index 00000000..bb0a3df3 --- /dev/null +++ b/java/com/cowlark/fluxengine/data/Disk.java @@ -0,0 +1,60 @@ +package com.cowlark.fluxengine.data; + +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.ListMultimap; +import java.util.Set; +import java.util.TreeSet; + +/** + * A disk, being the result of reading a physical disk, ported from + * lib/data/disk.h and lib/data/disk.cc. + */ +public class Disk +{ + public final ListMultimap tracksByPhysicalLocation = + ArrayListMultimap.create(); + public final ListMultimap sectorsByPhysicalLocation = + ArrayListMultimap.create(); + public Image image = null; + + /* 0 if the period is unknown (e.g. if this Disk was made from an image). */ + public double rotationalPeriod = 0; + + public Disk() + { + image = new Image(); + } + + public Disk(Image image, DiskLayout diskLayout) + { + this.image = image; + + ListMultimap sectorsGroupedByTrack = + ArrayListMultimap.create(); + for (Sector sector : image) + sectorsGroupedByTrack.put(sector.physicalLocation, sector); + + Set sectorLocations = new TreeSet<>(); + for (CylinderHead ch : sectorsGroupedByTrack.keySet()) + sectorLocations.add(ch); + + for (CylinderHead physicalLocation : sectorLocations) + { + PhysicalTrackLayout ptl = + diskLayout.layoutByPhysicalLocation.get(physicalLocation); + LogicalTrackLayout ltl = ptl.logicalTrackLayout; + + Track decodedTrack = new Track(); + decodedTrack.ltl = ltl; + decodedTrack.ptl = ptl; + tracksByPhysicalLocation.put(physicalLocation, decodedTrack); + + for (Sector sector : sectorsGroupedByTrack.get(physicalLocation)) + { + decodedTrack.allSectors.add(sector); + decodedTrack.normalisedSectors.add(sector); + sectorsByPhysicalLocation.put(physicalLocation, sector); + } + } + } +} diff --git a/java/com/cowlark/fluxengine/data/Fluxmap.java b/java/com/cowlark/fluxengine/data/Fluxmap.java index bfb76c06..c0b70c0b 100644 --- a/java/com/cowlark/fluxengine/data/Fluxmap.java +++ b/java/com/cowlark/fluxengine/data/Fluxmap.java @@ -3,6 +3,7 @@ import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; import static com.cowlark.fluxengine.external.FluxEngine.F_DESYNC; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; @@ -41,6 +42,13 @@ public int ticks() return ticks; } + /* The duration of the fluxmap in nanoseconds, ported from + * lib/data/fluxmap.h Fluxmap::duration(). */ + public double duration() + { + return ticks * NS_PER_TICK; + } + public int bytes() { return bytes.size(); diff --git a/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel index 692431e5..29b67803 100644 --- a/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel @@ -1,4 +1,5 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) @@ -14,3 +15,15 @@ java_proto_library( name = "fluxsink_java_proto", deps = [":fluxsink_proto"], ) + +java_library( + name = "fluxsink", + srcs = glob(["*.java"]), + deps = [ + ":fluxsink_java_proto", + "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + ], +) diff --git a/java/com/cowlark/fluxengine/fluxsink/FluxSink.java b/java/com/cowlark/fluxengine/fluxsink/FluxSink.java new file mode 100644 index 00000000..cc1fd809 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/FluxSink.java @@ -0,0 +1,18 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Fluxmap; + +/** + * A destination for flux data, ported from lib/fluxsink/fluxsink.h. + */ +public abstract class FluxSink +{ + /* Writes a fluxmap to a track and side. */ + public abstract void addFlux(int track, int side, Fluxmap fluxmap); + + public void addFlux(CylinderHead location, Fluxmap fluxmap) + { + addFlux(location.cylinder(), location.head(), fluxmap); + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java new file mode 100644 index 00000000..d4df5b7a --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java @@ -0,0 +1,60 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.FluxSourceSinkType; +import com.cowlark.fluxengine.core.FluxEngineException; + +/** + * Factory for creating flux sinks, ported from lib/fluxsink/fluxsink.h. + */ +public abstract class FluxSinkFactory +{ + public static FluxSinkFactory create(ConfigProto config) + { + if (!config.hasFluxSink()) + throw new FluxEngineException("no flux sink configured"); + return create(config.getFluxSink()); + } + + public static FluxSinkFactory create(FluxSinkProto config) + { + switch (config.getType()) + { + case FLUXTYPE_DRIVE: + return notImplemented("hardware"); + case FLUXTYPE_A2R: + return notImplemented("a2r"); + case FLUXTYPE_AU: + return notImplemented("au"); + case FLUXTYPE_VCD: + return notImplemented("vcd"); + case FLUXTYPE_SCP: + return notImplemented("scp"); + case FLUXTYPE_FLUX: + return notImplemented("fl2"); + default: + throw new FluxEngineException("no flux sink specified"); + } + } + + private static FluxSinkFactory notImplemented(String name) + { + throw new FluxEngineException(name + " flux sink is not implemented yet"); + } + + /* Creates a writer object. */ + public abstract FluxSink create(); + + /* Returns whether this is writing to real hardware or not. */ + public boolean isHardware() + { + return false; + } + + /* Returns the path (filename or directory) being written to, if there is + * one. */ + public String getPath() + { + return null; + } +} From f54db4a6825f0ca6be52d1d221bc6d5e0347ff52 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 19:27:29 +0200 Subject: [PATCH 121/192] Cache the result of UsbDevice.connect, to make it monotonic. --- .../fluxengine/algorithms/ReaderWriter.java | 2 +- .../cowlark/fluxengine/cli/RpmCommand.java | 2 +- .../cowlark/fluxengine/cli/SeekCommand.java | 2 +- .../fluxengine/cli/TestBandwidthCommand.java | 2 +- .../fluxengine/cli/TestVoltagesCommand.java | 2 +- .../fluxsource/HardwareFluxSource.java | 2 +- .../fluxengine/usb/GreaseweazleUsbDevice.java | 6 ++ .../com/cowlark/fluxengine/usb/UsbDevice.java | 6 +- .../cowlark/fluxengine/usb/UsbFactory.java | 33 ++++++++++- .../fluxsource/HardwareFluxSourceTest.java | 5 ++ .../com/cowlark/fluxengine/usb/BUILD.bazel | 15 +++++ .../fluxengine/usb/UsbFactoryTest.java | 59 +++++++++++++++++++ 12 files changed, 127 insertions(+), 9 deletions(-) create mode 100644 javatests/com/cowlark/fluxengine/usb/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java diff --git a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java index 671580f4..44bf642f 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java +++ b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java @@ -503,7 +503,7 @@ private static double measureDiskRotation(ConfigProto config) double oneRevolution = getRotationalPeriodFromConfig(config); if (oneRevolution == 0) { - UsbDevice device = UsbFactory.connect(config); + UsbDevice device = UsbFactory.reconnect(config); device.setDrive(config.getDrive().getDrive(), config.getDrive().getHighDensity(), config.getDrive().getIndexMode().getNumber()); diff --git a/java/com/cowlark/fluxengine/cli/RpmCommand.java b/java/com/cowlark/fluxengine/cli/RpmCommand.java index b57ce736..d80bb937 100644 --- a/java/com/cowlark/fluxengine/cli/RpmCommand.java +++ b/java/com/cowlark/fluxengine/cli/RpmCommand.java @@ -28,7 +28,7 @@ public void run(ImmutableList args) if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) throw new FluxEngineException("this only makes sense with a real disk drive"); - UsbDevice device = UsbFactory.connect(config); + UsbDevice device = UsbFactory.reconnect(config); double periodNs = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); if (periodNs != 0.0) diff --git a/java/com/cowlark/fluxengine/cli/SeekCommand.java b/java/com/cowlark/fluxengine/cli/SeekCommand.java index 04394e07..3cadc37a 100644 --- a/java/com/cowlark/fluxengine/cli/SeekCommand.java +++ b/java/com/cowlark/fluxengine/cli/SeekCommand.java @@ -38,7 +38,7 @@ public void run(ImmutableList args) if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) throw new FluxEngineException("this only makes sense with a real disk drive"); - UsbDevice device = UsbFactory.connect(config); + UsbDevice device = UsbFactory.reconnect(config); device.seek(track.get()); } } diff --git a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java index 93313bf9..2f816f21 100644 --- a/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestBandwidthCommand.java @@ -22,7 +22,7 @@ public void run(ImmutableList args) { ConfigProto config = new ConfigBuilder().fromFlags(args).build(); - UsbDevice device = UsbFactory.connect(config); + UsbDevice device = UsbFactory.reconnect(config); device.testBulkWrite(); device.testBulkRead(); } diff --git a/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java b/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java index 7d5ae862..2689587b 100644 --- a/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java +++ b/java/com/cowlark/fluxengine/cli/TestVoltagesCommand.java @@ -32,7 +32,7 @@ public void run(ImmutableList args) { ConfigProto config = new ConfigBuilder().fromFlags(args).build(); - UsbDevice device = UsbFactory.connect(config); + UsbDevice device = UsbFactory.reconnect(config); VoltageMeasurements voltages = device.measureVoltages(); System.out.printf( diff --git a/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java index 506cf790..0d66d687 100644 --- a/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java @@ -17,7 +17,7 @@ public class HardwareFluxSource extends FluxSource public HardwareFluxSource(ConfigProto config) { - this(config, UsbFactory.connect(config)); + this(config, UsbFactory.reconnect(config)); } /* Package-private for testing. */ diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index 97ed3384..63cc64e6 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -463,6 +463,12 @@ public VoltageMeasurements measureVoltages() throw new FluxEngineException("unsupported operation on the Greaseweazle"); } + @Override + public void close() + { + serial.closePort(); + } + private int readByte() { return readBytes(1).get(0) & 0xff; diff --git a/java/com/cowlark/fluxengine/usb/UsbDevice.java b/java/com/cowlark/fluxengine/usb/UsbDevice.java index 76307363..ad03029d 100644 --- a/java/com/cowlark/fluxengine/usb/UsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/UsbDevice.java @@ -5,7 +5,7 @@ /** * Base class for USB floppy drive devices, ported from lib/usb/usb.h. */ -public abstract class UsbDevice +public abstract class UsbDevice implements AutoCloseable { public void recalibrate() { @@ -33,6 +33,10 @@ public abstract Bytes read(int side, public abstract VoltageMeasurements measureVoltages(); + /* Closes the device, releasing any underlying resources. */ + @Override + public abstract void close(); + protected String usbError(int error) { return String.format("USB error %d", error); diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index f85ad2df..5cb4453c 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -2,7 +2,11 @@ import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.config.UsbFinder; +import com.cowlark.fluxengine.config.UsbFinder.CandidateDevice; import com.cowlark.fluxengine.core.FluxEngineException; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import java.util.Map; /** * USB device finder, ported from lib/usb/usbfinder.cc. @@ -14,10 +18,34 @@ private UsbFactory() { } + private static final Cache cache = CacheBuilder.newBuilder().build(); + + /* Connects a USB device, reusing a previously connected device for the + * same configuration. This is the Java equivalent of the C++ global + * getUsb(). If a different configuration requires a new device, the + * previously cached device is evicted and closed. */ + public static synchronized UsbDevice reconnect(ConfigProto config) + { + UsbDevice device = cache.getIfPresent(config); + if (device == null) + { + /* Only one device is in use at a time, so any other cached device + * is being replaced. Close it before opening the new one, since + * they may share the same serial port. */ + for (Map.Entry entry : cache.asMap().entrySet()) + entry.getValue().close(); + cache.invalidateAll(); + + device = connect(config); + cache.put(config, device); + } + return device; + } + public static UsbDevice connect(ConfigProto config) { - var candidateDevice = UsbFinder.selectDevice(config); - var device = switch (candidateDevice.type) + CandidateDevice candidateDevice = UsbFinder.selectDevice(config); + UsbDevice device = switch (candidateDevice.type) { case GREASEWEAZLE -> new GreaseweazleUsbDevice( candidateDevice.serialPort, @@ -25,6 +53,7 @@ public static UsbDevice connect(ConfigProto config) default -> throw new FluxEngineException("unsupported hardware device"); }; + device.setDrive( config.getDrive().getDrive(), config.getDrive().getHighDensity(), diff --git a/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java index 017d485e..8c2059e9 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java @@ -84,6 +84,11 @@ public VoltageMeasurements measureVoltages() { return null; } + + @Override + public void close() + { + } } private static ConfigProto config() diff --git a/javatests/com/cowlark/fluxengine/usb/BUILD.bazel b/javatests/com/cowlark/fluxengine/usb/BUILD.bazel new file mode 100644 index 00000000..95db07f0 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/usb/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "UsbFactoryTest", + srcs = ["UsbFactoryTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/usb", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java b/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java new file mode 100644 index 00000000..e0018f0a --- /dev/null +++ b/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java @@ -0,0 +1,59 @@ +package com.cowlark.fluxengine.usb; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class UsbFactoryTest +{ + private static ConfigProto config() + { + /* No serial specified: with a single connected device, selectDevice + * returns it. */ + return new ConfigBuilder().build(); + } + + @Test + public void reconnectReturnsSameInstanceForSameConfig() + { + ConfigProto config = config(); + + UsbDevice first = UsbFactory.reconnect(config); + UsbDevice second = UsbFactory.reconnect(config); + + assertThat(second).isSameInstanceAs(first); + } + + @Test + public void reconnectCachesByConfigValue() + { + /* The cache is keyed by ConfigProto value equality, so a distinct but + * equal config object must hit the same cache entry. */ + ConfigProto first = config(); + ConfigProto second = config(); + + UsbDevice a = UsbFactory.reconnect(first); + UsbDevice b = UsbFactory.reconnect(second); + + assertThat(a).isNotNull(); + assertThat(b).isSameInstanceAs(a); + } + + @Test + public void reconnectWithDifferentConfigEvictsAndClosesOldDevice() + { + ConfigProto first = config(); + ConfigProto second = new ConfigBuilder().set("drive.drive", "1").build(); + + UsbDevice a = UsbFactory.reconnect(first); + UsbDevice b = UsbFactory.reconnect(second); + + assertThat(a).isNotNull(); + assertThat(b).isNotSameInstanceAs(a); + } +} From 09412127ee52a16f93fca985a8cfa715e0ba4b4b Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 20:28:11 +0200 Subject: [PATCH 122/192] Hopefully make options work. --- .../fluxengine/config/ConfigBuilder.java | 173 +++++++++++++++ .../fluxengine/config/ConfigFlagGroup.java | 29 ++- .../config/InapplicableOptionException.java | 13 ++ .../cowlark/fluxengine/config/ProtoPath.java | 108 +++++++++- .../config/ProtoPathNotFoundException.java | 13 ++ .../com/cowlark/fluxengine/config/BUILD.bazel | 2 + .../fluxengine/config/ConfigBuilderTest.java | 204 ++++++++++++++++++ .../fluxengine/config/ProtoPathTest.java | 63 +++++- 8 files changed, 589 insertions(+), 16 deletions(-) create mode 100644 java/com/cowlark/fluxengine/config/InapplicableOptionException.java create mode 100644 java/com/cowlark/fluxengine/config/ProtoPathNotFoundException.java diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index acadc68b..0f7c04a2 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -34,6 +34,8 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.HashSet; +import java.util.Set; /** * The assembled configuration, built from the unmatched command-line @@ -44,6 +46,10 @@ public class ConfigBuilder private ConfigProto.Builder proto = ConfigProto.newBuilder() .setFluxSource(FluxSourceProto.newBuilder().setType(FLUXTYPE_DRIVE).build()); + /* The groups which have had an option applied, so that applyDefaultOptions + * knows not to apply their defaults. */ + private final Set appliedOptions = new HashSet<>(); + public ConfigBuilder() { } @@ -249,8 +255,175 @@ public ConfigBuilder set(String key, String value) return this; } + /* The result of looking up an option, ported from + * lib/config/config.h Config::OptionInfo. */ + public record OptionInfo(OptionGroupProto group, OptionProto option, boolean usesValue) + { + } + + /* Looks up an option by name, ported from Config::findOption. The group + * value parameter of the C++ version is not needed here, so it takes a + * key only. */ + public OptionInfo findOption(String name) + { + /* First look for any individual options. */ + + for (OptionProto option : proto.getOptionList()) + { + if (name.equals(option.getName())) + return new OptionInfo(null, option, false); + } + + /* Now search for individual options in unnamed groups. */ + + for (OptionGroupProto optionGroup : proto.getOptionGroupList()) + { + if (optionGroup.getName().isEmpty()) + { + for (OptionProto option : optionGroup.getOptionList()) + { + if (name.equals(option.getName())) + return new OptionInfo(optionGroup, option, false); + } + } + } + + /* Now look for named groups. A group itself is not an option; it is + * selected by supplying a value, so usesValue is true. */ + + for (OptionGroupProto optionGroup : proto.getOptionGroupList()) + { + if (name.equals(optionGroup.getName())) + return new OptionInfo(optionGroup, null, true); + } + + throw new ConfigException(String.format("option %s not found", name)); + } + + public void applyOption(OptionInfo option, String value) + { + OptionProto optionProto = option.option(); + if ((optionProto == null) && option.usesValue()) + { + /* A group with no option set means we need to select the option by + * value. */ + + for (OptionProto candidate : option.group().getOptionList()) + { + if (value.equals(candidate.getName())) + { + optionProto = candidate; + break; + } + } + + if (optionProto == null) + throw new InapplicableOptionException( + "value %s is not valid for option %s; valid values are: %s", + value, + option.group().getName(), + option.group() + .getOptionList() + .stream() + .map(OptionProto::getName) + .collect(java.util.stream.Collectors.joining(", "))); + } + + checkOptionValid(optionProto); + if (option.group() != null) + appliedOptions.add(option.group()); + proto.mergeFrom(optionProto.getConfig()); + } + + /* Applies the default option for every group which doesn't have one set, + * ported from Config::applyDefaultOptions. */ + private void applyDefaultOptions() + { + for (OptionGroupProto group : proto.getOptionGroupList()) + { + if (!appliedOptions.contains(group)) + { + for (OptionProto option : group.getOptionList()) + { + if (option.getSetByDefault()) + { + checkOptionValid(option); + appliedOptions.add(group); + proto.mergeFrom(option.getConfig()); + } + } + } + } + } + + private void checkOptionValid(OptionProto optionProto) + { + for (OptionPrerequisiteProto req : optionProto.getPrerequisiteList()) + { + boolean matched = false; + try + { + String value = ProtoPath.get(proto, req.getKey()); + for (String requiredValue : req.getValueList()) + matched |= requiredValue.equals(value); + } catch (ProtoPathNotFoundException e) + { + /* This field isn't available, therefore it cannot match. */ + } + + if (!matched) + { + StringBuilder ss = new StringBuilder(); + ss.append('['); + boolean first = true; + for (String requiredValue : req.getValueList()) + { + if (!first) + ss.append(", "); + ss.append(quote(requiredValue)); + first = false; + } + ss.append(']'); + + throw new InapplicableOptionException( + "option '%s' is inapplicable to this configuration " + + "because %s=%s could not be met", + optionProto.getName(), + req.getKey(), + ss.toString()); + } + } + } + + /* Quotes a string if it contains spaces or quote characters, ported from + * lib/core/utils.cc quote(). */ + private static String quote(String s) + { + boolean spaces = s.contains(" "); + if (!spaces && !s.contains("\\") && !s.contains("'") && !s.contains("\"")) + return s; + + StringBuilder ss = new StringBuilder(); + if (spaces) + ss.append('"'); + + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + if ((c == '\\') || (c == '"') || (c == '!')) + ss.append('\\'); + ss.append(c); + } + + if (spaces) + ss.append('"'); + + return ss.toString(); + } + public ConfigProto build() { + applyDefaultOptions(); validate(); return proto.build(); } diff --git a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java index 7320ab73..0283a62f 100644 --- a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java +++ b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java @@ -30,13 +30,32 @@ public ConfigFlagGroup(ConfigBuilder builder) @Override public Flag findFlag(String key) { - if (key.startsWith("--") && key.contains(".")) + if (key.startsWith("--")) { String path = key.substring(2); - return ActionFlag.builder() - .setGroup(this) - .setValueCallback(value -> builder.set(path, value)) - .build(); + if (key.contains(".")) + { + /* Dots: setting a config key. */ + return ActionFlag.builder() + .setGroup(this) + .setValueCallback(value -> builder.set(path, value)) + .build(); + } else + { + /* No dots: this is an option name; look it up (throws if + * unknown). */ + ConfigBuilder.OptionInfo option = builder.findOption(path); + if (option.usesValue()) + return ActionFlag.builder() + .setGroup(this) + .setValueCallback(arg -> builder.applyOption(option, arg)) + .build(); + else + return ActionFlag.builder() + .setGroup(this) + .setVoidCallback(() -> builder.applyOption(option, null)) + .build(); + } } return super.findFlag(key); } diff --git a/java/com/cowlark/fluxengine/config/InapplicableOptionException.java b/java/com/cowlark/fluxengine/config/InapplicableOptionException.java new file mode 100644 index 00000000..f15e5ea4 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/InapplicableOptionException.java @@ -0,0 +1,13 @@ +package com.cowlark.fluxengine.config; + +/** + * Thrown when an option cannot be applied to the current configuration, + * ported from lib/config/config.h. + */ +public class InapplicableOptionException extends ConfigException +{ + public InapplicableOptionException(String message, Object... args) + { + super(String.format(message, args)); + } +} diff --git a/java/com/cowlark/fluxengine/config/ProtoPath.java b/java/com/cowlark/fluxengine/config/ProtoPath.java index de45d935..457d77ec 100644 --- a/java/com/cowlark/fluxengine/config/ProtoPath.java +++ b/java/com/cowlark/fluxengine/config/ProtoPath.java @@ -27,6 +27,98 @@ public static void set(Message.Builder builder, String path, String value) setRecursive(builder, components, 0, value, path); } + /* Resolves a dotted path against a message and returns the leaf value as + * a string, ported from lib/config/proto.cc's findProtoPath/get. */ + public static String get(Message.Builder builder, String path) + { + List components = parsePath(path); + return getRecursive(builder, components, 0, path); + } + + private static String getRecursive(Message.Builder builder, + List path, + int pos, + String originalPath) + { + PathComponent component = path.get(pos); + FieldDescriptor field = findField(builder, component, originalPath); + + if (pos == path.size() - 1) + { + return getLeaf(builder, component, field); + } + + if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) + throw new ProtoPathNotFoundException( + "config field '" + component.name() + "' in '" + originalPath + + "' is not a message"); + + Message.Builder elementBuilder; + if (field.isRepeated()) + { + int index = requireIndex(component, field); + if (builder.getRepeatedFieldCount(field) <= index) + throw new ProtoPathNotFoundException( + "could not find config field '" + field.getName() + "'"); + Message element = (Message) builder.getRepeatedField(field, index); + elementBuilder = element.toBuilder(); + } else + { + if (component.index() >= 0) + throw new ProtoPathNotFoundException( + "config field '" + component.name() + + "' is not repeated but an index is provided"); + if (!builder.hasField(field)) + throw new ProtoPathNotFoundException( + "could not find config field '" + field.getName() + "'"); + elementBuilder = ((Message) builder.getField(field)).toBuilder(); + } + return getRecursive(elementBuilder, path, pos + 1, originalPath); + } + + private static String getLeaf(Message.Builder builder, + PathComponent component, + FieldDescriptor field) + { + if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE) + throw new ConfigException("config field '" + component.name() + + "' is a message and can't be directly fetched"); + + Object value; + if (field.isRepeated()) + { + int index = requireIndex(component, field); + if (builder.getRepeatedFieldCount(field) <= index) + throw new ProtoPathNotFoundException( + "could not find config field '" + field.getName() + "'"); + value = builder.getRepeatedField(field, index); + } else + { + if (component.index() >= 0) + throw new ProtoPathNotFoundException( + "config field '" + component.name() + + "' is not repeated but an index is provided"); + value = builder.getField(field); + } + return formatValue(field, value); + } + + private static String formatValue(FieldDescriptor field, Object value) + { + switch (field.getType()) + { + case FLOAT: + case DOUBLE: + return String.valueOf(value); + case BOOL: + return String.valueOf(value); + case ENUM: + return ((EnumValueDescriptor) value).getName(); + default: + return String.valueOf(value); + } + } + private static List parsePath(String path) { List components = new ArrayList<>(); @@ -59,7 +151,7 @@ private static void setRecursive(Message.Builder builder, } if (field.getJavaType() != FieldDescriptor.JavaType.MESSAGE) - throw new ConfigException( + throw new ProtoPathNotFoundException( "config field '" + component.name() + "' in '" + originalPath + "' is not a message"); @@ -74,8 +166,9 @@ private static void setRecursive(Message.Builder builder, } else { if (component.index() >= 0) - throw new ConfigException("config field '" + component.name() + - "' is not repeated but an index is provided"); + throw new ProtoPathNotFoundException( + "config field '" + component.name() + + "' is not repeated but an index is provided"); Message.Builder elementBuilder; if (builder.hasField(field)) elementBuilder = ((Message) builder.getField(field)).toBuilder(); @@ -105,8 +198,9 @@ private static void setLeaf(Message.Builder builder, } else { if (component.index() >= 0) - throw new ConfigException("config field '" + component.name() + - "' is not repeated but an index is provided"); + throw new ProtoPathNotFoundException( + "config field '" + component.name() + + "' is not repeated but an index is provided"); builder.setField(field, coerced); } } @@ -117,7 +211,7 @@ private static FieldDescriptor findField(Message.Builder builder, { FieldDescriptor field = builder.getDescriptorForType().findFieldByName(component.name()); if (field == null) - throw new ConfigException( + throw new ProtoPathNotFoundException( "no such config field '" + component.name() + "' in '" + path + "'"); return field; } @@ -125,7 +219,7 @@ private static FieldDescriptor findField(Message.Builder builder, private static int requireIndex(PathComponent component, FieldDescriptor field) { if (component.index() < 0) - throw new ConfigException( + throw new ProtoPathNotFoundException( "config field '" + component.name() + "' is repeated and must be indexed"); return component.index(); } diff --git a/java/com/cowlark/fluxengine/config/ProtoPathNotFoundException.java b/java/com/cowlark/fluxengine/config/ProtoPathNotFoundException.java new file mode 100644 index 00000000..1a1a7c5e --- /dev/null +++ b/java/com/cowlark/fluxengine/config/ProtoPathNotFoundException.java @@ -0,0 +1,13 @@ +package com.cowlark.fluxengine.config; + +/** + * Thrown when a config path cannot be resolved against a protobuf, ported + * from lib/config/proto.h. + */ +public class ProtoPathNotFoundException extends ConfigException +{ + public ProtoPathNotFoundException(String message) + { + super(message); + } +} diff --git a/javatests/com/cowlark/fluxengine/config/BUILD.bazel b/javatests/com/cowlark/fluxengine/config/BUILD.bazel index 1bba9f76..d82aba50 100644 --- a/javatests/com/cowlark/fluxengine/config/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/config/BUILD.bazel @@ -9,8 +9,10 @@ java_test( "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/config:drive_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/core/flags", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", "@maven//:com_google_guava_guava", "@maven//:com_google_truth_truth", "@maven//:junit_junit", diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index 975a55ae..2cf7a15e 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -3,6 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.google.common.collect.ImmutableList; import org.junit.Test; @@ -66,6 +67,209 @@ public void loadConfigFileLoadsBuiltInFormatByName() assertThat(proto.getShortname()).isEqualTo("Amiga"); } + @Test + public void findOptionLooksUpTopLevelOption() + { + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + ConfigBuilder.OptionInfo info = builder.findOption("hd"); + + assertThat(info.option().getName()).isEqualTo("hd"); + assertThat(info.group()).isNull(); + assertThat(info.usesValue()).isFalse(); + } + + @Test + public void buildAppliesDefaultOptions() + { + /* _global_options drivetype group has 80 set by default. */ + ConfigProto proto = builder().loadConfigFile("_global_options").build(); + + assertThat(proto.getDrive().getTracks()).isEqualTo("c0-80h0-1"); + assertThat(proto.getDrive().getDriveType()) + .isEqualTo(com.cowlark.fluxengine.external.DriveType.DRIVETYPE_80TRACK); + } + + @Test + public void buildDoesNotApplyDefaultForAppliedGroup() + { + /* If drivetype=40 is applied explicitly, the default (80) must not + * also be applied. */ + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + ConfigBuilder.OptionInfo info = builder.findOption("drivetype"); + builder.applyOption(info, "40"); + + ConfigProto proto = builder.build(); + + assertThat(proto.getDrive().getTracks()).isEqualTo("c0-40h0-1"); + } + + @Test + public void findOptionLooksUpOptionInUnnamedGroup() + { + ConfigBuilder builder = builder().loadConfigFile("amiga"); + + ConfigBuilder.OptionInfo info = builder.findOption("without_metadata"); + + assertThat(info.option().getName()).isEqualTo("without_metadata"); + assertThat(info.group()).isNotNull(); + assertThat(info.usesValue()).isFalse(); + } + + @Test + public void findOptionMissingThrows() + { + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + assertThrows(ConfigException.class, () -> builder.findOption("no such option")); + } + + @Test + public void fromFlagsLooksUpOption() + { + /* --hd is a top-level option in _global_options; without a dot it is + * looked up as an option rather than a config path. */ + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + builder.fromFlags(ImmutableList.of("--hd"), new FlagGroup()); + + assertThat(builder.findOption("hd").option().getName()).isEqualTo("hd"); + } + + @Test + public void fromFlagsUnknownOptionThrows() + { + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + assertThrows( + FluxEngineException.class, + () -> builder.fromFlags(ImmutableList.of("--no-such-option"), new FlagGroup())); + } + + @Test + public void fromFlagsOptionWithoutValue() + { + /* --hd is a top-level option with no value in _global_options. */ + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + builder.fromFlags(ImmutableList.of("--hd"), new FlagGroup()); + + assertThat(builder.findOption("hd").usesValue()).isFalse(); + } + + @Test + public void fromFlagsOptionWithValue() + { + /* --drivetype is a top-level option with a value in _global_options. */ + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + builder.fromFlags(ImmutableList.of("--drivetype=80"), new FlagGroup()); + + assertThat(builder.findOption("drivetype").usesValue()).isTrue(); + } + + @Test + public void fromFlagsConfigKeySetsValue() + { + /* A dotted key is a config path, not an option. */ + ConfigBuilder builder = builder(); + + builder.fromFlags(ImmutableList.of("--drive.drive=1"), new FlagGroup()); + + assertThat(builder.build().getDrive().getDrive()).isEqualTo(1); + } + + @Test + public void applyOptionIsCallable() + { + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + ConfigBuilder.OptionInfo info = builder.findOption("hd"); + builder.applyOption(info, null); + } + + @Test + public void applyOptionGroupSelectsOptionByValue() + { + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + ConfigBuilder.OptionInfo info = builder.findOption("drivetype"); + builder.applyOption(info, "80"); + } + + @Test + public void applyOptionGroupWithInvalidValueThrows() + { + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + ConfigBuilder.OptionInfo info = builder.findOption("drivetype"); + assertThrows( + ConfigException.class, + () -> builder.applyOption(info, "bogus")); + } + + @Test + public void checkOptionValidAppliesWhenPrerequisiteMet() throws Exception + { + Path file = Files.createTempFile("config", ".textproto"); + Files.writeString(file, """ + option { + name: "needs_serial" + prerequisite { + key: "usb.serial" + value: "test-serial" + } + config { + comment: "applied" + } + } + """); + + ConfigBuilder builder = builder().loadConfigFile(file.toString()).set("usb.serial", "test-serial"); + ConfigBuilder.OptionInfo info = builder.findOption("needs_serial"); + builder.applyOption(info, null); + + assertThat(builder.build().getComment()).isEqualTo("applied"); + } + + @Test + public void checkOptionValidThrowsWhenPrerequisiteNotMet() throws Exception + { + Path file = Files.createTempFile("config", ".textproto"); + Files.writeString(file, """ + option { + name: "needs_serial" + prerequisite { + key: "usb.serial" + value: "test-serial" + } + config { + comment: "applied" + } + } + """); + + ConfigBuilder builder = builder().loadConfigFile(file.toString()).set("usb.serial", "other"); + ConfigBuilder.OptionInfo info = builder.findOption("needs_serial"); + assertThrows( + InapplicableOptionException.class, + () -> builder.applyOption(info, null)); + } + + @Test + public void findOptionNamedGroupReturnsUsesValue() + { + /* Named groups (drivetype, drivespeed, bus) are found, but they select + * an option by value, so usesValue is true and no option is set. */ + ConfigBuilder builder = builder().loadConfigFile("_global_options"); + + ConfigBuilder.OptionInfo info = builder.findOption("drivetype"); + + assertThat(info.group()).isNotNull(); + assertThat(info.option()).isNull(); + assertThat(info.usesValue()).isTrue(); + } + @Test public void loadConfigFileLoadsBuiltInFormatBeforeFile() { diff --git a/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java b/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java index 10f21e71..42e88542 100644 --- a/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java +++ b/javatests/com/cowlark/fluxengine/config/ProtoPathTest.java @@ -18,6 +18,61 @@ private static ConfigProto set(String path, String value) return builder.build(); } + private static String get(String path) + { + return ProtoPath.get(ConfigProto.newBuilder(), path); + } + + @Test + public void getTopLevelString() + { + assertThat(get("tracks")).isEqualTo(""); + } + + @Test + public void getNestedInt() + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, "drive.drive", "5"); + assertThat(ProtoPath.get(builder, "drive.drive")).isEqualTo("5"); + } + + @Test + public void getNestedBool() + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, "drive.high_density", "true"); + assertThat(ProtoPath.get(builder, "drive.high_density")).isEqualTo("true"); + } + + @Test + public void getNestedEnum() + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, "drive.drive_type", "DRIVETYPE_80TRACK"); + assertThat(ProtoPath.get(builder, "drive.drive_type")).isEqualTo("DRIVETYPE_80TRACK"); + } + + @Test + public void getRepeatedStringWithIndex() + { + ConfigProto.Builder builder = ConfigProto.newBuilder(); + ProtoPath.set(builder, "documentation[2]", "hello"); + assertThat(ProtoPath.get(builder, "documentation[2]")).isEqualTo("hello"); + } + + @Test + public void getUnknownFieldThrows() + { + assertThrows(ProtoPathNotFoundException.class, () -> get("bogus")); + } + + @Test + public void getUnknownNestedFieldThrows() + { + assertThrows(ProtoPathNotFoundException.class, () -> get("drive.bogus")); + } + @Test public void setTopLevelString() { @@ -90,13 +145,13 @@ public void setRepeatedMessageFieldsMerge() @Test public void setUnknownFieldThrows() { - assertThrows(ConfigException.class, () -> set("bogus", "x")); + assertThrows(ProtoPathNotFoundException.class, () -> set("bogus", "x")); } @Test public void setUnknownNestedFieldThrows() { - assertThrows(ConfigException.class, () -> set("drive.bogus", "x")); + assertThrows(ProtoPathNotFoundException.class, () -> set("drive.bogus", "x")); } @Test @@ -120,12 +175,12 @@ public void setBadEnumThrows() @Test public void setRepeatedWithoutIndexThrows() { - assertThrows(ConfigException.class, () -> set("documentation", "x")); + assertThrows(ProtoPathNotFoundException.class, () -> set("documentation", "x")); } @Test public void setIndexOnScalarThrows() { - assertThrows(ConfigException.class, () -> set("tracks[0]", "x")); + assertThrows(ProtoPathNotFoundException.class, () -> set("tracks[0]", "x")); } } From 4841b10f68b484edfc07ecce21f4400a56d0deeb Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 21:45:45 +0200 Subject: [PATCH 123/192] Attempt to fix the logger rendering. --- .../fluxengine/core/DefaultLogRenderer.java | 90 +++++++++++++++++++ .../cowlark/fluxengine/core/LogMessage.java | 63 +++++++------ .../cowlark/fluxengine/core/LogRenderer.java | 29 ++---- java/com/cowlark/fluxengine/core/Logger.java | 6 +- 4 files changed, 128 insertions(+), 60 deletions(-) create mode 100644 java/com/cowlark/fluxengine/core/DefaultLogRenderer.java diff --git a/java/com/cowlark/fluxengine/core/DefaultLogRenderer.java b/java/com/cowlark/fluxengine/core/DefaultLogRenderer.java new file mode 100644 index 00000000..76b1409e --- /dev/null +++ b/java/com/cowlark/fluxengine/core/DefaultLogRenderer.java @@ -0,0 +1,90 @@ +package com.cowlark.fluxengine.core; + +import java.io.PrintStream; + +class DefaultLogRenderer extends LogRenderer +{ + private final PrintStream stream; + private boolean header = false; + private boolean newline = false; + private boolean space = false; + private int lineLen = 0; + + DefaultLogRenderer(PrintStream stream) + { + this.stream = stream; + } + + private void indent() + { + stream.print(" "); + lineLen = 7; + space = true; + } + + @Override + public LogRenderer add(String message) + { + if (newline && !header) + indent(); + + if (!space) + { + stream.print(' '); + lineLen++; + } + + newline = false; + header = false; + + lineLen += message.length(); + if (lineLen >= 80) + { + stream.println(); + indent(); + } + stream.print(message); + space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); + return this; + } + + @Override + public LogRenderer header(String message) + { + if (!newline) + stream.println(); + stream.print(message); + lineLen = message.length(); + header = true; + newline = true; + space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); + return this; + } + + @Override + public LogRenderer comma() + { + if (!newline || header) + { + stream.print(';'); + space = false; + } + return this; + } + + @Override + public LogRenderer newline() + { + if (!header) + { + if (!newline) + stream.println(); + + lineLen = 0; + header = false; + newline = true; + space = true; + } + return this; + } +} diff --git a/java/com/cowlark/fluxengine/core/LogMessage.java b/java/com/cowlark/fluxengine/core/LogMessage.java index 25feafeb..0220c769 100644 --- a/java/com/cowlark/fluxengine/core/LogMessage.java +++ b/java/com/cowlark/fluxengine/core/LogMessage.java @@ -1,131 +1,128 @@ package com.cowlark.fluxengine.core; /** - * A log message, ported from lib/core/logger.h. This is a marker interface; - * each message type renders itself to a string via {@link #render()}. + * A log message, ported from lib/core/logger.h. Each message type renders + * itself to a LogRenderer. */ public interface LogMessage { - /* Fallback rendering of this message to a string. */ - String render(); + /* Renders this message. */ + void render(LogRenderer r); record StringMessage(String message) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return message; + r.newline().add(message).newline(); } } record ErrorLogMessage(String message) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return message; + r.newline().add("Error:").add(message).newline(); } } record EmergencyStopMessage() implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return ""; + r.newline().add("Stop!").newline(); } } record BeginSpeedOperationLogMessage() implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return "Measuring rotational speed..."; + r.newline().add("Measuring rotational speed...").newline(); } } record EndSpeedOperationLogMessage(double rotationalPeriodNs) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return String.format( - "Rotational period is %.1fms (%.1frpm)", - rotationalPeriodNs / 1e6, - 60e9 / rotationalPeriodNs); + r.newline() + .add(String.format( + "Rotational period is %.1fms (%.1frpm)", + rotationalPeriodNs / 1e6, + 60e9 / rotationalPeriodNs)) + .newline(); } } record BeginReadOperationLogMessage(int track, int head) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return String.format("R%2d.%d", track, head); + r.header(String.format("R%2d.%d: ", track, head)); } } record EndReadOperationLogMessage() implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return ""; } } record BeginWriteOperationLogMessage(int track, int head) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return String.format("W%2d.%d", track, head); + r.header(String.format("W%2d.%d: ", track, head)); } } record EndWriteOperationLogMessage() implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return ""; } } record BeginOperationLogMessage(String message) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return message; } } record EndOperationLogMessage(String message) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return message; } } record OperationProgressLogMessage(int progress) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return ""; } } record OptionLogMessage(String message) implements LogMessage { @Override - public String render() + public void render(LogRenderer r) { - return message; + r.newline().add("OPTION:").add(message).newline(); } } } diff --git a/java/com/cowlark/fluxengine/core/LogRenderer.java b/java/com/cowlark/fluxengine/core/LogRenderer.java index f506fd68..234260f2 100644 --- a/java/com/cowlark/fluxengine/core/LogRenderer.java +++ b/java/com/cowlark/fluxengine/core/LogRenderer.java @@ -1,12 +1,5 @@ package com.cowlark.fluxengine.core; -import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.BeginSpeedOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.BeginWriteOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.EmergencyStopMessage; -import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.ErrorLogMessage; -import com.cowlark.fluxengine.core.LogMessage.OptionLogMessage; import java.io.PrintStream; /** @@ -21,19 +14,8 @@ public static LogRenderer create(PrintStream stream) public LogRenderer add(LogMessage message) { - return switch (message) - { - case ErrorLogMessage msg -> newline().add("Error:").add(msg.render()).newline(); - case EmergencyStopMessage msg -> newline().add("Stop!").newline(); - case BeginSpeedOperationLogMessage msg -> newline().add(msg.render()).newline(); - case EndSpeedOperationLogMessage msg -> newline().add(msg.render()).newline(); - case BeginReadOperationLogMessage msg -> - header(String.format("R%2d.%d: ", msg.track(), msg.head())); - case BeginWriteOperationLogMessage msg -> - header(String.format("W%2d.%d: ", msg.track(), msg.head())); - case OptionLogMessage msg -> newline().add("OPTION:").add(msg.render()).newline(); - default -> newline().add(message.render()).newline(); - }; + message.render(this); + return this; } public abstract LogRenderer add(String message); @@ -86,8 +68,7 @@ public LogRenderer add(String message) indent(); } stream.print(message); - space = !message.isEmpty() && - Character.isWhitespace(message.charAt(message.length() - 1)); + space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); return this; } @@ -100,8 +81,7 @@ public LogRenderer header(String message) lineLen = message.length(); header = true; newline = true; - space = !message.isEmpty() && - Character.isWhitespace(message.charAt(message.length() - 1)); + space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); return this; } @@ -132,4 +112,5 @@ public LogRenderer newline() return this; } } + } diff --git a/java/com/cowlark/fluxengine/core/Logger.java b/java/com/cowlark/fluxengine/core/Logger.java index ee8512fc..c8dbf939 100644 --- a/java/com/cowlark/fluxengine/core/Logger.java +++ b/java/com/cowlark/fluxengine/core/Logger.java @@ -9,15 +9,15 @@ public final class Logger { private static Consumer loggerImpl = - message -> LogRenderer.create(System.out).add(message); + new DefaultLogRenderer(System.out)::add; private Logger() { } - public static void log(String message) + public static void log(String message, Object... args) { - log(new StringMessage(message)); + log(new StringMessage(String.format(message, args))); } public static void log(LogMessage message) From b3be6daace3c5d94c1b1da7a1165213d3f6b6381 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 22:32:58 +0200 Subject: [PATCH 124/192] Port AmigaEncoder. --- java/com/cowlark/fluxengine/arch/BUILD.bazel | 2 + .../fluxengine/arch/amiga/AmigaEncoder.java | 148 ++++++++++++++++++ java/com/cowlark/fluxengine/core/Bits.java | 53 +++++++ .../cowlark/fluxengine/encoders/BUILD.bazel | 13 ++ .../cowlark/fluxengine/encoders/Encoder.java | 67 ++++++++ .../arch/amiga/AmigaEncoderTest.java | 66 ++++++++ .../cowlark/fluxengine/arch/amiga/BUILD.bazel | 15 ++ .../com/cowlark/fluxengine/core/BitsTest.java | 43 +++++ .../cowlark/fluxengine/encoders/BUILD.bazel | 18 +++ .../fluxengine/encoders/EncoderTest.java | 109 +++++++++++++ 10 files changed, 534 insertions(+) create mode 100644 java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java create mode 100644 java/com/cowlark/fluxengine/encoders/Encoder.java create mode 100644 javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java create mode 100644 javatests/com/cowlark/fluxengine/encoders/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/encoders/EncoderTest.java diff --git a/java/com/cowlark/fluxengine/arch/BUILD.bazel b/java/com/cowlark/fluxengine/arch/BUILD.bazel index fc447377..32e13713 100644 --- a/java/com/cowlark/fluxengine/arch/BUILD.bazel +++ b/java/com/cowlark/fluxengine/arch/BUILD.bazel @@ -29,6 +29,8 @@ java_library( "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/decoders", "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/encoders", + "//java/com/cowlark/fluxengine/encoders:encoders_java_proto", "//java/com/cowlark/fluxengine/external", ], ) diff --git a/java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java b/java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java new file mode 100644 index 00000000..13cfeddc --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java @@ -0,0 +1,148 @@ +package com.cowlark.fluxengine.arch.amiga; + +import com.cowlark.fluxengine.amiga.AmigaEncoderProto; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.FmMfm; +import java.util.List; + +/** + * The Amiga encoder, ported from arch/amiga/encoder.cc. + */ +public class AmigaEncoder extends Encoder +{ + private final ConfigProto fullConfig; + private final AmigaEncoderProto config; + private final boolean[] lastBit = new boolean[1]; + + public AmigaEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getAmiga(); + } + + private void writeBits(Bits bits, Bits.Cursor cursor, boolean[] src) + { + for (boolean bit : src) + { + if (cursor.get() < bits.size()) + { + lastBit[0] = bit; + bits.setBit(cursor.get(), bit); + cursor.advance(); + } + } + } + + private void writeBits(Bits bits, Bits.Cursor cursor, long data, int width) + { + cursor.advance(width); + lastBit[0] = (data & 1) != 0; + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private void writeBits(Bits bits, Bits.Cursor cursor, Bytes bytes) + { + Bits bitr = bytes.toBits(); + for (int i = 0; i < bitr.size(); i++) + { + if (cursor.get() < bits.size()) + { + bits.setBit(cursor.get(), bitr.getBit(i)); + cursor.advance(); + } + } + } + + private void writeInterleavedBytes(Bits bits, Bits.Cursor cursor, Bytes bytes, int[] checksum) + { + Bytes interleaved = Amiga.amigaInterleave(bytes); + Bytes mfm = FmMfm.encodeMfm(interleaved, lastBit); + checksum[0] ^= Amiga.amigaChecksum(mfm); + checksum[0] &= 0x55555555; + writeBits(bits, cursor, mfm); + } + + private void writeInterleavedWord(Bits bits, Bits.Cursor cursor, int word, int[] checksum) + { + Bytes b = new Bytes(4); + b.writer().writeBe32(word); + writeInterleavedBytes(bits, cursor, b, checksum); + } + + private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) + { + if ((sector.data.size() != 512) && (sector.data.size() != 528)) + throw new FluxEngineException("unsupported sector size --- you must pick 512 or 528"); + + int[] checksum = {0}; + + writeBits(bits, cursor, 0xaaaa, 2 * 8); + writeBits(bits, cursor, Amiga.AMIGA_SECTOR_RECORD, 6 * 8); + + Bytes header = Bytes.of( + 0xff, /* Amiga 1.0 format byte */ + (sector.location.logicalCylinder() << 1) | sector.location.logicalHead(), + sector.location.logicalSector(), + Amiga.AMIGA_SECTORS_PER_TRACK - sector.location.logicalSector()); + writeInterleavedBytes(bits, cursor, header, checksum); + Bytes recoveryInfo = new Bytes(16); + if (sector.data.size() == 528) + recoveryInfo = sector.data.slice(512, 16); + writeInterleavedBytes(bits, cursor, recoveryInfo, checksum); + writeInterleavedWord(bits, cursor, checksum[0], checksum); + + Bytes data = sector.data.slice(0, 512); + writeInterleavedWord( + bits, + cursor, + Amiga.amigaChecksum(FmMfm.encodeMfm(Amiga.amigaInterleave(data), lastBit)), + checksum); + writeInterleavedBytes(bits, cursor, data, checksum); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + /* Number of bits for one nominal revolution of a real 200ms Amiga + * disk. */ + int bitsPerRevolution = (int) (200e3 / config.getClockRateUs()); + Bits bits = new Bits(bitsPerRevolution); + Bits.Cursor cursor = new Bits.Cursor(0); + + bits.fillBitmapTo( + cursor, + (int) (config.getPostIndexGapMs() * 1000 / config.getClockRateUs()), + new boolean[]{true, false}); + lastBit[0] = false; + + for (Sector sector : sectors) + writeSector(bits, cursor, sector); + + if (cursor.get() >= bits.size()) + throw new FluxEngineException("track data overrun"); + bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod( + fullConfig, + config.getClockRateUs() * 1e3, + 200e6)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/core/Bits.java b/java/com/cowlark/fluxengine/core/Bits.java index acd66d61..445ee373 100644 --- a/java/com/cowlark/fluxengine/core/Bits.java +++ b/java/com/cowlark/fluxengine/core/Bits.java @@ -118,9 +118,62 @@ public Bytes toBytes() return bytes; } + /* Fills this Bits from the cursor's current position up to (but not + * including) terminateAt with the given pattern, advancing the cursor. */ + public void fillBitmapTo(Cursor cursor, int terminateAt, boolean[] pattern) + { + while (cursor.get() < terminateAt) + { + for (boolean b : pattern) + { + if (cursor.get() < size) + { + setBit(cursor.get(), b); + cursor.advance(); + } + } + } + } + private void checkIndex(int index) { if (index < 0 || index >= size) throw new IndexOutOfBoundsException(String.valueOf(index)); } + + /** + * A mutable cursor into a {@link Bits}, providing the in/out semantics of the + * C++ {@code unsigned& cursor} parameter passed to the bit-writing helpers. + * The current position is held directly, so a single cursor can be shared and + * advanced by successive calls. + */ + public static final class Cursor + { + private int index; + + public Cursor(int index) + { + this.index = index; + } + + public int get() + { + return index; + } + + public void set(int value) + { + index = value; + } + + public void advance() + { + index++; + } + + public void advance(int delta) + { + index += delta; + } + } } diff --git a/java/com/cowlark/fluxengine/encoders/BUILD.bazel b/java/com/cowlark/fluxengine/encoders/BUILD.bazel index 19616040..66e4fa28 100644 --- a/java/com/cowlark/fluxengine/encoders/BUILD.bazel +++ b/java/com/cowlark/fluxengine/encoders/BUILD.bazel @@ -1,4 +1,5 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) @@ -14,3 +15,15 @@ java_proto_library( name = "encoders_java_proto", deps = [":encoders_proto"], ) + +java_library( + name = "encoders", + srcs = glob(["*.java"]), + deps = [ + ":encoders_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_guava_guava", + ], +) diff --git a/java/com/cowlark/fluxengine/encoders/Encoder.java b/java/com/cowlark/fluxengine/encoders/Encoder.java new file mode 100644 index 00000000..1013db7f --- /dev/null +++ b/java/com/cowlark/fluxengine/encoders/Encoder.java @@ -0,0 +1,67 @@ +package com.cowlark.fluxengine.encoders; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.google.common.collect.ImmutableList; +import java.util.ArrayList; +import java.util.List; + +/** + * A track encoder, ported from lib/encoders/encoders.{h,cc}. + */ +public abstract class Encoder +{ + public static Encoder create(ConfigProto config) + { + throw new FluxEngineException("encoders are not implemented yet"); + } + + public Sector getSector(CylinderHead ch, Image image, int sectorId) + { + return image.get(ch.cylinder(), ch.head(), sectorId); + } + + public ImmutableList collectSectors(LogicalTrackLayout ltl, Image image) + { + ImmutableList.Builder sectors = ImmutableList.builder(); + + for (int sectorId : ltl.diskSectorOrder) + { + Sector sector = getSector( + new CylinderHead(ltl.logicalCylinder, ltl.logicalHead), + image, + sectorId); + if (sector == null) + throw new FluxEngineException(String.format( + "sector %d.%d.%d is missing from the image", + ltl.logicalCylinder, + ltl.logicalHead, + sectorId)); + sectors.add(sector); + } + + return sectors.build(); + } + + public abstract Fluxmap encode( + LogicalTrackLayout ltl, List sectors, Image image); + + public double calculatePhysicalClockPeriod(ConfigProto config, + double targetClockPeriod, double targetRotationalPeriod) + { + double currentRotationalPeriod = + config.getDrive().getRotationalPeriodMs() * 1e6; + if (currentRotationalPeriod == 0) + throw new FluxEngineException( + "you must set --drive.rotational_period_ms as it can't be " + + "autodetected"); + + return targetClockPeriod * + (currentRotationalPeriod / targetRotationalPeriod); + } +} diff --git a/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java new file mode 100644 index 00000000..a255f834 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java @@ -0,0 +1,66 @@ +package com.cowlark.fluxengine.arch.amiga; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class AmigaEncoderTest +{ + private ConfigProto makeConfig() + { + return new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("encoder.amiga.clock_rate_us", "2.0") + .build(); + } + + @Test + public void encodeProducesPulses() + { + ConfigProto config = makeConfig(); + AmigaEncoder encoder = new AmigaEncoder(config); + + Image image = new Image(); + Sector sector = image.put(0, 0, 0); + sector.data = new Bytes(512); + + List sectors = ImmutableList.of(sector); + + Fluxmap fluxmap = encoder.encode(null, sectors, image); + + assertThat(fluxmap.ticks()).isGreaterThan(0); + assertThat(fluxmap.bytes()).isGreaterThan(0); + } + + @Test + public void encodeRejectsBadSectorSize() + { + ConfigProto config = makeConfig(); + AmigaEncoder encoder = new AmigaEncoder(config); + + Image image = new Image(); + Sector sector = image.put(0, 0, 0); + sector.data = new Bytes(511); + + List sectors = ImmutableList.of(sector); + + FluxEngineException e = assertThrows( + FluxEngineException.class, + () -> encoder.encode(null, sectors, image)); + assertThat(e.getMessage()).contains("unsupported sector size"); + } +} diff --git a/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel b/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel index 033cfc33..bf6ff0e1 100644 --- a/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel @@ -12,3 +12,18 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "AmigaEncoderTest", + srcs = ["AmigaEncoderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/arch", + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/core/BitsTest.java b/javatests/com/cowlark/fluxengine/core/BitsTest.java index c31b88da..d33cda18 100644 --- a/javatests/com/cowlark/fluxengine/core/BitsTest.java +++ b/javatests/com/cowlark/fluxengine/core/BitsTest.java @@ -137,4 +137,47 @@ public void toBytesRoundTrip() Bytes bytes = Bytes.of(0xd6, 0xa5); assertThat(bytes.toBits().toBytes()).isEqualTo(bytes); } + + @Test + public void fillBitmapToPattern() + { + Bits bits = new Bits(4); + Bits.Cursor cursor = new Bits.Cursor(0); + + bits.fillBitmapTo(cursor, 4, new boolean[] {true, false}); + + assertThat(cursor.get()).isEqualTo(4); + assertThat(bits.get(0)).isTrue(); + assertThat(bits.get(1)).isFalse(); + assertThat(bits.get(2)).isTrue(); + assertThat(bits.get(3)).isFalse(); + } + + @Test + public void fillBitmapToRespectsTerminateAt() + { + Bits bits = new Bits(10); + Bits.Cursor cursor = new Bits.Cursor(3); + + bits.fillBitmapTo(cursor, 7, new boolean[] {false, true}); + + assertThat(cursor.get()).isEqualTo(7); + assertThat(bits.get(3)).isFalse(); + assertThat(bits.get(4)).isTrue(); + assertThat(bits.get(5)).isFalse(); + assertThat(bits.get(6)).isTrue(); + } + + @Test + public void fillBitmapToStopAtSize() + { + /* The bitmap ends at terminateAt; filling must stop exactly there. */ + Bits bits = new Bits(5); + Bits.Cursor cursor = new Bits.Cursor(0); + + bits.fillBitmapTo(cursor, 5, new boolean[] {true}); + + assertThat(cursor.get()).isEqualTo(5); + assertThat(bits.get(4)).isTrue(); + } } diff --git a/javatests/com/cowlark/fluxengine/encoders/BUILD.bazel b/javatests/com/cowlark/fluxengine/encoders/BUILD.bazel new file mode 100644 index 00000000..468e7963 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/encoders/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "EncoderTest", + srcs = ["EncoderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/encoders", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java b/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java new file mode 100644 index 00000000..9251a975 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java @@ -0,0 +1,109 @@ +package com.cowlark.fluxengine.encoders; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.google.common.collect.ImmutableList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class EncoderTest +{ + private static final class TestEncoder extends Encoder + { + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + return new Fluxmap(); + } + } + + @Test + public void createThrowsNotImplemented() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .build(); + + assertThrows(FluxEngineException.class, () -> Encoder.create(config)); + } + + @Test + public void collectSectorsCollectsInDiskOrder() + { + /* A single-track, single-side disk with sectors 0 and 1. */ + DiskLayout layout = new DiskLayout(1, 1, 2, 256); + LogicalTrackLayout ltl = layout.layoutByLogicalLocation.get( + new com.cowlark.fluxengine.data.CylinderHead(0, 0)); + assertThat(ltl).isNotNull(); + + Image image = new Image(); + image.put(0, 0, 0); + image.put(0, 0, 1); + + TestEncoder encoder = new TestEncoder(); + + ImmutableList sectors = encoder.collectSectors(ltl, image); + + assertThat(sectors).hasSize(2); + assertThat(sectors.get(0).location.logicalSector()).isEqualTo(0); + assertThat(sectors.get(1).location.logicalSector()).isEqualTo(1); + } + + @Test + public void collectSectorsMissingSectorThrows() + { + DiskLayout layout = new DiskLayout(1, 1, 2, 256); + LogicalTrackLayout ltl = layout.layoutByLogicalLocation.get( + new com.cowlark.fluxengine.data.CylinderHead(0, 0)); + + Image image = new Image(); + image.put(0, 0, 0); /* sector 1 missing */ + + TestEncoder encoder = new TestEncoder(); + + assertThrows( + FluxEngineException.class, + () -> encoder.collectSectors(ltl, image)); + } + + @Test + public void calculatePhysicalClockPeriod() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .build(); + + TestEncoder encoder = new TestEncoder(); + + assertThat(encoder.calculatePhysicalClockPeriod(config, 4000, 200e6)) + .isEqualTo(4000.0); + } + + @Test + public void calculatePhysicalClockPeriodUnsetThrows() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .build(); + + TestEncoder encoder = new TestEncoder(); + + assertThrows( + FluxEngineException.class, + () -> encoder.calculatePhysicalClockPeriod(config, 4000, 200e6)); + } +} From 6301f6e22d0a58e73b088dcae58a2bcdaf584dfe Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 23:02:07 +0200 Subject: [PATCH 125/192] Port the encoders. --- java/com/cowlark/fluxengine/arch/Arch.java | 50 +++ .../fluxengine/arch/agat/AgatEncoder.java | 117 +++++++ .../fluxengine/arch/apple2/Apple2Encoder.java | 246 ++++++++++++++ .../arch/brother/BrotherEncoder.java | 254 ++++++++++++++ java/com/cowlark/fluxengine/arch/c64/C64.java | 23 ++ .../arch/c64/Commodore64Encoder.java | 213 ++++++++++++ .../fluxengine/arch/ibm/IbmEncoder.java | 266 +++++++++++++++ .../arch/macintosh/MacintoshEncoder.java | 314 ++++++++++++++++++ .../arch/micropolis/MicropolisEncoder.java | 147 ++++++++ .../arch/northstar/NorthstarEncoder.java | 163 +++++++++ .../fluxengine/arch/tartu/TartuEncoder.java | 119 +++++++ .../arch/tids990/Tids990Encoder.java | 146 ++++++++ .../arch/victor9k/Victor9kEncoder.java | 223 +++++++++++++ .../cowlark/fluxengine/external/FmMfm.java | 20 +- .../fluxengine/arch/ArchEncoderTest.java | 73 ++++ .../com/cowlark/fluxengine/arch/BUILD.bazel | 17 + .../fluxengine/external/FmMfmTest.java | 4 +- 17 files changed, 2385 insertions(+), 10 deletions(-) create mode 100644 java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java create mode 100644 java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java create mode 100644 java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java create mode 100644 java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java create mode 100644 java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java create mode 100644 java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java create mode 100644 java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java create mode 100644 java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java create mode 100644 java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java create mode 100644 java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java create mode 100644 java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java create mode 100644 javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java create mode 100644 javatests/com/cowlark/fluxengine/arch/BUILD.bazel diff --git a/java/com/cowlark/fluxengine/arch/Arch.java b/java/com/cowlark/fluxengine/arch/Arch.java index b4178824..e4d11106 100644 --- a/java/com/cowlark/fluxengine/arch/Arch.java +++ b/java/com/cowlark/fluxengine/arch/Arch.java @@ -2,27 +2,41 @@ import com.cowlark.fluxengine.arch.aeslanier.AesLanierDecoder; import com.cowlark.fluxengine.arch.agat.AgatDecoder; +import com.cowlark.fluxengine.arch.agat.AgatEncoder; import com.cowlark.fluxengine.arch.amiga.AmigaDecoder; +import com.cowlark.fluxengine.arch.amiga.AmigaEncoder; import com.cowlark.fluxengine.arch.apple2.Apple2Decoder; +import com.cowlark.fluxengine.arch.apple2.Apple2Encoder; import com.cowlark.fluxengine.arch.brother.BrotherDecoder; +import com.cowlark.fluxengine.arch.brother.BrotherEncoder; import com.cowlark.fluxengine.arch.c64.Commodore64Decoder; +import com.cowlark.fluxengine.arch.c64.Commodore64Encoder; import com.cowlark.fluxengine.arch.f85.DurangoF85Decoder; import com.cowlark.fluxengine.arch.fb100.Fb100Decoder; import com.cowlark.fluxengine.arch.ibm.IbmDecoder; +import com.cowlark.fluxengine.arch.ibm.IbmEncoder; import com.cowlark.fluxengine.arch.macintosh.MacintoshDecoder; +import com.cowlark.fluxengine.arch.macintosh.MacintoshEncoder; import com.cowlark.fluxengine.arch.micropolis.MicropolisDecoder; +import com.cowlark.fluxengine.arch.micropolis.MicropolisEncoder; import com.cowlark.fluxengine.arch.mx.MxDecoder; import com.cowlark.fluxengine.arch.northstar.NorthstarDecoder; +import com.cowlark.fluxengine.arch.northstar.NorthstarEncoder; import com.cowlark.fluxengine.arch.rolandd20.RolandD20Decoder; import com.cowlark.fluxengine.arch.smaky6.Smaky6Decoder; import com.cowlark.fluxengine.arch.tartu.TartuDecoder; +import com.cowlark.fluxengine.arch.tartu.TartuEncoder; import com.cowlark.fluxengine.arch.tids990.Tids990Decoder; +import com.cowlark.fluxengine.arch.tids990.Tids990Encoder; import com.cowlark.fluxengine.arch.victor9k.Victor9kDecoder; +import com.cowlark.fluxengine.arch.victor9k.Victor9kEncoder; import com.cowlark.fluxengine.arch.zilogmcz.ZilogMczDecoder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.decoders.Decoder; import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.encoders.EncoderProto; /** * The Arch class, ported from arch/arch.{h,cc}. @@ -86,4 +100,40 @@ public static Decoder createDecoder(DecoderProto config) throw new FluxEngineException("no decoder specified"); } } + + public static Encoder createEncoder(ConfigProto config) + { + if (!config.hasEncoder()) + throw new FluxEngineException("no encoder configured"); + + switch (config.getEncoder().getFormatCase()) + { + case AGAT: + return new AgatEncoder(config); + case AMIGA: + return new AmigaEncoder(config); + case APPLE2: + return new Apple2Encoder(config); + case BROTHER: + return new BrotherEncoder(config); + case C64: + return new Commodore64Encoder(config); + case IBM: + return new IbmEncoder(config); + case MACINTOSH: + return new MacintoshEncoder(config); + case MICROPOLIS: + return new MicropolisEncoder(config); + case NORTHSTAR: + return new NorthstarEncoder(config); + case TARTU: + return new TartuEncoder(config); + case TIDS990: + return new Tids990Encoder(config); + case VICTOR9K: + return new Victor9kEncoder(config); + default: + throw new FluxEngineException("no encoder specified"); + } + } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java b/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java new file mode 100644 index 00000000..f190ec4f --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java @@ -0,0 +1,117 @@ +package com.cowlark.fluxengine.arch.agat; + +import com.cowlark.fluxengine.agat.AgatEncoderProto; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.FmMfm; +import java.util.List; + +/** + * The Agat encoder, ported from arch/agat/encoder.cc. + */ +public class AgatEncoder extends Encoder +{ + private final ConfigProto fullConfig; + private final AgatEncoderProto config; + private final boolean[] lastBit = new boolean[1]; + private Bits bits; + private Bits.Cursor cursor; + + public AgatEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getAgat(); + } + + private void writeRawBits(long data, int width) + { + cursor.advance(width); + lastBit[0] = (data & 1) != 0; + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private void writeBytes(Bytes bytes) + { + FmMfm.encodeMfm(bits, cursor, bytes, lastBit); + } + + private void writeByte(int byte_) + { + Bytes b = new Bytes(1); + b.writer().write8(byte_); + writeBytes(b); + } + + private void writeFillerRawBytes(int count, int byte_) + { + for (int i = 0; i < count; i++) + writeRawBits(byte_, 16); + } + + private void writeFillerBytes(int count, int byte_) + { + Bytes b = new Bytes(1); + b.writer().write8(byte_); + for (int i = 0; i < count; i++) + writeBytes(b); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + double clockRateUs = config.getTargetClockPeriodUs() / 2.0; + int bitsPerRevolution = + (int) ((config.getTargetRotationalPeriodMs() * 1000.0) / clockRateUs); + bits = new Bits(bitsPerRevolution); + cursor = new Bits.Cursor(0); + + writeFillerRawBytes(config.getPostIndexGapBytes(), 0xaaaa); + + for (Sector sector : sectors) + { + /* Header */ + + writeFillerRawBytes(config.getPreSectorGapBytes(), 0xaaaa); + writeRawBits(Agat.SECTOR_ID, 64); + writeByte(0x5a); + writeByte((sector.location.logicalCylinder() << 1) | sector.location.logicalHead()); + writeByte(sector.location.logicalSector()); + writeByte(0x5a); + + /* Data */ + + writeFillerRawBytes(config.getPreDataGapBytes(), 0xaaaa); + Bytes data = sector.data.slice(0, Agat.AGAT_SECTOR_SIZE); + writeRawBits(Agat.DATA_ID, 64); + writeBytes(data); + writeByte(Agat.agatChecksum(data)); + writeByte(0x5a); + } + + if (cursor.get() >= bits.size()) + throw new FluxEngineException("track data overrun"); + bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod( + fullConfig, + config.getTargetClockPeriodUs() * 1e3, + config.getTargetRotationalPeriodMs() * 1e6)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java b/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java new file mode 100644 index 00000000..01748cc3 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java @@ -0,0 +1,246 @@ +package com.cowlark.fluxengine.arch.apple2; + +import com.cowlark.fluxengine.apple2.Apple2EncoderProto; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import java.util.List; + +/** + * The Apple II encoder, ported from arch/apple2/encoder.cc. + */ +public class Apple2Encoder extends Encoder +{ + private static final int[] ENCODE_DATA_GCR = new int[64]; + + static + { + ENCODE_DATA_GCR[0x00] = 0x96; + ENCODE_DATA_GCR[0x01] = 0x97; + ENCODE_DATA_GCR[0x02] = 0x9a; + ENCODE_DATA_GCR[0x03] = 0x9b; + ENCODE_DATA_GCR[0x04] = 0x9d; + ENCODE_DATA_GCR[0x05] = 0x9e; + ENCODE_DATA_GCR[0x06] = 0x9f; + ENCODE_DATA_GCR[0x07] = 0xa6; + ENCODE_DATA_GCR[0x08] = 0xa7; + ENCODE_DATA_GCR[0x09] = 0xab; + ENCODE_DATA_GCR[0x0a] = 0xac; + ENCODE_DATA_GCR[0x0b] = 0xad; + ENCODE_DATA_GCR[0x0c] = 0xae; + ENCODE_DATA_GCR[0x0d] = 0xaf; + ENCODE_DATA_GCR[0x0e] = 0xb2; + ENCODE_DATA_GCR[0x0f] = 0xb3; + ENCODE_DATA_GCR[0x10] = 0xb4; + ENCODE_DATA_GCR[0x11] = 0xb5; + ENCODE_DATA_GCR[0x12] = 0xb6; + ENCODE_DATA_GCR[0x13] = 0xb7; + ENCODE_DATA_GCR[0x14] = 0xb9; + ENCODE_DATA_GCR[0x15] = 0xba; + ENCODE_DATA_GCR[0x16] = 0xbb; + ENCODE_DATA_GCR[0x17] = 0xbc; + ENCODE_DATA_GCR[0x18] = 0xbd; + ENCODE_DATA_GCR[0x19] = 0xbe; + ENCODE_DATA_GCR[0x1a] = 0xbf; + ENCODE_DATA_GCR[0x1b] = 0xcb; + ENCODE_DATA_GCR[0x1c] = 0xcd; + ENCODE_DATA_GCR[0x1d] = 0xce; + ENCODE_DATA_GCR[0x1e] = 0xcf; + ENCODE_DATA_GCR[0x1f] = 0xd3; + ENCODE_DATA_GCR[0x20] = 0xd6; + ENCODE_DATA_GCR[0x21] = 0xd7; + ENCODE_DATA_GCR[0x22] = 0xd9; + ENCODE_DATA_GCR[0x23] = 0xda; + ENCODE_DATA_GCR[0x24] = 0xdb; + ENCODE_DATA_GCR[0x25] = 0xdc; + ENCODE_DATA_GCR[0x26] = 0xdd; + ENCODE_DATA_GCR[0x27] = 0xde; + ENCODE_DATA_GCR[0x28] = 0xdf; + ENCODE_DATA_GCR[0x29] = 0xe5; + ENCODE_DATA_GCR[0x2a] = 0xe6; + ENCODE_DATA_GCR[0x2b] = 0xe7; + ENCODE_DATA_GCR[0x2c] = 0xe9; + ENCODE_DATA_GCR[0x2d] = 0xea; + ENCODE_DATA_GCR[0x2e] = 0xeb; + ENCODE_DATA_GCR[0x2f] = 0xec; + ENCODE_DATA_GCR[0x30] = 0xed; + ENCODE_DATA_GCR[0x31] = 0xee; + ENCODE_DATA_GCR[0x32] = 0xef; + ENCODE_DATA_GCR[0x33] = 0xf2; + ENCODE_DATA_GCR[0x34] = 0xf3; + ENCODE_DATA_GCR[0x35] = 0xf4; + ENCODE_DATA_GCR[0x36] = 0xf5; + ENCODE_DATA_GCR[0x37] = 0xf6; + ENCODE_DATA_GCR[0x38] = 0xf7; + ENCODE_DATA_GCR[0x39] = 0xf9; + ENCODE_DATA_GCR[0x3a] = 0xfa; + ENCODE_DATA_GCR[0x3b] = 0xfb; + ENCODE_DATA_GCR[0x3c] = 0xfc; + ENCODE_DATA_GCR[0x3d] = 0xfd; + ENCODE_DATA_GCR[0x3e] = 0xfe; + ENCODE_DATA_GCR[0x3f] = 0xff; + } + + private static int encodeDataGcr(int data) + { + if (data < 0 || data >= ENCODE_DATA_GCR.length) + return -1; + return ENCODE_DATA_GCR[data]; + } + + private final ConfigProto fullConfig; + private final Apple2EncoderProto config; + + public Apple2Encoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getApple2(); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + int bitsPerRevolution = + (int) ((config.getRotationalPeriodMs() * 1e3) / config.getClockPeriodUs()); + + Bits bits = new Bits(bitsPerRevolution); + Bits.Cursor cursor = new Bits.Cursor(0); + + for (Sector sector : sectors) + writeSector(bits, cursor, sector); + + if (cursor.get() >= bits.size()) + throw new FluxEngineException( + "track data overrun by " + (cursor.get() - bits.size()) + " bits"); + bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod( + fullConfig, + config.getClockPeriodUs() * 1e3, + config.getRotationalPeriodMs() * 1e6)); + return fluxmap; + } + + private int volumeId = 254; + + /* This is extremely inspired by the MESS implementation, written by Nathan + * Woods and R. Belmont: + * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp + * as well as Understanding the Apple II (1983) Chapter 9 + * https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater + */ + + private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) + { + if ((sector.status == Sector.Status.OK) || (sector.status == Sector.Status.BAD_CHECKSUM)) + { + // The special "FF40" sequence is used to synchronize the receiving + // shift register. It's written as "1111 1111 00"; FF indicates the + // 8 consecutive 1-bits, while "40" indicates the total number of + // microseconds. + // There is data to encode to disk. + if ((sector.data.size() != Apple2.APPLE2_SECTOR_LENGTH)) + throw new FluxEngineException( + "unsupported sector size " + sector.data.size() + " --- you must pick 256"); + + // Write address syncing leader : A sequence of "FF40"s; 5 of them + // are said to suffice to synchronize the decoder. + // "FF40" indicates that the actual data written is "1111 + // 1111 00" i.e., 8 1s and a total of 40 microseconds + // + // In standard formatting, the first logical sector apparently gets + // extra padding. + writeFf40(bits, cursor, sector.location.logicalSector() == 0 ? 32 : 8); + + int track = sector.location.logicalCylinder(); + if (sector.location.logicalHead() == 1) + track += config.getSideOneTrackOffset(); + + // Write address field: APPLE2_SECTOR_RECORD + sector identifier + + // DE AA EB + writeBits(bits, cursor, Apple2.APPLE2_SECTOR_RECORD, 24); + writeGcr44(bits, cursor, volumeId); + writeGcr44(bits, cursor, track); + writeGcr44(bits, cursor, sector.location.logicalSector()); + writeGcr44(bits, cursor, volumeId ^ track ^ sector.location.logicalSector()); + writeBits(bits, cursor, 0xDEAAEB, 24); + + // Write data syncing leader: FF40 + APPLE2_DATA_RECORD + sector + // data + sum + DE AA EB (+ mystery bits cut off of the scan?) + writeFf40(bits, cursor, 8); + writeBits(bits, cursor, Apple2.APPLE2_DATA_RECORD, 24); + + // Convert the sector data to GCR, append the checksum, and write it + // out + final int TWOBIT_COUNT = + 0x56; // Size of the 'twobit' area at the start of the GCR data + int checksum = 0; + for (int i = 0; i < Apple2.APPLE2_ENCODED_SECTOR_LENGTH; i++) + { + int value; + if (i >= TWOBIT_COUNT) + { + value = sector.data.getByte(i - TWOBIT_COUNT) >> 2; + } + else + { + int tmp = sector.data.getByte(i); + value = ((tmp & 1) << 1) | ((tmp & 2) >> 1); + + tmp = sector.data.getByte(i + TWOBIT_COUNT); + value |= ((tmp & 1) << 3) | ((tmp & 2) << 1); + + if (i + 2 * TWOBIT_COUNT < Apple2.APPLE2_SECTOR_LENGTH) + { + tmp = sector.data.getByte(i + 2 * TWOBIT_COUNT); + value |= ((tmp & 1) << 5) | ((tmp & 2) << 3); + } + } + checksum ^= value; + writeGcr6(bits, cursor, checksum); + checksum = value; + } + if (sector.status == Sector.Status.BAD_CHECKSUM) + checksum ^= 0x3f; + writeGcr6(bits, cursor, checksum); + writeBits(bits, cursor, 0xDEAAEB, 24); + } + } + + private void writeBit(Bits bits, Bits.Cursor cursor, boolean val) + { + if (cursor.get() < bits.size()) + bits.setBit(cursor.get(), val); + cursor.advance(); + } + + private void writeBits(Bits bits, Bits.Cursor cursor, int data, int width) + { + for (int i = width; i-- != 0;) + writeBit(bits, cursor, (data & (1 << i)) != 0); + } + + private void writeGcr44(Bits bits, Bits.Cursor cursor, int value) + { + writeBits(bits, cursor, (value << 7) | value | 0xaaaa, 16); + } + + private void writeGcr6(Bits bits, Bits.Cursor cursor, int value) + { + writeBits(bits, cursor, encodeDataGcr(value), 8); + } + + private void writeFf40(Bits bits, Bits.Cursor cursor, int n) + { + for (; n-- != 0;) + writeBits(bits, cursor, 0xff << 2, 10); + } +} diff --git a/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java b/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java new file mode 100644 index 00000000..5b63b161 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java @@ -0,0 +1,254 @@ +package com.cowlark.fluxengine.arch.brother; + +import com.cowlark.fluxengine.brother.BrotherEncoderProto; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.Crc; +import java.util.List; + +/** + * The Brother encoder, ported from arch/brother/encoder.cc. + */ +public class BrotherEncoder extends Encoder +{ + private static final int[] ENCODE_HEADER_GCR = new int[78]; + private static final int[] ENCODE_DATA_GCR = new int[32]; + + static + { + ENCODE_HEADER_GCR[0] = 0xDFB5; + ENCODE_HEADER_GCR[1] = 0x5B6F; + ENCODE_HEADER_GCR[2] = 0x7DF7; + ENCODE_HEADER_GCR[3] = 0xBFD5; + ENCODE_HEADER_GCR[4] = 0xF57F; + ENCODE_HEADER_GCR[5] = 0x6D5D; + ENCODE_HEADER_GCR[6] = 0xAFEB; + ENCODE_HEADER_GCR[7] = 0xDDB7; + ENCODE_HEADER_GCR[8] = 0x5775; + ENCODE_HEADER_GCR[9] = 0x7BFB; + ENCODE_HEADER_GCR[10] = 0xBDD7; + ENCODE_HEADER_GCR[11] = 0xEFAB; + ENCODE_HEADER_GCR[12] = 0x6B5F; + ENCODE_HEADER_GCR[13] = 0xADED; + ENCODE_HEADER_GCR[14] = 0xDBBB; + ENCODE_HEADER_GCR[15] = 0x5577; + ENCODE_HEADER_GCR[16] = 0x77DB; + ENCODE_HEADER_GCR[17] = 0xBBAD; + ENCODE_HEADER_GCR[18] = 0xED6B; + ENCODE_HEADER_GCR[19] = 0x5FEF; + ENCODE_HEADER_GCR[20] = 0xABBD; + ENCODE_HEADER_GCR[21] = 0xD77B; + ENCODE_HEADER_GCR[22] = 0xFB57; + ENCODE_HEADER_GCR[23] = 0x75DD; + ENCODE_HEADER_GCR[24] = 0xB7AF; + ENCODE_HEADER_GCR[25] = 0xEB6D; + ENCODE_HEADER_GCR[26] = 0x5DF5; + ENCODE_HEADER_GCR[27] = 0x7FBF; + ENCODE_HEADER_GCR[28] = 0xD57D; + ENCODE_HEADER_GCR[29] = 0xF75B; + ENCODE_HEADER_GCR[30] = 0x6FDF; + ENCODE_HEADER_GCR[31] = 0xB5B5; + ENCODE_HEADER_GCR[32] = 0xDF6F; + ENCODE_HEADER_GCR[33] = 0x5BF7; + ENCODE_HEADER_GCR[34] = 0x7DD5; + ENCODE_HEADER_GCR[35] = 0xBF7F; + ENCODE_HEADER_GCR[36] = 0xF55D; + ENCODE_HEADER_GCR[37] = 0x6DEB; + ENCODE_HEADER_GCR[38] = 0xAFB7; + ENCODE_HEADER_GCR[39] = 0xDD75; + ENCODE_HEADER_GCR[40] = 0x57FB; + ENCODE_HEADER_GCR[41] = 0x7BD7; + ENCODE_HEADER_GCR[42] = 0xBDAB; + ENCODE_HEADER_GCR[43] = 0xEF5F; + ENCODE_HEADER_GCR[44] = 0x6BED; + ENCODE_HEADER_GCR[45] = 0xADBB; + ENCODE_HEADER_GCR[46] = 0xDB77; + ENCODE_HEADER_GCR[47] = 0xBB55; + ENCODE_HEADER_GCR[48] = 0xEDDB; + ENCODE_HEADER_GCR[49] = 0x5FAD; + ENCODE_HEADER_GCR[50] = 0xAB6B; + ENCODE_HEADER_GCR[51] = 0xD7EF; + ENCODE_HEADER_GCR[52] = 0xFBBD; + ENCODE_HEADER_GCR[53] = 0x757B; + ENCODE_HEADER_GCR[54] = 0xB757; + ENCODE_HEADER_GCR[55] = 0xEBDD; + ENCODE_HEADER_GCR[56] = 0x5DAF; + ENCODE_HEADER_GCR[57] = 0x7F6D; + ENCODE_HEADER_GCR[58] = 0xD5F5; + ENCODE_HEADER_GCR[59] = 0xF7BF; + ENCODE_HEADER_GCR[60] = 0x6F7D; + ENCODE_HEADER_GCR[61] = 0xB55B; + ENCODE_HEADER_GCR[62] = 0xDFDF; + ENCODE_HEADER_GCR[63] = 0x5BB5; + ENCODE_HEADER_GCR[64] = 0x7D6F; + ENCODE_HEADER_GCR[65] = 0xBFF7; + ENCODE_HEADER_GCR[66] = 0xF5D5; + ENCODE_HEADER_GCR[67] = 0x6D7F; + ENCODE_HEADER_GCR[68] = 0xAF5D; + ENCODE_HEADER_GCR[69] = 0xDDEB; + ENCODE_HEADER_GCR[70] = 0x57B7; + ENCODE_HEADER_GCR[71] = 0x7B75; + ENCODE_HEADER_GCR[72] = 0xBDFB; + ENCODE_HEADER_GCR[73] = 0xEFD7; + ENCODE_HEADER_GCR[74] = 0x6BAB; + ENCODE_HEADER_GCR[75] = 0xAD5F; + ENCODE_HEADER_GCR[76] = 0xDBED; + ENCODE_HEADER_GCR[77] = 0x55BB; + + ENCODE_DATA_GCR[0] = 0x55; + ENCODE_DATA_GCR[1] = 0x57; + ENCODE_DATA_GCR[2] = 0x5b; + ENCODE_DATA_GCR[3] = 0x5d; + ENCODE_DATA_GCR[4] = 0x5f; + ENCODE_DATA_GCR[5] = 0x6b; + ENCODE_DATA_GCR[6] = 0x6d; + ENCODE_DATA_GCR[7] = 0x6f; + ENCODE_DATA_GCR[8] = 0x75; + ENCODE_DATA_GCR[9] = 0x77; + ENCODE_DATA_GCR[10] = 0x7b; + ENCODE_DATA_GCR[11] = 0x7d; + ENCODE_DATA_GCR[12] = 0x7f; + ENCODE_DATA_GCR[13] = 0xab; + ENCODE_DATA_GCR[14] = 0xad; + ENCODE_DATA_GCR[15] = 0xaf; + ENCODE_DATA_GCR[16] = 0xb5; + ENCODE_DATA_GCR[17] = 0xb7; + ENCODE_DATA_GCR[18] = 0xbb; + ENCODE_DATA_GCR[19] = 0xbd; + ENCODE_DATA_GCR[20] = 0xbf; + ENCODE_DATA_GCR[21] = 0xd5; + ENCODE_DATA_GCR[22] = 0xd7; + ENCODE_DATA_GCR[23] = 0xdb; + ENCODE_DATA_GCR[24] = 0xdd; + ENCODE_DATA_GCR[25] = 0xdf; + ENCODE_DATA_GCR[26] = 0xeb; + ENCODE_DATA_GCR[27] = 0xed; + ENCODE_DATA_GCR[28] = 0xef; + ENCODE_DATA_GCR[29] = 0xf5; + ENCODE_DATA_GCR[30] = 0xf7; + ENCODE_DATA_GCR[31] = 0xfb; + } + + private static int encodeHeaderGcr(int word) + { + if (word < 0 || word >= ENCODE_HEADER_GCR.length) + return -1; + return ENCODE_HEADER_GCR[word]; + } + + private static int encodeDataGcr(int data) + { + if (data < 0 || data >= ENCODE_DATA_GCR.length) + return -1; + return ENCODE_DATA_GCR[data]; + } + + private static void writeBits(Bits bits, Bits.Cursor cursor, int data, int width) + { + cursor.advance(width); + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private static void writeSectorHeader(Bits bits, Bits.Cursor cursor, int track, int sector) + { + writeBits(bits, cursor, 0xffffffff, 31); + writeBits(bits, cursor, Brother.BROTHER_SECTOR_RECORD, 32); + writeBits(bits, cursor, encodeHeaderGcr(track), 16); + writeBits(bits, cursor, encodeHeaderGcr(sector), 16); + writeBits(bits, cursor, encodeHeaderGcr(0x2f), 16); + } + + private static void writeSectorData(Bits bits, Bits.Cursor cursor, Bytes data) + { + writeBits(bits, cursor, 0xffffffff, 32); + writeBits(bits, cursor, Brother.BROTHER_DATA_RECORD, 32); + + if (data.size() != Brother.BROTHER_DATA_RECORD_PAYLOAD) + throw new FluxEngineException("unsupported sector size"); + + int[] fifo = {0}; + int[] width = {0}; + + /* Consume 5-bit quintets from a 16-bit fifo fed by 8-bit bytes. */ + java.util.function.IntConsumer writeByte = (byte_) -> { + fifo[0] = (fifo[0] | (byte_ << (8 - width[0]))) & 0xffff; + width[0] += 8; + + while (width[0] >= 5) + { + int quintet = fifo[0] >> 11; + fifo[0] = (fifo[0] << 5) & 0xffff; + width[0] -= 5; + + writeBits(bits, cursor, encodeDataGcr(quintet), 8); + } + }; + + for (int i = 0; i < data.size(); i++) + writeByte.accept(data.getByte(i)); + + int realCrc = Crc.crcbrother(data); + writeByte.accept(realCrc >> 16); + writeByte.accept(realCrc >> 8); + writeByte.accept(realCrc); + writeByte.accept(0x58); /* magic */ + writeByte.accept(0xd4); + while (width[0] != 0) + writeByte.accept(0); + } + + private final ConfigProto fullConfig; + private final BrotherEncoderProto config; + + public BrotherEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getBrother(); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + int bitsPerRevolution = (int) (200000.0 / config.getClockRateUs()); + Bits bits = new Bits(bitsPerRevolution); + Bits.Cursor cursor = new Bits.Cursor(0); + + int sectorCount = 0; + for (Sector sectorData : sectors) + { + double headerMs = config.getPostIndexGapMs() + sectorCount * config.getSectorSpacingMs(); + int headerCursor = (int) (headerMs * 1e3 / config.getClockRateUs()); + double dataMs = headerMs + config.getPostHeaderSpacingMs(); + int dataCursor = (int) (dataMs * 1e3 / config.getClockRateUs()); + + bits.fillBitmapTo(cursor, headerCursor, new boolean[] {true, false}); + writeSectorHeader( + bits, cursor, sectorData.location.logicalCylinder(), sectorData.location.logicalSector()); + bits.fillBitmapTo(cursor, dataCursor, new boolean[] {true, false}); + writeSectorData(bits, cursor, sectorData.data); + + sectorCount++; + } + + if (cursor.get() >= bits.size()) + throw new FluxEngineException("track data overrun"); + bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits(bits, (long) (config.getClockRateUs() * 1e3)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/arch/c64/C64.java b/java/com/cowlark/fluxengine/arch/c64/C64.java index be7b36b0..6a517c13 100644 --- a/java/com/cowlark/fluxengine/arch/c64/C64.java +++ b/java/com/cowlark/fluxengine/arch/c64/C64.java @@ -31,4 +31,27 @@ public final class C64 private C64() { } + + /* + * Track Sectors/track # Sectors Storage in Bytes Clock rate + * ----- ------------- --------- ---------------- ---------- + * 1-17 21 357 7820 3.25 + * 18-24 19 133 7170 3.5 + * 25-30 18 108 6300 3.75 + * 31-40(*) 17 85 6020 4 + * --- + * 683 (for a 35 track image) + * + * The clock rate is normalised for a 200ms drive. + */ + public static double clockRateUsForTrack(int track) + { + if (track < 17) + return 26.0 / 8.0; + if (track < 24) + return 28.0 / 8.0; + if (track < 30) + return 30.0 / 8.0; + return 32.0 / 8.0; + } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java b/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java new file mode 100644 index 00000000..a47ec715 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java @@ -0,0 +1,213 @@ +package com.cowlark.fluxengine.arch.c64; + +import com.cowlark.fluxengine.c64.Commodore64EncoderProto; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.Crc; +import java.util.List; + +/** + * The Commodore 64 encoder, ported from arch/c64/encoder.cc. + */ +public class Commodore64Encoder extends Encoder +{ + private static final int[] ENCODE_DATA_GCR = new int[16]; + + static + { + ENCODE_DATA_GCR[0x0] = 0x0a; + ENCODE_DATA_GCR[0x1] = 0x0b; + ENCODE_DATA_GCR[0x2] = 0x12; + ENCODE_DATA_GCR[0x3] = 0x13; + ENCODE_DATA_GCR[0x4] = 0x0e; + ENCODE_DATA_GCR[0x5] = 0x0f; + ENCODE_DATA_GCR[0x6] = 0x16; + ENCODE_DATA_GCR[0x7] = 0x17; + ENCODE_DATA_GCR[0x8] = 0x09; + ENCODE_DATA_GCR[0x9] = 0x19; + ENCODE_DATA_GCR[0xa] = 0x1a; + ENCODE_DATA_GCR[0xb] = 0x1b; + ENCODE_DATA_GCR[0xc] = 0x0d; + ENCODE_DATA_GCR[0xd] = 0x1d; + ENCODE_DATA_GCR[0xe] = 0x1e; + ENCODE_DATA_GCR[0xf] = 0x15; + } + + private static int encodeDataGcr(int data) + { + if (data < 0 || data >= ENCODE_DATA_GCR.length) + return -1; + return ENCODE_DATA_GCR[data]; + } + + private static void writeBits(Bits bits, Bits.Cursor cursor, boolean[] src) + { + for (boolean bit : src) + { + if (cursor.get() < bits.size()) + bits.setBit(cursor.get(), bit); + cursor.advance(); + } + } + + private static void writeBits(Bits bits, Bits.Cursor cursor, long data, int width) + { + cursor.advance(width); + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + /* See the big comment in the C++ file for the gory details of how 4 + * 8-bit bytes become five 8-bit GCR bytes; this encodes a single byte to + * its 10-bit GCR form. */ + private static boolean[] encodeData(int input) + { + boolean[] output = new boolean[10]; + + int lo = input >> 4; /* get the lo nibble */ + int hi = input & 15; /* get the hi nibble */ + + int loGcr = encodeDataGcr(lo); + int hiGcr = encodeDataGcr(hi); + + int b = 4; + for (int i = 0; i < 10; i++) + { + if (i < 5) + { + output[4 - i] = (loGcr & 1) != 0; + loGcr >>= 1; + } + else + { + output[i + b] = (hiGcr & 1) != 0; + hiGcr >>= 1; + b -= 2; + } + } + return output; + } + + private final ConfigProto fullConfig; + private final Commodore64EncoderProto config; + private int formatByte1; + private int formatByte2; + + public Commodore64Encoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getC64(); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + /* The format ID Character # 1 and # 2 are in the .d64 image only + * present in track 18 sector zero which contains the BAM info in byte + * 162 and 163. it is written in every header of every sector and track. + * headers are not stored in a d64 disk image so we have to get it from + * track 18 which contains the BAM. + */ + + Sector sectorData = image.get(C64.C64_BAM_TRACK, 0, 0); + if (sectorData != null) + { + ByteReader br = new ByteReader(sectorData.data); + br.seek(162); /* goto position of the first Disk ID Byte */ + formatByte1 = br.read8(); + formatByte2 = br.read8(); + } + else + { + formatByte1 = formatByte2 = 0; + } + + double clockRateUs = C64.clockRateUsForTrack(ltl.logicalCylinder); + int bitsPerRevolution = (int) (200000.0 / clockRateUs); + + Bits bits = new Bits(bitsPerRevolution); + Bits.Cursor cursor = new Bits.Cursor(0); + + bits.fillBitmapTo( + cursor, + (int) (config.getPostIndexGapUs() / clockRateUs), + new boolean[] {true, false}); + + for (Sector sector : sectors) + writeSector(bits, cursor, sector); + + if (cursor.get() >= bits.size()) + throw new FluxEngineException( + "track data overrun by " + (cursor.get() - bits.size()) + " bits"); + bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod(fullConfig, clockRateUs * 1e3, 200e6)); + return fluxmap; + } + + private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) + { + if ((sector.status == Sector.Status.OK) || (sector.status == Sector.Status.BAD_CHECKSUM)) + { + // There is data to encode to disk. + if ((sector.data.size() != C64.C64_SECTOR_LENGTH)) + throw new FluxEngineException( + "unsupported sector size " + sector.data.size() + " --- you must pick 256"); + + // 1. Write header Sync (not GCR) + for (int i = 0; i < 6; i++) + writeBits(bits, cursor, C64.C64_HEADER_DATA_SYNC, 1 * 8); /* sync */ + + // 2. Write Header info 10 GCR bytes + int encodedTrack = sector.location.logicalCylinder() + 1; + int encodedSector = sector.location.logicalSector(); + int headerChecksum = + (encodedTrack ^ encodedSector ^ formatByte1 ^ formatByte2); + writeBits(bits, cursor, encodeData(C64.C64_HEADER_BLOCK_ID)); + writeBits(bits, cursor, encodeData(headerChecksum)); + writeBits(bits, cursor, encodeData(encodedSector)); + writeBits(bits, cursor, encodeData(encodedTrack)); + writeBits(bits, cursor, encodeData(formatByte2)); + writeBits(bits, cursor, encodeData(formatByte1)); + writeBits(bits, cursor, encodeData(C64.C64_PADDING)); + writeBits(bits, cursor, encodeData(C64.C64_PADDING)); + + // 3. Write header GAP not GCR + for (int i = 0; i < 9; i++) + writeBits(bits, cursor, C64.C64_HEADER_GAP, 1 * 8); /* header gap */ + + // 4. Write Data sync not GCR + for (int i = 0; i < 6; i++) + writeBits(bits, cursor, C64.C64_HEADER_DATA_SYNC, 1 * 8); /* sync */ + + // 5. Write data block 325 GCR bytes + writeBits(bits, cursor, encodeData(C64.C64_DATA_BLOCK_ID)); + int dataChecksum = Crc.xorBytes(sector.data); + ByteReader br = new ByteReader(sector.data); + for (int i = 0; i < C64.C64_SECTOR_LENGTH; i++) + writeBits(bits, cursor, encodeData(br.read8())); + writeBits(bits, cursor, encodeData(dataChecksum)); + writeBits(bits, cursor, encodeData(C64.C64_PADDING)); + writeBits(bits, cursor, encodeData(C64.C64_PADDING)); + + // 6. Write inter-sector gap 9 - 12 bytes not GCR + for (int i = 0; i < 9; i++) + writeBits(bits, cursor, C64.C64_INTER_SECTOR_GAP, 1 * 8); /* sync */ + } + } +} diff --git a/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java b/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java new file mode 100644 index 00000000..b2386178 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java @@ -0,0 +1,266 @@ +package com.cowlark.fluxengine.arch.ibm; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; +import java.util.List; + +/** + * The IBM encoder, ported from arch/ibm/encoder.cc. + */ +public class IbmEncoder extends Encoder +{ + /* IAM record separator: + * 0xC2 is: + * data: 1 1 0 0 0 0 1 0 = 0xc2 + * mfm: 01 01 00 10 10 10 01 00 = 0x5254 + * special: 01 01 00 10 00 10 01 00 = 0x5224 + */ + private static final int MFM_IAM_SEPARATOR = 0x5224; + + /* FM IAM record: + * flux: XXXX-XXX-XXXX-X- = 0xf77a + * clock: X X - X - X X X = 0xd7 + * data: X X X X X X - - = 0xfc + */ + private static final int FM_IAM_RECORD = 0xf77a; + + /* MFM IAM record: + * data: 1 1 1 1 1 1 0 0 = 0xfc + * flux: 01 01 01 01 01 01 00 10 = 0x5552 + */ + private static final int MFM_IAM_RECORD = 0x5552; + + /* MFM record separator: + * 0xA1 is: + * data: 1 0 1 0 0 0 0 1 = 0xa1 + * mfm: 01 00 01 00 10 10 10 01 = 0x44a9 + * special: 01 00 01 00 10 00 10 01 = 0x4489 + * ^^^^^ + * When shifted out of phase, the special 0xa1 byte becomes an illegal + * encoding (you can't do 10 00). So this can't be spoofed by user data. + * + * shifted: 10 00 10 01 00 01 00 1 + * + * It's repeated three times. + */ + private static final int MFM_RECORD_SEPARATOR = 0x4489; + private static final int MFM_RECORD_SEPARATOR_BYTE = 0xa1; + + private static int decodeUint16(int raw) + { + Bytes b = new Bytes(2); + b.writer().writeBe16(raw); + return FmMfm.decodeFmMfm(b.toBits()).getByte(0) & 0xff; + } + + private final ConfigProto fullConfig; + private final IbmEncoderProto config; + private final boolean[] lastBit = new boolean[1]; + private Bits bits; + private Bits.Cursor cursor; + + public IbmEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getIbm(); + } + + private void writeRawBits(int data, int width) + { + cursor.advance(width); + lastBit[0] = (data & 1) != 0; + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private void writeBytes(Bytes bytes, IbmEncoderProto.TrackdataProto trackdata) + { + if (trackdata.getUseFm()) + FmMfm.encodeFm(bits, cursor, bytes); + else + FmMfm.encodeMfm(bits, cursor, bytes, lastBit); + } + + private void writeFillerRawBytes(int count, int byte_) + { + for (int i = 0; i < count; i++) + writeRawBits(byte_, 16); + } + + private void writeFillerBytes(int count, int byte_, IbmEncoderProto.TrackdataProto trackdata) + { + Bytes b = Bytes.of(byte_); + for (int i = 0; i < count; i++) + writeBytes(b, trackdata); + } + + private IbmEncoderProto.TrackdataProto getEncoderTrackData(int track, int head) + { + IbmEncoderProto.TrackdataProto.Builder builder = + IbmEncoderProto.TrackdataProto.newBuilder(); + for (IbmEncoderProto.TrackdataProto f : config.getTrackdataList()) + { + if (f.hasTrack() && (f.getTrack() != track)) + continue; + if (f.hasHead() && (f.getHead() != head)) + continue; + + builder.mergeFrom(f); + } + return builder.build(); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + IbmEncoderProto.TrackdataProto trackdata = + getEncoderTrackData(ltl.logicalCylinder, ltl.logicalHead); + + double clockRateUs = trackdata.getTargetClockPeriodUs(); + if (!trackdata.getUseFm()) + clockRateUs /= 2.0; + int bitsPerRevolution = + (int) ((trackdata.getTargetRotationalPeriodMs() * 1000.0) / clockRateUs); + bits = new Bits(bitsPerRevolution); + cursor = new Bits.Cursor(0); + + int idamUnencoded = decodeUint16(trackdata.getIdamByte()); + int damUnencoded = decodeUint16(trackdata.getDamByte()); + + int sectorSize = 0; + { + int s = ltl.sectorSize >> 7; + while (s > 1) + { + s >>= 1; + sectorSize += 1; + } + } + + int gapFill = trackdata.getGapFillByte(); + + writeFillerRawBytes(trackdata.getGap0(), gapFill); + if (trackdata.getEmitIam()) + { + writeFillerBytes(trackdata.getUseFm() ? 6 : 12, 0x00, trackdata); + if (!trackdata.getUseFm()) + { + for (int i = 0; i < 3; i++) + writeRawBits(MFM_IAM_SEPARATOR, 16); + } + writeRawBits(trackdata.getUseFm() ? FM_IAM_RECORD : MFM_IAM_RECORD, 16); + writeFillerRawBytes(trackdata.getGap1(), gapFill); + } + + boolean first = true; + for (Sector sectorData : sectors) + { + if (!first) + writeFillerRawBytes(trackdata.getGap3(), gapFill); + first = false; + + /* Writing the sector and data records are fantastically annoying. + * The CRC is calculated from the *very start* of the record, and + * include the malformed marker bytes. Our encoder doesn't know + * about this, of course, with the result that we have to construct + * the unencoded header, calculate the checksum, and then use the + * same logic to emit the bytes which require special encoding + * before encoding the rest of the header normally. */ + + { + Bytes header = new Bytes(0); + ByteWriter bw = header.writer(); + + writeFillerBytes(trackdata.getUseFm() ? 6 : 12, 0x00, trackdata); + if (!trackdata.getUseFm()) + { + for (int i = 0; i < 3; i++) + bw.write8(MFM_RECORD_SEPARATOR_BYTE); + } + bw.write8(idamUnencoded); + bw.write8(sectorData.location.logicalCylinder()); + bw.write8( + sectorData.location.logicalHead() ^ + (trackdata.getInvertSideByte() ? 1 : 0)); + bw.write8(sectorData.location.logicalSector()); + bw.write8(sectorSize); + int crc = Crc.crc16(Crc.CCITT_POLY, header); + bw.writeBe16(crc); + + int conventionalHeaderStart = 0; + if (!trackdata.getUseFm()) + { + for (int i = 0; i < 3; i++) + writeRawBits(MFM_RECORD_SEPARATOR, 16); + conventionalHeaderStart += 3; + } + writeRawBits(trackdata.getIdamByte(), 16); + conventionalHeaderStart += 1; + + writeBytes(header.slice(conventionalHeaderStart), trackdata); + } + + writeFillerRawBytes(trackdata.getGap2(), gapFill); + + { + Bytes data = new Bytes(0); + ByteWriter bw = data.writer(); + + writeFillerBytes(trackdata.getUseFm() ? 6 : 12, 0x00, trackdata); + if (!trackdata.getUseFm()) + { + for (int i = 0; i < 3; i++) + bw.write8(MFM_RECORD_SEPARATOR_BYTE); + } + bw.write8(damUnencoded); + + Bytes truncatedData = sectorData.data.slice(0, ltl.sectorSize); + bw.write(truncatedData); + int crc = Crc.crc16(Crc.CCITT_POLY, data); + bw.writeBe16(crc); + + int conventionalHeaderStart = 0; + if (!trackdata.getUseFm()) + { + for (int i = 0; i < 3; i++) + writeRawBits(MFM_RECORD_SEPARATOR, 16); + conventionalHeaderStart += 3; + } + writeRawBits(trackdata.getDamByte(), 16); + conventionalHeaderStart += 1; + + writeBytes(data.slice(conventionalHeaderStart), trackdata); + } + } + + if (cursor.get() >= bits.size()) + throw new FluxEngineException("track data overrun"); + while (cursor.get() < bits.size()) + writeFillerRawBytes(1, gapFill); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod( + fullConfig, + clockRateUs * 1e3, + trackdata.getTargetRotationalPeriodMs() * 1e6)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java new file mode 100644 index 00000000..a2223e7f --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java @@ -0,0 +1,314 @@ +package com.cowlark.fluxengine.arch.macintosh; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.macintosh.MacintoshEncoderProto; +import java.util.List; + +/** + * The Macintosh encoder, ported from arch/macintosh/encoder.cc. + */ +public class MacintoshEncoder extends Encoder +{ + private static final int[] ENCODE_DATA_GCR = new int[64]; + + static + { + ENCODE_DATA_GCR[0x00] = 0x96; + ENCODE_DATA_GCR[0x01] = 0x97; + ENCODE_DATA_GCR[0x02] = 0x9a; + ENCODE_DATA_GCR[0x03] = 0x9b; + ENCODE_DATA_GCR[0x04] = 0x9d; + ENCODE_DATA_GCR[0x05] = 0x9e; + ENCODE_DATA_GCR[0x06] = 0x9f; + ENCODE_DATA_GCR[0x07] = 0xa6; + ENCODE_DATA_GCR[0x08] = 0xa7; + ENCODE_DATA_GCR[0x09] = 0xab; + ENCODE_DATA_GCR[0x0a] = 0xac; + ENCODE_DATA_GCR[0x0b] = 0xad; + ENCODE_DATA_GCR[0x0c] = 0xae; + ENCODE_DATA_GCR[0x0d] = 0xaf; + ENCODE_DATA_GCR[0x0e] = 0xb2; + ENCODE_DATA_GCR[0x0f] = 0xb3; + ENCODE_DATA_GCR[0x10] = 0xb4; + ENCODE_DATA_GCR[0x11] = 0xb5; + ENCODE_DATA_GCR[0x12] = 0xb6; + ENCODE_DATA_GCR[0x13] = 0xb7; + ENCODE_DATA_GCR[0x14] = 0xb9; + ENCODE_DATA_GCR[0x15] = 0xba; + ENCODE_DATA_GCR[0x16] = 0xbb; + ENCODE_DATA_GCR[0x17] = 0xbc; + ENCODE_DATA_GCR[0x18] = 0xbd; + ENCODE_DATA_GCR[0x19] = 0xbe; + ENCODE_DATA_GCR[0x1a] = 0xbf; + ENCODE_DATA_GCR[0x1b] = 0xcb; + ENCODE_DATA_GCR[0x1c] = 0xcd; + ENCODE_DATA_GCR[0x1d] = 0xce; + ENCODE_DATA_GCR[0x1e] = 0xcf; + ENCODE_DATA_GCR[0x1f] = 0xd3; + ENCODE_DATA_GCR[0x20] = 0xd6; + ENCODE_DATA_GCR[0x21] = 0xd7; + ENCODE_DATA_GCR[0x22] = 0xd9; + ENCODE_DATA_GCR[0x23] = 0xda; + ENCODE_DATA_GCR[0x24] = 0xdb; + ENCODE_DATA_GCR[0x25] = 0xdc; + ENCODE_DATA_GCR[0x26] = 0xdd; + ENCODE_DATA_GCR[0x27] = 0xde; + ENCODE_DATA_GCR[0x28] = 0xdf; + ENCODE_DATA_GCR[0x29] = 0xe5; + ENCODE_DATA_GCR[0x2a] = 0xe6; + ENCODE_DATA_GCR[0x2b] = 0xe7; + ENCODE_DATA_GCR[0x2c] = 0xe9; + ENCODE_DATA_GCR[0x2d] = 0xea; + ENCODE_DATA_GCR[0x2e] = 0xeb; + ENCODE_DATA_GCR[0x2f] = 0xec; + ENCODE_DATA_GCR[0x30] = 0xed; + ENCODE_DATA_GCR[0x31] = 0xee; + ENCODE_DATA_GCR[0x32] = 0xef; + ENCODE_DATA_GCR[0x33] = 0xf2; + ENCODE_DATA_GCR[0x34] = 0xf3; + ENCODE_DATA_GCR[0x35] = 0xf4; + ENCODE_DATA_GCR[0x36] = 0xf5; + ENCODE_DATA_GCR[0x37] = 0xf6; + ENCODE_DATA_GCR[0x38] = 0xf7; + ENCODE_DATA_GCR[0x39] = 0xf9; + ENCODE_DATA_GCR[0x3a] = 0xfa; + ENCODE_DATA_GCR[0x3b] = 0xfb; + ENCODE_DATA_GCR[0x3c] = 0xfc; + ENCODE_DATA_GCR[0x3d] = 0xfd; + ENCODE_DATA_GCR[0x3e] = 0xfe; + ENCODE_DATA_GCR[0x3f] = 0xff; + } + + private static int encodeDataGcr(int data) + { + if (data < 0 || data >= ENCODE_DATA_GCR.length) + return -1; + return ENCODE_DATA_GCR[data]; + } + + private static double clockRateUsForTrack(int track) + { + if (track < 16) + return 2.63; + if (track < 32) + return 2.89; + if (track < 48) + return 3.20; + if (track < 64) + return 3.57; + return 3.98; + } + + @SuppressWarnings("unused") + private static int sectorsForTrack(int track) + { + if (track < 16) + return 12; + if (track < 32) + return 11; + if (track < 48) + return 10; + if (track < 64) + return 9; + return 8; + } + + /* This is extremely inspired by the MESS implementation, written by Nathan + * Woods and R. Belmont: + * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp + */ + private static Bytes encodeCrazyData(Bytes input) + { + Bytes output = new Bytes(0); + ByteWriter bw = output.writer(); + ByteReader br = new ByteReader(input); + + final int LOOKUP_LEN = Macintosh.MAC_SECTOR_LENGTH / 3; + + int[] b1 = new int[LOOKUP_LEN + 1]; + int[] b2 = new int[LOOKUP_LEN + 1]; + int[] b3 = new int[LOOKUP_LEN + 1]; + + int c1 = 0; + int c2 = 0; + int c3 = 0; + for (int j = 0; ; j++) + { + c1 = (c1 & 0xff) << 1; + if ((c1 & 0x0100) != 0) + c1++; + + int val = br.read8(); + c3 += val; + if ((c1 & 0x0100) != 0) + { + c3++; + c1 &= 0xff; + } + b1[j] = (val ^ c1) & 0xff; + + val = br.read8(); + c2 += val; + if (c3 > 0xff) + { + c2++; + c3 &= 0xff; + } + b2[j] = (val ^ c3) & 0xff; + + if (br.pos() == 524) + break; + + val = br.read8(); + c1 += val; + if (c2 > 0xff) + { + c1++; + c2 &= 0xff; + } + b3[j] = (val ^ c2) & 0xff; + } + int c4 = ((c1 & 0xc0) >> 6) | ((c2 & 0xc0) >> 4) | ((c3 & 0xc0) >> 2); + b3[LOOKUP_LEN] = 0; + + for (int i = 0; i <= LOOKUP_LEN; i++) + { + int w1 = b1[i] & 0x3f; + int w2 = b2[i] & 0x3f; + int w3 = b3[i] & 0x3f; + int w4 = (b1[i] & 0xc0) >> 2; + w4 |= (b2[i] & 0xc0) >> 4; + w4 |= (b3[i] & 0xc0) >> 6; + + bw.write8(w4); + bw.write8(w1); + bw.write8(w2); + + if (i != LOOKUP_LEN) + bw.write8(w3); + } + + bw.write8(c4 & 0x3f); + bw.write8(c3 & 0x3f); + bw.write8(c2 & 0x3f); + bw.write8(c1 & 0x3f); + + return output; + } + + private static void writeBits(Bits bits, Bits.Cursor cursor, boolean[] src) + { + for (boolean bit : src) + { + if (cursor.get() < bits.size()) + bits.setBit(cursor.get(), bit); + cursor.advance(); + } + } + + private static void writeBits(Bits bits, Bits.Cursor cursor, long data, int width) + { + cursor.advance(width); + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private static int encodeSide(int track, int side) + { + /* Mac disks, being weird, use the side byte to encode both the side (in + * bit 5) and also whether we're above track 0x3f (in bit 0). + */ + + return (side != 0 ? 0x20 : 0x00) | ((track > 0x3f) ? 0x01 : 0x00); + } + + private static void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) + { + if ((sector.data.size() != 512) && (sector.data.size() != 524)) + throw new FluxEngineException("unsupported sector size --- you must pick 512 or 524"); + + writeBits(bits, cursor, 0xff, 1 * 8); /* pad byte */ + for (int i = 0; i < 7; i++) + writeBits(bits, cursor, 0xff3fcff3fcffL, 6 * 8); /* sync */ + writeBits(bits, cursor, Macintosh.MAC_SECTOR_RECORD, 3 * 8); + + int encodedTrack = sector.location.logicalCylinder() & 0x3f; + int encodedSector = sector.location.logicalSector(); + int encodedSide = encodeSide(sector.location.logicalCylinder(), sector.location.logicalHead()); + int formatByte = Macintosh.MAC_FORMAT_BYTE; + int headerChecksum = + (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; + + writeBits(bits, cursor, encodeDataGcr(encodedTrack), 1 * 8); + writeBits(bits, cursor, encodeDataGcr(encodedSector), 1 * 8); + writeBits(bits, cursor, encodeDataGcr(encodedSide), 1 * 8); + writeBits(bits, cursor, encodeDataGcr(formatByte), 1 * 8); + writeBits(bits, cursor, encodeDataGcr(headerChecksum), 1 * 8); + + writeBits(bits, cursor, 0xdeaaff, 3 * 8); + writeBits(bits, cursor, 0xff3fcff3fcffL, 6 * 8); /* sync */ + writeBits(bits, cursor, Macintosh.MAC_DATA_RECORD, 3 * 8); + writeBits(bits, cursor, encodeDataGcr(sector.location.logicalSector()), 1 * 8); + + Bytes wireData = sector.data + .slice(512, 12) + .concat(sector.data.slice(0, 512)); + Bytes crazy = encodeCrazyData(wireData); + for (int i = 0; i < crazy.size(); i++) + writeBits(bits, cursor, encodeDataGcr(crazy.getByte(i) & 0xff), 1 * 8); + + writeBits(bits, cursor, 0xdeaaff, 3 * 8); + } + + private final ConfigProto fullConfig; + private final MacintoshEncoderProto config; + + public MacintoshEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getMacintosh(); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + double clockRateUs = clockRateUsForTrack(ltl.logicalCylinder); + int bitsPerRevolution = (int) (200000.0 / clockRateUs); + Bits bits = new Bits(bitsPerRevolution); + Bits.Cursor cursor = new Bits.Cursor(0); + + bits.fillBitmapTo( + cursor, + (int) (config.getPostIndexGapUs() / clockRateUs), + new boolean[] {true, false}); + + for (Sector sector : sectors) + writeSector(bits, cursor, sector); + + if (cursor.get() >= bits.size()) + throw new FluxEngineException( + "track data overrun by " + (cursor.get() - bits.size()) + " bits"); + bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod(fullConfig, clockRateUs * 1e3, 200e6)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java new file mode 100644 index 00000000..5a533f42 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java @@ -0,0 +1,147 @@ +package com.cowlark.fluxengine.arch.micropolis; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.micropolis.MicropolisEncoderProto; +import java.util.ArrayList; +import java.util.List; + +/** + * The Micropolis encoder, ported from arch/micropolis/encoder.cc. + */ +public class MicropolisEncoder extends Encoder +{ + private final ConfigProto fullConfig; + private final MicropolisEncoderProto config; + + public MicropolisEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getMicropolis(); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + int bitsPerRevolution = + (int) ((config.getRotationalPeriodMs() * 1e3) / config.getClockPeriodUs()); + + Bits bits = new Bits(bitsPerRevolution); + List indexes = new ArrayList<>(); + int prevCursor = 0; + Bits.Cursor cursor = new Bits.Cursor(0); + + for (Sector sectorData : sectors) + { + indexes.add(cursor.get()); + prevCursor = cursor.get(); + writeSector(bits, cursor, sectorData, config.getEccType()); + } + indexes.add(prevCursor + (cursor.get() - prevCursor) / 2); + indexes.add(cursor.get()); + + if (cursor.get() != bits.size()) + throw new FluxEngineException("track data mismatched length"); + + Fluxmap fluxmap = new Fluxmap(); + long clockPeriod = + (long) calculatePhysicalClockPeriod( + fullConfig, + config.getClockPeriodUs() * 1e3, + config.getRotationalPeriodMs() * 1e6); + int pos = 0; + for (int i = 1; i < indexes.size(); i++) + { + int end = indexes.get(i); + fluxmap.appendBits(bits.subList(pos, end), clockPeriod); + fluxmap.appendIndex(); + pos = end; + } + return fluxmap; + } + + private void writeSector(Bits bits, + Bits.Cursor cursor, + Sector sector, + MicropolisEncoderProto.EccType eccType) + { + if ((sector.data.size() != 256) && (sector.data.size() != Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE)) + throw new FluxEngineException("unsupported sector size --- you must pick 256 or 275"); + + int fullSectorSize = 40 + Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35; + Bytes fullSector = new Bytes(0); + ByteWriter fullSectorWriter = fullSector.writer(); + + /* sector preamble */ + for (int i = 0; i < 40; i++) + fullSectorWriter.write8(0); + + Bytes sectorData; + if (sector.data.size() == Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE) + { + if ((sector.data.getByte(0) & 0xff) != 0xFF) + throw new FluxEngineException( + "275 byte sector doesn't start with sync byte 0xFF. Corrupted sector"); + int wantChecksum = sector.data.getByte(1 + 2 + 266) & 0xff; + int gotChecksum = MicropolisDecoder.micropolisChecksum(sector.data.slice(1, 2 + 266)); + if (wantChecksum != gotChecksum) + System.err.println( + "Warning: checksum incorrect. Sector: " + sector.location.logicalSector()); + sectorData = sector.data; + } + else + { + sectorData = new Bytes(0); + ByteWriter writer = sectorData.writer(); + writer.write8(0xff); /* Sync */ + writer.write8(sector.location.logicalCylinder()); + writer.write8(sector.location.logicalSector()); + for (int i = 0; i < 10; i++) + writer.write8(0); /* Padding */ + writer.write(sector.data); + writer.write8(MicropolisDecoder.micropolisChecksum(sectorData.slice(1))); + + int eccPresent = 0; + int ecc = 0; + if (eccType == MicropolisEncoderProto.EccType.VECTOR) + { + eccPresent = 0xaa; + ecc = MicropolisDecoder.vectorGraphicEcc(sectorData.concat(new Bytes(4))); + } + writer.writeBe32(ecc); + writer.write8(eccPresent); + } + + fullSectorWriter.write(sectorData); + + /* sector postamble */ + for (int i = 0; i < 40; i++) + fullSectorWriter.write8(0); + /* filler */ + for (int i = 0; i < 35; i++) + fullSectorWriter.write8(0); + + if (fullSector.size() != fullSectorSize) + throw new FluxEngineException("sector mismatched length"); + + boolean[] lastBit = {false}; + FmMfm.encodeMfm(bits, cursor, fullSector, lastBit); + /* filler */ + for (int i = 0; i < 5; i++) + { + bits.setBit(cursor.get(), true); + cursor.advance(); + bits.setBit(cursor.get(), false); + cursor.advance(); + } + } +} diff --git a/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java b/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java new file mode 100644 index 00000000..c53199e2 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java @@ -0,0 +1,163 @@ +package com.cowlark.fluxengine.arch.northstar; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.northstar.NorthstarEncoderProto; +import java.util.List; + +/** + * The North Star encoder, ported from arch/northstar/encoder.cc. + */ +public class NorthstarEncoder extends Encoder +{ + private static final int GAP_FILL_SIZE_SD = 30; + private static final int PRE_HEADER_GAP_FILL_SIZE_SD = 9; + private static final int GAP_FILL_SIZE_DD = 62; + private static final int PRE_HEADER_GAP_FILL_SIZE_DD = 16; + + private static final int GAP1_FILL_BYTE = 0x4F; + private static final int GAP2_FILL_BYTE = 0x4F; + + private final ConfigProto fullConfig; + private final NorthstarEncoderProto config; + + public NorthstarEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getNorthstar(); + } + + private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) + { + int preambleSize = 0; + int encodedSectorSize = 0; + int gapFillSize = 0; + int preHeaderGapFillSize = 0; + + boolean doubleDensity; + + switch (sector.data.size()) + { + case Northstar.NORTHSTAR_PAYLOAD_SIZE_SD: + preambleSize = Northstar.NORTHSTAR_PREAMBLE_SIZE_SD; + encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + + Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_SD + GAP_FILL_SIZE_SD; + gapFillSize = GAP_FILL_SIZE_SD; + preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD; + doubleDensity = false; + break; + case Northstar.NORTHSTAR_PAYLOAD_SIZE_DD: + preambleSize = Northstar.NORTHSTAR_PREAMBLE_SIZE_DD; + encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + + Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_DD + GAP_FILL_SIZE_DD; + gapFillSize = GAP_FILL_SIZE_DD; + preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD; + doubleDensity = true; + break; + default: + throw new FluxEngineException("unsupported sector size --- you must pick 256 or 512"); + } + + int fullSectorSize = preambleSize + encodedSectorSize; + Bytes fullSector = new Bytes(0); + ByteWriter fw = fullSector.writer(); + + /* sector gap after index pulse */ + for (int i = 0; i < preHeaderGapFillSize; i++) + fw.write8(GAP1_FILL_BYTE); + + /* sector preamble */ + for (int i = 0; i < preambleSize; i++) + fw.write8(0); + + Bytes sectorData; + if (sector.data.size() == encodedSectorSize) + sectorData = sector.data; + else + { + sectorData = new Bytes(0); + ByteWriter writer = sectorData.writer(); + writer.write8(0xFB); /* sync character */ + if (doubleDensity) + { + writer.write8(0xFB); /* Double-density has two sync characters */ + } + writer.write(sector.data); + if (doubleDensity) + { + writer.write8(NorthstarDecoder.northstarChecksum(sectorData.slice(2))); + } + else + { + writer.write8(NorthstarDecoder.northstarChecksum(sectorData.slice(1))); + } + } + + fw.write(sectorData); + + /* sector postamble */ + for (int i = 0; i < gapFillSize; i++) + fw.write8(GAP2_FILL_BYTE); + + if (sector.location.logicalSector() != 9) + { + if (fullSector.size() != fullSectorSize) + throw new FluxEngineException(String.format( + "sector mismatched length (%d); expected %d, got %d", + sector.data.size(), + fullSector.size(), + fullSectorSize)); + } + + boolean[] lastBit = {false}; + + if (doubleDensity) + { + FmMfm.encodeMfm(bits, cursor, fullSector, lastBit); + } + else + { + FmMfm.encodeFm(bits, cursor, fullSector); + } + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + int bitsPerRevolution = 100000; + double clockRateUs = config.getClockPeriodUs(); + + Sector sector = sectors.get(0); + if (sector.data.size() == Northstar.NORTHSTAR_PAYLOAD_SIZE_SD) + bitsPerRevolution /= 2; /* FM */ + else + clockRateUs /= 2.00; + + Bits bits = new Bits(bitsPerRevolution); + Bits.Cursor cursor = new Bits.Cursor(0); + + for (Sector sectorData : sectors) + writeSector(bits, cursor, sectorData); + + if (cursor.get() > bits.size()) + throw new FluxEngineException("track data overrun"); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod( + fullConfig, + clockRateUs * 1e3, + config.getRotationalPeriodMs() * 1e6)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java b/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java new file mode 100644 index 00000000..42ae34d6 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java @@ -0,0 +1,119 @@ +package com.cowlark.fluxengine.arch.tartu; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.tartu.TartuEncoderProto; +import java.util.List; + +/** + * The Tartu encoder, ported from arch/tartu/encoder.cc. + */ +public class TartuEncoder extends Encoder +{ + private final ConfigProto fullConfig; + private final TartuEncoderProto config; + private final boolean[] lastBit = new boolean[1]; + private double clockRateUs; + private Bits bits; + private Bits.Cursor cursor; + + public TartuEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getTartu(); + } + + private void writeBytes(Bytes bytes) + { + FmMfm.encodeMfm(bits, cursor, bytes, lastBit); + } + + private void writeRawBits(long data, int width) + { + cursor.advance(width); + lastBit[0] = (data & 1) != 0; + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private void writeFillerRawBitsUs(double us) + { + int count = (int) ((us / clockRateUs) / 2); + for (int i = 0; i < count; i++) + writeRawBits(0b10, 2); + } + + private void writeSector(Sector sectorData) + { + writeRawBits(config.getHeaderMarker(), 64); + { + Bytes bytes = new Bytes(0); + ByteWriter bw = bytes.writer(); + bw.write8( + (sectorData.location.logicalCylinder() << 1) | sectorData.location.logicalHead()); + bw.write8(1); + bw.write8(sectorData.location.logicalSector()); + bw.write8(~Crc.sumBytes(bytes.slice(0, 3))); + writeBytes(bytes); + } + + writeFillerRawBitsUs(config.getGap3Us()); + writeRawBits(config.getDataMarker(), 64); + { + Bytes bytes = new Bytes(0); + ByteWriter bw = bytes.writer(); + bw.write(sectorData.data); + bw.write8(~Crc.sumBytes(bytes.slice(0, sectorData.data.size()))); + writeBytes(bytes); + } + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + clockRateUs = config.getClockPeriodUs(); + int bitsPerRevolution = + (int) ((config.getTargetRotationalPeriodMs() * 1000.0) / clockRateUs); + + bits = new Bits(bitsPerRevolution); + cursor = new Bits.Cursor(0); + + writeFillerRawBitsUs(config.getGap1Us()); + boolean first = true; + for (Sector sectorData : sectors) + { + if (!first) + writeFillerRawBitsUs(config.getGap4Us()); + first = false; + writeSector(sectorData); + } + + if (cursor.get() > bits.size()) + throw new FluxEngineException("track data overrun"); + writeFillerRawBitsUs(config.getTargetRotationalPeriodMs() * 1000.0); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod( + fullConfig, + clockRateUs * 1e3, + config.getTargetRotationalPeriodMs() * 1e6)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java b/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java new file mode 100644 index 00000000..6ff536d4 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java @@ -0,0 +1,146 @@ +package com.cowlark.fluxengine.arch.tids990; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.tids990.Tids990EncoderProto; +import java.util.List; + +/** + * The TI DS990 encoder, ported from arch/tids990/encoder.cc. + */ +public class Tids990Encoder extends Encoder +{ + private static int decodeUint16(int raw) + { + Bytes b = new Bytes(2); + b.writer().writeBe16(raw); + return FmMfm.decodeFmMfm(b.toBits()).getByte(0) & 0xff; + } + + private final ConfigProto fullConfig; + private final Tids990EncoderProto config; + private final boolean[] lastBit = new boolean[1]; + private Bits bits; + private Bits.Cursor cursor; + + public Tids990Encoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getTids990(); + } + + private void writeRawBits(int data, int width) + { + cursor.advance(width); + lastBit[0] = (data & 1) != 0; + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private void writeBytes(Bytes bytes) + { + FmMfm.encodeMfm(bits, cursor, bytes, lastBit); + } + + private void writeBytes(int count, int byte_) + { + Bytes bytes = Bytes.of(byte_); + for (int i = 0; i < count; i++) + writeBytes(bytes); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + double clockRateUs = config.getClockPeriodUs() / 2.0; + int bitsPerRevolution = + (int) ((config.getRotationalPeriodMs() * 1000.0) / clockRateUs); + bits = new Bits(bitsPerRevolution); + cursor = new Bits.Cursor(0); + + int am1Unencoded = decodeUint16(config.getAm1Byte()); + int am2Unencoded = decodeUint16(config.getAm2Byte()); + + writeBytes(config.getGap1Bytes(), 0x55); + + boolean first = true; + for (Sector sectorData : sectors) + { + if (!first) + writeBytes(config.getGap3Bytes(), 0x55); + first = false; + + /* Writing the sector and data records are fantastically annoying. + * The CRC is calculated from the *very start* of the record, and + * include the malformed marker bytes. Our encoder doesn't know + * about this, of course, with the result that we have to construct + * the unencoded header, calculate the checksum, and then use the + * same logic to emit the bytes which require special encoding + * before encoding the rest of the header normally. */ + + { + Bytes header = new Bytes(0); + ByteWriter bw = header.writer(); + + writeBytes(12, 0x55); + bw.write8(am1Unencoded); + bw.write8(sectorData.location.logicalHead() << 3); + bw.write8(sectorData.location.logicalCylinder()); + bw.write8(config.getSectorCount()); + bw.write8(sectorData.location.logicalSector()); + bw.writeBe16(sectorData.data.size()); + int crc = Crc.crc16(Crc.CCITT_POLY, header); + bw.writeBe16(crc); + + writeRawBits(config.getAm1Byte(), 16); + writeBytes(header.slice(1)); + } + + writeBytes(config.getGap2Bytes(), 0x55); + + { + Bytes data = new Bytes(0); + ByteWriter bw = data.writer(); + + writeBytes(12, 0x55); + bw.write8(am2Unencoded); + + bw.write(sectorData.data); + int crc = Crc.crc16(Crc.CCITT_POLY, data); + bw.writeBe16(crc); + + writeRawBits(config.getAm2Byte(), 16); + writeBytes(data.slice(1)); + } + } + + if (cursor.get() >= bits.size()) + throw new FluxEngineException("track data overrun"); + while (cursor.get() < bits.size()) + writeBytes(1, 0x55); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits( + bits, + (long) calculatePhysicalClockPeriod( + fullConfig, + clockRateUs * 1e3, + config.getRotationalPeriodMs() * 1e6)); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java new file mode 100644 index 00000000..79107bf6 --- /dev/null +++ b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java @@ -0,0 +1,223 @@ +package com.cowlark.fluxengine.arch.victor9k; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bits; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.external.Crc; +import com.cowlark.fluxengine.victor9k.Victor9kEncoderProto; +import java.util.List; + +/** + * The Victor 9k encoder, ported from arch/victor9k/encoder.cc. + */ +public class Victor9kEncoder extends Encoder +{ + private static final int[] ENCODE_DATA_GCR = new int[16]; + + static + { + ENCODE_DATA_GCR[0x0] = 0x0a; + ENCODE_DATA_GCR[0x1] = 0x0b; + ENCODE_DATA_GCR[0x2] = 0x12; + ENCODE_DATA_GCR[0x3] = 0x13; + ENCODE_DATA_GCR[0x4] = 0x0e; + ENCODE_DATA_GCR[0x5] = 0x0f; + ENCODE_DATA_GCR[0x6] = 0x16; + ENCODE_DATA_GCR[0x7] = 0x17; + ENCODE_DATA_GCR[0x8] = 0x09; + ENCODE_DATA_GCR[0x9] = 0x19; + ENCODE_DATA_GCR[0xa] = 0x1a; + ENCODE_DATA_GCR[0xb] = 0x1b; + ENCODE_DATA_GCR[0xc] = 0x0d; + ENCODE_DATA_GCR[0xd] = 0x1d; + ENCODE_DATA_GCR[0xe] = 0x1e; + ENCODE_DATA_GCR[0xf] = 0x15; + } + + private static int encodeDataGcr(int data) + { + data &= 0x0f; + return ENCODE_DATA_GCR[data]; + } + + private final ConfigProto fullConfig; + private final Victor9kEncoderProto config; + private final boolean[] lastBit = new boolean[1]; + + public Victor9kEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getVictor9K(); + } + + private void writeZeroBits(Bits bits, Bits.Cursor cursor, int count) + { + while (count-- != 0) + { + if (cursor.get() < bits.size()) + { + lastBit[0] = false; + bits.setBit(cursor.get(), false); + } + cursor.advance(); + } + } + + private void writeOneBits(Bits bits, Bits.Cursor cursor, int count) + { + while (count-- != 0) + { + if (cursor.get() < bits.size()) + { + lastBit[0] = true; + bits.setBit(cursor.get(), true); + } + cursor.advance(); + } + } + + private void writeBits(Bits bits, Bits.Cursor cursor, boolean[] src) + { + for (boolean bit : src) + { + if (cursor.get() < bits.size()) + { + lastBit[0] = bit; + bits.setBit(cursor.get(), bit); + } + cursor.advance(); + } + } + + private void writeBits(Bits bits, Bits.Cursor cursor, long data, int width) + { + cursor.advance(width); + lastBit[0] = (data & 1) != 0; + for (int i = 0; i < width; i++) + { + int pos = cursor.get() - i - 1; + if (pos < bits.size()) + bits.setBit(pos, (data & 1) != 0); + data >>= 1; + } + } + + private void writeBits(Bits bits, Bits.Cursor cursor, Bytes bytes) + { + Bits bitr = bytes.toBits(); + for (int i = 0; i < bitr.size(); i++) + { + if (cursor.get() < bits.size()) + bits.setBit(cursor.get(), bitr.getBit(i)); + cursor.advance(); + } + } + + private void writeByte(Bits bits, Bits.Cursor cursor, int b) + { + writeBits(bits, cursor, encodeDataGcr(b >> 4), 5); + writeBits(bits, cursor, encodeDataGcr(b), 5); + } + + private void writeBytes(Bits bits, Bits.Cursor cursor, Bytes bytes) + { + for (int i = 0; i < bytes.size(); i++) + writeByte(bits, cursor, bytes.getByte(i) & 0xff); + } + + private void writeGap(Bits bits, Bits.Cursor cursor, int length) + { + for (int i = 0; i < length / 10; i++) + writeByte(bits, cursor, '0'); + } + + private void writeSector(Bits bits, + Bits.Cursor cursor, + Victor9kEncoderProto.TrackdataProto trackdata, + Sector sector) + { + writeOneBits(bits, cursor, trackdata.getPreHeaderSyncBits()); + writeBits(bits, cursor, Victor9k.VICTOR9K_SECTOR_RECORD, 10); + + int encodedTrack = sector.location.logicalCylinder() | (sector.location.logicalHead() << 7); + int encodedSector = sector.location.logicalSector(); + writeBytes( + bits, + cursor, + Bytes.of( + encodedTrack, + encodedSector, + (encodedTrack + encodedSector) & 0xff)); + + writeGap(bits, cursor, trackdata.getPostHeaderGapBits()); + + writeOneBits(bits, cursor, trackdata.getPreDataSyncBits()); + writeBits(bits, cursor, Victor9k.VICTOR9K_DATA_RECORD, 10); + + writeBytes(bits, cursor, sector.data); + + Bytes checksum = new Bytes(2); + checksum.writer().writeLe16(Crc.sumBytes(sector.data)); + writeBytes(bits, cursor, checksum); + writeGap(bits, cursor, trackdata.getPostDataGapBits()); + } + + private Victor9kEncoderProto.TrackdataProto getTrackFormat(int track, int head) + { + Victor9kEncoderProto.TrackdataProto.Builder builder = + Victor9kEncoderProto.TrackdataProto.newBuilder(); + for (Victor9kEncoderProto.TrackdataProto f : config.getTrackdataList()) + { + if (f.hasMinTrack() && (track < f.getMinTrack())) + continue; + if (f.hasMaxTrack() && (track > f.getMaxTrack())) + continue; + if (f.hasHead() && (head != f.getHead())) + continue; + + builder.mergeFrom(f); + } + return builder.build(); + } + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + Victor9kEncoderProto.TrackdataProto trackdata = + getTrackFormat(ltl.logicalCylinder, ltl.logicalHead); + + int bitsPerRevolution = + (int) ((trackdata.getRotationalPeriodMs() * 1e3) / trackdata.getClockPeriodUs()); + Bits bits = new Bits(bitsPerRevolution); + long clockPeriod = + (long) calculatePhysicalClockPeriod( + fullConfig, + trackdata.getClockPeriodUs() * 1e3, + trackdata.getRotationalPeriodMs() * 1e6); + Bits.Cursor cursor = new Bits.Cursor(0); + + bits.fillBitmapTo( + cursor, + (int) (trackdata.getPostIndexGapUs() * 1e3 / clockPeriod), + new boolean[] {true, false}); + lastBit[0] = false; + + for (Sector sector : sectors) + writeSector(bits, cursor, trackdata, sector); + + if (cursor.get() >= bits.size()) + throw new FluxEngineException( + "track data overrun by " + (cursor.get() - bits.size()) + " bits"); + bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendBits(bits, clockPeriod); + return fluxmap; + } +} diff --git a/java/com/cowlark/fluxengine/external/FmMfm.java b/java/com/cowlark/fluxengine/external/FmMfm.java index 6cf34de0..4aee1078 100644 --- a/java/com/cowlark/fluxengine/external/FmMfm.java +++ b/java/com/cowlark/fluxengine/external/FmMfm.java @@ -68,7 +68,7 @@ public static Bytes decodeFmMfm(Bits bits) return bytes; } - public static void encodeFm(Bits bits, int[] cursor, Bytes input) + public static void encodeFm(Bits bits, Bits.Cursor cursor, Bytes input) { if (bits.size() == 0) return; @@ -82,16 +82,18 @@ public static void encodeFm(Bits bits, int[] cursor, Bytes input) boolean bit = (b & 0x80) != 0; b <<= 1; - if (cursor[0] >= len) + if (cursor.get() >= len) return; - bits.set(cursor[0]++, true); - bits.set(cursor[0]++, bit); + bits.set(cursor.get(), true); + cursor.advance(); + bits.set(cursor.get(), bit); + cursor.advance(); } } } - public static void encodeMfm(Bits bits, int[] cursor, Bytes data, boolean[] lastBit) + public static void encodeMfm(Bits bits, Bits.Cursor cursor, Bytes data, boolean[] lastBit) { if (bits.size() == 0) return; @@ -105,11 +107,13 @@ public static void encodeMfm(Bits bits, int[] cursor, Bytes data, boolean[] last boolean bit = (b & 0x80) != 0; b <<= 1; - if (cursor[0] >= len) + if (cursor.get() >= len) return; - bits.set(cursor[0]++, !lastBit[0] && !bit); - bits.set(cursor[0]++, bit); + bits.set(cursor.get(), !lastBit[0] && !bit); + cursor.advance(); + bits.set(cursor.get(), bit); + cursor.advance(); lastBit[0] = bit; } } diff --git a/javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java b/javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java new file mode 100644 index 00000000..6bd79288 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java @@ -0,0 +1,73 @@ +package com.cowlark.fluxengine.arch; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.encoders.Encoder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ArchEncoderTest +{ + @Test + public void noEncoderConfiguredThrows() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .build(); + + assertThrows( + FluxEngineException.class, + () -> Arch.createEncoder(config)); + } + + @Test + public void createAmigaEncoder() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("encoder.amiga.clock_rate_us", "2.0") + .build(); + + Encoder encoder = Arch.createEncoder(config); + + assertThat(encoder).isInstanceOf( + com.cowlark.fluxengine.arch.amiga.AmigaEncoder.class); + } + + @Test + public void createIbmEncoder() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("encoder.ibm.trackdata[0].emit_iam", "false") + .build(); + + Encoder encoder = Arch.createEncoder(config); + + assertThat(encoder).isInstanceOf( + com.cowlark.fluxengine.arch.ibm.IbmEncoder.class); + } + + @Test + public void createTartuEncoder() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("encoder.tartu.clock_period_us", "2.0") + .build(); + + Encoder encoder = Arch.createEncoder(config); + + assertThat(encoder).isInstanceOf( + com.cowlark.fluxengine.arch.tartu.TartuEncoder.class); + } +} diff --git a/javatests/com/cowlark/fluxengine/arch/BUILD.bazel b/javatests/com/cowlark/fluxengine/arch/BUILD.bazel new file mode 100644 index 00000000..dc977b59 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/arch/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "ArchEncoderTest", + srcs = ["ArchEncoderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/arch", + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/encoders", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/external/FmMfmTest.java b/javatests/com/cowlark/fluxengine/external/FmMfmTest.java index e10001fe..baacee05 100644 --- a/javatests/com/cowlark/fluxengine/external/FmMfmTest.java +++ b/javatests/com/cowlark/fluxengine/external/FmMfmTest.java @@ -14,7 +14,7 @@ public class FmMfmTest private static Bits wrapEncodeMfm(Bytes bytes) { Bits bits = new Bits(16); - int[] cursor = {0}; + Bits.Cursor cursor = new Bits.Cursor(0); boolean[] lastBit = {false}; FmMfm.encodeMfm(bits, cursor, bytes, lastBit); return bits; @@ -23,7 +23,7 @@ private static Bits wrapEncodeMfm(Bytes bytes) private static Bits wrapEncodeFm(Bytes bytes) { Bits bits = new Bits(16); - int[] cursor = {0}; + Bits.Cursor cursor = new Bits.Cursor(0); FmMfm.encodeFm(bits, cursor, bytes); return bits; } From 510aaa8934953f5f77f3cb71fb93de1a8fba8e8d Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 8 Aug 2026 23:48:07 +0200 Subject: [PATCH 126/192] Port the flux sinks. --- MODULE.bazel | 1 + .../fluxengine/algorithms/ReaderWriter.java | 7 +- java/com/cowlark/fluxengine/external/Scp.java | 25 ++ .../fluxengine/fluxsink/A2RFluxSink.java | 237 ++++++++++++++++++ .../fluxsink/A2RFluxSinkFactory.java | 36 +++ .../fluxengine/fluxsink/AuFluxSink.java | 87 +++++++ .../fluxsink/AuFluxSinkFactory.java | 34 +++ .../cowlark/fluxengine/fluxsink/BUILD.bazel | 6 + .../fluxengine/fluxsink/Fl2FluxSink.java | 93 +++++++ .../fluxsink/Fl2FluxSinkFactory.java | 36 +++ .../cowlark/fluxengine/fluxsink/FluxSink.java | 10 +- .../fluxengine/fluxsink/FluxSinkFactory.java | 36 ++- .../fluxengine/fluxsink/HardwareFluxSink.java | 43 ++++ .../fluxsink/HardwareFluxSinkFactory.java | 34 +++ .../fluxengine/fluxsink/ScpFluxSink.java | 208 +++++++++++++++ .../fluxsink/ScpFluxSinkFactory.java | 40 +++ .../fluxengine/fluxsink/VcdFluxSink.java | 86 +++++++ .../fluxsink/VcdFluxSinkFactory.java | 32 +++ .../cowlark/fluxengine/fluxsink/BUILD.bazel | 36 +++ .../fluxengine/fluxsink/Fl2FluxSinkTest.java | 80 ++++++ .../fluxengine/fluxsink/FluxSinkTest.java | 142 +++++++++++ 21 files changed, 1293 insertions(+), 16 deletions(-) create mode 100644 java/com/cowlark/fluxengine/external/Scp.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/A2RFluxSinkFactory.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/AuFluxSinkFactory.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkFactory.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/HardwareFluxSinkFactory.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/VcdFluxSink.java create mode 100644 java/com/cowlark/fluxengine/fluxsink/VcdFluxSinkFactory.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java diff --git a/MODULE.bazel b/MODULE.bazel index e1bddb1b..9d1e9097 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -12,6 +12,7 @@ maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( artifacts = [ "org.projectlombok:lombok:1.18.46", + "org.apache.commons:commons-lang3:3.17.0", "com.fazecast:jSerialComm:2.11.4", "com.google.guava:guava:33.6.0-jre", "com.google.truth:truth:1.4.5", diff --git a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java index 44bf642f..26ea1af0 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java +++ b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java @@ -65,7 +65,7 @@ public static void readDiskCommand( FluxSinkFactory outputFluxSinkFactory = null; if (config.getDecoder().hasCopyFluxTo()) outputFluxSinkFactory = FluxSinkFactory.create( - config.getDecoder().getCopyFluxTo()); + config, config.getDecoder().getCopyFluxTo()); Map> tracksByLogicalLocation = new HashMap<>(); @@ -86,10 +86,9 @@ public static void readDiskCommand( else disk.rotationalPeriod = getRotationalPeriodFromConfig(config); + try (FluxSink outputFluxSink = + outputFluxSinkFactory != null ? outputFluxSinkFactory.create() : null) { - FluxSink outputFluxSink = null; - if (outputFluxSinkFactory != null) - outputFluxSink = outputFluxSinkFactory.create(); int index = 0; for (Map.Entry entry : diskLayout.layoutByLogicalLocation.entrySet()) diff --git a/java/com/cowlark/fluxengine/external/Scp.java b/java/com/cowlark/fluxengine/external/Scp.java new file mode 100644 index 00000000..d970165f --- /dev/null +++ b/java/com/cowlark/fluxengine/external/Scp.java @@ -0,0 +1,25 @@ +package com.cowlark.fluxengine.external; + +/** + * Constants and structures for the SCP flux file format, ported from + * lib/external/scp.h. + */ +public final class Scp +{ + public static final int SCP_FLAG_INDEXED = (1 << 0); + public static final int SCP_FLAG_96TPI = (1 << 1); + public static final int SCP_FLAG_360RPM = (1 << 2); + public static final int SCP_FLAG_NORMALIZED = (1 << 3); + public static final int SCP_FLAG_READWRITE = (1 << 4); + public static final int SCP_FLAG_FOOTER = (1 << 5); + + /* Size of the file header, including the 168 track offsets. */ + public static final int SCP_HEADER_SIZE = 16 + 168 * 4; + + /* Size of a track header (the 'TRK' id plus 5 revolution records). */ + public static final int SCP_TRACK_SIZE = 4 + 5 * 12; + + private Scp() + { + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java new file mode 100644 index 00000000..bc6d0d1f --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java @@ -0,0 +1,237 @@ +package com.cowlark.fluxengine.fluxsink; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.A2R; +import com.cowlark.fluxengine.external.DriveType; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * A flux sink which writes an A2R flux file, ported from + * lib/fluxsink/a2rfluxsink.cc. + */ +public class A2RFluxSink extends FluxSink +{ + private static final String VERSION_STRING = String.format("%-32s", "FluxEngine"); + + private static long ticksToA2r(long ticks) + { + return (long) (ticks * NS_PER_TICK / A2R.NS_PER_TICK); + } + + private final String filename; + private final ConfigProto config; + private final Bytes bytes = new Bytes(0); + private final ByteWriter writer = bytes.writer(); + private final Bytes strmBytes = new Bytes(0); + private final ByteWriter strmWriter = strmBytes.writer(); + private final Map metadata = new LinkedHashMap<>(); + + private int minHead; + private int maxHead; + private int minCylinder; + private int maxCylinder; + + public A2RFluxSink(String filename, ConfigProto config) + { + this.filename = filename; + this.config = config; + metadata.put("image_date", + DateTimeFormatter.ISO_INSTANT.format( + ZonedDateTime.now(ZoneOffset.UTC))); + } + + private void writeChunkAndData(int chunkId, Bytes data) + { + writer.writeLe32(chunkId); + writer.writeLe32(data.size()); + writer.write(data); + } + + private void writeHeader() + { + writer.write(Bytes.of( + A2R.FILEHEADER[0] & 0xff, + A2R.FILEHEADER[1] & 0xff, + A2R.FILEHEADER[2] & 0xff, + A2R.FILEHEADER[3] & 0xff, + A2R.FILEHEADER[4] & 0xff, + A2R.FILEHEADER[5] & 0xff, + A2R.FILEHEADER[6] & 0xff, + A2R.FILEHEADER[7] & 0xff)); + } + + private void writeInfo() + { + Bytes info = new Bytes(0); + ByteWriter infoWriter = info.writer(); + infoWriter.write8(A2R.INFO_CHUNK_VERSION); + infoWriter.write(VERSION_STRING.getBytes()); + + infoWriter.write8( + (config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) + ? A2R.DISK_525 + : A2R.DISK_35); + + infoWriter.write8(1); /* write protected */ + infoWriter.write8(1); /* synchronized */ + writeChunkAndData(A2R.CHUNK_INFO, info); + } + + private void writeMeta() + { + Bytes meta = new Bytes(0); + ByteWriter metaWriter = meta.writer(); + for (Map.Entry i : metadata.entrySet()) + { + metaWriter.write(i.getKey().getBytes()); + metaWriter.write8('\t'); + metaWriter.write(i.getValue().getBytes()); + metaWriter.write8('\n'); + } + writeChunkAndData(A2R.CHUNK_META, meta); + } + + private void writeStream() + { + /* A STRM always ends with a 255, even though this could ALSO + * indicate the first byte of a multi-byte sequence */ + strmWriter.write8(255); + + writeChunkAndData(A2R.CHUNK_STRM, strmBytes); + } + + @Override + public void addFlux(int cylinder, int head, Fluxmap fluxmap) + { + if (fluxmap.bytes() == 0) + { + return; + } + + // Writing from an image (as opposed to from a floppy) will + // contain exactly one revolution and no index events. + FluxmapReader fmrCheck = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + fmrCheck.skipToEvent(F_BIT_INDEX); + boolean isImage = fmrCheck.eof(); + + // Write the flux data into its own Bytes + Bytes trackBytes = new Bytes(0); + ByteWriter trackWriter = trackBytes.writer(); + + int[] revolutionHolder = {0}; + long[] loopPointHolder = {0}; + long[] totalTicksHolder = {0}; + + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + java.util.function.IntConsumer writeOneFlux = (ticks) -> { + long value = ticksToA2r(ticks); + while (value > 254) + { + trackWriter.write8(255); + value -= 255; + } + trackWriter.write8((int) value); + }; + + java.util.function.IntConsumer writeFlux = (maxTicks) -> { + long ticksSinceLastPulse = 0; + + while (!fmr.eof() && totalTicksHolder[0] < maxTicks) + { + FluxmapReader.Event event = fmr.getNextEvent(); + long ticks = event.ticks(); + + ticksSinceLastPulse += ticks; + totalTicksHolder[0] += ticks; + + if ((event.event() & F_BIT_PULSE) != 0) + { + writeOneFlux.accept((int) ticksSinceLastPulse); + ticksSinceLastPulse = 0; + } + + if ((event.event() & F_BIT_INDEX) != 0 && revolutionHolder[0] == 0) + { + loopPointHolder[0] = totalTicksHolder[0]; + revolutionHolder[0] += 1; + } + } + }; + + if (isImage) + { + // A timing stream with no index represents exactly one + // revolution with no index. However, a2r nominally contains + // 450 degress of rotation, 250ms at 300rpm. + writeFlux.accept(Integer.MAX_VALUE); + loopPointHolder[0] = totalTicksHolder[0]; + fmr.rewind(); + revolutionHolder[0] += 1; + writeFlux.accept((int) (totalTicksHolder[0] * 5 / 4)); + } else + { + // We have an index, so this is a real read from a floppy + // and should be "one revolution plus a bit" + fmr.skipToEvent(F_BIT_INDEX); + writeFlux.accept(Integer.MAX_VALUE); + } + + if (config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) + strmWriter.write8(cylinder); + else + strmWriter.write8((cylinder << 1) | head); + + strmWriter.write8(A2R.TIMING); + strmWriter.writeLe32(trackBytes.size()); + strmWriter.writeLe32((int) ticksToA2r(loopPointHolder[0])); + strmWriter.write(trackBytes); + } + + @Override + public void close() + { + // FIXME: should use a passed-in DiskLayout object. + DiskLayout diskLayout = DiskLayout.createDiskLayout(config); + + minCylinder = diskLayout.minPhysicalCylinder; + maxCylinder = diskLayout.maxPhysicalCylinder; + minHead = diskLayout.minPhysicalHead; + maxHead = diskLayout.maxPhysicalHead; + + Logger.log("A2R: writing A2R " + + ((minHead == maxHead) ? "single sided" : "double sided") + + " file containing " + (maxCylinder - minCylinder + 1) + " tracks..."); + + writeHeader(); + writeInfo(); + writeStream(); + writeMeta(); + + try + { + Files.write(Path.of(filename), bytes.toByteArray()); + } catch (IOException e) + { + throw new FluxEngineException("cannot open output file"); + } + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/A2RFluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSinkFactory.java new file mode 100644 index 00000000..8e4075c6 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSinkFactory.java @@ -0,0 +1,36 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.config.ConfigProto; + +/** + * A factory for A2R flux sinks, ported from lib/fluxsink/a2rfluxsink.cc. + */ +public class A2RFluxSinkFactory extends FluxSinkFactory +{ + private final String filename; + private final ConfigProto config; + + public A2RFluxSinkFactory(String filename, ConfigProto config) + { + this.filename = filename; + this.config = config; + } + + @Override + public FluxSink create() + { + return new A2RFluxSink(filename, config); + } + + @Override + public String getPath() + { + return filename; + } + + @Override + public String toString() + { + return "a2r(" + filename + ")"; + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java new file mode 100644 index 00000000..64b6ee2e --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java @@ -0,0 +1,87 @@ +package com.cowlark.fluxengine.fluxsink; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.TICK_FREQUENCY; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.decoders.DecoderProto; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * A flux sink which writes Sun .au audio files, ported from + * lib/fluxsink/aufluxsink.cc. + */ +public class AuFluxSink extends FluxSink +{ + private final String directory; + private final boolean indexMarkers; + + public AuFluxSink(String directory, boolean indexMarkers) + { + this.directory = directory; + this.indexMarkers = indexMarkers; + } + + @Override + public void addFlux(int track, int head, Fluxmap fluxmap) + { + Logger.log("Warning: do not play these files, or you will break your " + + "speakers and/or ears!"); + + int totalTicks = fluxmap.ticks() + 2; + int channels = indexMarkers ? 2 : 1; + + try + { + Files.createDirectories(Path.of(directory)); + } catch (IOException e) + { + throw new FluxEngineException("cannot create directory '" + directory + "'"); + } + + Bytes data = new Bytes(totalTicks * channels); + for (int i = 0; i < data.size(); i++) + data.setByte(i, (byte) 0x80); + + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + long timestamp = 0; + while (!fmr.eof()) + { + FluxmapReader.Event event = fmr.getNextEvent(); + if (fmr.eof()) + break; + timestamp += event.ticks(); + + if ((event.event() & F_BIT_PULSE) != 0) + data.setByte((int) timestamp * channels, (byte) 0x7f); + if (indexMarkers && (event.event() & F_BIT_INDEX) != 0) + data.setByte((int) timestamp * channels + 1, (byte) 0x7f); + } + + /* Write header */ + Bytes header = new Bytes(24); + header.writer() + .writeBe32(0x2e736e64) + .writeBe32(24) + .writeBe32(totalTicks * channels) + .writeBe32(2) /* 8-bit PCM */ + .writeBe32(TICK_FREQUENCY) + .writeBe32(channels); /* channels */ + + String filename = String.format("%s/c%02d.h%01d.au", directory, track, head); + try + { + Files.write(Path.of(filename), header.concat(data).toByteArray()); + } catch (IOException e) + { + throw new FluxEngineException("cannot open output file"); + } + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/AuFluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/AuFluxSinkFactory.java new file mode 100644 index 00000000..fb9c06b8 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/AuFluxSinkFactory.java @@ -0,0 +1,34 @@ +package com.cowlark.fluxengine.fluxsink; + +/** + * A factory for Sun .au flux sinks, ported from lib/fluxsink/aufluxsink.cc. + */ +public class AuFluxSinkFactory extends FluxSinkFactory +{ + private final String directory; + private final boolean indexMarkers; + + public AuFluxSinkFactory(String directory, boolean indexMarkers) + { + this.directory = directory; + this.indexMarkers = indexMarkers; + } + + @Override + public FluxSink create() + { + return new AuFluxSink(directory, indexMarkers); + } + + @Override + public String getPath() + { + return directory; + } + + @Override + public String toString() + { + return "au(" + directory + ")"; + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel index 29b67803..6e4b21dd 100644 --- a/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsink/BUILD.bazel @@ -25,5 +25,11 @@ java_library( "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/external", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "//java/com/cowlark/fluxengine/usb", + "@com_google_protobuf//java/core", + "@maven//:org_apache_commons_commons_lang3", ], ) diff --git a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java new file mode 100644 index 00000000..0197047d --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java @@ -0,0 +1,93 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.external.FluxFileProto; +import com.cowlark.fluxengine.external.FluxFileVersion; +import com.cowlark.fluxengine.external.FluxMagic; +import com.cowlark.fluxengine.external.TrackFluxProto; +import com.google.protobuf.ByteString; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.tuple.Pair; + +/** + * A flux sink which writes an FL2 flux file, ported from + * lib/fluxsink/fl2fluxsink.cc. + */ +public class Fl2FluxSink extends FluxSink +{ + private final String filename; + private final ConfigProto config; + private final Map, List> data = new HashMap<>(); + + public Fl2FluxSink(String filename, ConfigProto config) + { + this.filename = filename; + this.config = config; + + try + { + Path path = Path.of(filename); + Files.write(path, new byte[0]); + Files.delete(path); + } catch (IOException e) + { + throw new FluxEngineException("cannot open output file"); + } + } + + @Override + public void addFlux(int track, int head, Fluxmap fluxmap) + { + data.computeIfAbsent(Pair.of(track, head), k -> new ArrayList<>()) + .add(fluxmap.rawBytes()); + } + + @Override + public void close() + { + Logger.log("FL2: writing " + filename); + + FluxFileProto.Builder proto = FluxFileProto.newBuilder(); + for (Map.Entry, List> e : data.entrySet()) + { + TrackFluxProto.Builder track = TrackFluxProto.newBuilder(); + track.setTrack(e.getKey().getLeft()); + track.setHead(e.getKey().getRight()); + for (Bytes fluxBytes : e.getValue()) + track.addFlux(ByteString.copyFrom(fluxBytes.toByteArray())); + proto.addTrack(track); + } + + proto.setRotationalPeriodMs( + config.getDrive().getRotationalPeriodMs()); + proto.setDriveType(config.getDrive().getDriveType()); + proto.setFormatType(config.getLayout().getFormatType()); + + saveFl2File(filename, proto); + } + + private static void saveFl2File(String filename, FluxFileProto.Builder proto) + { + proto.setMagic(FluxMagic.MAGIC.getNumber()); + proto.setVersion(FluxFileVersion.VERSION_2); + + try + { + Files.write(Path.of(filename), proto.build().toByteArray()); + } catch (IOException e) + { + throw new FluxEngineException( + "unable to write output file '" + filename + "'"); + } + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkFactory.java new file mode 100644 index 00000000..04b84048 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkFactory.java @@ -0,0 +1,36 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.config.ConfigProto; + +/** + * A factory for FL2 flux sinks, ported from lib/fluxsink/fl2fluxsink.cc. + */ +public class Fl2FluxSinkFactory extends FluxSinkFactory +{ + private final String filename; + private final ConfigProto config; + + public Fl2FluxSinkFactory(String filename, ConfigProto config) + { + this.filename = filename; + this.config = config; + } + + @Override + public FluxSink create() + { + return new Fl2FluxSink(filename, config); + } + + @Override + public String getPath() + { + return filename; + } + + @Override + public String toString() + { + return "fl2(" + filename + ")"; + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/FluxSink.java b/java/com/cowlark/fluxengine/fluxsink/FluxSink.java index cc1fd809..54dab4c7 100644 --- a/java/com/cowlark/fluxengine/fluxsink/FluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/FluxSink.java @@ -6,7 +6,7 @@ /** * A destination for flux data, ported from lib/fluxsink/fluxsink.h. */ -public abstract class FluxSink +public abstract class FluxSink implements AutoCloseable { /* Writes a fluxmap to a track and side. */ public abstract void addFlux(int track, int side, Fluxmap fluxmap); @@ -15,4 +15,12 @@ public void addFlux(CylinderHead location, Fluxmap fluxmap) { addFlux(location.cylinder(), location.head(), fluxmap); } + + /* Flushes any buffered data. The C++ writes this in the destructor; Java + * has no destructor, so this must be called explicitly once all tracks + * have been written. */ + @Override + public void close() + { + } } diff --git a/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java index d4df5b7a..b9ecc72f 100644 --- a/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java +++ b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java @@ -13,33 +13,47 @@ public static FluxSinkFactory create(ConfigProto config) { if (!config.hasFluxSink()) throw new FluxEngineException("no flux sink configured"); - return create(config.getFluxSink()); + return create(config, config.getFluxSink()); } - public static FluxSinkFactory create(FluxSinkProto config) + public static FluxSinkFactory create( + ConfigProto config, FluxSinkProto sinkConfig) { - switch (config.getType()) + switch (sinkConfig.getType()) { case FLUXTYPE_DRIVE: - return notImplemented("hardware"); + return new HardwareFluxSinkFactory(config); case FLUXTYPE_A2R: - return notImplemented("a2r"); + return new A2RFluxSinkFactory(sinkConfig.getA2R().getFilename(), config); case FLUXTYPE_AU: - return notImplemented("au"); + return new AuFluxSinkFactory( + sinkConfig.getAu().getDirectory(), + sinkConfig.getAu().getIndexMarkers()); case FLUXTYPE_VCD: - return notImplemented("vcd"); + return new VcdFluxSinkFactory(sinkConfig.getVcd().getDirectory()); case FLUXTYPE_SCP: - return notImplemented("scp"); + return new ScpFluxSinkFactory( + sinkConfig.getScp().getFilename(), + sinkConfig.getScp().getTypeByte(), + sinkConfig.getScp().getAlignWithIndex(), + config); case FLUXTYPE_FLUX: - return notImplemented("fl2"); + return createFl2FluxSinkFactory(sinkConfig.getFl2(), config); default: throw new FluxEngineException("no flux sink specified"); } } - private static FluxSinkFactory notImplemented(String name) + public static Fl2FluxSinkFactory createFl2FluxSinkFactory( + Fl2FluxSinkProto config, ConfigProto fullConfig) { - throw new FluxEngineException(name + " flux sink is not implemented yet"); + return new Fl2FluxSinkFactory(config.getFilename(), fullConfig); + } + + public static Fl2FluxSinkFactory createFl2FluxSinkFactory( + String filename, ConfigProto fullConfig) + { + return new Fl2FluxSinkFactory(filename, fullConfig); } /* Creates a writer object. */ diff --git a/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java new file mode 100644 index 00000000..bc34a6a6 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java @@ -0,0 +1,43 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; + +/** + * A flux sink which writes to a real floppy drive, ported from + * lib/fluxsink/hardwarefluxsink.cc. + */ +public class HardwareFluxSink extends FluxSink +{ + private final ConfigProto config; + private final UsbDevice device; + + public HardwareFluxSink(ConfigProto config) + { + this(config, UsbFactory.reconnect(config)); + } + + HardwareFluxSink(ConfigProto config, UsbDevice device) + { + this.config = config; + this.device = device; + } + + @Override + public void addFlux(int track, int side, Fluxmap fluxmap) + { + device.setDrive(config.getDrive().getDrive(), + config.getDrive().getHighDensity(), + config.getDrive().getIndexMode().getNumber()); + device.seek(track); + device.write(side, fluxmap.rawBytes(), config.getDrive().getHardSectorThresholdNs()); + } + + @Override + public void close() + { + device.close(); + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSinkFactory.java new file mode 100644 index 00000000..ed421507 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSinkFactory.java @@ -0,0 +1,34 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.config.ConfigProto; + +/** + * A factory for hardware flux sinks, ported from lib/fluxsink/hardwarefluxsink.cc. + */ +public class HardwareFluxSinkFactory extends FluxSinkFactory +{ + private final ConfigProto config; + + public HardwareFluxSinkFactory(ConfigProto config) + { + this.config = config; + } + + @Override + public FluxSink create() + { + return new HardwareFluxSink(config); + } + + @Override + public boolean isHardware() + { + return true; + } + + @Override + public String toString() + { + return "hardware"; + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java new file mode 100644 index 00000000..437536da --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java @@ -0,0 +1,208 @@ +package com.cowlark.fluxengine.fluxsink; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.external.DriveType; +import com.cowlark.fluxengine.external.Scp; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * A flux sink which writes an SCP flux file, ported from + * lib/fluxsink/scpfluxsink.cc. + */ +public class ScpFluxSink extends FluxSink +{ + private static int strackno(int track, int side) + { + return (track << 1) | side; + } + + private static void writeLe32(byte[] dest, int offset, int v) + { + dest[offset] = (byte) v; + dest[offset + 1] = (byte) (v >> 8); + dest[offset + 2] = (byte) (v >> 16); + dest[offset + 3] = (byte) (v >> 24); + } + + private static int appendChecksum(int checksum, Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + while (!br.eof()) + checksum += br.read8(); + return checksum; + } + + private final String filename; + private final int typeByte; + private final boolean alignWithIndex; + private final ConfigProto config; + + /* The 688-byte file header. */ + private final byte[] fileheader = new byte[Scp.SCP_HEADER_SIZE]; + private final Bytes trackdata = new Bytes(0); + + public ScpFluxSink(String filename, int typeByte, boolean alignWithIndex, ConfigProto config) + { + this.filename = filename; + this.typeByte = typeByte; + this.alignWithIndex = alignWithIndex; + this.config = config; + + // FIXME: should use a passed-in DiskLayout object. + DiskLayout diskLayout = DiskLayout.createDiskLayout(config); + int minCylinder = diskLayout.minPhysicalCylinder; + int maxCylinder = diskLayout.maxPhysicalCylinder; + int minHead = diskLayout.minPhysicalHead; + int maxHead = diskLayout.maxPhysicalHead; + + fileheader[0] = 'S'; + fileheader[1] = 'C'; + fileheader[2] = 'P'; + fileheader[3] = 0x18; /* Version 1.8 of the spec */ + fileheader[4] = (byte) typeByte; + fileheader[6] = (byte) strackno(minCylinder, minHead); + fileheader[7] = (byte) strackno(maxCylinder, maxHead); + int flags = Scp.SCP_FLAG_INDEXED; + if (config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) + throw new FluxEngineException( + "you can't write Apple II flux images to SCP files yet"); + if (config.getDrive().getDriveType() != DriveType.DRIVETYPE_40TRACK) + flags |= Scp.SCP_FLAG_96TPI; + fileheader[8] = (byte) flags; + fileheader[9] = 0; /* cell width */ + if ((minHead == 0) && (maxHead == 0)) + fileheader[10] = 1; + else if ((minHead == 1) && (maxHead == 1)) + fileheader[10] = 2; + else + fileheader[10] = 0; + + Logger.log("SCP: writing " + (((flags & Scp.SCP_FLAG_96TPI) != 0) ? 96 : 48) + + " tpi " + ((minHead == maxHead) ? "single sided" : "double sided") + + " file containing " + (fileheader[7] - fileheader[6] + 1) + " tracks"); + } + + @Override + public void addFlux(int track, int head, Fluxmap fluxmap) + { + ByteWriter trackdataWriter = trackdata.writer(); + trackdataWriter.seekToEnd(); + int strack = strackno(track, head); + + if (strack >= 168) + { + Logger.log("SCP: cannot write track " + track + " head " + head + + ", there are not enough Track Data Headers."); + return; + } + /* ScpTrack: 'TRK' id, strack, then 5 revolution records. */ + byte[] trackHeader = new byte[Scp.SCP_TRACK_SIZE]; + trackHeader[0] = 'T'; + trackHeader[1] = 'R'; + trackHeader[2] = 'K'; + trackHeader[3] = (byte) strack; + + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + Bytes fluxdata = new Bytes(0); + ByteWriter fluxdataWriter = fluxdata.writer(); + + int revolution = + -1; /* -1 indicates that we are before the first index pulse */ + if (alignWithIndex) + { + fmr.skipToEvent(F_BIT_INDEX); + revolution = 0; + } + long revTicks = 0; + long totalTicks = 0; + long ticksSinceLastPulse = 0; + int startOffset = 0; + while (revolution < 5) + { + FluxmapReader.Event event = fmr.getNextEvent(); + long ticks = event.ticks(); + + ticksSinceLastPulse += ticks; + totalTicks += ticks; + revTicks += ticks; + + /* if we haven't output any revolutions yet by the end of the + * track, assume that the whole track is one rev also discard + * any duplicate index pulses */ + if (((fmr.eof() && revolution <= 0) || + (((event.event() & F_BIT_INDEX) != 0) && revTicks > 0))) + { + if (fmr.eof() && revolution == -1) + revolution = 0; + if (revolution >= 0) + { + int revOffset = 4 + revolution * 12; + writeLe32(trackHeader, revOffset + 8, + startOffset + Scp.SCP_TRACK_SIZE); + writeLe32(trackHeader, revOffset + 4, + (fluxdataWriter.pos() - startOffset) / 2); + writeLe32(trackHeader, revOffset, + (int) (revTicks * NS_PER_TICK / 25)); + } + revolution++; + revTicks = 0; + startOffset = fluxdataWriter.pos(); + } + if (fmr.eof()) + break; + + if ((event.event() & F_BIT_PULSE) != 0) + { + long t = (long) (ticksSinceLastPulse * NS_PER_TICK / 25); + while (t >= 0x10000) + { + fluxdataWriter.writeBe16(0); + t -= 0x10000; + } + fluxdataWriter.writeBe16((int) t); + ticksSinceLastPulse = 0; + } + } + + fileheader[5] = (byte) revolution; + writeLe32(fileheader, 16 + strack * 4, + trackdataWriter.pos() + Scp.SCP_HEADER_SIZE); + trackdataWriter.write(trackHeader); + trackdataWriter.write(fluxdata); + } + + @Override + public void close() + { + int checksum = 0; + checksum = appendChecksum(checksum, + new Bytes(java.util.Arrays.copyOfRange(fileheader, 0x10, fileheader.length))); + checksum = appendChecksum(checksum, trackdata); + writeLe32(fileheader, 12, checksum); + + Logger.log("SCP: writing output file"); + Bytes out = new Bytes(fileheader).concat(trackdata); + try + { + Files.write(Path.of(filename), out.toByteArray()); + } catch (IOException e) + { + throw new FluxEngineException("cannot open output file"); + } + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java new file mode 100644 index 00000000..94669e71 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java @@ -0,0 +1,40 @@ +package com.cowlark.fluxengine.fluxsink; + +import com.cowlark.fluxengine.config.ConfigProto; + +/** + * A factory for SCP flux sinks, ported from lib/fluxsink/scpfluxsink.cc. + */ +public class ScpFluxSinkFactory extends FluxSinkFactory +{ + private final String filename; + private final int typeByte; + private final boolean alignWithIndex; + private final ConfigProto config; + + public ScpFluxSinkFactory(String filename, int typeByte, boolean alignWithIndex, ConfigProto config) + { + this.filename = filename; + this.typeByte = typeByte; + this.alignWithIndex = alignWithIndex; + this.config = config; + } + + @Override + public FluxSink create() + { + return new ScpFluxSink(filename, typeByte, alignWithIndex, config); + } + + @Override + public String getPath() + { + return filename; + } + + @Override + public String toString() + { + return "scp(" + filename + ")"; + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/VcdFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/VcdFluxSink.java new file mode 100644 index 00000000..fe3cad43 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/VcdFluxSink.java @@ -0,0 +1,86 @@ +package com.cowlark.fluxengine.fluxsink; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.decoders.DecoderProto; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * A flux sink which writes VCD (Value Change Dump) files, ported from + * lib/fluxsink/vcdfluxsink.cc. + */ +public class VcdFluxSink extends FluxSink +{ + private final String directory; + + public VcdFluxSink(String directory) + { + this.directory = directory; + } + + @Override + public void addFlux(int track, int head, Fluxmap fluxmap) + { + try + { + Files.createDirectories(Path.of(directory)); + } catch (IOException e) + { + throw new FluxEngineException("cannot create directory '" + directory + "'"); + } + + StringBuilder sb = new StringBuilder(); + sb.append("$timescale 1ns $end\n"); + sb.append("$var wire 1 i index $end\n"); + sb.append("$var wire 1 p pulse $end\n"); + sb.append("$upscope $end\n"); + sb.append("$enddefinitions $end\n"); + sb.append("$dumpvars 0i 0p $end\n"); + + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + long timestamp = 0; + long lasttimestamp = 0; + while (!fmr.eof()) + { + FluxmapReader.Event event = fmr.getNextEvent(); + if (fmr.eof()) + break; + + long newtimestamp = timestamp + event.ticks(); + if (newtimestamp != lasttimestamp) + { + sb.append("\n#"); + sb.append((long) ((lasttimestamp + 1) * NS_PER_TICK)); + sb.append(" 0i 0p\n"); + timestamp = newtimestamp; + sb.append("#"); + sb.append((long) (timestamp * NS_PER_TICK)); + sb.append(" "); + } + + if ((event.event() & F_BIT_PULSE) != 0) + sb.append("1p "); + if ((event.event() & F_BIT_INDEX) != 0) + sb.append("1i "); + + lasttimestamp = timestamp; + } + sb.append("\n"); + + String filename = String.format("%s/c%02d.h%01d.vcd", directory, track, head); + try + { + Files.write(Path.of(filename), sb.toString().getBytes()); + } catch (IOException e) + { + throw new FluxEngineException("cannot open output file"); + } + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/VcdFluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/VcdFluxSinkFactory.java new file mode 100644 index 00000000..ef498ed8 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsink/VcdFluxSinkFactory.java @@ -0,0 +1,32 @@ +package com.cowlark.fluxengine.fluxsink; + +/** + * A factory for VCD flux sinks, ported from lib/fluxsink/vcdfluxsink.cc. + */ +public class VcdFluxSinkFactory extends FluxSinkFactory +{ + private final String directory; + + public VcdFluxSinkFactory(String directory) + { + this.directory = directory; + } + + @Override + public FluxSink create() + { + return new VcdFluxSink(directory); + } + + @Override + public String getPath() + { + return directory; + } + + @Override + public String toString() + { + return "vcd(" + directory + ")"; + } +} diff --git a/javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel new file mode 100644 index 00000000..5552d3da --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel @@ -0,0 +1,36 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "Fl2FluxSinkTest", + srcs = ["Fl2FluxSinkTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "//java/com/cowlark/fluxengine/fluxsink", + "//java/com/cowlark/fluxengine/fluxsink:fluxsink_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "FluxSinkTest", + srcs = ["FluxSinkTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external", + "//java/com/cowlark/fluxengine/fluxsink", + "//java/com/cowlark/fluxengine/fluxsink:fluxsink_java_proto", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java b/javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java new file mode 100644 index 00000000..a30ed962 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java @@ -0,0 +1,80 @@ +package com.cowlark.fluxengine.fluxsink; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.external.FluxFileProto; +import com.cowlark.fluxengine.external.FluxFileVersion; +import com.cowlark.fluxengine.external.FluxMagic; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +@RunWith(JUnit4.class) +public class Fl2FluxSinkTest +{ + private static ConfigProto makeConfig() + { + return new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .build(); + } + + private static Fluxmap makeFluxmap() + { + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendInterval(100); + fluxmap.appendPulse(); + fluxmap.appendInterval(50); + fluxmap.appendPulse(); + return fluxmap; + } + + @Test + public void writesFile() throws IOException + { + Path path = Files.createTempFile("flux", ".fl2"); + Files.delete(path); + + Fl2FluxSink sink = new Fl2FluxSink(path.toString(), makeConfig()); + sink.addFlux(0, 0, makeFluxmap()); + sink.addFlux(0, 1, makeFluxmap()); + sink.close(); + + byte[] data = Files.readAllBytes(path); + assertThat(data.length).isGreaterThan(0); + + FluxFileProto proto = FluxFileProto.parseFrom(data); + assertThat(proto.getMagic()).isEqualTo(FluxMagic.MAGIC.getNumber()); + assertThat(proto.getVersion()).isEqualTo(FluxFileVersion.VERSION_2); + assertThat(proto.getRotationalPeriodMs()).isEqualTo(200.0); + assertThat(proto.getTrackCount()).isEqualTo(2); + assertThat(proto.getTrack(0).getTrack()).isEqualTo(0); + assertThat(proto.getTrack(0).getHead()).isEqualTo(0); + assertThat(proto.getTrack(0).getFluxCount()).isEqualTo(1); + assertThat(proto.getTrack(1).getTrack()).isEqualTo(0); + assertThat(proto.getTrack(1).getHead()).isEqualTo(1); + } + + @Test + public void factoryWiring() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("flux_sink.type", "FLUXTYPE_FLUX") + .set("flux_sink.fl2.filename", "test.fl2") + .build(); + + FluxSinkFactory factory = FluxSinkFactory.create(config); + + assertThat(factory).isInstanceOf(Fl2FluxSinkFactory.class); + assertThat(factory.getPath()).isEqualTo("test.fl2"); + assertThat(factory.isHardware()).isFalse(); + } +} diff --git a/javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java b/javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java new file mode 100644 index 00000000..0f4d44c3 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java @@ -0,0 +1,142 @@ +package com.cowlark.fluxengine.fluxsink; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.external.Scp; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +@RunWith(JUnit4.class) +public class FluxSinkTest +{ + private static ConfigProto makeConfig() + { + return new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("layout.tracks", "1") + .set("layout.sides", "1") + .set("layout.layoutdata[0].sector_size", "256") + .set("layout.layoutdata[0].physical.start_sector", "0") + .set("layout.layoutdata[0].physical.count", "8") + .build(); + } + + private static Fluxmap makeFluxmap() + { + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendInterval(100); + fluxmap.appendPulse(); + fluxmap.appendInterval(50); + fluxmap.appendPulse(); + fluxmap.appendIndex(); + return fluxmap; + } + + @Test + public void vcdWritesFile() throws IOException + { + Path dir = Files.createTempDirectory("vcd"); + VcdFluxSink sink = new VcdFluxSink(dir.toString()); + sink.addFlux(0, 0, makeFluxmap()); + + String contents = Files.readString(dir.resolve("c00.h0.vcd")); + assertThat(contents).contains("$timescale 1ns $end"); + assertThat(contents).contains("$var wire 1 p pulse $end"); + assertThat(contents).contains("$enddefinitions $end"); + } + + @Test + public void auWritesFile() throws IOException + { + Path dir = Files.createTempDirectory("au"); + AuFluxSink sink = new AuFluxSink(dir.toString(), true); + sink.addFlux(0, 0, makeFluxmap()); + + Bytes data = new Bytes(Files.readAllBytes(dir.resolve("c00.h0.au"))); + ByteReader br = new ByteReader(data); + assertThat(br.readBe32()).isEqualTo(0x2e736e64); + assertThat(br.readBe32()).isEqualTo(24); + assertThat(br.readBe32()).isEqualTo((makeFluxmap().ticks() + 2) * 2); + assertThat(br.readBe32()).isEqualTo(2); /* 8-bit PCM */ + assertThat(br.readBe32()).isEqualTo(12000000); /* TICK_FREQUENCY */ + assertThat(br.readBe32()).isEqualTo(2); /* channels */ + } + + @Test + public void a2rWritesFile() throws IOException + { + Path path = Files.createTempFile("flux", ".a2r"); + Files.delete(path); + + A2RFluxSink sink = new A2RFluxSink(path.toString(), makeConfig()); + sink.addFlux(0, 0, makeFluxmap()); + sink.close(); + + Bytes data = new Bytes(Files.readAllBytes(path)); + assertThat(data.size()).isGreaterThan(0); + /* File header: A2R2 then 0xff 0x0a 0x0d 0x0a. */ + assertThat(new String(data.slice(0, 4).toByteArray())).isEqualTo("A2R2"); + assertThat(data.getByte(4) & 0xff).isEqualTo(0xff); + assertThat(data.getByte(5) & 0xff).isEqualTo(0x0a); + } + + @Test + public void scpWritesFile() throws IOException + { + Path path = Files.createTempFile("flux", ".scp"); + Files.delete(path); + + ScpFluxSink sink = new ScpFluxSink(path.toString(), 0xff, false, makeConfig()); + sink.addFlux(0, 0, makeFluxmap()); + sink.close(); + + Bytes data = new Bytes(Files.readAllBytes(path)); + assertThat(data.size()).isGreaterThan(Scp.SCP_HEADER_SIZE); + assertThat(new String(data.slice(0, 3).toByteArray())).isEqualTo("SCP"); + assertThat(data.getByte(3) & 0xff).isEqualTo(0x18); /* version */ + assertThat(data.getByte(4) & 0xff).isEqualTo(0xff); /* type byte */ + } + + @Test + public void scpRejectsApple2() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("drive.drive_type", "DRIVETYPE_APPLE2") + .set("layout.tracks", "1") + .set("layout.sides", "1") + .build(); + + assertThrows( + FluxEngineException.class, + () -> new ScpFluxSink("test.scp", 0xff, false, config)); + } + + @Test + public void factoryWiring() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("flux_sink.type", "FLUXTYPE_A2R") + .set("flux_sink.a2r.filename", "test.a2r") + .build(); + + FluxSinkFactory factory = FluxSinkFactory.create(config); + assertThat(factory).isInstanceOf(A2RFluxSinkFactory.class); + assertThat(factory.getPath()).isEqualTo("test.a2r"); + assertThat(factory.isHardware()).isFalse(); + } +} From c764358bf5a3c55be7a110b20d44d346ed423116 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 00:01:20 +0200 Subject: [PATCH 127/192] Port the image readers. --- .../cowlark/fluxengine/cli/ReadCommand.java | 1 - .../fluxengine/imagereader/BUILD.bazel | 20 + .../imagereader/D64ImageReader.java | 81 ++++ .../imagereader/D88ImageReader.java | 225 +++++++++++ .../imagereader/DimImageReader.java | 157 ++++++++ .../imagereader/DiskCopyImageReader.java | 136 +++++++ .../imagereader/FdiImageReader.java | 126 ++++++ .../fluxengine/imagereader/ImageReader.java | 79 ++++ .../imagereader/ImdImageReader.java | 370 ++++++++++++++++++ .../imagereader/ImgImageReader.java | 81 ++++ .../imagereader/Jv3ImageReader.java | 120 ++++++ .../imagereader/NfdImageReader.java | 168 ++++++++ .../imagereader/NsiImageReader.java | 108 +++++ .../imagereader/Td0ImageReader.java | 192 +++++++++ .../fluxengine/imagereader/BUILD.bazel | 20 + .../imagereader/ImageReaderTest.java | 161 ++++++++ 16 files changed, 2044 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/imagereader/D64ImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/D88ImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/DimImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/FdiImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/ImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/ImdImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/ImgImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/NfdImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/NsiImageReader.java create mode 100644 java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java create mode 100644 javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index ca99b067..e455a7ba 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -61,7 +61,6 @@ public void run(ImmutableList args) if (config.getDecoder().getCopyFluxTo().getType() == FLUXTYPE_DRIVE) throw new FluxEngineException("you cannot copy flux to a hardware device"); - // Unsupported: DiskLayout diskLayout = new DiskLayout(config); FluxSource fluxSource = FluxSource.create(config); Decoder decoder = Arch.createDecoder(config); diff --git a/java/com/cowlark/fluxengine/imagereader/BUILD.bazel b/java/com/cowlark/fluxengine/imagereader/BUILD.bazel index be823b6f..039f8852 100644 --- a/java/com/cowlark/fluxengine/imagereader/BUILD.bazel +++ b/java/com/cowlark/fluxengine/imagereader/BUILD.bazel @@ -1,4 +1,5 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") +load("@rules_java//java:defs.bzl", "java_library") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) @@ -14,3 +15,22 @@ java_proto_library( name = "imagereader_java_proto", deps = [":imagereader_proto"], ) + +java_library( + name = "imagereader", + srcs = glob(["*.java"]), + deps = [ + ":imagereader_java_proto", + "//java/com/cowlark/fluxengine/arch:arch_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/config:drive_java_proto", + "//java/com/cowlark/fluxengine/config:layout_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/encoders:encoders_java_proto", + "//java/com/cowlark/fluxengine/external", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "@maven//:com_google_guava_guava", + ], +) diff --git a/java/com/cowlark/fluxengine/imagereader/D64ImageReader.java b/java/com/cowlark/fluxengine/imagereader/D64ImageReader.java new file mode 100644 index 00000000..d93ded46 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/D64ImageReader.java @@ -0,0 +1,81 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Reads a D64 (Commodore 1541) sector image, ported from + * lib/imagereader/d64imagereader.cc. + */ +public class D64ImageReader extends ImageReader +{ + public D64ImageReader(ImageReaderProto config) + { + super(config); + } + + private static int sectorsPerTrack(int track) + { + if (track < 17) + return 21; + if (track < 24) + return 19; + if (track < 30) + return 18; + return 17; + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + int inputFileSize = data.size(); + + int numCylinders = 39; + int numHeads = 1; + + Logger.log("D64: reading image with " + numCylinders + " tracks, " + numHeads + " heads"); + + int offset = 0; + + Image image = new Image(); + for (int track = 0; track < 40; track++) + { + int numSectors = sectorsPerTrack(track); + for (int head = 0; head < numHeads; head++) + { + for (int sectorId = 0; sectorId < numSectors; sectorId++) + { + Sector sector = image.put(track, head, sectorId); + if (offset < inputFileSize) + { /* still data available sector OK */ + sector.status = Sector.Status.OK; + sector.data = data.slice(offset, 256); + offset += 256; + } + else + { /* no more data in input file. Write sectors with status: + * DATA_MISSING */ + sector.status = Sector.Status.DATA_MISSING; + } + } + } + } + + image.calculateSize(); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java b/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java new file mode 100644 index 00000000..7491b492 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java @@ -0,0 +1,225 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.EncoderProto; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; +import com.cowlark.fluxengine.external.FormatType; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/* Reader based on this partial documentation of the D88 format: + * https://www.pc98.org/project/doc/d88.html + */ +public class D88ImageReader extends ImageReader +{ + public D88ImageReader(ImageReaderProto config) + { + super(config); + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + + /* The DIM header technically has a bit field for sectors present, + * however it is currently ignored by this reader */ + Bytes header = data.slice(0, 0x24); /* read first entry of track table as well */ + + String diskName = header.slice(0, 0x16).toString(); + if (diskName.length() > 0 && diskName.charAt(0) != 0) + Logger.log("D88: disk name: " + diskName); + + ByteReader headerReader = new ByteReader(header); + + int mediaFlag = headerReader.seek(0x1b).read8(); + int fileSize = data.size(); + + int diskSize = headerReader.seek(0x1c).readLe32(); + + if (diskSize > fileSize) + Logger.log("D88: found multiple disk images. Only using first"); + + int trackTableEnd = headerReader.seek(0x20).readLe32(); + int trackTableSize = trackTableEnd - 0x20; + + ByteReader trackTableReader = new ByteReader(data.slice(0x20, trackTableSize)); + + ConfigProto.Builder extra = ConfigProto.newBuilder(); + IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); + int clockRate = 500; + if (mediaFlag == 0x20) + { + extra.getDriveBuilder().setHighDensity(true); + extra.getLayoutBuilder().setFormatType(FormatType.FORMATTYPE_80TRACK); + } + else + { + clockRate = 300; + extra.getDriveBuilder().setHighDensity(false); + extra.getLayoutBuilder().setFormatType(FormatType.FORMATTYPE_40TRACK); + } + + com.cowlark.fluxengine.config.LayoutProto.Builder layout = extra.getLayoutBuilder(); + Image image = new Image(); + ByteReader br = new ByteReader(data); + br.seek(0x20 + trackTableSize); + for (int track = 0; track < trackTableSize / 4; track++) + { + int trackOffset = trackTableReader.seek(track * 4).readLe32(); + if (trackOffset == 0) + continue; + + int currentTrackTrack = -1; + int currentSectorsInTrack = + 0xffff; /* don't know # of sectors until we read the first one */ + int trackSectorSize = -1; + int trackMfm = -1; + + IbmEncoderProto.TrackdataProto.Builder trackdata = + ibm.addTrackdataBuilder(); + trackdata.setTargetClockPeriodUs(1e3 / clockRate); + trackdata.setTargetRotationalPeriodMs(167); + + com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = + layout.addLayoutdataBuilder(); + com.cowlark.fluxengine.config.SectorListProto.Builder physical = + layoutdata.getPhysicalBuilder(); + + for (int sectorInTrack = 0; sectorInTrack < currentSectorsInTrack; sectorInTrack++) + { + ByteReader sectorHeaderReader = new ByteReader(br.read(0x10)); + int cyl = sectorHeaderReader.seek(0).read8(); + int head = sectorHeaderReader.seek(1).read8(); + int sectorId = sectorHeaderReader.seek(2).read8(); + int sectorSize = 128 << sectorHeaderReader.seek(3).read8(); + int sectorsInTrack = sectorHeaderReader.seek(4).readLe16(); + int fm = sectorHeaderReader.seek(6).read8(); + int ddam = sectorHeaderReader.seek(7).read8(); + int fddStatusCode = sectorHeaderReader.seek(8).read8(); + int rpm = sectorHeaderReader.seek(13).read8(); + int dataLength = sectorHeaderReader.seek(14).readLe16(); + if (dataLength < sectorSize) + { + dataLength = sectorSize; + } + /* D88 provides much more sector information that is currently + * ignored */ + if (ddam != 0) + throw new FluxEngineException("D88: nonzero ddam currently unsupported"); + if (rpm != 0) + throw new FluxEngineException( + "D88: 1.44MB 300rpm formats currently unsupported"); + if (fddStatusCode != 0) + throw new FluxEngineException( + "D88: nonzero fdd status codes are currently unsupported"); + if (currentSectorsInTrack == 0xffff) + { + currentSectorsInTrack = sectorsInTrack; + } + else if (currentSectorsInTrack != sectorsInTrack) + { + throw new FluxEngineException("D88: mismatched number of sectors in track"); + } + if (currentTrackTrack < 0) + { + currentTrackTrack = cyl; + } + else if (currentTrackTrack != cyl) + { + throw new FluxEngineException( + "D88: all sectors in a track must belong to the same track"); + } + if (trackSectorSize < 0) + { + trackSectorSize = sectorSize; + /* this is the first sector we've read, use its settings for + * per-track data */ + + layoutdata.setTrack(cyl); + layoutdata.setSide(head); + layoutdata.setSectorSize(sectorSize); + + trackdata.setTrack(cyl); + trackdata.setHead(head); + trackdata.setUseFm(fm != 0); + if (fm != 0) + { + trackdata.setGapFillByte(0xffff); + trackdata.setIdamByte(0xf57e); + trackdata.setDamByte(0xf56f); + } + /* create timings to approximately match N88-BASIC */ + if (clockRate == 300) + { + if (sectorSize <= 256) + { + trackdata.setGap0(0x1b); + trackdata.setGap2(0x14); + trackdata.setGap3(0x1b); + } + } + else + { + if (sectorSize <= 128) + { + trackdata.setGap0(0x1b); + trackdata.setGap2(0x09); + trackdata.setGap3(0x1b); + } + else if (sectorSize <= 256) + { + trackdata.setGap0(0x36); + trackdata.setGap3(0x36); + } + } + } + else if (trackSectorSize != sectorSize) + { + throw new FluxEngineException( + "D88: multiple sector sizes per track are currently unsupported"); + } + + Bytes sectorData = br.read(sectorSize); + br.skip(dataLength - sectorSize); + physical.addSector(sectorId); + Sector sector = image.put(cyl, head, sectorId); + sector.status = Sector.Status.OK; + sector.data = sectorData; + } + + if (mediaFlag != 0x20) + { + IbmEncoderProto.TrackdataProto.Builder trackdata2 = + ibm.addTrackdataBuilder(); + trackdata2.setTargetClockPeriodUs(1e3 / clockRate); + trackdata2.setTargetRotationalPeriodMs(167); + } + } + + image.calculateSize(); + Geometry geometry = image.getGeometry(); + Logger.log("D88: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides"); + + layout.setTracks(geometry.numCylinders); + layout.setSides(geometry.numHeads); + + extraConfig = extra.build(); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/DimImageReader.java b/java/com/cowlark/fluxengine/imagereader/DimImageReader.java new file mode 100644 index 00000000..fef53819 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/DimImageReader.java @@ -0,0 +1,157 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.EncoderProto; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/* Reader based on this partial documentation of the DIM format: + * https://www.pc98.org/project/doc/dim.html + */ +public class DimImageReader extends ImageReader +{ + public DimImageReader(ImageReaderProto config, ConfigProto fullConfig) + { + super(config, fullConfig); + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + + Bytes header = data.slice(0, 256); + if (!header.slice(0xAB, 13).equals(new Bytes("DIFC HEADER "))) + throw new FluxEngineException("DIM: could not find DIM header, is this a DIM file?"); + + /* the DIM header technically has a bit field for sectors present, + * however it is currently ignored by this reader */ + + int mediaByte = header.getByte(0) & 0xff; + int tracks; + int sectorsPerTrack; + int sectorSize; + switch (mediaByte) + { + case 0: + tracks = 77; + sectorsPerTrack = 8; + sectorSize = 1024; + break; + case 1: + tracks = 80; + sectorsPerTrack = 9; + sectorSize = 1024; + break; + case 2: + tracks = 80; + sectorsPerTrack = 15; + sectorSize = 512; + break; + case 3: + tracks = 80; + sectorsPerTrack = 18; + sectorSize = 512; + break; + default: + throw new FluxEngineException("DIM: unsupported media byte"); + } + + Image image = new Image(); + int trackCount = 0; + ByteReader br = new ByteReader(data.slice(256)); + for (int track = 0; track < tracks; track++) + { + if (br.eof()) + break; + + for (int side = 0; side < 2; side++) + { + for (int sectorId = 1; sectorId <= sectorsPerTrack; sectorId++) + { + Bytes sectorData = br.read(sectorSize); + + Sector sector = image.put(track, side, sectorId); + sector.status = Sector.Status.OK; + sector.data = sectorData; + } + } + + trackCount++; + } + + ConfigProto.Builder extra = ConfigProto.newBuilder(); + com.cowlark.fluxengine.config.LayoutProto.Builder layout = extra.getLayoutBuilder(); + if (fullConfig.getEncoder().getFormatCase() == EncoderProto.FormatCase.FORMAT_NOT_SET) + { + IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = + ibm.addTrackdataBuilder(); + trackdata.setTargetClockPeriodUs(2); + + com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = + layout.addLayoutdataBuilder(); + com.cowlark.fluxengine.config.SectorListProto.Builder physical = + layoutdata.getPhysicalBuilder(); + switch (mediaByte) + { + case 0x00: + Logger.log( + "DIM: automatically setting format to 1.2MB (1024 byte sectors)"); + trackdata.setTargetRotationalPeriodMs(167); + layoutdata.setSectorSize(1024); + for (int i = 0; i < 9; i++) + physical.addSector(i); + break; + case 0x02: + Logger.log("DIM: automatically setting format to 1.2MB (512 byte sectors)"); + trackdata.setTargetRotationalPeriodMs(167); + layoutdata.setSectorSize(512); + for (int i = 0; i < 15; i++) + physical.addSector(i); + break; + case 0x03: + Logger.log("DIM: automatically setting format to 1.44MB"); + trackdata.setTargetRotationalPeriodMs(200); + layoutdata.setSectorSize(512); + for (int i = 0; i < 18; i++) + physical.addSector(i); + break; + default: + throw new FluxEngineException(String.format( + "DIM: unknown media byte 0x%02x, could not determine write " + + "profile automatically", + mediaByte)); + } + + extra.getDecoderBuilder().getIbmBuilder(); + } + + image.calculateSize(); + Geometry geometry = image.getGeometry(); + Logger.log("DIM: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + + " sides, " + (data.size() - 256) / 1024 + " kB total"); + + layout.setTracks(geometry.numCylinders); + layout.setSides(geometry.numHeads); + + extraConfig = extra.build(); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java b/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java new file mode 100644 index 00000000..d1d3e12b --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java @@ -0,0 +1,136 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Reads a DiskCopy (Mac) sector image, ported from + * lib/imagereader/diskcopyimagereader.cc. + */ +public class DiskCopyImageReader extends ImageReader +{ + public DiskCopyImageReader(ImageReaderProto config) + { + super(config); + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + ByteReader br = new ByteReader(data); + + br.seek(1); + String label = br.read(data.getByte(0) & 0xff).toString(); + + br.seek(0x40); + int dataSize = br.readBe32(); + + br.seek(0x50); + int encoding = br.read8(); + int formatByte = br.read8(); + + int numCylinders = 80; + int numHeads = 2; + int numSectors = 0; + boolean mfm = false; + + switch (encoding) + { + case 0: /* GCR CLV 400kB */ + numHeads = 1; + break; + + case 1: /* GCR CLV 800kB */ + break; + + case 2: /* MFM CAV 720kB */ + numSectors = 9; + mfm = true; + break; + + case 3: /* MFM CAV 1440kB */ + numSectors = 18; + mfm = true; + break; + + default: + throw new FluxEngineException( + "don't understand DiskCopy disks of type " + encoding); + } + + Logger.log("DC42: reading image with " + numCylinders + " tracks, " + + numHeads + " heads; " + (mfm ? "MFM" : "GCR") + "; " + label); + + int dataPtr = 0x54; + int tagPtr = dataPtr + dataSize; + + Image image = new Image(); + for (int track = 0; track < numCylinders; track++) + { + int sectorCount = sectorsPerTrack(track, numSectors, mfm); + for (int head = 0; head < numHeads; head++) + { + for (int sectorId = 0; sectorId < sectorCount; sectorId++) + { + br.seek(dataPtr); + Bytes payload = br.read(512); + dataPtr += 512; + + br.seek(tagPtr); + Bytes tag = br.read(12); + tagPtr += 12; + + Sector sector = image.put(track, head, sectorId); + sector.status = Sector.Status.OK; + sector.data = payload.concat(tag); + } + } + } + + ConfigProto.Builder extra = ConfigProto.newBuilder(); + extra.getLayoutBuilder().addLayoutdataBuilder().setSectorSize(524); + extraConfig = extra.build(); + + Geometry geometry = new Geometry(); + geometry.numCylinders = numCylinders; + geometry.numHeads = numHeads; + geometry.numSectors = 12; + geometry.sectorSize = 512 + 12; + geometry.irregular = true; + image.setGeometry(geometry); + return image; + } + + private static int sectorsPerTrack(int track, int numSectors, boolean mfm) + { + if (mfm) + return numSectors; + + if (track < 16) + return 12; + if (track < 32) + return 11; + if (track < 48) + return 10; + if (track < 64) + return 9; + return 8; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/FdiImageReader.java b/java/com/cowlark/fluxengine/imagereader/FdiImageReader.java new file mode 100644 index 00000000..820458b3 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/FdiImageReader.java @@ -0,0 +1,126 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.EncoderProto; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/* Reader based on this partial documentation of the FDI format: + * https://www.pc98.org/project/doc/hdi.html + */ +public class FdiImageReader extends ImageReader +{ + public FdiImageReader(ImageReaderProto config, ConfigProto fullConfig) + { + super(config, fullConfig); + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + + ByteReader headerReader = new ByteReader(data.slice(0, 32)); + if (headerReader.seek(0).readLe32() != 0) + throw new FluxEngineException("FDI: could not find FDI header, is this a FDI file?"); + + /* we currently don't use fddType but it could be used to automatically + * select profile parameters in the future */ + int fddType = headerReader.seek(4).readLe32(); + int headerSize = headerReader.seek(0x08).readLe32(); + int sectorSize = headerReader.seek(0x10).readLe32(); + int sectorsPerTrack = headerReader.seek(0x14).readLe32(); + int sides = headerReader.seek(0x18).readLe32(); + int tracks = headerReader.seek(0x1c).readLe32(); + + ByteReader br = new ByteReader(data.slice(headerSize)); + + Image image = new Image(); + int trackCount = 0; + for (int track = 0; track < tracks; track++) + { + if (br.eof()) + break; + + for (int side = 0; side < sides; side++) + { + for (int sectorId = 1; sectorId <= sectorsPerTrack; sectorId++) + { + Bytes sectorData = br.read(sectorSize); + + Sector sector = image.put(track, side, sectorId); + sector.status = Sector.Status.OK; + sector.data = sectorData; + } + } + + trackCount++; + } + + ConfigProto.Builder extra = ConfigProto.newBuilder(); + com.cowlark.fluxengine.config.LayoutProto.Builder layout = extra.getLayoutBuilder(); + if (fullConfig.getEncoder().getFormatCase() == EncoderProto.FormatCase.FORMAT_NOT_SET) + { + IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = + ibm.addTrackdataBuilder(); + trackdata.setTargetClockPeriodUs(2); + + com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = + layout.addLayoutdataBuilder(); + com.cowlark.fluxengine.config.SectorListProto.Builder physical = + layoutdata.getPhysicalBuilder(); + switch (fddType) + { + case 0x90: + Logger.log("FDI: automatically setting format to 1.2MB (1024 byte sectors)"); + trackdata.setTargetRotationalPeriodMs(167); + layoutdata.setSectorSize(1024); + for (int i = 0; i < 9; i++) + physical.addSector(i); + break; + + case 0x30: + Logger.log("FDI: automatically setting format to 1.44MB"); + trackdata.setTargetRotationalPeriodMs(200); + layoutdata.setSectorSize(512); + for (int i = 0; i < 18; i++) + physical.addSector(i); + break; + + default: + throw new FluxEngineException(String.format( + "FDI: unknown fdd type 0x%02x, could not determine write " + + "profile automatically", + fddType)); + } + } + + image.calculateSize(); + Geometry geometry = image.getGeometry(); + Logger.log("FDI: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + + " sides, " + (data.size() - headerSize) / 1024 + " kB total"); + + layout.setTracks(geometry.numCylinders); + layout.setSides(geometry.numHeads); + + extraConfig = extra.build(); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/ImageReader.java b/java/com/cowlark/fluxengine/imagereader/ImageReader.java new file mode 100644 index 00000000..6c02640a --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/ImageReader.java @@ -0,0 +1,79 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Image; + +/** + * Reads sector images from disk, ported from + * lib/imagereader/imagereader.{h,cc}. + */ +public abstract class ImageReader +{ + protected final ImageReaderProto config; + protected final ConfigProto fullConfig; + protected ConfigProto extraConfig = ConfigProto.getDefaultInstance(); + + public ImageReader(ImageReaderProto config) + { + this(config, ConfigProto.getDefaultInstance()); + } + + public ImageReader(ImageReaderProto config, ConfigProto fullConfig) + { + this.config = config; + this.fullConfig = fullConfig; + } + + public static ImageReader create(ConfigProto config) + { + if (!config.hasImageReader()) + throw new FluxEngineException("no image reader configured"); + return create(config, config.getImageReader()); + } + + public static ImageReader create(ImageReaderProto config) + { + return create(ConfigProto.getDefaultInstance(), config); + } + + public static ImageReader create(ConfigProto fullConfig, ImageReaderProto config) + { + switch (config.getType()) + { + case IMAGETYPE_DIM: + return new DimImageReader(config, fullConfig); + case IMAGETYPE_D88: + return new D88ImageReader(config); + case IMAGETYPE_FDI: + return new FdiImageReader(config, fullConfig); + case IMAGETYPE_IMD: + return new ImdImageReader(config); + case IMAGETYPE_IMG: + return new ImgImageReader(config, fullConfig); + case IMAGETYPE_DISKCOPY: + return new DiskCopyImageReader(config); + case IMAGETYPE_JV3: + return new Jv3ImageReader(config); + case IMAGETYPE_D64: + return new D64ImageReader(config); + case IMAGETYPE_NFD: + return new NfdImageReader(config); + case IMAGETYPE_NSI: + return new NsiImageReader(config); + case IMAGETYPE_TD0: + return new Td0ImageReader(config); + default: + throw new FluxEngineException("bad input file config"); + } + } + + /* Returns any extra config the image might want to contribute. */ + public ConfigProto getExtraConfig() + { + return extraConfig; + } + + /* Reads the image. */ + public abstract Image readImage(); +} diff --git a/java/com/cowlark/fluxengine/imagereader/ImdImageReader.java b/java/com/cowlark/fluxengine/imagereader/ImdImageReader.java new file mode 100644 index 00000000..5a25b38d --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/ImdImageReader.java @@ -0,0 +1,370 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.EncoderProto; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +public class ImdImageReader extends ImageReader +{ + private static final int SEC_CYL_MAP_FLAG = 0x80; + private static final int SEC_HEAD_MAP_FLAG = 0x40; + private static final int HEAD_MASK = 0x3F; + private static final int END_OF_FILE = 0x1A; + + public ImdImageReader(ImageReaderProto config) + { + super(config); + } + + private static int getModulationAndSpeed(int flags, boolean[] fm) + { + switch (flags) + { + case 0: /* 500 kbps FM */ + fm[0] = true; + return 500; + case 1: /* 300 kbps FM */ + fm[0] = true; + return 300; + case 2: /* 250 kbps FM */ + fm[0] = true; + return 250; + case 3: /* 500 kbps MFM */ + fm[0] = false; + return 500; + case 4: /* 300 kbps MFM */ + fm[0] = false; + return 300; + case 5: /* 250 kbps MFM */ + fm[0] = false; + return 250; + default: + throw new FluxEngineException( + "IMD: don't understand IMD disks with this modulation and speed " + + flags); + } + } + + private static int getSectorSize(int flags) + { + switch (flags) + { + case 0: + return 128; + case 1: + return 256; + case 2: + return 512; + case 3: + return 1024; + case 4: + return 2048; + case 5: + return 4096; + case 6: + return 8192; + default: + throw new FluxEngineException("not reachable"); + } + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("IMD: cannot open input file"); + } + int inputFileSize = data.size(); + ByteReader br = new ByteReader(data); + Image image = new Image(); + int modeValue = 0; + int track = 0; + int head = 0; + int numSectors = 0; + int sectorSizeCode = 0; + + ConfigProto.Builder extra = ConfigProto.newBuilder(); + com.cowlark.fluxengine.config.LayoutProto.Builder layout = extra.getLayoutBuilder(); + + int n = 0; + int headerPtr = 0; + int modulationSpeed = 0; + int sectorSize = 0; + List sectorSkew = new ArrayList<>(); + + /* Read comment */ + StringBuilder comment = new StringBuilder(); + int b; + while ((b = br.read8()) != -1 && b != END_OF_FILE) + { + comment.append((char) b); + n++; + } + headerPtr = n; /* set pointer to after comment */ + Logger.log("Comment in IMD file: " + comment); + + boolean[] fm = {false}; + int trackSectorSize = -1; + + for (; ; ) + { + if (headerPtr >= inputFileSize - 1) + { + break; + } + /* first read header */ + modeValue = br.read8(); + headerPtr++; + modulationSpeed = getModulationAndSpeed(modeValue, fm); + track = br.read8(); + headerPtr++; + head = br.read8(); + headerPtr++; + numSectors = br.read8(); + headerPtr++; + sectorSizeCode = br.read8(); + headerPtr++; + sectorSize = getSectorSize(sectorSizeCode); + + boolean blnOptionalCylinderMap = false; + boolean blnOptionalHeadMap = false; + List optionalsectorMap = new ArrayList<>(); + List optionalheadMap = new ArrayList<>(); + + /* The Sector Cylinder Map has one entry for each sector, and + * contains the logical Cylinder ID for the corresponding sector in + * the Sector Numbering Map. */ + if ((head & SEC_CYL_MAP_FLAG) != 0) + { + /* Read optional cylinder map */ + for (b = 0; b < numSectors; b++) + { + optionalsectorMap.add(br.read8()); + headerPtr++; + } + blnOptionalCylinderMap = true; + head = head ^ SEC_CYL_MAP_FLAG; + } + + /* Read optional sector head map */ + if ((head & SEC_HEAD_MAP_FLAG) != 0) + { + /* Read optional sector head map */ + for (b = 0; b < numSectors; b++) + { + optionalheadMap.add(br.read8()); + headerPtr++; + } + blnOptionalHeadMap = true; + head = head ^ SEC_HEAD_MAP_FLAG; + } + + /* read sector numbering map */ + sectorSkew.clear(); + boolean blnBase0 = false; /* check what first start number of the sector is */ + for (b = 0; b < numSectors; b++) + { + int t = br.read8(); + if (t == 0x00) + blnBase0 = true; + if (blnBase0) + { + t = t + 1; + } + sectorSkew.add(t); + headerPtr++; + } + + IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = + ibm.addTrackdataBuilder(); + + com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = + layout.addLayoutdataBuilder(); + + trackdata.setTargetClockPeriodUs(1e3 / modulationSpeed); + trackdata.setTargetRotationalPeriodMs(200); + if (trackSectorSize < 0) + { + trackSectorSize = sectorSize; + /* this is the first sector we've read, use its settings for + * per-track data */ + trackdata.setTrack(track); + trackdata.setHead(head); + trackdata.setUseFm(fm[0]); + + layoutdata.setTrack(track); + layoutdata.setSide(head); + layoutdata.setSectorSize(sectorSize); + } + else if (trackSectorSize != sectorSize) + { + throw new FluxEngineException( + "IMD: multiple sector sizes per track are currently unsupported"); + } + + /* read the sectors */ + for (int s = 0; s < numSectors; s++) + { + Bytes sectordata = new Bytes(0); + Bytes compressed = new Bytes(sectorSize); + int sectorId = sectorSkew.get(s); + Sector sector = image.put(track, head, sectorId); + /* read the status of the sector */ + int statusSector = br.read8(); + headerPtr++; + + switch (statusSector) + { + case 0: /* Sector data unavailable - could not be read */ + sector.status = Sector.Status.MISSING; + break; + + case 1: /* Normal data: (Sector Size) bytes follow */ + sectordata = br.read(sectorSize); + headerPtr += sectorSize; + sector.data = sectordata; + sector.status = Sector.Status.OK; + break; + + case 2: /* Compressed: All bytes in sector have same value (xx) */ + compressed.setByte(0, (byte) br.read8()); + headerPtr++; + for (int k = 1; k < sectorSize; k++) + { + br.seek(headerPtr); + compressed.setByte(k, (byte) br.read8()); + } + sector.data = compressed; + sector.status = Sector.Status.OK; + break; + + case 3: /* Normal data with "Deleted-Data address mark" */ + sector.status = Sector.Status.DATA_MISSING; + sectordata = br.read(sectorSize); + headerPtr += sectorSize; + sector.data = sectordata; + break; + + case 4: /* Compressed with "Deleted-Data address mark" */ + compressed.setByte(0, (byte) br.read8()); + headerPtr++; + for (int k = 1; k < sectorSize; k++) + { + br.seek(headerPtr); + compressed.setByte(k, (byte) br.read8()); + } + sector.data = compressed; + sector.status = Sector.Status.DATA_MISSING; + break; + + case 5: /* Normal data read with data error */ + sectordata = br.read(sectorSize); + headerPtr += sectorSize; + sector.status = Sector.Status.BAD_CHECKSUM; + sector.data = sectordata; + break; + + case 6: /* Compressed read with data error */ + compressed.setByte(0, (byte) br.read8()); + headerPtr++; + for (int k = 1; k < sectorSize; k++) + { + br.seek(headerPtr); + compressed.setByte(k, (byte) br.read8()); + } + sector.data = compressed; + sector.status = Sector.Status.BAD_CHECKSUM; + break; + + case 7: /* Deleted data read with data error */ + sectordata = br.read(sectorSize); + headerPtr += sectorSize; + sector.status = Sector.Status.BAD_CHECKSUM; + sector.data = sectordata; + break; + + case 8: /* Compressed, Deleted read with data error */ + compressed.setByte(0, (byte) br.read8()); + headerPtr++; + for (int k = 1; k < sectorSize; k++) + { + br.seek(headerPtr); + compressed.setByte(k, (byte) br.read8()); + } + sector.data = compressed; + sector.status = Sector.Status.BAD_CHECKSUM; + break; + + default: + throw new FluxEngineException(String.format( + "IMD: Don't understand IMD files with sector status %d, " + + "track %d, sector %d", + statusSector, + track, + s)); + } + + if (blnOptionalCylinderMap) + { + sector.location = new com.cowlark.fluxengine.data.LogicalLocation( + optionalsectorMap.get(s), sector.location.logicalHead(), + sector.location.logicalSector()); + blnOptionalCylinderMap = false; + } + else + sector.location = new com.cowlark.fluxengine.data.LogicalLocation( + track, sector.location.logicalHead(), + sector.location.logicalSector()); + + if (blnOptionalHeadMap) + { + sector.location = new com.cowlark.fluxengine.data.LogicalLocation( + sector.location.logicalCylinder(), optionalheadMap.get(s), + sector.location.logicalSector()); + blnOptionalHeadMap = false; + } + else + sector.location = new com.cowlark.fluxengine.data.LogicalLocation( + sector.location.logicalCylinder(), head, + sector.location.logicalSector()); + } + } + + if (extra.getEncoder().getFormatCase() != EncoderProto.FormatCase.FORMAT_NOT_SET) + Logger.log("IMD: overriding configured format"); + + image.calculateSize(); + Geometry geometry = image.getGeometry(); + int headSize = numSectors * sectorSize; + int trackSize = headSize * (head + 1); + + Logger.log("IMD: read " + (track + 1) + " tracks, " + (head + 1) + " heads; " + + (fm[0] ? "FM" : "MFM") + "; " + modulationSpeed + " kbps; " + numSectors + + " sectors; sectorsize " + sectorSize + "; " + + (track + 1) * trackSize / 1024 + " kB total."); + + layout.setTracks(geometry.numCylinders); + layout.setSides(geometry.numHeads); + + extraConfig = extra.build(); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/ImgImageReader.java b/java/com/cowlark/fluxengine/imagereader/ImgImageReader.java new file mode 100644 index 00000000..7d290952 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/ImgImageReader.java @@ -0,0 +1,81 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.LayoutProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Path; + +/** + * Reads a raw (non-interleaved) sector image, ported from + * lib/imagereader/imgimagereader.cc. + */ +public class ImgImageReader extends ImageReader +{ + public ImgImageReader(ImageReaderProto config, ConfigProto fullConfig) + { + super(config, fullConfig); + } + + @Override + public Image readImage() + { + LayoutProto layout = fullConfig.getLayout(); + if (!layout.hasTracks() || !layout.hasSides()) + throw new FluxEngineException( + "IMG: bad configuration; did you remember to set the " + + "tracks, sides and trackdata fields in the layout?"); + + DiskLayout diskLayout = new DiskLayout(fullConfig); + boolean inFilesystemOrder = config.getImg().getFilesystemSectorOrder(); + Image image = new Image(); + + try (InputStream inputFile = Files.newInputStream(Path.of(config.getFilename()))) + { + Iterable locations = inFilesystemOrder ? + diskLayout.logicalLocationsInFilesystemOrder : + diskLayout.logicalLocations; + for (CylinderHead logicalLocation : locations) + { + LogicalTrackLayout ltl = + diskLayout.layoutByLogicalLocation.get(logicalLocation); + + Iterable sectorOrder = inFilesystemOrder ? + ltl.filesystemSectorOrder : + ltl.naturalSectorOrder; + for (int sectorId : sectorOrder) + { + byte[] buf = new byte[ltl.sectorSize]; + int read = inputFile.read(buf); + if (read == -1) + break; + + Sector sector = image.put( + logicalLocation.cylinder(), logicalLocation.head(), sectorId); + sector.status = Sector.Status.OK; + sector.data = new Bytes(buf); + } + } + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + + image.calculateSize(); + Geometry geometry = image.getGeometry(); + Logger.log("IMG: read " + geometry.numCylinders + " tracks, " + + geometry.numHeads + " sides, " + geometry.totalBytes / 1024 + + " kB total from " + config.getFilename()); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java b/java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java new file mode 100644 index 00000000..9813df14 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java @@ -0,0 +1,120 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/* JV3 files are kinda weird. There's a fixed layout for up to 2901 sectors, + * which may appear in any order, followed by the same again for more sectors. + * To find the second data block you need to know the size of the first data + * block, which requires parsing it. + * + * https://www.tim-mann.org/trs80/dskconfig.html + */ +public class Jv3ImageReader extends ImageReader +{ + private static final int JV3_DENSITY = 0x80; /* 1=dden, 0=sden */ + private static final int JV3_DAM = 0x60; /* data address mark code */ + private static final int JV3_SIDE = 0x10; /* 0=side 0, 1=side 1 */ + private static final int JV3_ERROR = 0x08; /* 0=ok, 1=CRC error */ + private static final int JV3_NONIBM = 0x04; /* 0=normal, 1=short */ + private static final int JV3_SIZE = + 0x03; /* in used sectors: 0=256,1=128,2=1024,3=512 + in free sectors: 0=512,1=1024,2=128,3=256 */ + + private static final int JV3_FREE = 0xFF; /* in track and sector fields of free sectors */ + private static final int JV3_FREEF = 0xFC; /* in flags field, or'd with size code */ + + private static int getSectorSize(int flags) + { + if ((flags & JV3_FREEF) == JV3_FREEF) + { + switch (flags & JV3_SIZE) + { + case 0: + return 512; + case 1: + return 1024; + case 2: + return 128; + case 3: + return 256; + } + } + else + { + switch (flags & JV3_SIZE) + { + case 0: + return 256; + case 1: + return 128; + case 2: + return 1024; + case 3: + return 512; + } + } + throw new FluxEngineException("not reachable"); + } + + public Jv3ImageReader(ImageReaderProto config) + { + super(config); + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + int inputFileSize = data.size(); + int headerPtr = 0; + Image image = new Image(); + for (; ; ) + { + int dataPtr = headerPtr + 2901 * 3 + 1; + if (dataPtr >= inputFileSize) + break; + + for (int i = 0; i < 2901; i++) + { + ByteReader headerReader = new ByteReader(data.slice(headerPtr, 3)); + int track = headerReader.seek(0).read8(); + int sectorId = headerReader.seek(1).read8(); + int flags = headerReader.seek(2).read8(); + int sectorSize = getSectorSize(flags); + if ((flags & JV3_FREEF) != JV3_FREEF) + { + Bytes sectorData = data.slice(dataPtr, sectorSize); + + int head = (flags & JV3_SIDE) != 0 ? 1 : 0; + Sector sector = image.put(track, head, sectorId); + sector.status = Sector.Status.OK; + sector.data = sectorData; + } + + headerPtr += 3; + dataPtr += sectorSize; + } + + /* dataPtr is now pointing at the beginning of the next chunk. */ + + headerPtr = dataPtr; + } + + image.calculateSize(); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java b/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java new file mode 100644 index 00000000..54ad44fd --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java @@ -0,0 +1,168 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.EncoderProto; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; +import com.cowlark.fluxengine.external.FormatType; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/* Reader based on this partial documentation of the D88 format: + * https://www.pc98.org/project/doc/d88.html + */ +public class NfdImageReader extends ImageReader +{ + public NfdImageReader(ImageReaderProto config) + { + super(config); + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + + Bytes fileId = data.slice(0, 14); + if (fileId.equals(new Bytes("T98FDDIMAGE.R1"))) + { + throw new FluxEngineException("NFD: r1 images are not currently supported"); + } + if (!fileId.equals(new Bytes("T98FDDIMAGE.R0"))) + { + throw new FluxEngineException("NFD: could not find NFD header"); + } + + ByteReader headerReader = new ByteReader(data); + + int heads = headerReader.seek(0x115).read8(); + if (heads != 2) + { + throw new FluxEngineException("NFD: unsupported number of heads"); + } + + ConfigProto.Builder extra = ConfigProto.newBuilder(); + IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); + com.cowlark.fluxengine.config.LayoutProto.Builder layout = extra.getLayoutBuilder(); + Logger.log("NFD: HD 1.2MB mode"); + Logger.log("NFD: forcing high density mode"); + extra.getDriveBuilder().setHighDensity(true); + extra.getLayoutBuilder().setFormatType(FormatType.FORMATTYPE_80TRACK); + + Image image = new Image(); + ByteReader br = new ByteReader(data); + br.seek(0x10a10); + for (int track = 0; track < 163; track++) + { + IbmEncoderProto.TrackdataProto.Builder trackdata = + ibm.addTrackdataBuilder(); + trackdata.setTargetClockPeriodUs(2); + trackdata.setTargetRotationalPeriodMs(167); + + com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = + layout.addLayoutdataBuilder(); + com.cowlark.fluxengine.config.SectorListProto.Builder physical = + layoutdata.getPhysicalBuilder(); + int currentTrackTrack = -1; + int currentTrackHead = -1; + int trackSectorSize = -1; + + for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++) + { + ByteReader sectorHeaderReader = + new ByteReader(data.slice(0x120 + track * 26 * 16 + sectorInTrack * 16, 16)); + int cyl = sectorHeaderReader.seek(0).read8(); + int head = sectorHeaderReader.seek(1).read8(); + int sectorId = sectorHeaderReader.seek(2).read8(); + int sectorSize = 128 << sectorHeaderReader.seek(3).read8(); + int mfm = sectorHeaderReader.seek(4).read8(); + int ddam = sectorHeaderReader.seek(5).read8(); + int status = sectorHeaderReader.seek(6).read8(); + sectorHeaderReader.skip(9); /* skip ST0, ST1, ST2, PDA, reserved(5) */ + if (cyl == 0xFF) + continue; + if (ddam != 0) + throw new FluxEngineException("NFD: nonzero ddam currently unsupported"); + if (status != 0) + throw new FluxEngineException( + "NFD: nonzero fdd status codes are currently unsupported"); + if (currentTrackTrack < 0) + { + currentTrackTrack = cyl; + currentTrackHead = head; + } + else if (currentTrackTrack != cyl) + { + throw new FluxEngineException( + "NFD: all sectors in a track must belong to the same track"); + } + else if (currentTrackHead != head) + { + throw new FluxEngineException( + "NFD: all sectors in a track must belong to the same head"); + } + if (trackSectorSize < 0) + { + trackSectorSize = sectorSize; + /* this is the first sector we've read, use its settings for + * per-track data */ + trackdata.setTrack(cyl); + trackdata.setHead(head); + layoutdata.setTrack(cyl); + layoutdata.setSide(head); + layoutdata.setSectorSize(sectorSize); + trackdata.setUseFm(mfm == 0); + if (mfm == 0) + { + trackdata.setGapFillByte(0xffff); + trackdata.setIdamByte(0xf57e); + trackdata.setDamByte(0xf56f); + } + /* create timings to approximately match N88-BASIC */ + if (sectorSize <= 128) + { + trackdata.setGap0(0x1b); + trackdata.setGap2(0x09); + trackdata.setGap3(0x1b); + } + else if (sectorSize <= 256) + { + trackdata.setGap0(0x36); + trackdata.setGap3(0x36); + } + } + else if (trackSectorSize != sectorSize) + { + throw new FluxEngineException( + "NFD: multiple sector sizes per track are currently unsupported"); + } + Bytes sectorData = br.read(sectorSize); + physical.addSector(sectorId); + Sector sector = image.put(cyl, head, sectorId); + sector.status = Sector.Status.OK; + sector.data = sectorData; + } + } + + image.calculateSize(); + Geometry geometry = image.getGeometry(); + Logger.log("NFD: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides"); + + extraConfig = extra.build(); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/NsiImageReader.java b/java/com/cowlark/fluxengine/imagereader/NsiImageReader.java new file mode 100644 index 00000000..3f48c47e --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/NsiImageReader.java @@ -0,0 +1,108 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +/* Image reader for Northstar floppy disk images */ +public class NsiImageReader extends ImageReader +{ + public NsiImageReader(ImageReaderProto config) + { + super(config); + } + + @Override + public Image readImage() + { + Bytes data; + try + { + data = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + int fsize = data.size(); + + Logger.log("NSI: Autodetecting geometry based on file size: " + fsize); + + int numCylinders = 35; + int numSectors = 10; + int numHeads = 2; + int sectorSize = 512; + + switch (fsize) + { + case 358400: + numHeads = 2; + sectorSize = 512; + break; + + case 179200: + numHeads = 1; + sectorSize = 512; + break; + + case 89600: + numHeads = 1; + sectorSize = 256; + break; + + default: + throw new FluxEngineException("NSI: unknown file size"); + } + + int trackSize = numSectors * sectorSize; + + Logger.log("reading " + numCylinders + " tracks, " + numHeads + " heads, " + + numSectors + " sectors, " + sectorSize + " bytes per sector, " + + numCylinders * numHeads * trackSize / 1024 + " kB total"); + + Image image = new Image(); + ByteReader br = new ByteReader(data); + int sectorFileOffset; + + for (int head = 0; head < numHeads; head++) + { + for (int track = 0; track < numCylinders; track++) + { + for (int sectorId = 0; sectorId < numSectors; sectorId++) + { + if (head == 0) + { /* Head 0 is from track 0-34 */ + sectorFileOffset = track * trackSize + sectorId * sectorSize; + } + else + { /* Head 1 is from track 70-35 */ + sectorFileOffset = (trackSize * numCylinders) + /* Skip over side 0 */ + ((numCylinders - track - 1) * trackSize) + + (sectorId * sectorSize); /* Sector offset from beginning of track. */ + } + + br.seek(sectorFileOffset); + Bytes sectorData = br.read(sectorSize); + + Sector sector = image.put(track, head, sectorId); + sector.status = Sector.Status.OK; + sector.data = sectorData; + } + } + } + + Geometry geometry = new Geometry(); + geometry.numCylinders = numCylinders; + geometry.numHeads = numHeads; + geometry.numSectors = numSectors; + geometry.sectorSize = sectorSize; + image.setGeometry(geometry); + return image; + } +} diff --git a/java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java b/java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java new file mode 100644 index 00000000..c3ac9946 --- /dev/null +++ b/java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java @@ -0,0 +1,192 @@ +package com.cowlark.fluxengine.imagereader; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Geometry; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.external.Crc; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +/* The best description of the Teledisk format I've found is available here: + * + * https://web.archive.org/web/20210420230238/http://dunfield.classiccmp.org/img47321/td0notes.txt + */ +public class Td0ImageReader extends ImageReader +{ + private static final int TD0_ENCODING_RAW = 0; + private static final int TD0_ENCODING_REPEATED = 1; + private static final int TD0_ENCODING_RLE = 2; + + private static final int TD0_FLAG_DUPLICATE = 0x01; + private static final int TD0_FLAG_CRC_ERROR = 0x02; + private static final int TD0_FLAG_DELETED = 0x04; + private static final int TD0_FLAG_SKIPPED = 0x10; + private static final int TD0_FLAG_IDNODATA = 0x20; + private static final int TD0_FLAG_DATANOID = 0x40; + + public Td0ImageReader(ImageReaderProto config) + { + super(config); + } + + @Override + public Image readImage() + { + Bytes input; + try + { + input = new Bytes(Files.readAllBytes(Path.of(config.getFilename()))); + } catch (IOException e) + { + throw new FluxEngineException("cannot open input file"); + } + ByteReader br = new ByteReader(input); + + int signature = br.readBe16(); + br.skip(2); /* sequence and checksequence */ + int version = br.read8(); + br.skip(2); /* data rate, drive type */ + int stepping = br.read8(); + br.skip(1); /* sparse flag */ + int sides = (br.read8() == 1) ? 1 : 2; + int headerCrc = br.readLe16(); + + int gotCrc = Crc.crc16(0xa097, 0, input.slice(0, 10)); + if (gotCrc != headerCrc) + throw new FluxEngineException("TD0: header checksum mismatch"); + if (signature != 0x5444) + throw new FluxEngineException( + "TD0: unsupported file type (only uncompressed files are supported for now)"); + + String comment = "(no comment)"; + if ((stepping & 0x80) != 0) + { + /* Comment block */ + + br.skip(2); /* comment CRC */ + int length = br.readLe16(); + br.skip(6); /* timestamp */ + comment = br.read(length).toString(); + comment = comment.replace('\0', '\n'); + + /* Strip trailing whitespace */ + + int end = comment.length(); + while (end > 0 && Character.isWhitespace(comment.charAt(end - 1))) + end--; + comment = comment.substring(0, end); + } + + Logger.log("TD0: TeleDisk " + version / 10 + "." + version % 10 + ": " + comment); + + int totalSize = 0; + Image image = new Image(); + for (; ; ) + { + /* Read track header */ + + int sectorCount = br.read8(); + if (sectorCount == 0xff) + break; + + int physicalCylinder = br.read8(); + int physicalHead = br.read8() & 1; + br.skip(1); /* crc */ + + for (int i = 0; i < sectorCount; i++) + { + /* Read sector */ + + int logicalCylinder = br.read8(); + int logicalHead = br.read8(); + int sectorId = br.read8(); + int sectorSizeEncoded = br.read8(); + int sectorSize = 128 << sectorSizeEncoded; + int flags = br.read8(); + br.skip(1); /* CRC */ + + int dataSize = br.readLe16(); + Bytes encodedData = br.read(dataSize); + ByteReader bre = new ByteReader(encodedData); + int encoding = bre.read8(); + + Bytes data; + if ((flags & (TD0_FLAG_SKIPPED | TD0_FLAG_IDNODATA)) == 0) + { + switch (encoding) + { + case TD0_ENCODING_RAW: + data = encodedData.slice(1); + break; + + case TD0_ENCODING_REPEATED: + { + data = new Bytes(0); + ByteWriter bw = data.writer(); + while (!bre.eof()) + { + int pattern = bre.readLe16(); + int count = bre.readLe16(); + while (count-- != 0) + bw.writeLe16(pattern); + } + break; + } + + case TD0_ENCODING_RLE: + { + data = new Bytes(0); + ByteWriter bw = data.writer(); + while (!bre.eof()) + { + int length = bre.read8() * 2; + if (length == 0) + { + /* Literal block */ + + length = bre.read8(); + bw.write(bre.read(length)); + } + else + { + /* Repeated block */ + + int count = bre.read8(); + Bytes b = bre.read(length); + while (count-- != 0) + bw.write(b); + } + } + break; + } + + default: + data = new Bytes(0); + break; + } + } + else + data = new Bytes(0); + + Sector sector = image.put(logicalCylinder, logicalHead, sectorId); + sector.status = Sector.Status.OK; + sector.data = data.slice(0, sectorSize); + totalSize += sectorSize; + } + } + + image.calculateSize(); + Geometry geometry = image.getGeometry(); + Logger.log("TD0: found " + geometry.numCylinders + " tracks, " + geometry.numHeads + + " sides, " + geometry.numSectors + " sectors, " + geometry.sectorSize + + " bytes per sector, " + totalSize / 1024 + " kB total"); + return image; + } +} diff --git a/javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel b/javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel new file mode 100644 index 00000000..6beb8c90 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel @@ -0,0 +1,20 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "ImageReaderTest", + srcs = ["ImageReaderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:common_java_proto", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/imagereader", + "//java/com/cowlark/fluxengine/imagereader:imagereader_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java b/javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java new file mode 100644 index 00000000..54cf2574 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java @@ -0,0 +1,161 @@ +package com.cowlark.fluxengine.imagereader; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.ImageReaderWriterType; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.Sector; +import java.nio.file.Files; +import java.nio.file.Path; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ImageReaderTest +{ + @Test + public void createD64ImageReader() + { + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_D64) + .build(); + + assertThat(ImageReader.create(config)).isInstanceOf(D64ImageReader.class); + } + + @Test + public void createImgImageReader() + { + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_IMG) + .build(); + + assertThat(ImageReader.create(ConfigProto.getDefaultInstance(), config)) + .isInstanceOf(ImgImageReader.class); + } + + @Test + public void createNsiImageReader() + { + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_NSI) + .build(); + + assertThat(ImageReader.create(config)).isInstanceOf(NsiImageReader.class); + } + + @Test + public void createTd0ImageReader() + { + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_TD0) + .build(); + + assertThat(ImageReader.create(config)).isInstanceOf(Td0ImageReader.class); + } + + @Test + public void createBadTypeThrows() + { + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_NOT_SET) + .build(); + + assertThrows(FluxEngineException.class, () -> ImageReader.create(config)); + } + + @Test + public void createNoReaderConfiguredThrows() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .build(); + + assertThrows(FluxEngineException.class, () -> ImageReader.create(config)); + } + + @Test + public void d64ReadsSectorData() throws Exception + { + /* 40 tracks; the first track has 21 sectors of 256 bytes. Write a + * single byte of payload at the start of sector 0. */ + Path file = Files.createTempFile("image", ".d64"); + byte[] data = new byte[256 * 21]; + data[0] = 0x42; + Files.write(file, data); + + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_D64) + .setFilename(file.toString()) + .build(); + + Image image = new D64ImageReader(config).readImage(); + + Sector sector = image.get(0, 0, 0); + assertThat(sector).isNotNull(); + assertThat(sector.status).isEqualTo(Sector.Status.OK); + assertThat(sector.data.getByte(0) & 0xff).isEqualTo(0x42); + assertThat(sector.data.size()).isEqualTo(256); + } + + @Test + public void d64ShortFileMarksMissing() throws Exception + { + Path file = Files.createTempFile("image", ".d64"); + Files.write(file, new byte[10]); + + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_D64) + .setFilename(file.toString()) + .build(); + + Image image = new D64ImageReader(config).readImage(); + + /* Track 0, sector 0 is present (10 bytes available); track 39, sector + * 0 has no data. */ + assertThat(image.get(0, 0, 0).status).isEqualTo(Sector.Status.OK); + assertThat(image.get(39, 0, 0).status).isEqualTo(Sector.Status.DATA_MISSING); + } + + @Test + public void nsiReadsSectorData() throws Exception + { + /* 35 tracks x 2 heads x 10 sectors x 512 bytes. */ + Path file = Files.createTempFile("image", ".nsi"); + byte[] data = new byte[358400]; + data[0] = 0x43; + Files.write(file, data); + + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_NSI) + .setFilename(file.toString()) + .build(); + + Image image = new NsiImageReader(config).readImage(); + + Sector sector = image.get(0, 0, 0); + assertThat(sector).isNotNull(); + assertThat(sector.data.getByte(0) & 0xff).isEqualTo(0x43); + assertThat(image.get(34, 1, 0)).isNotNull(); + } + + @Test + public void nsiUnknownSizeThrows() throws Exception + { + Path file = Files.createTempFile("image", ".nsi"); + Files.write(file, new byte[12345]); + + ImageReaderProto config = ImageReaderProto.newBuilder() + .setType(ImageReaderWriterType.IMAGETYPE_NSI) + .setFilename(file.toString()) + .build(); + + assertThrows(FluxEngineException.class, () -> new NsiImageReader(config).readImage()); + } +} From 703d28ec607ba0f855bca6ed703e3b03fa86b0f3 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 00:02:15 +0200 Subject: [PATCH 128/192] Rearrange. --- .../fluxengine/algorithms/ReaderWriter.java | 212 ++++++++---------- java/com/cowlark/fluxengine/core/Utils.java | 36 +++ 2 files changed, 127 insertions(+), 121 deletions(-) create mode 100644 java/com/cowlark/fluxengine/core/Utils.java diff --git a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java index 26ea1af0..78be4c44 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java +++ b/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.algorithms; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.LogMessage.BeginOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; @@ -11,6 +10,7 @@ import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.OperationProgressLogMessage; import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.core.Utils; import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.data.Disk; import com.cowlark.fluxengine.data.DiskLayout; @@ -43,40 +43,38 @@ public final class ReaderWriter { private static enum ReadResult { - GOOD_READ, - BAD_AND_CAN_RETRY, - BAD_AND_CAN_NOT_RETRY + GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY } private static enum BadSectorsState { - HAS_NO_BAD_SECTORS, - HAS_BAD_SECTORS + HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS } private ReaderWriter() { } - public static void readDiskCommand( - ConfigProto config, DiskLayout diskLayout, FluxSource fluxSource, - Decoder decoder, Disk disk) + public static void readDiskCommand(ConfigProto config, + DiskLayout diskLayout, + FluxSource fluxSource, + Decoder decoder, + Disk disk) { FluxSinkFactory outputFluxSinkFactory = null; if (config.getDecoder().hasCopyFluxTo()) - outputFluxSinkFactory = FluxSinkFactory.create( - config, config.getDecoder().getCopyFluxTo()); + outputFluxSinkFactory = + FluxSinkFactory.create(config, config.getDecoder().getCopyFluxTo()); - Map> tracksByLogicalLocation = - new HashMap<>(); + Map> tracksByLogicalLocation = new HashMap<>(); for (Map.Entry entry : disk.tracksByPhysicalLocation.entries()) { Track track = entry.getValue(); - tracksByLogicalLocation - .computeIfAbsent(new CylinderHead( - track.ltl.logicalCylinder, track.ltl.logicalHead), - k -> new ArrayList<>()) - .add(track); + tracksByLogicalLocation.computeIfAbsent( + new CylinderHead( + track.ltl.logicalCylinder, + track.ltl.logicalHead), + k -> new ArrayList<>()).add(track); } Logger.log(new BeginOperationLogMessage("Reading and decoding disk")); @@ -86,8 +84,9 @@ public static void readDiskCommand( else disk.rotationalPeriod = getRotationalPeriodFromConfig(config); - try (FluxSink outputFluxSink = - outputFluxSinkFactory != null ? outputFluxSinkFactory.create() : null) + try (FluxSink outputFluxSink = outputFluxSinkFactory != null ? + outputFluxSinkFactory.create() : + null) { int index = 0; for (Map.Entry entry : @@ -101,30 +100,38 @@ public static void readDiskCommand( testForEmergencyStop(); - List trackFluxes = - tracksByLogicalLocation.computeIfAbsent( - logicalLocation, k -> new ArrayList<>()); + List trackFluxes = tracksByLogicalLocation.computeIfAbsent( + logicalLocation, + k -> new ArrayList<>()); List trackSectors = new ArrayList<>(); - readAndDecodeTrack(config, diskLayout, fluxSource, decoder, - ltl, trackFluxes, trackSectors); + readAndDecodeTrack( + config, + diskLayout, + fluxSource, + decoder, + ltl, + trackFluxes, + trackSectors); /* Replace all tracks on the disk by the new combined set. */ for (Track flux : trackFluxes) disk.tracksByPhysicalLocation.removeAll(new CylinderHead( - flux.ptl.physicalCylinder, flux.ptl.physicalHead)); + flux.ptl.physicalCylinder, + flux.ptl.physicalHead)); for (Track flux : trackFluxes) - disk.tracksByPhysicalLocation.put(new CylinderHead( - flux.ptl.physicalCylinder, flux.ptl.physicalHead), flux); + disk.tracksByPhysicalLocation.put( + new CylinderHead( + flux.ptl.physicalCylinder, + flux.ptl.physicalHead), + flux); /* Likewise for sectors. */ for (Sector sector : trackSectors) - disk.sectorsByPhysicalLocation.removeAll( - sector.physicalLocation); + disk.sectorsByPhysicalLocation.removeAll(sector.physicalLocation); for (Sector sector : trackSectors) - disk.sectorsByPhysicalLocation.put( - sector.physicalLocation, sector); + disk.sectorsByPhysicalLocation.put(sector.physicalLocation, sector); if (outputFluxSink != null) { @@ -137,12 +144,10 @@ public static void readDiskCommand( if (config.getDecoder().getDumpRecords()) { - List sortedRecords = - new ArrayList<>(); + List sortedRecords = new ArrayList<>(); for (Track data : trackFluxes) sortedRecords.addAll(data.records); - sortedRecords.sort(Comparator.comparingDouble( - r -> r.startTimeNs)); + sortedRecords.sort(Comparator.comparingDouble(r -> r.startTimeNs)); System.out.println("\nRaw (undecoded) records follow:\n"); for (com.cowlark.fluxengine.data.Record record : sortedRecords) @@ -151,7 +156,7 @@ public static void readDiskCommand( "I+%.2fus with %.2fus clock%n", record.startTimeNs / 1000.0, record.clockNs / 1000.0); - hexdump(System.out, record.rawData); + Utils.hexdump(System.out, record.rawData); System.out.println(); } } @@ -159,8 +164,7 @@ public static void readDiskCommand( if (config.getDecoder().getDumpSectors()) { List sectors = collectSectors(trackSectors, false); - sectors.sort(Comparator - .comparing((Sector s) -> s.location.logicalCylinder()) + sectors.sort(Comparator.comparing((Sector s) -> s.location.logicalCylinder()) .thenComparing((Sector s) -> s.location.logicalHead()) .thenComparing((Sector s) -> s.location.logicalSector())); @@ -168,15 +172,14 @@ public static void readDiskCommand( for (Sector sector : sectors) { System.out.printf( - "%d.%02d.%02d: I+%.2fus with %.2fus clock: " - + "status %s%n", + "%d.%02d.%02d: I+%.2fus with %.2fus clock: " + "status %s%n", sector.location.logicalCylinder(), sector.location.logicalHead(), sector.location.logicalSector(), sector.headerStartTimeNs / 1000.0, sector.clockNs / 1000.0, Sector.statusToString(sector.status)); - hexdump(System.out, sector.data); + Utils.hexdump(System.out, sector.data); System.out.println(); } } @@ -197,8 +200,11 @@ public static void readDiskCommand( Logger.log(new EndOperationLogMessage("Read complete")); } - public static void readDiskCommand(ConfigProto config, DiskLayout diskLayout, - FluxSource fluxSource, Decoder decoder, ImageWriter writer) + public static void readDiskCommand(ConfigProto config, + DiskLayout diskLayout, + FluxSource fluxSource, + Decoder decoder, + ImageWriter writer) { Disk disk = new Disk(); readDiskCommand(config, diskLayout, fluxSource, decoder, disk); @@ -211,13 +217,11 @@ public static void readDiskCommand(ConfigProto config, DiskLayout diskLayout, /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a * good and bad version of the same sector, the bad version is dropped). */ - private static List collectSectors( - List trackSectors, boolean collapseConflicts) + private static List collectSectors(List trackSectors, boolean collapseConflicts) { Map> sectors = new LinkedHashMap<>(); for (Sector sector : trackSectors) - sectors.computeIfAbsent(sector.location, k -> new ArrayList<>()) - .add(sector); + sectors.computeIfAbsent(sector.location, k -> new ArrayList<>()).add(sector); List sectorSet = new ArrayList<>(); for (Map.Entry> entry : sectors.entrySet()) @@ -227,8 +231,7 @@ private static List collectSectors( for (int i = 1; i < bucket.size(); i++) { Sector right = bucket.get(i); - if ((newSector.status == Sector.Status.OK) && - (right.status == Sector.Status.OK) && + if ((newSector.status == Sector.Status.OK) && (right.status == Sector.Status.OK) && (!newSector.data.equals(right.data))) { if (!collapseConflicts) @@ -287,8 +290,9 @@ private static class CombinationResult List sectors; } - private static CombinationResult combineRecordAndSectors( - List tracks, Decoder decoder, LogicalTrackLayout ltl) + private static CombinationResult combineRecordAndSectors(List tracks, + Decoder decoder, + LogicalTrackLayout ltl) { CombinationResult cr = new CombinationResult(); cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; @@ -303,12 +307,11 @@ private static CombinationResult combineRecordAndSectors( for (int sectorId : ltl.diskSectorOrder) { - Sector sector = new Sector(new LogicalLocation( - ltl.logicalCylinder, ltl.logicalHead, sectorId)); + Sector sector = + new Sector(new LogicalLocation(ltl.logicalCylinder, ltl.logicalHead, sectorId)); sector.status = Sector.Status.MISSING; - sector.physicalLocation = - new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); + sector.physicalLocation = new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); trackSectors.add(sector); } @@ -324,8 +327,7 @@ private static CombinationResult combineRecordAndSectors( return cr; } - private static void adjustTrackOnError( - FluxSource fluxSource, int baseTrack, ConfigProto config) + private static void adjustTrackOnError(FluxSource fluxSource, int baseTrack, ConfigProto config) { switch (config.getDrive().getErrorBehaviour()) { @@ -354,8 +356,7 @@ private static class ReadGroupResult private static class FluxSourceIteratorHolder { private final FluxSource fluxSource; - private final Map cache = - new HashMap<>(); + private final Map cache = new HashMap<>(); FluxSourceIteratorHolder(FluxSource fluxSource) { @@ -376,9 +377,11 @@ FluxSourceIterator getIterator(int physicalCylinder, int head) } private static ReadGroupResult readGroup(DiskLayout diskLayout, - FluxSourceIteratorHolder fluxSourceIteratorHolder, - LogicalTrackLayout ltl, List tracks, Decoder decoder, - ConfigProto config) + FluxSourceIteratorHolder fluxSourceIteratorHolder, + LogicalTrackLayout ltl, + List tracks, + Decoder decoder, + ConfigProto config) { ReadGroupResult rgr = new ReadGroupResult(); rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; @@ -387,8 +390,7 @@ private static ReadGroupResult readGroup(DiskLayout diskLayout, * sectors. */ { - CombinationResult cr = - combineRecordAndSectors(tracks, decoder, ltl); + CombinationResult cr = combineRecordAndSectors(tracks, decoder, ltl); rgr.combinedSectors = cr.sectors; if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) { @@ -399,29 +401,29 @@ private static ReadGroupResult readGroup(DiskLayout diskLayout, } } - for (int offset = 0; offset < ltl.groupSize; - offset += diskLayout.headWidth) + for (int offset = 0; offset < ltl.groupSize; offset += diskLayout.headWidth) { int physicalCylinder = ltl.physicalCylinder + offset; int physicalHead = ltl.physicalHead; - PhysicalTrackLayout ptl = diskLayout.layoutByPhysicalLocation.get( - new CylinderHead(physicalCylinder, physicalHead)); + PhysicalTrackLayout ptl = diskLayout.layoutByPhysicalLocation.get(new CylinderHead( + physicalCylinder, + physicalHead)); /* Do the physical read. */ - Logger.log(new BeginReadOperationLogMessage( - physicalCylinder, physicalHead)); + Logger.log(new BeginReadOperationLogMessage(physicalCylinder, physicalHead)); FluxSourceIterator fluxSourceIterator = - fluxSourceIteratorHolder.getIterator( - physicalCylinder, physicalHead); + fluxSourceIteratorHolder.getIterator(physicalCylinder, physicalHead); if (!fluxSourceIterator.hasNext()) continue; Fluxmap fluxmap = fluxSourceIterator.next(); Logger.log(new EndReadOperationLogMessage()); - Logger.log(String.format("%d ms in %d bytes", - (int) (fluxmap.duration() / 1e6), fluxmap.bytes())); + Logger.log(String.format( + "%d ms in %d bytes", + (int) (fluxmap.duration() / 1e6), + fluxmap.bytes())); Track flux = decoder.decodeToSectors(fluxmap, ptl); flux.normalisedSectors = collectSectors(flux.allSectors); @@ -429,8 +431,7 @@ private static ReadGroupResult readGroup(DiskLayout diskLayout, /* Decode what we've got so far. */ - CombinationResult cr = - combineRecordAndSectors(tracks, decoder, ltl); + CombinationResult cr = combineRecordAndSectors(tracks, decoder, ltl); rgr.combinedSectors = cr.sectors; if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) { @@ -450,9 +451,12 @@ private static ReadGroupResult readGroup(DiskLayout diskLayout, } private static void readAndDecodeTrack(ConfigProto config, - DiskLayout diskLayout, FluxSource fluxSource, Decoder decoder, - LogicalTrackLayout ltl, List tracks, - List combinedSectors) + DiskLayout diskLayout, + FluxSource fluxSource, + Decoder decoder, + LogicalTrackLayout ltl, + List tracks, + List combinedSectors) { if (fluxSource.isHardware()) measureDiskRotation(config); @@ -462,8 +466,8 @@ private static void readAndDecodeTrack(ConfigProto config, int retriesRemaining = config.getDecoder().getRetries(); for (; ; ) { - ReadGroupResult rgr = readGroup(diskLayout, - fluxSourceIteratorHolder, ltl, tracks, decoder, config); + ReadGroupResult rgr = + readGroup(diskLayout, fluxSourceIteratorHolder, ltl, tracks, decoder, config); combinedSectors.clear(); combinedSectors.addAll(rgr.combinedSectors); if (rgr.result == ReadResult.GOOD_READ) @@ -483,8 +487,7 @@ private static void readAndDecodeTrack(ConfigProto config, if (fluxSource.isHardware()) { adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); - Logger.log(String.format("retrying; %d retries remaining", - retriesRemaining)); + Logger.log(String.format("retrying; %d retries remaining", retriesRemaining)); retriesRemaining--; } } @@ -503,17 +506,16 @@ private static double measureDiskRotation(ConfigProto config) if (oneRevolution == 0) { UsbDevice device = UsbFactory.reconnect(config); - device.setDrive(config.getDrive().getDrive(), + device.setDrive( + config.getDrive().getDrive(), config.getDrive().getHighDensity(), config.getDrive().getIndexMode().getNumber()); - Logger.log(new BeginOperationLogMessage( - "Measuring drive rotational speed")); + Logger.log(new BeginOperationLogMessage("Measuring drive rotational speed")); int retries = 5; do { - oneRevolution = device.getRotationalPeriod( - config.getDrive().getHardSectorCount()); + oneRevolution = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); retries--; } while ((oneRevolution == 0) && (retries > 0)); Logger.log(new EndOperationLogMessage("")); @@ -529,36 +531,4 @@ private static double measureDiskRotation(ConfigProto config) private static void testForEmergencyStop() { } - - private static void hexdump(java.io.PrintStream stream, Bytes buffer) - { - int pos = 0; - - while (pos < buffer.size()) - { - stream.printf("%05x : ", pos); - for (int i = 0; i < 16; i++) - { - if ((pos + i) < buffer.size()) - stream.printf("%02x ", buffer.getByte(pos + i)); - else - stream.print("-- "); - } - stream.print(" : "); - for (int i = 0; i < 16; i++) - { - if ((pos + i) >= buffer.size()) - break; - - int c = buffer.getByte(pos + i) & 0xff; - if ((c >= 32) && (c <= 126)) - stream.print((char) c); - else - stream.print('.'); - } - stream.println(); - - pos += 16; - } - } } diff --git a/java/com/cowlark/fluxengine/core/Utils.java b/java/com/cowlark/fluxengine/core/Utils.java new file mode 100644 index 00000000..56ecedd4 --- /dev/null +++ b/java/com/cowlark/fluxengine/core/Utils.java @@ -0,0 +1,36 @@ +package com.cowlark.fluxengine.core; + +public class Utils +{ + public static void hexdump(java.io.PrintStream stream, Bytes buffer) + { + int pos = 0; + + while (pos < buffer.size()) + { + stream.printf("%05x : ", pos); + for (int i = 0; i < 16; i++) + { + if ((pos + i) < buffer.size()) + stream.printf("%02x ", buffer.getByte(pos + i)); + else + stream.print("-- "); + } + stream.print(" : "); + for (int i = 0; i < 16; i++) + { + if ((pos + i) >= buffer.size()) + break; + + int c = buffer.getByte(pos + i) & 0xff; + if ((c >= 32) && (c <= 126)) + stream.print((char) c); + else + stream.print('.'); + } + stream.println(); + + pos += 16; + } + } +} From 4518215b2ff9221e00da4fc1b94ff8f217ec1e75 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 00:21:32 +0200 Subject: [PATCH 129/192] Port the writer code; rearrange stuff. --- .../cowlark/fluxengine/algorithms/BUILD.bazel | 1 + .../cowlark/fluxengine/algorithms/Common.java | 98 ++++++++ .../{ReaderWriter.java => Reader.java} | 134 ++-------- .../cowlark/fluxengine/algorithms/Writer.java | 237 ++++++++++++++++++ .../cowlark/fluxengine/cli/ReadCommand.java | 4 +- .../cowlark/fluxengine/algorithms/BUILD.bazel | 50 ++++ .../fluxengine/algorithms/CommonTest.java | 148 +++++++++++ .../fluxengine/algorithms/ReaderTest.java | 155 ++++++++++++ .../fluxengine/algorithms/WriterTest.java | 146 +++++++++++ 9 files changed, 861 insertions(+), 112 deletions(-) create mode 100644 java/com/cowlark/fluxengine/algorithms/Common.java rename java/com/cowlark/fluxengine/algorithms/{ReaderWriter.java => Reader.java} (79%) create mode 100644 java/com/cowlark/fluxengine/algorithms/Writer.java create mode 100644 javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/algorithms/CommonTest.java create mode 100644 javatests/com/cowlark/fluxengine/algorithms/ReaderTest.java create mode 100644 javatests/com/cowlark/fluxengine/algorithms/WriterTest.java diff --git a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel index 406c53ff..f6445053 100644 --- a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -12,6 +12,7 @@ java_library( "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/decoders", "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "//java/com/cowlark/fluxengine/encoders", "//java/com/cowlark/fluxengine/fluxsink", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/imagewriter", diff --git a/java/com/cowlark/fluxengine/algorithms/Common.java b/java/com/cowlark/fluxengine/algorithms/Common.java new file mode 100644 index 00000000..4394eeaa --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/Common.java @@ -0,0 +1,98 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; +import java.util.HashMap; +import java.util.Map; + +class Common +{ + static double getRotationalPeriodFromConfig(ConfigProto config) + { + return config.getDrive().getRotationalPeriodMs() * 1e6; + } + + static double measureDiskRotation(ConfigProto config) + { + Logger.log(new LogMessage.BeginSpeedOperationLogMessage()); + + double oneRevolution = getRotationalPeriodFromConfig(config); + if (oneRevolution == 0) + { + UsbDevice device = UsbFactory.reconnect(config); + device.setDrive( + config.getDrive().getDrive(), + config.getDrive().getHighDensity(), + config.getDrive().getIndexMode().getNumber()); + + Logger.log(new LogMessage.BeginOperationLogMessage("Measuring drive rotational speed")); + int retries = 5; + do + { + oneRevolution = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); + retries--; + } while ((oneRevolution == 0) && (retries > 0)); + Logger.log(new LogMessage.EndOperationLogMessage("")); + } + + if (oneRevolution == 0) + throw new FluxEngineException("Failed\nIs a disk in the drive?"); + + Logger.log(new LogMessage.EndSpeedOperationLogMessage(oneRevolution)); + return oneRevolution; + } + + static void testForEmergencyStop() + { + } + + static void adjustTrackOnError(FluxSource fluxSource, int baseTrack, ConfigProto config) + { + switch (config.getDrive().getErrorBehaviour()) + { + case NOTHING: + break; + + case RECALIBRATE: + fluxSource.recalibrate(); + break; + + case JIGGLE: + if (baseTrack > 0) + fluxSource.seek(baseTrack - 1); + else + fluxSource.seek(baseTrack + 1); + break; + } + } + + static class FluxSourceIteratorHolder + { + private final FluxSource fluxSource; + private final Map cache = new HashMap<>(); + + FluxSourceIteratorHolder(FluxSource fluxSource) + { + this.fluxSource = fluxSource; + } + + FluxSourceIterator getIterator(int physicalCylinder, int head) + { + CylinderHead key = new CylinderHead(physicalCylinder, head); + FluxSourceIterator it = cache.get(key); + if (it == null) + { + it = fluxSource.readFlux(physicalCylinder, head); + cache.put(key, it); + } + return it; + } + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java b/java/com/cowlark/fluxengine/algorithms/Reader.java similarity index 79% rename from java/com/cowlark/fluxengine/algorithms/ReaderWriter.java rename to java/com/cowlark/fluxengine/algorithms/Reader.java index 78be4c44..daf20838 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReaderWriter.java +++ b/java/com/cowlark/fluxengine/algorithms/Reader.java @@ -1,13 +1,10 @@ package com.cowlark.fluxengine.algorithms; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.LogMessage.BeginOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.BeginSpeedOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.EndOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.EndReadOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.OperationProgressLogMessage; import com.cowlark.fluxengine.core.Logger; import com.cowlark.fluxengine.core.Utils; @@ -27,8 +24,6 @@ import com.cowlark.fluxengine.fluxsource.FluxSource; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import com.cowlark.fluxengine.imagewriter.ImageWriter; -import com.cowlark.fluxengine.usb.UsbDevice; -import com.cowlark.fluxengine.usb.UsbFactory; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; @@ -39,19 +34,19 @@ /** * Disk read/write algorithms, ported from lib/algorithms/readerwriter.cc. */ -public final class ReaderWriter +public final class Reader { - private static enum ReadResult + static enum ReadResult { GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY } - private static enum BadSectorsState + static enum BadSectorsState { HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS } - private ReaderWriter() + private Reader() { } @@ -80,9 +75,9 @@ public static void readDiskCommand(ConfigProto config, Logger.log(new BeginOperationLogMessage("Reading and decoding disk")); if (fluxSource.isHardware()) - disk.rotationalPeriod = measureDiskRotation(config); + disk.rotationalPeriod = Common.measureDiskRotation(config); else - disk.rotationalPeriod = getRotationalPeriodFromConfig(config); + disk.rotationalPeriod = Common.getRotationalPeriodFromConfig(config); try (FluxSink outputFluxSink = outputFluxSinkFactory != null ? outputFluxSinkFactory.create() : @@ -98,7 +93,7 @@ public static void readDiskCommand(ConfigProto config, index * 100 / diskLayout.layoutByLogicalLocation.size())); index++; - testForEmergencyStop(); + Common.testForEmergencyStop(); List trackFluxes = tracksByLogicalLocation.computeIfAbsent( logicalLocation, @@ -217,7 +212,7 @@ public static void readDiskCommand(ConfigProto config, /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a * good and bad version of the same sector, the bad version is dropped). */ - private static List collectSectors(List trackSectors, boolean collapseConflicts) + static List collectSectors(List trackSectors, boolean collapseConflicts) { Map> sectors = new LinkedHashMap<>(); for (Sector sector : trackSectors) @@ -263,7 +258,7 @@ private static List collectSectors(List trackSectors, boolean co return sectorSet; } - private static List collectSectors(List trackSectors) + static List collectSectors(List trackSectors) { return collectSectors(trackSectors, true); } @@ -284,15 +279,15 @@ private static Sector copySector(Sector sector) return s; } - private static class CombinationResult + static class CombinationResult { BadSectorsState result; List sectors; } - private static CombinationResult combineRecordAndSectors(List tracks, - Decoder decoder, - LogicalTrackLayout ltl) + static CombinationResult combineRecordAndSectors(List tracks, + Decoder decoder, + LogicalTrackLayout ltl) { CombinationResult cr = new CombinationResult(); cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; @@ -327,61 +322,18 @@ private static CombinationResult combineRecordAndSectors(List tracks, return cr; } - private static void adjustTrackOnError(FluxSource fluxSource, int baseTrack, ConfigProto config) - { - switch (config.getDrive().getErrorBehaviour()) - { - case NOTHING: - break; - - case RECALIBRATE: - fluxSource.recalibrate(); - break; - - case JIGGLE: - if (baseTrack > 0) - fluxSource.seek(baseTrack - 1); - else - fluxSource.seek(baseTrack + 1); - break; - } - } - - private static class ReadGroupResult + static class ReadGroupResult { ReadResult result; List combinedSectors; } - private static class FluxSourceIteratorHolder - { - private final FluxSource fluxSource; - private final Map cache = new HashMap<>(); - - FluxSourceIteratorHolder(FluxSource fluxSource) - { - this.fluxSource = fluxSource; - } - - FluxSourceIterator getIterator(int physicalCylinder, int head) - { - CylinderHead key = new CylinderHead(physicalCylinder, head); - FluxSourceIterator it = cache.get(key); - if (it == null) - { - it = fluxSource.readFlux(physicalCylinder, head); - cache.put(key, it); - } - return it; - } - } - - private static ReadGroupResult readGroup(DiskLayout diskLayout, - FluxSourceIteratorHolder fluxSourceIteratorHolder, - LogicalTrackLayout ltl, - List tracks, - Decoder decoder, - ConfigProto config) + static ReadGroupResult readGroup(DiskLayout diskLayout, + Common.FluxSourceIteratorHolder fluxSourceIteratorHolder, + LogicalTrackLayout ltl, + List tracks, + Decoder decoder, + ConfigProto config) { ReadGroupResult rgr = new ReadGroupResult(); rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; @@ -459,10 +411,10 @@ private static void readAndDecodeTrack(ConfigProto config, List combinedSectors) { if (fluxSource.isHardware()) - measureDiskRotation(config); + Common.measureDiskRotation(config); - FluxSourceIteratorHolder fluxSourceIteratorHolder = - new FluxSourceIteratorHolder(fluxSource); + Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = + new Common.FluxSourceIteratorHolder(fluxSource); int retriesRemaining = config.getDecoder().getRetries(); for (; ; ) { @@ -486,49 +438,11 @@ private static void readAndDecodeTrack(ConfigProto config, if (fluxSource.isHardware()) { - adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); + Common.adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); Logger.log(String.format("retrying; %d retries remaining", retriesRemaining)); retriesRemaining--; } } } - private static double getRotationalPeriodFromConfig(ConfigProto config) - { - return config.getDrive().getRotationalPeriodMs() * 1e6; - } - - private static double measureDiskRotation(ConfigProto config) - { - Logger.log(new BeginSpeedOperationLogMessage()); - - double oneRevolution = getRotationalPeriodFromConfig(config); - if (oneRevolution == 0) - { - UsbDevice device = UsbFactory.reconnect(config); - device.setDrive( - config.getDrive().getDrive(), - config.getDrive().getHighDensity(), - config.getDrive().getIndexMode().getNumber()); - - Logger.log(new BeginOperationLogMessage("Measuring drive rotational speed")); - int retries = 5; - do - { - oneRevolution = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); - retries--; - } while ((oneRevolution == 0) && (retries > 0)); - Logger.log(new EndOperationLogMessage("")); - } - - if (oneRevolution == 0) - throw new FluxEngineException("Failed\nIs a disk in the drive?"); - - Logger.log(new EndSpeedOperationLogMessage(oneRevolution)); - return oneRevolution; - } - - private static void testForEmergencyStop() - { - } } diff --git a/java/com/cowlark/fluxengine/algorithms/Writer.java b/java/com/cowlark/fluxengine/algorithms/Writer.java new file mode 100644 index 00000000..1056d7c5 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/Writer.java @@ -0,0 +1,237 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.data.Track; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Writes images to disks, ported from lib/algorithms/readerwriter.cc. + */ +public final class Writer +{ + private Writer() + { + } + + private static void writeTracks(ConfigProto config, + DiskLayout diskLayout, + FluxSinkFactory fluxSinkFactory, + Function producer, + Predicate verifier, + List logicalLocations) + { + Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + + if (fluxSinkFactory.isHardware()) + Common.measureDiskRotation(config); + try (FluxSink fluxSink = fluxSinkFactory.create()) + { + int index = 0; + for (CylinderHead ch : logicalLocations) + { + Logger.log(new LogMessage.OperationProgressLogMessage( + index * 100 / logicalLocations.size())); + index++; + + Common.testForEmergencyStop(); + + LogicalTrackLayout ltl = diskLayout.layoutByLogicalLocation.get(ch); + int retriesRemaining = config.getDecoder().getRetries(); + for (; ; ) + { + for (int offset = 0; offset < ltl.groupSize; offset += diskLayout.headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + + Logger.log(new LogMessage.BeginWriteOperationLogMessage( + physicalCylinder, + ltl.physicalHead)); + + boolean erase = false; + if (offset == config.getDrive().getGroupOffset()) + { + Fluxmap fluxmap = producer.apply(ltl); + if (fluxmap == null) + erase = true; + else + { + fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); + Logger.log(String.format( + "writing %d ms in %d bytes", + (int) (fluxmap.duration() / 1e6), + fluxmap.bytes())); + } + } else + erase = true; + + if (erase) + { + /* Erase this track rather than writing. */ + + Fluxmap blank = new Fluxmap(); + fluxSink.addFlux(physicalCylinder, physicalHead, blank); + Logger.log("erased"); + } + + Logger.log(new LogMessage.EndWriteOperationLogMessage()); + } + + if (verifier.test(ltl)) + break; + + if (retriesRemaining == 0) + throw new FluxEngineException("fatal error on write"); + + Logger.log(String.format("retrying; %d retries remaining", retriesRemaining)); + retriesRemaining--; + } + } + } + + Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); + } + + private static void writeTracks(ConfigProto config, + DiskLayout diskLayout, + FluxSinkFactory fluxSinkFactory, + Encoder encoder, + Image image, + List chs) + { + writeTracks( + config, diskLayout, fluxSinkFactory, ltl -> { + List sectors = encoder.collectSectors(ltl, image); + return encoder.encode(ltl, sectors, image); + }, ltl -> true, chs); + } + + private static void writeTracksAndVerify(ConfigProto config, + DiskLayout diskLayout, + FluxSinkFactory fluxSinkFactory, + Encoder encoder, + FluxSource fluxSource, + Decoder decoder, + Image image, + List chs) + { + writeTracks( + config, diskLayout, fluxSinkFactory, ltl -> { + List sectors = encoder.collectSectors(ltl, image); + return encoder.encode(ltl, sectors, image); + }, ltl -> { + Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = + new Common.FluxSourceIteratorHolder(fluxSource); + List tracks = new ArrayList<>(); + Reader.ReadGroupResult rgr = Reader.readGroup( + diskLayout, + fluxSourceIteratorHolder, + ltl, + tracks, + decoder, + config); + + if (rgr.result != Reader.ReadResult.GOOD_READ) + { + Common.adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); + Logger.log("bad read"); + return false; + } + + Image wanted = new Image(); + for (Sector sector : encoder.collectSectors(ltl, image)) + wanted.put( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()).data = sector.data; + + for (Sector sector : rgr.combinedSectors) + { + Sector s = wanted.get( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()); + if (s == null) + { + Logger.log("spurious sector on verify"); + return false; + } + if (!s.data.equals(sector.data.slice(0, s.data.size()))) + { + Logger.log("data mismatch on verify"); + return false; + } + wanted.erase( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()); + } + if (!wanted.empty()) + { + Logger.log("missing sector on verify"); + return false; + } + return true; + }, chs); + } + + public static void writeDiskCommand(ConfigProto config, + DiskLayout diskLayout, + Image image, + Encoder encoder, + FluxSinkFactory fluxSinkFactory, + Decoder decoder, + FluxSource fluxSource, + List physicalLocations) + { + List chs = new ArrayList<>(diskLayout.layoutByLogicalLocation.keySet()); + if (fluxSource != null && decoder != null) + writeTracksAndVerify( + config, + diskLayout, + fluxSinkFactory, + encoder, + fluxSource, + decoder, + image, + chs); + else + writeTracks(config, diskLayout, fluxSinkFactory, encoder, image, chs); + } + + public static void writeDiskCommand(ConfigProto config, + DiskLayout diskLayout, + Image image, + Encoder encoder, + FluxSinkFactory fluxSinkFactory, + Decoder decoder, + FluxSource fluxSource) + { + writeDiskCommand( + config, + diskLayout, + image, + encoder, + fluxSinkFactory, + decoder, + fluxSource, + new ArrayList<>(diskLayout.layoutByLogicalLocation.keySet())); + } +} diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index e455a7ba..92a0e7f6 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -3,7 +3,7 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; import com.cowlark.fluxengine.arch.Arch; -import com.cowlark.fluxengine.algorithms.ReaderWriter; +import com.cowlark.fluxengine.algorithms.Reader; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; @@ -65,6 +65,6 @@ public void run(ImmutableList args) FluxSource fluxSource = FluxSource.create(config); Decoder decoder = Arch.createDecoder(config); ImageWriter writer = ImageWriter.create(config); - ReaderWriter.readDiskCommand(config, diskLayout, fluxSource, decoder, writer); + Reader.readDiskCommand(config, diskLayout, fluxSource, decoder, writer); } } diff --git a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel new file mode 100644 index 00000000..2b337efc --- /dev/null +++ b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -0,0 +1,50 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "WriterTest", + srcs = ["WriterTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/algorithms", + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/encoders", + "//java/com/cowlark/fluxengine/fluxsink", + "//java/com/cowlark/fluxengine/fluxsource", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "CommonTest", + srcs = ["CommonTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/algorithms", + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/fluxsource", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + +java_test( + name = "ReaderTest", + srcs = ["ReaderTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/algorithms", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java b/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java new file mode 100644 index 00000000..f0458470 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java @@ -0,0 +1,148 @@ +package com.cowlark.fluxengine.algorithms; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class CommonTest +{ + private static ConfigProto makeConfig() + { + return new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .build(); + } + + private static class RecordingFluxSource extends FluxSource + { + final List seeks = new ArrayList<>(); + int recalibrations = 0; + + @Override + public void recalibrate() + { + recalibrations++; + } + + @Override + public void seek(int cylinder) + { + seeks.add(cylinder); + } + + @Override + public FluxSourceIterator readFlux(int cylinder, int head) + { + return null; + } + } + + @Test + public void getRotationalPeriodFromConfig() + { + assertThat(Common.getRotationalPeriodFromConfig(makeConfig())).isEqualTo(200e6); + } + + @Test + public void measureDiskRotationUsesConfigPeriod() + { + /* The period is set in the config, so no hardware access happens. */ + assertThat(Common.measureDiskRotation(makeConfig())).isEqualTo(200e6); + } + + @Test + public void adjustTrackOnErrorNothing() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.error_behaviour", "NOTHING") + .build(); + RecordingFluxSource fluxSource = new RecordingFluxSource(); + + Common.adjustTrackOnError(fluxSource, 5, config); + + assertThat(fluxSource.recalibrations).isEqualTo(0); + assertThat(fluxSource.seeks).isEmpty(); + } + + @Test + public void adjustTrackOnErrorRecalibrate() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.error_behaviour", "RECALIBRATE") + .build(); + RecordingFluxSource fluxSource = new RecordingFluxSource(); + + Common.adjustTrackOnError(fluxSource, 5, config); + + assertThat(fluxSource.recalibrations).isEqualTo(1); + assertThat(fluxSource.seeks).isEmpty(); + } + + @Test + public void adjustTrackOnErrorJiggle() + { + ConfigProto config = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.error_behaviour", "JIGGLE") + .build(); + RecordingFluxSource fluxSource = new RecordingFluxSource(); + + Common.adjustTrackOnError(fluxSource, 5, config); + assertThat(fluxSource.seeks).containsExactly(4); + + Common.adjustTrackOnError(fluxSource, 0, config); + assertThat(fluxSource.seeks).containsExactly(4, 1); + } + + @Test + public void fluxSourceIteratorHolderCaches() + { + final int[] reads = {0}; + FluxSource fluxSource = new FluxSource() + { + @Override + public FluxSourceIterator readFlux(int cylinder, int head) + { + reads[0]++; + return new FluxSourceIterator() + { + @Override + public boolean hasNext() + { + return false; + } + + @Override + public com.cowlark.fluxengine.data.Fluxmap next() + { + return null; + } + }; + } + }; + + Common.FluxSourceIteratorHolder holder = new Common.FluxSourceIteratorHolder(fluxSource); + + FluxSourceIterator it1 = holder.getIterator(1, 0); + FluxSourceIterator it2 = holder.getIterator(1, 0); + FluxSourceIterator it3 = holder.getIterator(2, 1); + + assertThat(reads[0]).isEqualTo(2); + assertThat(it1).isSameInstanceAs(it2); + assertThat(it3).isNotSameInstanceAs(it1); + assertThat(new CylinderHead(1, 0)).isEqualTo(new CylinderHead(1, 0)); + } +} diff --git a/javatests/com/cowlark/fluxengine/algorithms/ReaderTest.java b/javatests/com/cowlark/fluxengine/algorithms/ReaderTest.java new file mode 100644 index 00000000..b7d83af5 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/algorithms/ReaderTest.java @@ -0,0 +1,155 @@ +package com.cowlark.fluxengine.algorithms; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.data.Track; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ReaderTest +{ + private static LogicalTrackLayout makeLtl() + { + ImmutableList order = ImmutableList.of(0, 1, 2); + return new LogicalTrackLayout( + 0, 0, 1, 0, 0, 3, 256, order, order, order, + ImmutableMap.of(0, 0, 1, 1, 2, 2), ImmutableMap.of(0, 0, 1, 1, 2, 2)); + } + + private static Sector makeSector(int sectorId, Sector.Status status) + { + Sector sector = new Sector(new LogicalLocation(0, 0, sectorId)); + sector.status = status; + return sector; + } + + @Test + public void collectSectorsDeduplicatesOkAndBad() + { + List sectors = new ArrayList<>(); + sectors.add(makeSector(0, Sector.Status.OK)); + sectors.add(makeSector(0, Sector.Status.BAD_CHECKSUM)); + sectors.add(makeSector(1, Sector.Status.BAD_CHECKSUM)); + sectors.add(makeSector(1, Sector.Status.OK)); + sectors.add(makeSector(2, Sector.Status.BAD_CHECKSUM)); + + List result = Reader.collectSectors(sectors, true); + + assertThat(result).hasSize(3); + assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); + assertThat(result.get(1).status).isEqualTo(Sector.Status.OK); + assertThat(result.get(2).status).isEqualTo(Sector.Status.BAD_CHECKSUM); + } + + @Test + public void collectSectorsPrefersOkOverMissing() + { + List sectors = new ArrayList<>(); + sectors.add(makeSector(0, Sector.Status.MISSING)); + sectors.add(makeSector(0, Sector.Status.OK)); + + List result = Reader.collectSectors(sectors); + + assertThat(result).hasSize(1); + assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); + } + + @Test + public void collectSectorsConflictWhenBothOkDifferentData() + { + Sector a = makeSector(0, Sector.Status.OK); + a.data = Bytes.of(1); + Sector b = makeSector(0, Sector.Status.OK); + b.data = Bytes.of(2); + + /* collapseConflicts=false keeps both as CONFLICT. */ + List result = Reader.collectSectors(List.of(a, b), false); + assertThat(result).hasSize(2); + assertThat(result.get(0).status).isEqualTo(Sector.Status.CONFLICT); + assertThat(result.get(1).status).isEqualTo(Sector.Status.CONFLICT); + + /* collapseConflicts=true collapses to a single CONFLICT. */ + List collapsed = Reader.collectSectors(List.of(a, b), true); + assertThat(collapsed).hasSize(1); + assertThat(collapsed.get(0).status).isEqualTo(Sector.Status.CONFLICT); + } + + @Test + public void collectSectorsOkDataSameCollapses() + { + Sector a = makeSector(0, Sector.Status.OK); + a.data = Bytes.of(1); + Sector b = makeSector(0, Sector.Status.OK); + b.data = Bytes.of(1); + + List result = Reader.collectSectors(List.of(a, b), false); + + assertThat(result).hasSize(1); + assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); + } + + @Test + public void combineRecordAndSectorsFillsMissing() + { + /* A track with only sector 0 present; the layout wants 0,1,2. */ + Track track = new Track(); + track.allSectors = new ArrayList<>(); + track.allSectors.add(makeSector(0, Sector.Status.OK)); + + Reader.CombinationResult cr = Reader.combineRecordAndSectors( + List.of(track), null, makeLtl()); + + assertThat(cr.result).isEqualTo(Reader.BadSectorsState.HAS_BAD_SECTORS); + assertThat(cr.sectors).hasSize(3); + + Sector s0 = cr.sectors.stream() + .filter(s -> s.location.logicalSector() == 0).findFirst().get(); + Sector s1 = cr.sectors.stream() + .filter(s -> s.location.logicalSector() == 1).findFirst().get(); + Sector s2 = cr.sectors.stream() + .filter(s -> s.location.logicalSector() == 2).findFirst().get(); + assertThat(s0.status).isEqualTo(Sector.Status.OK); + assertThat(s1.status).isEqualTo(Sector.Status.MISSING); + assertThat(s2.status).isEqualTo(Sector.Status.MISSING); + } + + @Test + public void combineRecordAndSectorsNoBadWhenAllPresent() + { + Track track = new Track(); + track.allSectors = new ArrayList<>(); + track.allSectors.add(makeSector(0, Sector.Status.OK)); + track.allSectors.add(makeSector(1, Sector.Status.OK)); + track.allSectors.add(makeSector(2, Sector.Status.OK)); + + Reader.CombinationResult cr = Reader.combineRecordAndSectors( + List.of(track), null, makeLtl()); + + assertThat(cr.result).isEqualTo(Reader.BadSectorsState.HAS_NO_BAD_SECTORS); + assertThat(cr.sectors).hasSize(3); + for (Sector sector : cr.sectors) + assertThat(sector.status).isEqualTo(Sector.Status.OK); + } + + @Test + public void combineRecordAndSectorsEmptyTrackIsBad() + { + Reader.CombinationResult cr = Reader.combineRecordAndSectors( + List.of(), null, makeLtl()); + + assertThat(cr.result).isEqualTo(Reader.BadSectorsState.HAS_BAD_SECTORS); + assertThat(cr.sectors).hasSize(3); + for (Sector sector : cr.sectors) + assertThat(sector.status).isEqualTo(Sector.Status.MISSING); + } +} diff --git a/javatests/com/cowlark/fluxengine/algorithms/WriterTest.java b/javatests/com/cowlark/fluxengine/algorithms/WriterTest.java new file mode 100644 index 00000000..335ea5f1 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/algorithms/WriterTest.java @@ -0,0 +1,146 @@ +package com.cowlark.fluxengine.algorithms; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@RunWith(JUnit4.class) +public class WriterTest +{ + private static class RecordingFluxSink extends FluxSink + { + final Map written = new HashMap<>(); + + @Override + public void addFlux(int track, int head, Fluxmap fluxmap) + { + written.put(new CylinderHead(track, head), fluxmap); + } + } + + private static class RecordingFluxSinkFactory extends FluxSinkFactory + { + final RecordingFluxSink sink = new RecordingFluxSink(); + + @Override + public FluxSink create() + { + return sink; + } + } + + private static class TestEncoder extends Encoder + { + final List encoded = new ArrayList<>(); + + @Override + public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) + { + encoded.add(ltl); + Fluxmap fluxmap = new Fluxmap(); + fluxmap.appendInterval(100); + fluxmap.appendPulse(); + return fluxmap; + } + } + + private static ConfigProto makeConfig() + { + return new ConfigBuilder().set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("layout.tracks", "1") + .set("layout.sides", "1") + .set("layout.layoutdata[0].sector_size", "256") + .set("layout.layoutdata[0].physical.start_sector", "0") + .set("layout.layoutdata[0].physical.count", "8") + .build(); + } + + private static Image makeImage() + { + Image image = new Image(); + for (int sectorId = 0; sectorId < 8; sectorId++) + { + Sector sector = image.put(0, 0, sectorId); + sector.status = Sector.Status.OK; + sector.data = Bytes.of(sectorId); + } + return image; + } + + @Test + public void writesAllLogicalLocations() + { + ConfigProto config = makeConfig(); + DiskLayout diskLayout = new DiskLayout(config); + Image image = makeImage(); + + RecordingFluxSinkFactory factory = new RecordingFluxSinkFactory(); + TestEncoder encoder = new TestEncoder(); + + Writer.writeDiskCommand(config, diskLayout, image, encoder, factory, null, null); + + assertThat(encoder.encoded).hasSize(1); + assertThat(encoder.encoded.get(0).logicalCylinder).isEqualTo(0); + assertThat(factory.sink.written.keySet()).containsExactly(new CylinderHead(0, 0)); + assertThat(factory.sink.written.get(new CylinderHead(0, 0)).bytes()).isGreaterThan(0); + } + + @Test + public void writesWithoutVerifyWhenNoSource() + { + ConfigProto config = makeConfig(); + DiskLayout diskLayout = new DiskLayout(config); + Image image = makeImage(); + + RecordingFluxSinkFactory factory = new RecordingFluxSinkFactory(); + TestEncoder encoder = new TestEncoder(); + + /* decoder/fluxSource are null, so no verification happens. */ + Writer.writeDiskCommand(config, diskLayout, image, encoder, factory, null, null); + + assertThat(factory.sink.written).hasSize(1); + } + + @Test + public void emptyImageThrows() + { + ConfigProto config = makeConfig(); + DiskLayout diskLayout = new DiskLayout(config); + Image image = new Image(); + + RecordingFluxSinkFactory factory = new RecordingFluxSinkFactory(); + TestEncoder encoder = new TestEncoder(); + + /* The encoder needs all sectors present in the image, so an empty + * image is an error. */ + org.junit.Assert.assertThrows( + com.cowlark.fluxengine.core.FluxEngineException.class, + () -> Writer.writeDiskCommand( + config, + diskLayout, + image, + encoder, + factory, + null, + null)); + } +} From 9497f6e0224bcbbfab1fa78d65827f00682aeb7e Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 00:28:37 +0200 Subject: [PATCH 130/192] Port WriteCommand. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 3 + java/com/cowlark/fluxengine/cli/Command.java | 2 +- .../cowlark/fluxengine/cli/WriteCommand.java | 95 +++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/cli/WriteCommand.java diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 77d5649a..7adee190 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -15,8 +15,11 @@ java_library( "//java/com/cowlark/fluxengine/core/flags", "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/decoders", + "//java/com/cowlark/fluxengine/encoders", + "//java/com/cowlark/fluxengine/fluxsink", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/imagewriter", + "//java/com/cowlark/fluxengine/imagereader", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", ], diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 2daf34ff..589f82ff 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -35,7 +35,7 @@ public interface Command "analyse", () -> new CommandGroup(ANALYSABLES, "Disk and drive analysis tools.")) .put("read", ReadCommand::new) - .put("write", stub("write", "Writes a sector image to a disk.")) + .put("write", WriteCommand::new) .put( "fluxfile", () -> new CommandGroup( diff --git a/java/com/cowlark/fluxengine/cli/WriteCommand.java b/java/com/cowlark/fluxengine/cli/WriteCommand.java new file mode 100644 index 00000000..7fc35da4 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/WriteCommand.java @@ -0,0 +1,95 @@ +package com.cowlark.fluxengine.cli; + +import com.cowlark.fluxengine.algorithms.Writer; +import com.cowlark.fluxengine.arch.Arch; +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.flags.ActionFlag; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.imagereader.ImageReader; +import com.google.common.collect.ImmutableList; + +/** + * Write a sector image to a disk, modelled after src/fe-write.cc. + */ +public class WriteCommand implements Command +{ + private FlagGroup flags = new FlagGroup(); + private ValueFlag sourceImageFlag = StringFlag.builder() + .setGroup(flags) + .setName("--input") + .setName("-i") + .setHelpText("source image to read from") + .build(); + private ValueFlag destFluxFlag = StringFlag.builder() + .setGroup(flags) + .setName("--dest") + .setName("-d") + .setHelpText("flux destination to write to") + .build(); + private ActionFlag noVerifyFlag = ActionFlag.builder() + .setGroup(flags) + .setName("--no-verify") + .setName("-n") + .setHelpText("skip verification of write") + .setVoidCallback(() -> verify = false) + .build(); + + private boolean verify = true; + + @Override + public String getHelp() + { + return "Writes a sector image to a disk."; + } + + @Override + public void run(ImmutableList args) + { + ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); + if (sourceImageFlag.isSet()) + builder.withImageReader(sourceImageFlag.get()); + + String dest = destFluxFlag.isSet() ? destFluxFlag.get() : "drive:0"; + builder.withFluxSink(dest); + ConfigProto config = builder.build(); + + ImageReader reader = ImageReader.create(config); + Image image = reader.readImage(); + + config = config.toBuilder() + .mergeFrom(reader.getExtraConfig()) + .build(); + + DiskLayout diskLayout = new DiskLayout(config); + Encoder encoder = Arch.createEncoder(config); + FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); + + Decoder decoder = null; + FluxSource verificationFluxSource = null; + if (config.hasDecoder() && fluxSinkFactory.isHardware() && verify) + { + decoder = Arch.createDecoder(config); + ConfigBuilder verifyBuilder = new ConfigBuilder().fromFlags(args, flags); + verifyBuilder.withFluxSource(dest); + verificationFluxSource = FluxSource.create(verifyBuilder.build()); + } + + Writer.writeDiskCommand( + config, + diskLayout, + image, + encoder, + fluxSinkFactory, + decoder, + verificationFluxSource); + } +} From 8b04badc4d25efac17447a2cb653eb800cc9d749 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 00:37:53 +0200 Subject: [PATCH 131/192] Port RawwriteCommand. --- .../cowlark/fluxengine/algorithms/Writer.java | 22 ++++++ java/com/cowlark/fluxengine/cli/Command.java | 6 +- .../fluxengine/cli/RawwriteCommand.java | 78 +++++++++++++++++++ 3 files changed, 102 insertions(+), 4 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/RawwriteCommand.java diff --git a/java/com/cowlark/fluxengine/algorithms/Writer.java b/java/com/cowlark/fluxengine/algorithms/Writer.java index 1056d7c5..bc159cb0 100644 --- a/java/com/cowlark/fluxengine/algorithms/Writer.java +++ b/java/com/cowlark/fluxengine/algorithms/Writer.java @@ -16,6 +16,7 @@ import com.cowlark.fluxengine.fluxsink.FluxSink; import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import java.util.ArrayList; import java.util.List; import java.util.function.Function; @@ -234,4 +235,25 @@ public static void writeDiskCommand(ConfigProto config, fluxSource, new ArrayList<>(diskLayout.layoutByLogicalLocation.keySet())); } + + public static void writeRawDiskCommand(ConfigProto config, + DiskLayout diskLayout, + FluxSource fluxSource, + FluxSinkFactory fluxSinkFactory) + { + writeTracks( + config, + diskLayout, + fluxSinkFactory, + ltl -> + { + FluxSourceIterator iterator = + fluxSource.readFlux(ltl.physicalCylinder, ltl.physicalHead); + if (!iterator.hasNext()) + return null; + return iterator.next(); + }, + ltl -> true, + diskLayout.logicalLocations); + } } diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 589f82ff..4d928454 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -43,10 +43,8 @@ public interface Command "Flux file manipulation operations.")) .put("format", stub("format", "Format a disk and make a file system on it.")) .put( - "rawwrite", stub( - "rawwrite", - "Writes a flux file to a disk. Warning: you can't use this to" + - " copy disks.")) + "rawwrite", + RawwriteCommand::new) .put( "convert", stub("convert", "Converts a flux file from one format to another.")) diff --git a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java new file mode 100644 index 00000000..6ae6a85b --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java @@ -0,0 +1,78 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_ERASE; + +import com.cowlark.fluxengine.algorithms.Writer; +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.flags.ActionFlag; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.google.common.collect.ImmutableList; + +/** + * Write a flux file to a disk, modelled after src/fe-rawwrite.cc. + */ +public class RawwriteCommand implements Command +{ + private FlagGroup flags = new FlagGroup(); + private ValueFlag sourceFluxFlag = StringFlag.builder() + .setGroup(flags) + .setName("--source") + .setName("-s") + .setHelpText("source flux file to read from") + .build(); + private ValueFlag destFluxFlag = StringFlag.builder() + .setGroup(flags) + .setName("--dest") + .setName("-d") + .setHelpText("flux destination to write to") + .build(); + private ActionFlag eraseFlag = ActionFlag.builder() + .setGroup(flags) + .setName("--erase") + .setHelpText("erases the destination") + .setVoidCallback(this::setErase) + .build(); + + private boolean erase = false; + + @Override + public String getHelp() + { + return "Writes a flux file to a disk. Warning: you can't use this to copy disks."; + } + + private void setErase() + { + erase = true; + } + + @Override + public void run(ImmutableList args) + { + ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); + if (sourceFluxFlag.isSet()) + builder.withFluxSource(sourceFluxFlag.get()); + String dest = destFluxFlag.isSet() ? destFluxFlag.get() : "drive:0"; + builder.withFluxSink(dest); + if (erase) + builder.withFluxSource("erase:"); + ConfigProto config = builder.build(); + + if (config.getFluxSource().getType() == FLUXTYPE_DRIVE) + throw new FluxEngineException("you can't use rawwrite to read from hardware"); + + FluxSource fluxSource = FluxSource.create(config); + FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); + DiskLayout diskLayout = new DiskLayout(config); + + Writer.writeRawDiskCommand(config, diskLayout, fluxSource, fluxSinkFactory); + } +} From 415cb6adab8e4ac2565f531663cfdb88379b87eb Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 00:49:56 +0200 Subject: [PATCH 132/192] Rearrange the CLI commands a bit. --- java/com/cowlark/fluxengine/cli/Command.java | 44 +++++++++++--------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 4d928454..93d08cf4 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -28,42 +28,48 @@ public interface Command .put("voltages", TestVoltagesCommand::new) .build(); + + ImmutableMap> VFSABLES = + ImmutableMap.>builder() + .put("ls", stub("ls", "Show files on disk (or image).")) + .put("mv", stub("mv", "Rename a file on a disk (or image).")) + .put("rm", stub("rm", "Deletes a file (or directory) off a disk (or image).")) + .put("getfile", stub("getfile", "Read a file off a disk (or image).")) + .put( + "getfileinfo", + stub("getfileinfo", "Read file metadata off a disk (or image).")) + .put("putfile", stub("putfile", "Write a file to disk (or image).")) + .put("mkdir", stub("mkdir", "Create a directory on disk (or image).")) + .put( + "getdiskinfo", + stub("getdiskinfo", "Read volume metadata off a disk (or image).")) + .put("format", stub("format", "Format a disk and make a file system on it.")) + .build(); + ImmutableMap> COMMANDS = ImmutableMap.>builder() - .put("inspect", stub("inspect", "Low-level analysis and inspection of a disk.")) .put( "analyse", () -> new CommandGroup(ANALYSABLES, "Disk and drive analysis tools.")) - .put("read", ReadCommand::new) - .put("write", WriteCommand::new) + .put("test", () -> new CommandGroup(TESTABLES, "Various testing commands.")) .put( "fluxfile", () -> new CommandGroup( FLUXFILEABLES, "Flux file manipulation operations.")) - .put("format", stub("format", "Format a disk and make a file system on it.")) .put( - "rawwrite", - RawwriteCommand::new) + "vfs", + () -> new CommandGroup(VFSABLES, "File system manipulation commands.")) + .put("read", ReadCommand::new) + .put("write", WriteCommand::new) + .put("rawwrite", RawwriteCommand::new) .put( "convert", stub("convert", "Converts a flux file from one format to another.")) - .put( - "getdiskinfo", - stub("getdiskinfo", "Read volume metadata off a disk (or image).")) - .put("ls", stub("ls", "Show files on disk (or image).")) - .put("mv", stub("mv", "Rename a file on a disk (or image).")) - .put("rm", stub("rm", "Deletes a file (or directory) off a disk (or image).")) - .put("getfile", stub("getfile", "Read a file off a disk (or image).")) - .put( - "getfileinfo", - stub("getfileinfo", "Read file metadata off a disk (or image).")) - .put("putfile", stub("putfile", "Write a file to disk (or image).")) - .put("mkdir", stub("mkdir", "Create a directory on disk (or image).")) .put("rpm", RpmCommand::new) .put("seek", SeekCommand::new) .put("devices", DevicesCommand::new) - .put("test", () -> new CommandGroup(TESTABLES, "Various testing commands.")) + .put("inspect", stub("inspect", "Low-level analysis and inspection of a disk.")) .build(); /* Consume arguments until we reach a real command, instantiate it, and From 60d2b45dfae78fd24563fa94e4f92b26a37f695f Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 01:01:15 +0200 Subject: [PATCH 133/192] Added the stub of the gui. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 3 +- java/com/cowlark/fluxengine/cli/Command.java | 1 + .../cowlark/fluxengine/cli/GuiCommand.java | 19 ++++++++++++ java/com/cowlark/fluxengine/gui/BUILD.bazel | 16 ++++++++++ java/com/cowlark/fluxengine/gui/Gui.java | 30 +++++++++++++++++++ .../cowlark/fluxengine/reflect-config.json | 12 ++++++++ 6 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 java/com/cowlark/fluxengine/cli/GuiCommand.java create mode 100644 java/com/cowlark/fluxengine/gui/BUILD.bazel create mode 100644 java/com/cowlark/fluxengine/gui/Gui.java diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 7adee190..42f50f25 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -18,8 +18,9 @@ java_library( "//java/com/cowlark/fluxengine/encoders", "//java/com/cowlark/fluxengine/fluxsink", "//java/com/cowlark/fluxengine/fluxsource", - "//java/com/cowlark/fluxengine/imagewriter", + "//java/com/cowlark/fluxengine/gui", "//java/com/cowlark/fluxengine/imagereader", + "//java/com/cowlark/fluxengine/imagewriter", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", ], diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 93d08cf4..68ea58f6 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -70,6 +70,7 @@ public interface Command .put("seek", SeekCommand::new) .put("devices", DevicesCommand::new) .put("inspect", stub("inspect", "Low-level analysis and inspection of a disk.")) + .put("gui", GuiCommand::new) .build(); /* Consume arguments until we reach a real command, instantiate it, and diff --git a/java/com/cowlark/fluxengine/cli/GuiCommand.java b/java/com/cowlark/fluxengine/cli/GuiCommand.java new file mode 100644 index 00000000..33c0cdf1 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/GuiCommand.java @@ -0,0 +1,19 @@ +package com.cowlark.fluxengine.cli; + +import com.cowlark.fluxengine.gui.Gui; +import com.google.common.collect.ImmutableList; + +public class GuiCommand implements Command +{ + @Override + public String getHelp() + { + return "Launch the GUI."; + } + + @Override + public void run(ImmutableList args) + { + Gui.main(args.toArray(new String[0])); + } +} diff --git a/java/com/cowlark/fluxengine/gui/BUILD.bazel b/java/com/cowlark/fluxengine/gui/BUILD.bazel new file mode 100644 index 00000000..cabc03b8 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_java//java:defs.bzl", "java_binary", "java_library") + +package(default_visibility = ["//visibility:public"]) + +java_library( + name = "gui", + srcs = glob(["*.java"]), + deps = [ + "@maven//:org_openjfx_javafx_base", + "@maven//:org_openjfx_javafx_base_linux", + "@maven//:org_openjfx_javafx_controls", + "@maven//:org_openjfx_javafx_controls_linux", + "@maven//:org_openjfx_javafx_graphics", + "@maven//:org_openjfx_javafx_graphics_linux", + ], +) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java new file mode 100644 index 00000000..f834a1d5 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -0,0 +1,30 @@ +package com.cowlark.fluxengine.gui; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.layout.StackPane; +import javafx.stage.Stage; + +/** + * The FluxEngine GUI, ported from src/gui/main.cc. + */ +public class Gui extends Application +{ + @Override + public void start(Stage stage) + { + Label label = new Label("FluxEngine"); + StackPane root = new StackPane(label); + Scene scene = new Scene(root, 800, 600); + + stage.setTitle("FluxEngine"); + stage.setScene(scene); + stage.show(); + } + + public static void main(String[] args) + { + launch(Gui.class, args); + } +} diff --git a/java/com/cowlark/fluxengine/reflect-config.json b/java/com/cowlark/fluxengine/reflect-config.json index 54ec922c..1646797e 100644 --- a/java/com/cowlark/fluxengine/reflect-config.json +++ b/java/com/cowlark/fluxengine/reflect-config.json @@ -4,6 +4,18 @@ "allDeclaredFields": true, "queryAllDeclaredMethods": true }, + { + "name": "com.cowlark.fluxengine.gui.Gui", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "com.sun.javafx.tk.quantum.QuantumToolkit", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, { "name": "groovy.lang.Closure" }, From 8c0b9e0ec716350754f7ca268a4e3857c72baed0 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 01:38:34 +0200 Subject: [PATCH 134/192] Port Inspect. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 4 +- java/com/cowlark/fluxengine/cli/Command.java | 2 +- .../fluxengine/cli/InspectCommand.java | 327 ++++++++++++++++++ .../com/cowlark/fluxengine/cli/BUILD.bazel | 15 + .../fluxengine/cli/InspectCommandTest.java | 52 +++ 5 files changed, 398 insertions(+), 2 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/InspectCommand.java create mode 100644 javatests/com/cowlark/fluxengine/cli/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/cli/InspectCommandTest.java diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 42f50f25..3344bbb8 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -15,12 +15,14 @@ java_library( "//java/com/cowlark/fluxengine/core/flags", "//java/com/cowlark/fluxengine/data", "//java/com/cowlark/fluxengine/decoders", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", "//java/com/cowlark/fluxengine/encoders", + "//java/com/cowlark/fluxengine/external", "//java/com/cowlark/fluxengine/fluxsink", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/gui", - "//java/com/cowlark/fluxengine/imagereader", "//java/com/cowlark/fluxengine/imagewriter", + "//java/com/cowlark/fluxengine/imagereader", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", ], diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 68ea58f6..99d15592 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -69,7 +69,7 @@ public interface Command .put("rpm", RpmCommand::new) .put("seek", SeekCommand::new) .put("devices", DevicesCommand::new) - .put("inspect", stub("inspect", "Low-level analysis and inspection of a disk.")) + .put("inspect", InspectCommand::new) .put("gui", GuiCommand::new) .build(); diff --git a/java/com/cowlark/fluxengine/cli/InspectCommand.java b/java/com/cowlark/fluxengine/cli/InspectCommand.java new file mode 100644 index 00000000..1968677d --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/InspectCommand.java @@ -0,0 +1,327 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; +import static com.cowlark.fluxengine.external.FluxEngine.US_PER_TICK; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Utils; +import com.cowlark.fluxengine.core.flags.DoubleFlag; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.IntFlag; +import com.cowlark.fluxengine.core.flags.SettableFlag; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.data.Locations; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.cowlark.fluxengine.decoders.FluxDecoder; +import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import com.google.common.collect.ImmutableList; + +/** + * Low-level analysis and inspection of a disk, modelled after + * src/fe-inspect.cc. + */ +public class InspectCommand implements Command +{ + private static final String[] BLOCK_ELEMENTS = {" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉", "█"}; + + private FlagGroup flags = new FlagGroup(); + private ValueFlag sourceFluxFlag = StringFlag.builder() + .setGroup(flags) + .setName("--source") + .setName("-s") + .setHelpText("'drive:' flux source to use") + .build(); + private ValueFlag destTracksFlag = StringFlag.builder() + .setGroup(flags) + .setName("--tracks") + .setName("-t") + .setHelpText("tracks to write to") + .setDefaultValue("c0h0") + .build(); + private SettableFlag dumpFluxFlag = SettableFlag.builder() + .setGroup(flags) + .setName("--dump-flux") + .setName("-F") + .setHelpText("Dump raw magnetic disk flux.") + .build(); + private SettableFlag dumpBitstreamFlag = SettableFlag.builder() + .setGroup(flags) + .setName("--dump-bitstream") + .setName("-B") + .setHelpText("Dump aligned bitstream.") + .build(); + private ValueFlag dumpRawFlag = IntFlag.builder() + .setGroup(flags) + .setName("--dump-raw") + .setName("-R") + .setHelpText("Dump raw binary with offset.") + .build(); + private SettableFlag dumpMfmFmFlag = SettableFlag.builder() + .setGroup(flags) + .setName("--mfmfm") + .setHelpText("When dumping raw binary, do MFM/FM decoding first.") + .build(); + private SettableFlag dumpBytecodesFlag = SettableFlag.builder() + .setGroup(flags) + .setName("--dump-bytecodes") + .setName("-H") + .setHelpText("Dump the raw FluxEngine bytecodes.") + .build(); + private ValueFlag fluxmapResolutionFlag = IntFlag.builder() + .setGroup(flags) + .setName("--fluxmap-resolution") + .setHelpText("Resolution of flux visualisation (nanoseconds). 0 to autoscale") + .build(); + private ValueFlag seekFlag = DoubleFlag.builder() + .setGroup(flags) + .setName("--seek") + .setName("-S") + .setHelpText("Seek this many milliseconds into the track before displaying it.") + .build(); + private ValueFlag manualClockRateFlag = DoubleFlag.builder() + .setGroup(flags) + .setName("--manual-clock-rate-us") + .setName("-u") + .setHelpText("If not zero, force this clock rate; if zero, try to autodetect it.") + .setDefaultValue(0.0) + .build(); + private ValueFlag noiseFloorFactorFlag = DoubleFlag.builder() + .setGroup(flags) + .setName("--noise-floor-factor") + .setHelpText("Clock detection noise floor (min + (max-min)*factor).") + .setDefaultValue(0.01) + .build(); + private ValueFlag signalLevelFactorFlag = DoubleFlag.builder() + .setGroup(flags) + .setName("--signal-level-factor") + .setHelpText("Clock detection signal level (min + (max-min)*factor).") + .setDefaultValue(0.05) + .build(); + + @Override + public String getHelp() + { + return "Low-level analysis and inspection of a disk."; + } + + private double guessClock(Fluxmap fluxmap, FluxmapReader fmr) + { + double manualClockRate = manualClockRateFlag.get(); + if (manualClockRate != 0.0) + return manualClockRate * 1000.0; + + FluxmapReader.ClockData data = + fmr.guessClock(noiseFloorFactorFlag.get(), signalLevelFactorFlag.get()); + + System.out.println("\nClock detection histogram:"); + + int max = Integer.MIN_VALUE; + for (int b : data.buckets) + max = Math.max(max, b); + if (max == 0) + max = 1; + + boolean skipping = true; + for (int i = 0; i < 256; i++) + { + int value = data.buckets[i]; + if (value < data.noiseFloor / 2) + { + if (!skipping) + System.out.println("..."); + skipping = true; + } else + { + skipping = false; + + int bar = 320 * value / max; + int fullblocks = bar / 8; + + StringBuilder s = new StringBuilder(); + for (int j = 0; j < fullblocks; j++) + s.append(BLOCK_ELEMENTS[8]); + s.append(BLOCK_ELEMENTS[bar & 7]); + + System.out.printf("%3d %.2f %7d %s%n", i, i * US_PER_TICK, value, s); + } + } + + System.out.printf("Noise floor: %d%n", data.noiseFloor); + System.out.printf("Signal level: %d%n", data.signalLevel); + System.out.printf("Peak start: %.2f us%n", data.peakStartTicks * US_PER_TICK); + System.out.printf("Peak end: %.2f us%n", data.peakEndTicks * US_PER_TICK); + System.out.printf("Median: %.2f us%n", data.medianTicks * US_PER_TICK); + + return data.medianTicks * NS_PER_TICK; + } + + @Override + public void run(ImmutableList args) + { + ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); + if (sourceFluxFlag.isSet()) + builder.withFluxSource(sourceFluxFlag.get()); + ConfigProto config = builder.build(); + + FluxSource fluxSource = FluxSource.create(config); + ImmutableList tracks = + Locations.parseCylinderHeadsString(destTracksFlag.get()); + if (tracks.size() != 1) + throw new FluxEngineException("you must specify exactly one track"); + CylinderHead ch = tracks.get(0); + FluxSourceIterator iterator = fluxSource.readFlux(ch.cylinder(), ch.head()); + Fluxmap fluxmap = iterator.next(); + + System.out.printf( + "0x%x bytes of data in %.3fms%n", + fluxmap.bytes(), + fluxmap.duration() / 1e6); + System.out.printf( + "Required USB bandwidth: %dkB/s%n", + (int) (fluxmap.bytes() / 1024.0 / (fluxmap.duration() / 1e9))); + + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + double clockPeriod = guessClock(fluxmap, fmr); + System.out.printf("%.2f us clock detected.", clockPeriod / 1000.0); + System.out.flush(); + + fmr.seek((long) (seekFlag.get() * 1000000.0 / NS_PER_TICK)); + + if (dumpFluxFlag.get()) + { + System.out.println("\n\nMagnetic flux follows (times in us):"); + + int resolution = fluxmapResolutionFlag.get(); + if (resolution == 0) + resolution = (int) (clockPeriod / 4); + + double nextclock = clockPeriod; + + double now = fmr.tell().getDurationNs(); + long ticks = (long) (now / NS_PER_TICK); + + System.out.printf("%10.3f:-", ticks * US_PER_TICK); + double lasttransition = 0; + while (!fmr.eof()) + { + FluxmapReader.EventResult r = fmr.findEvent(F_BIT_PULSE); + long thisTicks = r.ticks(); + ticks += thisTicks; + + double transition = ticks * NS_PER_TICK; + double next; + + boolean clocked = false; + + boolean bannered = false; + for (; ; ) + { + next = now + resolution; + clocked = now >= nextclock; + if (clocked) + nextclock += clockPeriod; + if (next >= transition) + break; + if (!bannered) + { + System.out.printf("%n%10.3f:%c", next / 1000.0, clocked ? '-' : ' '); + bannered = true; + } + now = next; + } + + double length = transition - lasttransition; + if (!bannered) + { + System.out.printf("%n%10.3f:%c", next / 1000.0, clocked ? '-' : ' '); + bannered = true; + } + System.out.printf( + "==== %06x %10.3f +%.3f = %.1f clocks", + fmr.tell().bytes(), + transition / 1000.0, + length / 1000.0, + length / clockPeriod); + lasttransition = transition; + } + } + + if (dumpBitstreamFlag.get()) + { + System.out.printf( + "\n\nAligned bitstream from %.3fms follows:%n", + fmr.tell().getDurationNs() / 1000000.0); + + FluxDecoder decoder = new FluxDecoder(fmr, clockPeriod, config.getDecoder()); + while (!fmr.eof()) + { + System.out.printf( + "%06x %10.3f : ", + fmr.tell().bytes(), + fmr.tell().getDurationNs() / 1000000.0); + for (int i = 0; i < 50; i++) + { + if (fmr.eof()) + break; + boolean b = decoder.readBit(); + System.out.print(b ? 'X' : '-'); + } + + System.out.println(); + } + } + + if (dumpRawFlag.isSet()) + { + System.out.printf( + "\n\nRaw binary with offset %d from %.3fms follows:%n", + dumpRawFlag.get(), + fmr.tell().getDurationNs() / 1000000.0); + + FluxDecoder decoder = new FluxDecoder(fmr, clockPeriod, config.getDecoder()); + for (int i = 0; i < dumpRawFlag.get(); i++) + decoder.readBit(); + + while (!fmr.eof()) + { + System.out.printf( + "%06x %10.3f : ", + fmr.tell().bytes(), + fmr.tell().getDurationNs() / 1000000.0); + + Bytes bytes; + if (dumpMfmFmFlag.get()) + bytes = FmMfm.decodeFmMfm(decoder.readBits(32 * 8)); + else + bytes = decoder.readBits(16 * 8).toBytes(); + + for (int i = 0; i < 16; i++) + { + if (i >= bytes.size()) + break; + System.out.printf("%02x ", bytes.getByte(i) & 0xff); + } + + System.out.println(); + } + } + System.out.println(); + + if (dumpBytecodesFlag.get()) + { + System.out.println("Raw FluxEngine bytecodes follow:"); + + Utils.hexdump(System.out, fluxmap.rawBytes()); + } + } +} diff --git a/javatests/com/cowlark/fluxengine/cli/BUILD.bazel b/javatests/com/cowlark/fluxengine/cli/BUILD.bazel new file mode 100644 index 00000000..4a50ea40 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/cli/BUILD.bazel @@ -0,0 +1,15 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "InspectCommandTest", + srcs = ["InspectCommandTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/cli/InspectCommandTest.java b/javatests/com/cowlark/fluxengine/cli/InspectCommandTest.java new file mode 100644 index 00000000..936a4bef --- /dev/null +++ b/javatests/com/cowlark/fluxengine/cli/InspectCommandTest.java @@ -0,0 +1,52 @@ +package com.cowlark.fluxengine.cli; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.decoders.DecoderProto; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class InspectCommandTest +{ + @Test + public void guessClockDetectsTightClock() + { + /* Pulses every 12 ticks, giving a 12-tick clock. */ + Fluxmap fluxmap = new Fluxmap(); + for (int i = 0; i < 5000; i++) + { + fluxmap.appendInterval(12); + fluxmap.appendPulse(); + } + + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + FluxmapReader.ClockData data = fmr.guessClock(0.01, 0.05); + + assertThat(data.medianTicks).isEqualTo(12); + assertThat(data.buckets[12]).isGreaterThan(0); + } + + @Test + public void guessClockSkipsLongIntervals() + { + /* Intervals longer than 255 ticks are skipped by the histogram. */ + Fluxmap fluxmap = new Fluxmap(); + for (int i = 0; i < 100; i++) + { + fluxmap.appendInterval(300); + fluxmap.appendPulse(); + } + + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + FluxmapReader.ClockData data = fmr.guessClock(0.01, 0.05); + + int total = 0; + for (int b : data.buckets) + total += b; + assertThat(total).isEqualTo(0); + } +} From 931f6136522c33002783a69bf0a8fa9a53c6d6f2 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 01:48:53 +0200 Subject: [PATCH 135/192] Port ConvertCommand. --- .../cowlark/fluxengine/algorithms/Reader.java | 7 +- .../cowlark/fluxengine/algorithms/Writer.java | 7 +- java/com/cowlark/fluxengine/cli/Command.java | 4 +- .../fluxengine/cli/ConvertCommand.java | 91 +++++++++++++++++++ 4 files changed, 98 insertions(+), 11 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/ConvertCommand.java diff --git a/java/com/cowlark/fluxengine/algorithms/Reader.java b/java/com/cowlark/fluxengine/algorithms/Reader.java index daf20838..b4b69e8a 100644 --- a/java/com/cowlark/fluxengine/algorithms/Reader.java +++ b/java/com/cowlark/fluxengine/algorithms/Reader.java @@ -372,10 +372,9 @@ static ReadGroupResult readGroup(DiskLayout diskLayout, Fluxmap fluxmap = fluxSourceIterator.next(); Logger.log(new EndReadOperationLogMessage()); - Logger.log(String.format( - "%d ms in %d bytes", + Logger.log("%d ms in %d bytes", (int) (fluxmap.duration() / 1e6), - fluxmap.bytes())); + fluxmap.bytes()); Track flux = decoder.decodeToSectors(fluxmap, ptl); flux.normalisedSectors = collectSectors(flux.allSectors); @@ -439,7 +438,7 @@ private static void readAndDecodeTrack(ConfigProto config, if (fluxSource.isHardware()) { Common.adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); - Logger.log(String.format("retrying; %d retries remaining", retriesRemaining)); + Logger.log("retrying; %d retries remaining", retriesRemaining); retriesRemaining--; } } diff --git a/java/com/cowlark/fluxengine/algorithms/Writer.java b/java/com/cowlark/fluxengine/algorithms/Writer.java index bc159cb0..79b88991 100644 --- a/java/com/cowlark/fluxengine/algorithms/Writer.java +++ b/java/com/cowlark/fluxengine/algorithms/Writer.java @@ -75,10 +75,9 @@ private static void writeTracks(ConfigProto config, else { fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); - Logger.log(String.format( - "writing %d ms in %d bytes", + Logger.log("writing %d ms in %d bytes", (int) (fluxmap.duration() / 1e6), - fluxmap.bytes())); + fluxmap.bytes()); } } else erase = true; @@ -101,7 +100,7 @@ private static void writeTracks(ConfigProto config, if (retriesRemaining == 0) throw new FluxEngineException("fatal error on write"); - Logger.log(String.format("retrying; %d retries remaining", retriesRemaining)); + Logger.log("retrying; %d retries remaining", retriesRemaining); retriesRemaining--; } } diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 99d15592..1995d811 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -63,9 +63,7 @@ public interface Command .put("read", ReadCommand::new) .put("write", WriteCommand::new) .put("rawwrite", RawwriteCommand::new) - .put( - "convert", - stub("convert", "Converts a flux file from one format to another.")) + .put("convert", ConvertCommand::new) .put("rpm", RpmCommand::new) .put("seek", SeekCommand::new) .put("devices", DevicesCommand::new) diff --git a/java/com/cowlark/fluxengine/cli/ConvertCommand.java b/java/com/cowlark/fluxengine/cli/ConvertCommand.java new file mode 100644 index 00000000..9c4712a9 --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/ConvertCommand.java @@ -0,0 +1,91 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; +import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_NOT_SET; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import com.google.common.collect.ImmutableList; + +/** + * Converts a flux file from one format to another, modelled after + * src/fe-convert.cc. + */ +public class ConvertCommand implements Command +{ + private FlagGroup flags = new FlagGroup(); + private ValueFlag sourceFluxFlag = StringFlag.builder() + .setGroup(flags) + .setName("--source") + .setName("-s") + .setHelpText("flux file to read from") + .build(); + private ValueFlag destImageFlag = StringFlag.builder() + .setGroup(flags) + .setName("--dest") + .setName("-d") + .setHelpText("flux file to write to") + .build(); + + @Override + public String getHelp() + { + return "Converts a flux file from one format to another."; + } + + @Override + public void run(ImmutableList args) + { + ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); + if (sourceFluxFlag.isSet()) + builder.withFluxSource(sourceFluxFlag.get()); + if (destImageFlag.isSet()) + builder.withFluxSink(destImageFlag.get()); + ConfigProto config = builder.build(); + + if ((config.getFluxSink().getType() == FLUXTYPE_DRIVE) || + (config.getFluxSource().getType() == FLUXTYPE_DRIVE)) + throw new FluxEngineException("you cannot read or write flux to a hardware device"); + if ((config.getFluxSink().getType() == FLUXTYPE_NOT_SET) || + (config.getFluxSource().getType() == FLUXTYPE_NOT_SET)) + throw new FluxEngineException( + "you must specify both a source and destination flux filename"); + + FluxSource fluxSource = FluxSource.create(config); + + DiskLayout diskLayout = new DiskLayout(config); + int minCylinder = diskLayout.minPhysicalCylinder; + int maxCylinder = diskLayout.maxPhysicalCylinder; + int minHead = diskLayout.minPhysicalHead; + int maxHead = diskLayout.maxPhysicalHead; + Logger.log("CONVERT: seen cylinders %d..%d, heads %d..%d", + minCylinder, + maxCylinder, + minHead, + maxHead); + + FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); + try (FluxSink fluxSink = fluxSinkFactory.create()) + { + for (CylinderHead physicalLocation : diskLayout.physicalLocations) + { + FluxSourceIterator fi = fluxSource.readFlux( + physicalLocation.cylinder(), physicalLocation.head()); + while (fi.hasNext()) + fluxSink.addFlux(physicalLocation, fi.next()); + } + } + } +} From 31217f9c3e9731a08e6848f0197267743f7e883c Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 01:49:39 +0200 Subject: [PATCH 136/192] Adjust the logger API. --- .../cowlark/fluxengine/algorithms/Reader.java | 14 ++--- .../cowlark/fluxengine/algorithms/Writer.java | 25 ++++----- .../fluxengine/cli/ConvertCommand.java | 8 +-- java/com/cowlark/fluxengine/core/Logger.java | 2 +- .../fluxengine/fluxsink/A2RFluxSink.java | 16 +++--- .../fluxengine/fluxsink/AuFluxSink.java | 7 ++- .../fluxengine/fluxsink/Fl2FluxSink.java | 11 ++-- .../fluxengine/fluxsink/ScpFluxSink.java | 31 +++++------ .../imagereader/D64ImageReader.java | 7 ++- .../imagereader/D88ImageReader.java | 36 +++++-------- .../imagereader/DimImageReader.java | 17 +++--- .../imagereader/DiskCopyImageReader.java | 5 +- .../imagereader/FdiImageReader.java | 14 +++-- .../imagereader/ImdImageReader.java | 54 +++++++++---------- .../imagereader/ImgImageReader.java | 22 ++++---- .../imagereader/NfdImageReader.java | 28 +++++----- .../imagereader/NsiImageReader.java | 13 +++-- .../imagereader/Td0ImageReader.java | 11 ++-- .../cowlark/fluxengine/core/LoggerTest.java | 6 +-- 19 files changed, 140 insertions(+), 187 deletions(-) diff --git a/java/com/cowlark/fluxengine/algorithms/Reader.java b/java/com/cowlark/fluxengine/algorithms/Reader.java index b4b69e8a..450614b2 100644 --- a/java/com/cowlark/fluxengine/algorithms/Reader.java +++ b/java/com/cowlark/fluxengine/algorithms/Reader.java @@ -66,9 +66,7 @@ public static void readDiskCommand(ConfigProto config, { Track track = entry.getValue(); tracksByLogicalLocation.computeIfAbsent( - new CylinderHead( - track.ltl.logicalCylinder, - track.ltl.logicalHead), + new CylinderHead(track.ltl.logicalCylinder, track.ltl.logicalHead), k -> new ArrayList<>()).add(track); } @@ -372,9 +370,7 @@ static ReadGroupResult readGroup(DiskLayout diskLayout, Fluxmap fluxmap = fluxSourceIterator.next(); Logger.log(new EndReadOperationLogMessage()); - Logger.log("%d ms in %d bytes", - (int) (fluxmap.duration() / 1e6), - fluxmap.bytes()); + Logger.logf("%d ms in %d bytes", (int) (fluxmap.duration() / 1e6), fluxmap.bytes()); Track flux = decoder.decodeToSectors(fluxmap, ptl); flux.normalisedSectors = collectSectors(flux.allSectors); @@ -425,20 +421,20 @@ private static void readAndDecodeTrack(ConfigProto config, break; if (rgr.result == ReadResult.BAD_AND_CAN_NOT_RETRY) { - Logger.log("no more data; giving up"); + Logger.logf("no more data; giving up"); break; } if (retriesRemaining == 0) { - Logger.log("giving up"); + Logger.logf("giving up"); break; } if (fluxSource.isHardware()) { Common.adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); - Logger.log("retrying; %d retries remaining", retriesRemaining); + Logger.logf("retrying; %d retries remaining", retriesRemaining); retriesRemaining--; } } diff --git a/java/com/cowlark/fluxengine/algorithms/Writer.java b/java/com/cowlark/fluxengine/algorithms/Writer.java index 79b88991..c535d2ba 100644 --- a/java/com/cowlark/fluxengine/algorithms/Writer.java +++ b/java/com/cowlark/fluxengine/algorithms/Writer.java @@ -75,7 +75,8 @@ private static void writeTracks(ConfigProto config, else { fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); - Logger.log("writing %d ms in %d bytes", + Logger.logf( + "writing %d ms in %d bytes", (int) (fluxmap.duration() / 1e6), fluxmap.bytes()); } @@ -88,7 +89,7 @@ private static void writeTracks(ConfigProto config, Fluxmap blank = new Fluxmap(); fluxSink.addFlux(physicalCylinder, physicalHead, blank); - Logger.log("erased"); + Logger.logf("erased"); } Logger.log(new LogMessage.EndWriteOperationLogMessage()); @@ -100,7 +101,7 @@ private static void writeTracks(ConfigProto config, if (retriesRemaining == 0) throw new FluxEngineException("fatal error on write"); - Logger.log("retrying; %d retries remaining", retriesRemaining); + Logger.logf("retrying; %d retries remaining", retriesRemaining); retriesRemaining--; } } @@ -151,7 +152,7 @@ private static void writeTracksAndVerify(ConfigProto config, if (rgr.result != Reader.ReadResult.GOOD_READ) { Common.adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); - Logger.log("bad read"); + Logger.logf("bad read"); return false; } @@ -170,12 +171,12 @@ private static void writeTracksAndVerify(ConfigProto config, sector.location.logicalSector()); if (s == null) { - Logger.log("spurious sector on verify"); + Logger.logf("spurious sector on verify"); return false; } if (!s.data.equals(sector.data.slice(0, s.data.size()))) { - Logger.log("data mismatch on verify"); + Logger.logf("data mismatch on verify"); return false; } wanted.erase( @@ -185,7 +186,7 @@ private static void writeTracksAndVerify(ConfigProto config, } if (!wanted.empty()) { - Logger.log("missing sector on verify"); + Logger.logf("missing sector on verify"); return false; } return true; @@ -241,18 +242,12 @@ public static void writeRawDiskCommand(ConfigProto config, FluxSinkFactory fluxSinkFactory) { writeTracks( - config, - diskLayout, - fluxSinkFactory, - ltl -> - { + config, diskLayout, fluxSinkFactory, ltl -> { FluxSourceIterator iterator = fluxSource.readFlux(ltl.physicalCylinder, ltl.physicalHead); if (!iterator.hasNext()) return null; return iterator.next(); - }, - ltl -> true, - diskLayout.logicalLocations); + }, ltl -> true, diskLayout.logicalLocations); } } diff --git a/java/com/cowlark/fluxengine/cli/ConvertCommand.java b/java/com/cowlark/fluxengine/cli/ConvertCommand.java index 9c4712a9..d4c449eb 100644 --- a/java/com/cowlark/fluxengine/cli/ConvertCommand.java +++ b/java/com/cowlark/fluxengine/cli/ConvertCommand.java @@ -12,7 +12,6 @@ import com.cowlark.fluxengine.core.flags.ValueFlag; import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.data.DiskLayout; -import com.cowlark.fluxengine.data.Fluxmap; import com.cowlark.fluxengine.fluxsink.FluxSink; import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; import com.cowlark.fluxengine.fluxsource.FluxSource; @@ -70,7 +69,8 @@ public void run(ImmutableList args) int maxCylinder = diskLayout.maxPhysicalCylinder; int minHead = diskLayout.minPhysicalHead; int maxHead = diskLayout.maxPhysicalHead; - Logger.log("CONVERT: seen cylinders %d..%d, heads %d..%d", + Logger.logf( + "CONVERT: seen cylinders %d..%d, heads %d..%d", minCylinder, maxCylinder, minHead, @@ -81,8 +81,8 @@ public void run(ImmutableList args) { for (CylinderHead physicalLocation : diskLayout.physicalLocations) { - FluxSourceIterator fi = fluxSource.readFlux( - physicalLocation.cylinder(), physicalLocation.head()); + FluxSourceIterator fi = + fluxSource.readFlux(physicalLocation.cylinder(), physicalLocation.head()); while (fi.hasNext()) fluxSink.addFlux(physicalLocation, fi.next()); } diff --git a/java/com/cowlark/fluxengine/core/Logger.java b/java/com/cowlark/fluxengine/core/Logger.java index c8dbf939..388f6965 100644 --- a/java/com/cowlark/fluxengine/core/Logger.java +++ b/java/com/cowlark/fluxengine/core/Logger.java @@ -15,7 +15,7 @@ private Logger() { } - public static void log(String message, Object... args) + public static void logf(String message, Object... args) { log(new StringMessage(String.format(message, args))); } diff --git a/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java index bc6d0d1f..0c7c4e67 100644 --- a/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java @@ -54,9 +54,9 @@ public A2RFluxSink(String filename, ConfigProto config) { this.filename = filename; this.config = config; - metadata.put("image_date", - DateTimeFormatter.ISO_INSTANT.format( - ZonedDateTime.now(ZoneOffset.UTC))); + metadata.put( + "image_date", + DateTimeFormatter.ISO_INSTANT.format(ZonedDateTime.now(ZoneOffset.UTC))); } private void writeChunkAndData(int chunkId, Bytes data) @@ -86,10 +86,9 @@ private void writeInfo() infoWriter.write8(A2R.INFO_CHUNK_VERSION); infoWriter.write(VERSION_STRING.getBytes()); - infoWriter.write8( - (config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) - ? A2R.DISK_525 - : A2R.DISK_35); + infoWriter.write8((config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) ? + A2R.DISK_525 : + A2R.DISK_35); infoWriter.write8(1); /* write protected */ infoWriter.write8(1); /* synchronized */ @@ -217,8 +216,7 @@ public void close() minHead = diskLayout.minPhysicalHead; maxHead = diskLayout.maxPhysicalHead; - Logger.log("A2R: writing A2R " + - ((minHead == maxHead) ? "single sided" : "double sided") + + Logger.logf("A2R: writing A2R " + ((minHead == maxHead) ? "single sided" : "double sided") + " file containing " + (maxCylinder - minCylinder + 1) + " tracks..."); writeHeader(); diff --git a/java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java index 64b6ee2e..43e4edcf 100644 --- a/java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/AuFluxSink.java @@ -32,8 +32,8 @@ public AuFluxSink(String directory, boolean indexMarkers) @Override public void addFlux(int track, int head, Fluxmap fluxmap) { - Logger.log("Warning: do not play these files, or you will break your " - + "speakers and/or ears!"); + Logger.logf("Warning: do not play these files, or you will break your " + + "speakers and/or ears!"); int totalTicks = fluxmap.ticks() + 2; int channels = indexMarkers ? 2 : 1; @@ -71,8 +71,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) .writeBe32(0x2e736e64) .writeBe32(24) .writeBe32(totalTicks * channels) - .writeBe32(2) /* 8-bit PCM */ - .writeBe32(TICK_FREQUENCY) + .writeBe32(2) /* 8-bit PCM */.writeBe32(TICK_FREQUENCY) .writeBe32(channels); /* channels */ String filename = String.format("%s/c%02d.h%01d.au", directory, track, head); diff --git a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java index 0197047d..4ac452f9 100644 --- a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java @@ -48,14 +48,13 @@ public Fl2FluxSink(String filename, ConfigProto config) @Override public void addFlux(int track, int head, Fluxmap fluxmap) { - data.computeIfAbsent(Pair.of(track, head), k -> new ArrayList<>()) - .add(fluxmap.rawBytes()); + data.computeIfAbsent(Pair.of(track, head), k -> new ArrayList<>()).add(fluxmap.rawBytes()); } @Override public void close() { - Logger.log("FL2: writing " + filename); + Logger.logf("FL2: writing " + filename); FluxFileProto.Builder proto = FluxFileProto.newBuilder(); for (Map.Entry, List> e : data.entrySet()) @@ -68,8 +67,7 @@ public void close() proto.addTrack(track); } - proto.setRotationalPeriodMs( - config.getDrive().getRotationalPeriodMs()); + proto.setRotationalPeriodMs(config.getDrive().getRotationalPeriodMs()); proto.setDriveType(config.getDrive().getDriveType()); proto.setFormatType(config.getLayout().getFormatType()); @@ -86,8 +84,7 @@ private static void saveFl2File(String filename, FluxFileProto.Builder proto) Files.write(Path.of(filename), proto.build().toByteArray()); } catch (IOException e) { - throw new FluxEngineException( - "unable to write output file '" + filename + "'"); + throw new FluxEngineException("unable to write output file '" + filename + "'"); } } } diff --git a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java index 437536da..7c2f1ba3 100644 --- a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java @@ -79,8 +79,7 @@ public ScpFluxSink(String filename, int typeByte, boolean alignWithIndex, Config fileheader[7] = (byte) strackno(maxCylinder, maxHead); int flags = Scp.SCP_FLAG_INDEXED; if (config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) - throw new FluxEngineException( - "you can't write Apple II flux images to SCP files yet"); + throw new FluxEngineException("you can't write Apple II flux images to SCP files yet"); if (config.getDrive().getDriveType() != DriveType.DRIVETYPE_40TRACK) flags |= Scp.SCP_FLAG_96TPI; fileheader[8] = (byte) flags; @@ -92,9 +91,9 @@ else if ((minHead == 1) && (maxHead == 1)) else fileheader[10] = 0; - Logger.log("SCP: writing " + (((flags & Scp.SCP_FLAG_96TPI) != 0) ? 96 : 48) + - " tpi " + ((minHead == maxHead) ? "single sided" : "double sided") + - " file containing " + (fileheader[7] - fileheader[6] + 1) + " tracks"); + Logger.logf("SCP: writing " + (((flags & Scp.SCP_FLAG_96TPI) != 0) ? 96 : 48) + " tpi " + + ((minHead == maxHead) ? "single sided" : "double sided") + " file containing " + + (fileheader[7] - fileheader[6] + 1) + " tracks"); } @Override @@ -106,7 +105,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) if (strack >= 168) { - Logger.log("SCP: cannot write track " + track + " head " + head + + Logger.logf("SCP: cannot write track " + track + " head " + head + ", there are not enough Track Data Headers."); return; } @@ -121,8 +120,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) Bytes fluxdata = new Bytes(0); ByteWriter fluxdataWriter = fluxdata.writer(); - int revolution = - -1; /* -1 indicates that we are before the first index pulse */ + int revolution = -1; /* -1 indicates that we are before the first index pulse */ if (alignWithIndex) { fmr.skipToEvent(F_BIT_INDEX); @@ -152,12 +150,9 @@ public void addFlux(int track, int head, Fluxmap fluxmap) if (revolution >= 0) { int revOffset = 4 + revolution * 12; - writeLe32(trackHeader, revOffset + 8, - startOffset + Scp.SCP_TRACK_SIZE); - writeLe32(trackHeader, revOffset + 4, - (fluxdataWriter.pos() - startOffset) / 2); - writeLe32(trackHeader, revOffset, - (int) (revTicks * NS_PER_TICK / 25)); + writeLe32(trackHeader, revOffset + 8, startOffset + Scp.SCP_TRACK_SIZE); + writeLe32(trackHeader, revOffset + 4, (fluxdataWriter.pos() - startOffset) / 2); + writeLe32(trackHeader, revOffset, (int) (revTicks * NS_PER_TICK / 25)); } revolution++; revTicks = 0; @@ -180,8 +175,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) } fileheader[5] = (byte) revolution; - writeLe32(fileheader, 16 + strack * 4, - trackdataWriter.pos() + Scp.SCP_HEADER_SIZE); + writeLe32(fileheader, 16 + strack * 4, trackdataWriter.pos() + Scp.SCP_HEADER_SIZE); trackdataWriter.write(trackHeader); trackdataWriter.write(fluxdata); } @@ -190,12 +184,13 @@ public void addFlux(int track, int head, Fluxmap fluxmap) public void close() { int checksum = 0; - checksum = appendChecksum(checksum, + checksum = appendChecksum( + checksum, new Bytes(java.util.Arrays.copyOfRange(fileheader, 0x10, fileheader.length))); checksum = appendChecksum(checksum, trackdata); writeLe32(fileheader, 12, checksum); - Logger.log("SCP: writing output file"); + Logger.logf("SCP: writing output file"); Bytes out = new Bytes(fileheader).concat(trackdata); try { diff --git a/java/com/cowlark/fluxengine/imagereader/D64ImageReader.java b/java/com/cowlark/fluxengine/imagereader/D64ImageReader.java index d93ded46..ec89e977 100644 --- a/java/com/cowlark/fluxengine/imagereader/D64ImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/D64ImageReader.java @@ -47,7 +47,7 @@ public Image readImage() int numCylinders = 39; int numHeads = 1; - Logger.log("D64: reading image with " + numCylinders + " tracks, " + numHeads + " heads"); + Logger.logf("D64: reading image with " + numCylinders + " tracks, " + numHeads + " heads"); int offset = 0; @@ -65,10 +65,9 @@ public Image readImage() sector.status = Sector.Status.OK; sector.data = data.slice(offset, 256); offset += 256; - } - else + } else { /* no more data in input file. Write sectors with status: - * DATA_MISSING */ + * DATA_MISSING */ sector.status = Sector.Status.DATA_MISSING; } } diff --git a/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java b/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java index 7491b492..063c0985 100644 --- a/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java @@ -8,7 +8,6 @@ import com.cowlark.fluxengine.data.Geometry; import com.cowlark.fluxengine.data.Image; import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.encoders.EncoderProto; import com.cowlark.fluxengine.ibm.IbmEncoderProto; import com.cowlark.fluxengine.external.FormatType; import java.io.IOException; @@ -43,7 +42,7 @@ public Image readImage() String diskName = header.slice(0, 0x16).toString(); if (diskName.length() > 0 && diskName.charAt(0) != 0) - Logger.log("D88: disk name: " + diskName); + Logger.logf("D88: disk name: " + diskName); ByteReader headerReader = new ByteReader(header); @@ -53,7 +52,7 @@ public Image readImage() int diskSize = headerReader.seek(0x1c).readLe32(); if (diskSize > fileSize) - Logger.log("D88: found multiple disk images. Only using first"); + Logger.logf("D88: found multiple disk images. Only using first"); int trackTableEnd = headerReader.seek(0x20).readLe32(); int trackTableSize = trackTableEnd - 0x20; @@ -67,8 +66,7 @@ public Image readImage() { extra.getDriveBuilder().setHighDensity(true); extra.getLayoutBuilder().setFormatType(FormatType.FORMATTYPE_80TRACK); - } - else + } else { clockRate = 300; extra.getDriveBuilder().setHighDensity(false); @@ -91,8 +89,7 @@ public Image readImage() int trackSectorSize = -1; int trackMfm = -1; - IbmEncoderProto.TrackdataProto.Builder trackdata = - ibm.addTrackdataBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = ibm.addTrackdataBuilder(); trackdata.setTargetClockPeriodUs(1e3 / clockRate); trackdata.setTargetRotationalPeriodMs(167); @@ -123,24 +120,22 @@ public Image readImage() if (ddam != 0) throw new FluxEngineException("D88: nonzero ddam currently unsupported"); if (rpm != 0) - throw new FluxEngineException( - "D88: 1.44MB 300rpm formats currently unsupported"); + throw new FluxEngineException("D88: 1.44MB 300rpm formats currently " + + "unsupported"); if (fddStatusCode != 0) throw new FluxEngineException( "D88: nonzero fdd status codes are currently unsupported"); if (currentSectorsInTrack == 0xffff) { currentSectorsInTrack = sectorsInTrack; - } - else if (currentSectorsInTrack != sectorsInTrack) + } else if (currentSectorsInTrack != sectorsInTrack) { throw new FluxEngineException("D88: mismatched number of sectors in track"); } if (currentTrackTrack < 0) { currentTrackTrack = cyl; - } - else if (currentTrackTrack != cyl) + } else if (currentTrackTrack != cyl) { throw new FluxEngineException( "D88: all sectors in a track must belong to the same track"); @@ -173,23 +168,20 @@ else if (currentTrackTrack != cyl) trackdata.setGap2(0x14); trackdata.setGap3(0x1b); } - } - else + } else { if (sectorSize <= 128) { trackdata.setGap0(0x1b); trackdata.setGap2(0x09); trackdata.setGap3(0x1b); - } - else if (sectorSize <= 256) + } else if (sectorSize <= 256) { trackdata.setGap0(0x36); trackdata.setGap3(0x36); } } - } - else if (trackSectorSize != sectorSize) + } else if (trackSectorSize != sectorSize) { throw new FluxEngineException( "D88: multiple sector sizes per track are currently unsupported"); @@ -205,8 +197,7 @@ else if (trackSectorSize != sectorSize) if (mediaFlag != 0x20) { - IbmEncoderProto.TrackdataProto.Builder trackdata2 = - ibm.addTrackdataBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata2 = ibm.addTrackdataBuilder(); trackdata2.setTargetClockPeriodUs(1e3 / clockRate); trackdata2.setTargetRotationalPeriodMs(167); } @@ -214,7 +205,8 @@ else if (trackSectorSize != sectorSize) image.calculateSize(); Geometry geometry = image.getGeometry(); - Logger.log("D88: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides"); + Logger.logf( + "D88: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides"); layout.setTracks(geometry.numCylinders); layout.setSides(geometry.numHeads); diff --git a/java/com/cowlark/fluxengine/imagereader/DimImageReader.java b/java/com/cowlark/fluxengine/imagereader/DimImageReader.java index fef53819..25ab19eb 100644 --- a/java/com/cowlark/fluxengine/imagereader/DimImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/DimImageReader.java @@ -101,8 +101,7 @@ public Image readImage() if (fullConfig.getEncoder().getFormatCase() == EncoderProto.FormatCase.FORMAT_NOT_SET) { IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); - IbmEncoderProto.TrackdataProto.Builder trackdata = - ibm.addTrackdataBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = ibm.addTrackdataBuilder(); trackdata.setTargetClockPeriodUs(2); com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = @@ -112,22 +111,21 @@ public Image readImage() switch (mediaByte) { case 0x00: - Logger.log( - "DIM: automatically setting format to 1.2MB (1024 byte sectors)"); + Logger.logf("DIM: automatically setting format to 1.2MB (1024 byte sectors)"); trackdata.setTargetRotationalPeriodMs(167); layoutdata.setSectorSize(1024); for (int i = 0; i < 9; i++) physical.addSector(i); break; case 0x02: - Logger.log("DIM: automatically setting format to 1.2MB (512 byte sectors)"); + Logger.logf("DIM: automatically setting format to 1.2MB (512 byte sectors)"); trackdata.setTargetRotationalPeriodMs(167); layoutdata.setSectorSize(512); for (int i = 0; i < 15; i++) physical.addSector(i); break; case 0x03: - Logger.log("DIM: automatically setting format to 1.44MB"); + Logger.logf("DIM: automatically setting format to 1.44MB"); trackdata.setTargetRotationalPeriodMs(200); layoutdata.setSectorSize(512); for (int i = 0; i < 18; i++) @@ -135,9 +133,8 @@ public Image readImage() break; default: throw new FluxEngineException(String.format( - "DIM: unknown media byte 0x%02x, could not determine write " - + "profile automatically", - mediaByte)); + "DIM: unknown media byte 0x%02x, could not determine write " + + "profile automatically", mediaByte)); } extra.getDecoderBuilder().getIbmBuilder(); @@ -145,7 +142,7 @@ public Image readImage() image.calculateSize(); Geometry geometry = image.getGeometry(); - Logger.log("DIM: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + + Logger.logf("DIM: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides, " + (data.size() - 256) / 1024 + " kB total"); layout.setTracks(geometry.numCylinders); diff --git a/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java b/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java index d1d3e12b..17351532 100644 --- a/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java @@ -75,8 +75,9 @@ public Image readImage() "don't understand DiskCopy disks of type " + encoding); } - Logger.log("DC42: reading image with " + numCylinders + " tracks, " + - numHeads + " heads; " + (mfm ? "MFM" : "GCR") + "; " + label); + Logger.logf( + "DC42: reading image with " + numCylinders + " tracks, " + numHeads + " heads; " + + (mfm ? "MFM" : "GCR") + "; " + label); int dataPtr = 0x54; int tagPtr = dataPtr + dataSize; diff --git a/java/com/cowlark/fluxengine/imagereader/FdiImageReader.java b/java/com/cowlark/fluxengine/imagereader/FdiImageReader.java index 820458b3..9c1f2cc9 100644 --- a/java/com/cowlark/fluxengine/imagereader/FdiImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/FdiImageReader.java @@ -78,8 +78,7 @@ public Image readImage() if (fullConfig.getEncoder().getFormatCase() == EncoderProto.FormatCase.FORMAT_NOT_SET) { IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); - IbmEncoderProto.TrackdataProto.Builder trackdata = - ibm.addTrackdataBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = ibm.addTrackdataBuilder(); trackdata.setTargetClockPeriodUs(2); com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = @@ -89,7 +88,7 @@ public Image readImage() switch (fddType) { case 0x90: - Logger.log("FDI: automatically setting format to 1.2MB (1024 byte sectors)"); + Logger.logf("FDI: automatically setting format to 1.2MB (1024 byte sectors)"); trackdata.setTargetRotationalPeriodMs(167); layoutdata.setSectorSize(1024); for (int i = 0; i < 9; i++) @@ -97,7 +96,7 @@ public Image readImage() break; case 0x30: - Logger.log("FDI: automatically setting format to 1.44MB"); + Logger.logf("FDI: automatically setting format to 1.44MB"); trackdata.setTargetRotationalPeriodMs(200); layoutdata.setSectorSize(512); for (int i = 0; i < 18; i++) @@ -106,15 +105,14 @@ public Image readImage() default: throw new FluxEngineException(String.format( - "FDI: unknown fdd type 0x%02x, could not determine write " - + "profile automatically", - fddType)); + "FDI: unknown fdd type 0x%02x, could not determine write " + + "profile automatically", fddType)); } } image.calculateSize(); Geometry geometry = image.getGeometry(); - Logger.log("FDI: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + + Logger.logf("FDI: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides, " + (data.size() - headerSize) / 1024 + " kB total"); layout.setTracks(geometry.numCylinders); diff --git a/java/com/cowlark/fluxengine/imagereader/ImdImageReader.java b/java/com/cowlark/fluxengine/imagereader/ImdImageReader.java index 5a25b38d..1df8600b 100644 --- a/java/com/cowlark/fluxengine/imagereader/ImdImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/ImdImageReader.java @@ -52,8 +52,7 @@ private static int getModulationAndSpeed(int flags, boolean[] fm) return 250; default: throw new FluxEngineException( - "IMD: don't understand IMD disks with this modulation and speed " + - flags); + "IMD: don't understand IMD disks with this modulation and speed " + flags); } } @@ -118,7 +117,7 @@ public Image readImage() n++; } headerPtr = n; /* set pointer to after comment */ - Logger.log("Comment in IMD file: " + comment); + Logger.logf("Comment in IMD file: " + comment); boolean[] fm = {false}; int trackSectorSize = -1; @@ -193,8 +192,7 @@ public Image readImage() } IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); - IbmEncoderProto.TrackdataProto.Builder trackdata = - ibm.addTrackdataBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = ibm.addTrackdataBuilder(); com.cowlark.fluxengine.config.LayoutProto.LayoutdataProto.Builder layoutdata = layout.addLayoutdataBuilder(); @@ -213,8 +211,7 @@ public Image readImage() layoutdata.setTrack(track); layoutdata.setSide(head); layoutdata.setSectorSize(sectorSize); - } - else if (trackSectorSize != sectorSize) + } else if (trackSectorSize != sectorSize) { throw new FluxEngineException( "IMD: multiple sector sizes per track are currently unsupported"); @@ -315,51 +312,52 @@ else if (trackSectorSize != sectorSize) default: throw new FluxEngineException(String.format( - "IMD: Don't understand IMD files with sector status %d, " - + "track %d, sector %d", - statusSector, - track, - s)); + "IMD: Don't understand IMD files with sector status %d, " + + "track %d, sector %d", statusSector, track, s)); } if (blnOptionalCylinderMap) { sector.location = new com.cowlark.fluxengine.data.LogicalLocation( - optionalsectorMap.get(s), sector.location.logicalHead(), + optionalsectorMap.get(s), + sector.location.logicalHead(), sector.location.logicalSector()); blnOptionalCylinderMap = false; - } - else + } else sector.location = new com.cowlark.fluxengine.data.LogicalLocation( - track, sector.location.logicalHead(), + track, + sector.location.logicalHead(), sector.location.logicalSector()); if (blnOptionalHeadMap) { - sector.location = new com.cowlark.fluxengine.data.LogicalLocation( - sector.location.logicalCylinder(), optionalheadMap.get(s), - sector.location.logicalSector()); + sector.location = + new com.cowlark.fluxengine.data.LogicalLocation( + sector.location.logicalCylinder(), + optionalheadMap.get(s), + sector.location.logicalSector()); blnOptionalHeadMap = false; - } - else - sector.location = new com.cowlark.fluxengine.data.LogicalLocation( - sector.location.logicalCylinder(), head, - sector.location.logicalSector()); + } else + sector.location = + new com.cowlark.fluxengine.data.LogicalLocation( + sector.location.logicalCylinder(), + head, + sector.location.logicalSector()); } } if (extra.getEncoder().getFormatCase() != EncoderProto.FormatCase.FORMAT_NOT_SET) - Logger.log("IMD: overriding configured format"); + Logger.logf("IMD: overriding configured format"); image.calculateSize(); Geometry geometry = image.getGeometry(); int headSize = numSectors * sectorSize; int trackSize = headSize * (head + 1); - Logger.log("IMD: read " + (track + 1) + " tracks, " + (head + 1) + " heads; " + + Logger.logf("IMD: read " + (track + 1) + " tracks, " + (head + 1) + " heads; " + (fm[0] ? "FM" : "MFM") + "; " + modulationSpeed + " kbps; " + numSectors + - " sectors; sectorsize " + sectorSize + "; " + - (track + 1) * trackSize / 1024 + " kB total."); + " sectors; sectorsize " + sectorSize + "; " + (track + 1) * trackSize / 1024 + + " kB total."); layout.setTracks(geometry.numCylinders); layout.setSides(geometry.numHeads); diff --git a/java/com/cowlark/fluxengine/imagereader/ImgImageReader.java b/java/com/cowlark/fluxengine/imagereader/ImgImageReader.java index 7d290952..0fc02014 100644 --- a/java/com/cowlark/fluxengine/imagereader/ImgImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/ImgImageReader.java @@ -32,9 +32,8 @@ public Image readImage() { LayoutProto layout = fullConfig.getLayout(); if (!layout.hasTracks() || !layout.hasSides()) - throw new FluxEngineException( - "IMG: bad configuration; did you remember to set the " - + "tracks, sides and trackdata fields in the layout?"); + throw new FluxEngineException("IMG: bad configuration; did you remember to set the " + + "tracks, sides and trackdata fields in the layout?"); DiskLayout diskLayout = new DiskLayout(fullConfig); boolean inFilesystemOrder = config.getImg().getFilesystemSectorOrder(); @@ -47,12 +46,10 @@ public Image readImage() diskLayout.logicalLocations; for (CylinderHead logicalLocation : locations) { - LogicalTrackLayout ltl = - diskLayout.layoutByLogicalLocation.get(logicalLocation); + LogicalTrackLayout ltl = diskLayout.layoutByLogicalLocation.get(logicalLocation); - Iterable sectorOrder = inFilesystemOrder ? - ltl.filesystemSectorOrder : - ltl.naturalSectorOrder; + Iterable sectorOrder = + inFilesystemOrder ? ltl.filesystemSectorOrder : ltl.naturalSectorOrder; for (int sectorId : sectorOrder) { byte[] buf = new byte[ltl.sectorSize]; @@ -60,8 +57,8 @@ public Image readImage() if (read == -1) break; - Sector sector = image.put( - logicalLocation.cylinder(), logicalLocation.head(), sectorId); + Sector sector = + image.put(logicalLocation.cylinder(), logicalLocation.head(), sectorId); sector.status = Sector.Status.OK; sector.data = new Bytes(buf); } @@ -73,9 +70,8 @@ public Image readImage() image.calculateSize(); Geometry geometry = image.getGeometry(); - Logger.log("IMG: read " + geometry.numCylinders + " tracks, " + - geometry.numHeads + " sides, " + geometry.totalBytes / 1024 + - " kB total from " + config.getFilename()); + Logger.logf("IMG: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + + " sides, " + geometry.totalBytes / 1024 + " kB total from " + config.getFilename()); return image; } } diff --git a/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java b/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java index 54ad44fd..2a063467 100644 --- a/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java @@ -8,7 +8,6 @@ import com.cowlark.fluxengine.data.Geometry; import com.cowlark.fluxengine.data.Image; import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.encoders.EncoderProto; import com.cowlark.fluxengine.ibm.IbmEncoderProto; import com.cowlark.fluxengine.external.FormatType; import java.io.IOException; @@ -58,8 +57,8 @@ public Image readImage() ConfigProto.Builder extra = ConfigProto.newBuilder(); IbmEncoderProto.Builder ibm = extra.getEncoderBuilder().getIbmBuilder(); com.cowlark.fluxengine.config.LayoutProto.Builder layout = extra.getLayoutBuilder(); - Logger.log("NFD: HD 1.2MB mode"); - Logger.log("NFD: forcing high density mode"); + Logger.logf("NFD: HD 1.2MB mode"); + Logger.logf("NFD: forcing high density mode"); extra.getDriveBuilder().setHighDensity(true); extra.getLayoutBuilder().setFormatType(FormatType.FORMATTYPE_80TRACK); @@ -68,8 +67,7 @@ public Image readImage() br.seek(0x10a10); for (int track = 0; track < 163; track++) { - IbmEncoderProto.TrackdataProto.Builder trackdata = - ibm.addTrackdataBuilder(); + IbmEncoderProto.TrackdataProto.Builder trackdata = ibm.addTrackdataBuilder(); trackdata.setTargetClockPeriodUs(2); trackdata.setTargetRotationalPeriodMs(167); @@ -83,8 +81,9 @@ public Image readImage() for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++) { - ByteReader sectorHeaderReader = - new ByteReader(data.slice(0x120 + track * 26 * 16 + sectorInTrack * 16, 16)); + ByteReader sectorHeaderReader = new ByteReader(data.slice( + 0x120 + track * 26 * 16 + sectorInTrack * 16, + 16)); int cyl = sectorHeaderReader.seek(0).read8(); int head = sectorHeaderReader.seek(1).read8(); int sectorId = sectorHeaderReader.seek(2).read8(); @@ -104,13 +103,11 @@ public Image readImage() { currentTrackTrack = cyl; currentTrackHead = head; - } - else if (currentTrackTrack != cyl) + } else if (currentTrackTrack != cyl) { throw new FluxEngineException( "NFD: all sectors in a track must belong to the same track"); - } - else if (currentTrackHead != head) + } else if (currentTrackHead != head) { throw new FluxEngineException( "NFD: all sectors in a track must belong to the same head"); @@ -138,14 +135,12 @@ else if (currentTrackHead != head) trackdata.setGap0(0x1b); trackdata.setGap2(0x09); trackdata.setGap3(0x1b); - } - else if (sectorSize <= 256) + } else if (sectorSize <= 256) { trackdata.setGap0(0x36); trackdata.setGap3(0x36); } - } - else if (trackSectorSize != sectorSize) + } else if (trackSectorSize != sectorSize) { throw new FluxEngineException( "NFD: multiple sector sizes per track are currently unsupported"); @@ -160,7 +155,8 @@ else if (trackSectorSize != sectorSize) image.calculateSize(); Geometry geometry = image.getGeometry(); - Logger.log("NFD: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides"); + Logger.logf( + "NFD: read " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides"); extraConfig = extra.build(); return image; diff --git a/java/com/cowlark/fluxengine/imagereader/NsiImageReader.java b/java/com/cowlark/fluxengine/imagereader/NsiImageReader.java index 3f48c47e..3a1936e6 100644 --- a/java/com/cowlark/fluxengine/imagereader/NsiImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/NsiImageReader.java @@ -32,7 +32,7 @@ public Image readImage() } int fsize = data.size(); - Logger.log("NSI: Autodetecting geometry based on file size: " + fsize); + Logger.logf("NSI: Autodetecting geometry based on file size: " + fsize); int numCylinders = 35; int numSectors = 10; @@ -62,8 +62,8 @@ public Image readImage() int trackSize = numSectors * sectorSize; - Logger.log("reading " + numCylinders + " tracks, " + numHeads + " heads, " + - numSectors + " sectors, " + sectorSize + " bytes per sector, " + + Logger.logf("reading " + numCylinders + " tracks, " + numHeads + " heads, " + numSectors + + " sectors, " + sectorSize + " bytes per sector, " + numCylinders * numHeads * trackSize / 1024 + " kB total"); Image image = new Image(); @@ -79,12 +79,11 @@ public Image readImage() if (head == 0) { /* Head 0 is from track 0-34 */ sectorFileOffset = track * trackSize + sectorId * sectorSize; - } - else + } else { /* Head 1 is from track 70-35 */ sectorFileOffset = (trackSize * numCylinders) + /* Skip over side 0 */ - ((numCylinders - track - 1) * trackSize) + - (sectorId * sectorSize); /* Sector offset from beginning of track. */ + ((numCylinders - track - 1) * trackSize) + (sectorId * + sectorSize); /* Sector offset from beginning of track. */ } br.seek(sectorFileOffset); diff --git a/java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java b/java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java index c3ac9946..507f4518 100644 --- a/java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/Td0ImageReader.java @@ -10,7 +10,6 @@ import com.cowlark.fluxengine.data.Sector; import com.cowlark.fluxengine.external.Crc; import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -84,7 +83,7 @@ public Image readImage() comment = comment.substring(0, end); } - Logger.log("TD0: TeleDisk " + version / 10 + "." + version % 10 + ": " + comment); + Logger.logf("TD0: TeleDisk " + version / 10 + "." + version % 10 + ": " + comment); int totalSize = 0; Image image = new Image(); @@ -153,8 +152,7 @@ public Image readImage() length = bre.read8(); bw.write(bre.read(length)); - } - else + } else { /* Repeated block */ @@ -171,8 +169,7 @@ public Image readImage() data = new Bytes(0); break; } - } - else + } else data = new Bytes(0); Sector sector = image.put(logicalCylinder, logicalHead, sectorId); @@ -184,7 +181,7 @@ public Image readImage() image.calculateSize(); Geometry geometry = image.getGeometry(); - Logger.log("TD0: found " + geometry.numCylinders + " tracks, " + geometry.numHeads + + Logger.logf("TD0: found " + geometry.numCylinders + " tracks, " + geometry.numHeads + " sides, " + geometry.numSectors + " sectors, " + geometry.sectorSize + " bytes per sector, " + totalSize / 1024 + " kB total"); return image; diff --git a/javatests/com/cowlark/fluxengine/core/LoggerTest.java b/javatests/com/cowlark/fluxengine/core/LoggerTest.java index f7213e6c..1571c160 100644 --- a/javatests/com/cowlark/fluxengine/core/LoggerTest.java +++ b/javatests/com/cowlark/fluxengine/core/LoggerTest.java @@ -17,12 +17,12 @@ public class LoggerTest { @Test - public void logStringWrapsInStringMessage() + public void logfStringWrapsInStringMessage() { List messages = new ArrayList<>(); Logger.setLogger(messages::add); - Logger.log("hello"); + Logger.logf("hello"); assertThat(messages).containsExactly(new StringMessage("hello")); } @@ -58,7 +58,7 @@ public void logUsesSetLogger() List messages = new ArrayList<>(); Logger.setLogger(messages::add); - Logger.log("one"); + Logger.logf("one"); Logger.log(new StringMessage("two")); assertThat(messages).hasSize(2); From 521f53177dbf5c5e214254c12b4a4461e6705cad Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 12:33:02 +0200 Subject: [PATCH 137/192] Port the FluxEngine USB stuff. --- .../fluxengine/usb/FluxEngineUsbDevice.java | 475 ++++++++++++++++++ .../cowlark/fluxengine/usb/UsbFactory.java | 1 + 2 files changed, 476 insertions(+) create mode 100644 java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java diff --git a/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java b/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java new file mode 100644 index 00000000..74223aad --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java @@ -0,0 +1,475 @@ +package com.cowlark.fluxengine.usb; + +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_DEBUG; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERROR; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_GET_VERSION_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_GET_VERSION_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_MEASURE_SPEED_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_MEASURE_SPEED_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_MEASURE_VOLTAGES_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_MEASURE_VOLTAGES_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_RECALIBRATE_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_RECALIBRATE_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SEEK_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SEEK_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_READ_TEST_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_READ_TEST_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_WRITE_TEST_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_WRITE_TEST_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_READ_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_READ_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_WRITE_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_WRITE_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERASE_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERASE_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SET_DRIVE_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SET_DRIVE_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_ERROR_BAD_COMMAND; +import static com.cowlark.fluxengine.external.FluxEngine.F_ERROR_UNDERRUN; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_CMD_IN_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_CMD_OUT_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_DATA_IN_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_DATA_OUT_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_PROTOCOL_VERSION; +import static com.cowlark.fluxengine.external.FluxEngine.FRAME_SIZE; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.FluxEngineException; +import java.util.ArrayList; +import java.util.List; +import javax.usb.UsbConfiguration; +import javax.usb.UsbConst; + +import javax.usb.UsbEndpoint; +import javax.usb.UsbException; +import javax.usb.UsbInterface; +import javax.usb.UsbPipe; + +/** + * FluxEngine floppy drive device, ported from lib/usb/fluxengineusb.cc. + */ +class FluxEngineUsbDevice extends UsbDevice +{ + private static final int MAX_TRANSFER = 32 * 1024; + + private final javax.usb.UsbDevice device; + private final UsbInterface usbInterface; + private final UsbPipe cmdOut; + private final UsbPipe cmdIn; + private final UsbPipe dataOut; + private final UsbPipe dataIn; + private final byte[] buffer = new byte[FRAME_SIZE]; + + FluxEngineUsbDevice(javax.usb.UsbDevice device) + { + this.device = device; + + UsbInterface iface = null; + try + { + for (Object o : device.getUsbConfigurations()) + { + UsbConfiguration config = (UsbConfiguration) o; + for (Object i : config.getUsbInterfaces()) + { + UsbInterface candidate = (UsbInterface) i; + if (candidate.getUsbEndpoints().size() >= 4) + iface = candidate; + } + } + if (iface == null) + throw new FluxEngineException("FluxEngine: no suitable USB interface found"); + + iface.claim(); + usbInterface = iface; + + List endpoints = iface.getUsbEndpoints(); + UsbPipe cOut = null; + UsbPipe cIn = null; + UsbPipe dOut = null; + UsbPipe dIn = null; + for (UsbEndpoint endpoint : endpoints) + { + int address = endpoint.getUsbEndpointDescriptor().bEndpointAddress() & 0xff; + UsbPipe pipe = endpoint.getUsbPipe(); + pipe.open(); + switch (address) + { + case FLUXENGINE_CMD_OUT_EP: + cOut = pipe; + break; + case FLUXENGINE_CMD_IN_EP: + cIn = pipe; + break; + case FLUXENGINE_DATA_OUT_EP: + dOut = pipe; + break; + case FLUXENGINE_DATA_IN_EP: + dIn = pipe; + break; + } + } + if (cOut == null || cIn == null || dOut == null || dIn == null) + throw new FluxEngineException("FluxEngine: could not open all USB pipes"); + cmdOut = cOut; + cmdIn = cIn; + dataOut = dOut; + dataIn = dIn; + } catch (UsbException e) + { + throw new FluxEngineException("FluxEngine: USB error: " + e.getMessage()); + } + + int version = getVersion(); + if (version != FLUXENGINE_PROTOCOL_VERSION) + throw new FluxEngineException(String.format( + "your FluxEngine firmware is at version %d but the client is for version %d; " + + "please upgrade", + version, + FLUXENGINE_PROTOCOL_VERSION)); + } + + private static double getCurrentTime() + { + return System.nanoTime() / 1e9; + } + + private void usbCmdSend(byte[] data) + { + try + { + cmdOut.syncSubmit(data); + } catch (UsbException e) + { + throw new FluxEngineException("FluxEngine: command send failed: " + e.getMessage()); + } + } + + private byte[] usbCmdRecv(int len) + { + byte[] data = new byte[len]; + try + { + cmdIn.syncSubmit(data); + } catch (UsbException e) + { + throw new FluxEngineException("FluxEngine: command recv failed: " + e.getMessage()); + } + return data; + } + + private void usbDataSend(Bytes bytes) + { + int ptr = 0; + while (ptr < bytes.size()) + { + int len = Math.min(bytes.size() - ptr, MAX_TRANSFER); + byte[] data = new byte[len]; + for (int i = 0; i < len; i++) + data[i] = bytes.getByte(ptr + i); + try + { + dataOut.syncSubmit(data); + } catch (UsbException e) + { + throw new FluxEngineException("FluxEngine: data send failed: " + e.getMessage()); + } + ptr += len; + } + } + + private Bytes usbDataRecv(int maxLength) + { + Bytes bytes = new Bytes(0); + ByteWriter bw = bytes.writer(); + int ptr = 0; + while (ptr < maxLength) + { + int len = Math.min(maxLength - ptr, MAX_TRANSFER); + byte[] data = new byte[len]; + int transferred; + try + { + transferred = dataIn.syncSubmit(data); + } catch (UsbException e) + { + throw new FluxEngineException("FluxEngine: data recv failed: " + e.getMessage()); + } + for (int i = 0; i < transferred; i++) + bw.write8(data[i] & 0xff); + ptr += transferred; + if (transferred < MAX_TRANSFER) + break; + } + return bytes; + } + + private void badReply() + { + int type = buffer[0] & 0xff; + if (type != F_FRAME_ERROR) + throw new FluxEngineException(String.format("bad USB reply 0x%2x", type)); + switch (buffer[1] & 0xff) + { + case F_ERROR_BAD_COMMAND: + throw new FluxEngineException("device did not understand command"); + + case F_ERROR_UNDERRUN: + throw new FluxEngineException("USB underrun (not enough bandwidth)"); + + default: + throw new FluxEngineException("unknown device error " + (buffer[1] & 0xff)); + } + } + + private byte[] awaitReply(int desired) + { + for (; ; ) + { + byte[] r = usbCmdRecv(FRAME_SIZE); + System.arraycopy(r, 0, buffer, 0, FRAME_SIZE); + int type = r[0] & 0xff; + if (type == F_FRAME_DEBUG) + { + /* The debug payload is a NUL-terminated string. */ + StringBuilder sb = new StringBuilder(); + for (int i = 2; i < r.length && r[i] != 0; i++) + sb.append((char) r[i]); + System.out.println("dev: " + sb); + continue; + } + if (type != desired) + badReply(); + return r; + } + } + + private int getVersion() + { + byte[] f = {F_FRAME_GET_VERSION_CMD, 2}; + usbCmdSend(f); + byte[] r = awaitReply(F_FRAME_GET_VERSION_REPLY); + return r[2] & 0xff; + } + + @Override + public void seek(int track) + { + byte[] f = {F_FRAME_SEEK_CMD, 3, (byte) track}; + usbCmdSend(f); + awaitReply(F_FRAME_SEEK_REPLY); + } + + @Override + public void recalibrate() + { + byte[] f = {F_FRAME_RECALIBRATE_CMD, 2}; + usbCmdSend(f); + awaitReply(F_FRAME_RECALIBRATE_REPLY); + } + + @Override + public double getRotationalPeriod(int hardSectorCount) + { + byte[] f = {F_FRAME_MEASURE_SPEED_CMD, 3, (byte) hardSectorCount}; + usbCmdSend(f); + + byte[] r = awaitReply(F_FRAME_MEASURE_SPEED_REPLY); + int periodMs = (r[2] & 0xff) | ((r[3] & 0xff) << 8); + return periodMs * 1000000.0; + } + + @Override + public void testBulkWrite() + { + byte[] f = {F_FRAME_BULK_WRITE_TEST_CMD, 2}; + usbCmdSend(f); + + /* These must match the device. */ + final int XSIZE = 64; + final int YSIZE = 256; + final int ZSIZE = 64; + + System.out.print("Reading data: "); + System.out.flush(); + double startTime = getCurrentTime(); + Bytes bulkBuffer = usbDataRecv(XSIZE * YSIZE * ZSIZE); + double elapsedTime = getCurrentTime() - startTime; + + System.out.println("transferred " + bulkBuffer.size() + + " bytes from device -> PC in " + (int) (elapsedTime * 1000.0) + " ms (" + + (int) ((bulkBuffer.size() / 1024.0) / elapsedTime) + " kB/s)"); + + for (int x = 0; x < XSIZE; x++) + for (int y = 0; y < YSIZE; y++) + for (int z = 0; z < ZSIZE; z++) + { + int offset = x * XSIZE * YSIZE + y * ZSIZE + z; + if ((bulkBuffer.getByte(offset) & 0xff) != (x + y + z) % 256) + throw new FluxEngineException(String.format( + "data transfer corrupted at 0x%x %d.%d.%d", + offset, + x, + y, + z)); + } + + awaitReply(F_FRAME_BULK_WRITE_TEST_REPLY); + } + + @Override + public void testBulkRead() + { + byte[] f = {F_FRAME_BULK_READ_TEST_CMD, 2}; + usbCmdSend(f); + + /* These must match the device. */ + final int XSIZE = 64; + final int YSIZE = 256; + final int ZSIZE = 64; + + Bytes bulkBuffer = new Bytes(XSIZE * YSIZE * ZSIZE); + for (int x = 0; x < XSIZE; x++) + for (int y = 0; y < YSIZE; y++) + for (int z = 0; z < ZSIZE; z++) + { + int offset = x * XSIZE * YSIZE + y * ZSIZE + z; + bulkBuffer.setByte(offset, (byte) (x + y + z)); + } + + System.out.print("Writing data: "); + System.out.flush(); + double startTime = getCurrentTime(); + usbDataSend(bulkBuffer); + double elapsedTime = getCurrentTime() - startTime; + + System.out.println("transferred " + bulkBuffer.size() + + " bytes from PC -> device in " + (int) (elapsedTime * 1000.0) + " ms (" + + (int) ((bulkBuffer.size() / 1024.0) / elapsedTime) + " kB/s)"); + + awaitReply(F_FRAME_BULK_READ_TEST_REPLY); + } + + @Override + public Bytes read(int side, + boolean synced, + double readTimeNs, + double hardSectorThresholdNs) + { + Bytes f = new Bytes(0); + ByteWriter bw = f.writer(); + bw.write8(F_FRAME_READ_CMD); + bw.write8(6); + bw.write8(side); + bw.write8(synced ? 1 : 0); + int milliseconds = (int) (readTimeNs / 1e6); + bw.write8(milliseconds & 0xff); + bw.write8((milliseconds >> 8) & 0xff); + bw.write8((int) ((hardSectorThresholdNs + 5e5) / 1e6)); /* round to nearest ms */ + usbCmdSend(f.toByteArray()); + + Bytes buffer = usbDataRecv(1024 * 1024); + + awaitReply(F_FRAME_READ_REPLY); + return buffer; + } + + @Override + public void write(int side, Bytes bytes, double hardSectorThresholdNs) + { + int safelen = bytes.size() & ~(FRAME_SIZE - 1); + Bytes safeBytes = bytes.slice(0, safelen); + + Bytes f = new Bytes(0); + ByteWriter bw = f.writer(); + bw.write8(F_FRAME_WRITE_CMD); + bw.write8(7); + bw.write8(side); + bw.write8(safelen & 0xff); + bw.write8((safelen >> 8) & 0xff); + bw.write8((safelen >> 16) & 0xff); + bw.write8((safelen >> 24) & 0xff); + bw.write8((int) ((hardSectorThresholdNs + 5e5) / 1e6)); /* round to nearest ms */ + usbCmdSend(f.toByteArray()); + usbDataSend(safeBytes); + + awaitReply(F_FRAME_WRITE_REPLY); + } + + @Override + public void erase(int side, double hardSectorThresholdNs) + { + Bytes f = new Bytes(0); + ByteWriter bw = f.writer(); + bw.write8(F_FRAME_ERASE_CMD); + bw.write8(3); + bw.write8(side); + bw.write8((int) ((hardSectorThresholdNs + 5e5) / 1e6)); /* round to nearest ms */ + usbCmdSend(f.toByteArray()); + + awaitReply(F_FRAME_ERASE_REPLY); + } + + @Override + public void setDrive(int drive, boolean highDensity, int indexMode) + { + byte[] f = { + F_FRAME_SET_DRIVE_CMD, 5, (byte) drive, (byte) (highDensity ? 1 : 0), (byte) indexMode + }; + usbCmdSend(f); + awaitReply(F_FRAME_SET_DRIVE_REPLY); + } + + @Override + public VoltageMeasurements measureVoltages() + { + byte[] f = {F_FRAME_MEASURE_VOLTAGES_CMD, 2}; + usbCmdSend(f); + + byte[] r = awaitReply(F_FRAME_MEASURE_VOLTAGES_REPLY); + + VoltageMeasurements measurements = new VoltageMeasurements(); + int ptr = 2; + measurements.outputBothOff = readVoltages(r, ptr); + ptr += 4; + measurements.outputDrive0Selected = readVoltages(r, ptr); + ptr += 4; + measurements.outputDrive1Selected = readVoltages(r, ptr); + ptr += 4; + measurements.outputDrive0Running = readVoltages(r, ptr); + ptr += 4; + measurements.outputDrive1Running = readVoltages(r, ptr); + ptr += 4; + measurements.inputBothOff = readVoltages(r, ptr); + ptr += 4; + measurements.inputDrive0Selected = readVoltages(r, ptr); + ptr += 4; + measurements.inputDrive1Selected = readVoltages(r, ptr); + ptr += 4; + measurements.inputDrive0Running = readVoltages(r, ptr); + ptr += 4; + measurements.inputDrive1Running = readVoltages(r, ptr); + return measurements; + } + + private static Voltages readVoltages(byte[] r, int ptr) + { + int logic0 = (r[ptr] & 0xff) | ((r[ptr + 1] & 0xff) << 8); + int logic1 = (r[ptr + 2] & 0xff) | ((r[ptr + 3] & 0xff) << 8); + return new Voltages(logic0, logic1); + } + + @Override + public void close() + { + try + { + if (usbInterface.isClaimed()) + usbInterface.release(); + } catch (UsbException e) + { + throw new FluxEngineException("FluxEngine: USB error: " + e.getMessage()); + } + } +} diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 5cb4453c..43e5185d 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -50,6 +50,7 @@ public static UsbDevice connect(ConfigProto config) case GREASEWEAZLE -> new GreaseweazleUsbDevice( candidateDevice.serialPort, config.getUsb().getGreaseweazle()); + case FLUXENGINE -> new FluxEngineUsbDevice(candidateDevice.device); default -> throw new FluxEngineException("unsupported hardware device"); }; From f3a07c079d4ffae07140054b78f58ee14047d1fc Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 12:40:08 +0200 Subject: [PATCH 138/192] Port ApplesauceUsbDevice. --- .../fluxengine/usb/ApplesauceUsbDevice.java | 423 ++++++++++++++++++ java/com/cowlark/fluxengine/usb/BUILD.bazel | 2 + .../cowlark/fluxengine/usb/UsbFactory.java | 3 + 3 files changed, 428 insertions(+) create mode 100644 java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java diff --git a/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java b/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java new file mode 100644 index 00000000..aab5f53f --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java @@ -0,0 +1,423 @@ +package com.cowlark.fluxengine.usb; + +import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.FluxmapReader; +import com.cowlark.fluxengine.decoders.DecoderProto; +import com.fazecast.jSerialComm.SerialPort; +import java.util.ArrayList; +import java.util.List; + +/** + * Applesauce floppy drive device, ported from lib/usb/applesauceusb.cc. + */ +class ApplesauceUsbDevice extends UsbDevice +{ + private static long ssRandNext(long x) + { + return (x & 1) != 0 ? (x >> 1) ^ 0x80000062L : x >> 1; + } + + private static Bytes applesauceReadDataToFluxEngine(Bytes asdata, + double clock, + List indexMarks) + { + ByteReader br = new ByteReader(asdata); + Fluxmap fluxmap = new Fluxmap(); + int indexIt = 0; + fluxmap.appendIndex(); + + long totalTicks = 0; + while (!br.eof()) + { + int b = br.read8(); + fluxmap.appendInterval((int) (b * clock / NS_PER_TICK)); + if (b != 255) + fluxmap.appendPulse(); + + totalTicks += b; + if ((indexIt < indexMarks.size()) && (totalTicks > indexMarks.get(indexIt))) + { + fluxmap.appendIndex(); + indexIt++; + } + } + + return fluxmap.rawBytes(); + } + + private static Bytes fluxEngineToApplesauceWriteData(Bytes fldata) + { + Fluxmap fluxmap = new Fluxmap(fldata); + FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); + Bytes asdata = new Bytes(0); + ByteWriter bw = asdata.writer(); + + while (!fmr.eof()) + { + FluxmapReader.EventResult r = fmr.findEvent(F_BIT_PULSE); + long ticks = r.ticks(); + if (!r.found()) + break; + + long applesauceTicks = (long) (ticks * NS_PER_TICK); + while (applesauceTicks >= 0xffff) + { + bw.writeLe16(0xffff); + applesauceTicks -= 0xffff; + } + if (applesauceTicks == 0) + throw new FluxEngineException("bad data!"); + bw.writeLe16((int) applesauceTicks); + } + + bw.writeLe16(0); + return asdata; + } + + private final SerialPort serial; + private final ApplesauceProto config; + private boolean connected; + + ApplesauceUsbDevice(String port, ApplesauceProto config) + { + this.config = config; + this.serial = SerialPort.getCommPort(port); + serial.setBaudRate(38400); + serial.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); + if (!serial.openPort()) + throw new FluxEngineException("Unable to open serial port " + port); + + String s = sendrecv("?"); + if (!s.equals("Applesauce")) + throw new FluxEngineException(String.format( + "Applesauce device not responding (expected 'Applesauce', got '%s')", s)); + + doCommand("client:v2"); + } + + private static double getCurrentTime() + { + return System.nanoTime() / 1e9; + } + + private String sendrecv(String command) + { + if (config.getVerbose()) + System.out.println("> " + command); + writeLine(command); + String r = readLine(); + if (config.getVerbose()) + System.out.println("< " + r); + return r; + } + + private void checkCommandResult(String result) + { + if (!result.equals(".")) + throw new FluxEngineException("low-level Applesauce error: '" + result + "'"); + } + + private void doCommand(String command) + { + checkCommandResult(sendrecv(command)); + } + + private String doCommandX(String command) + { + doCommand(command); + String r = readLine(); + if (config.getVerbose()) + System.out.println("<< " + r); + return r; + } + + private void connect() + { + if (!connected) + { + try + { + doCommand("connect"); + doCommand("drive:enable"); + doCommand("motor:on"); + doCommand("head:zero"); + connected = true; + } catch (FluxEngineException e) + { + throw new FluxEngineException("Applesauce could not connect to a drive"); + } + } + } + + @Override + public void seek(int track) + { + if (track == 0) + doCommand("head:zero"); + else + doCommand(String.format("head:track%d", track)); + } + + @Override + public double getRotationalPeriod(int hardSectorCount) + { + if (hardSectorCount != 0) + throw new FluxEngineException( + "hard sectors are currently unsupported on the Applesauce"); + + connect(); + try + { + double periodUs = Double.parseDouble(doCommandX("sync:?speed")); + writeByte('X'); + String r = readLine(); + if (config.getVerbose()) + System.out.println("<< " + r); + return periodUs * 1e3; + } catch (FluxEngineException e) + { + return 0; + } + } + + @Override + public void testBulkWrite() + { + int max = Integer.parseInt(sendrecv("data:?max")); + System.out.print("Writing data: "); + + doCommand(String.format("data:>%d", max)); + + Bytes junk = new Bytes(max); + long seed = 0; + for (int i = 0; i < max; i++) + { + junk.setByte(i, (byte) seed); + seed = ssRandNext(seed); + } + double startTime = getCurrentTime(); + writeBytes(junk); + readLine(); + double elapsedTime = getCurrentTime() - startTime; + + System.out.printf( + "transferred %d bytes from PC -> device in %d ms (%d kb/s)%n", + max, + (int) (elapsedTime * 1000.0), + (int) ((max / 1024.0) / elapsedTime)); + } + + @Override + public void testBulkRead() + { + int max = Integer.parseInt(sendrecv("data:?max")); + System.out.print("Reading data: "); + + doCommand(String.format("data:<%d", max)); + + double startTime = getCurrentTime(); + readBytes(max); + double elapsedTime = getCurrentTime() - startTime; + + System.out.printf( + "transferred %d bytes from device -> PC in %d ms (%d kb/s)%n", + max, + (int) (elapsedTime * 1000.0), + (int) ((max / 1024.0) / elapsedTime)); + } + + @Override + public Bytes read(int side, + boolean synced, + double readTimeNs, + double hardSectorThresholdNs) + { + if (hardSectorThresholdNs != 0.0) + throw new FluxEngineException( + "hard sectors are currently unsupported on the Applesauce"); + boolean shortRead = readTimeNs < 400e6; + Logger.logf("applesauce: timed reads not supported; using read of %s revolutions", + shortRead ? "1.25" : "2.25"); + + connect(); + doCommand(String.format("head:side%d", side)); + doCommand("sync:on"); + doCommand("data:clear"); + String r = doCommandX(shortRead ? "disk:read" : "disk:readx"); + List rsplit = split(r, '|'); + if (rsplit.size() < 2) + throw new FluxEngineException( + "unrecognised Applesauce response to disk:read: '" + r + "'"); + + int bufferSize = Integer.parseInt(rsplit.get(0)); + double tickSize = Double.parseDouble(rsplit.get(1)) / 1e3; + + List indexMarks = new ArrayList<>(); + for (int i = 2; i < rsplit.size(); i++) + indexMarks.add(Integer.parseInt(rsplit.get(i))); + + doCommand(String.format("data:<%d", bufferSize)); + + Bytes rawData = readBytes(bufferSize); + return applesauceReadDataToFluxEngine(rawData, tickSize, indexMarks); + } + + private void checkWritable() + { + if (sendrecv("disk:?write").equals("-")) + throw new FluxEngineException("cannot write --- disk is write protected"); + if (sendrecv("?safe").equals("+")) + throw new FluxEngineException("cannot write --- Applesauce 'safe' switch is on"); + if (sendrecv("?vers").compareTo("0300") < 0) + throw new FluxEngineException( + "cannot write --- need Applesauce firmware 2.0 or above"); + } + + @Override + public void write(int side, Bytes fldata, double hardSectorThresholdNs) + { + if (hardSectorThresholdNs != 0.0) + throw new FluxEngineException( + "hard sectors are currently unsupported on the Applesauce"); + checkWritable(); + + connect(); + doCommand(String.format("head:side%d", side)); + doCommand("sync:on"); + doCommand("disk:wipe"); + doCommand("data:clear"); + doCommand("disk:wclear"); + + Bytes asdata = fluxEngineToApplesauceWriteData(fldata); + doCommand(String.format("data:>%d", asdata.size())); + writeBytes(asdata); + checkCommandResult(readLine()); + doCommand("disk:wcmd0,0"); + doCommand("disk:write"); + } + + @Override + public void erase(int side, double hardSectorThresholdNs) + { + if (hardSectorThresholdNs != 0.0) + throw new FluxEngineException( + "hard sectors are currently unsupported on the Applesauce"); + checkWritable(); + + connect(); + doCommand(String.format("disk:side%d", side)); + doCommand("disk:wipe"); + } + + @Override + public void setDrive(int drive, boolean highDensity, int indexMode) + { + if (drive != 0) + throw new FluxEngineException("the Applesauce only supports drive 0"); + + connect(); + doCommand(String.format("dpc:density%s", highDensity ? "+" : "-")); + } + + @Override + public VoltageMeasurements measureVoltages() + { + throw new FluxEngineException("unsupported operation on the Applesauce"); + } + + @Override + public void close() + { + try + { + sendrecv("disconnect"); + } finally + { + serial.closePort(); + } + } + + private static List split(String s, char separator) + { + List result = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + if (c == separator) + { + result.add(current.toString()); + current.setLength(0); + } else + current.append(c); + } + result.add(current.toString()); + return result; + } + + private void writeLine(String s) + { + writeBytes(s.getBytes()); + writeByte('\n'); + } + + private String readLine() + { + StringBuilder sb = new StringBuilder(); + for (; ; ) + { + int b = readByte(); + if (b == '\r') + continue; + if (b == '\n') + return sb.toString(); + sb.append((char) b); + } + } + + private int readByte() + { + return readBytes(1).getByte(0) & 0xff; + } + + private Bytes readBytes(int count) + { + Bytes result = new Bytes(0); + ByteWriter bw = result.writer(); + byte[] chunk = new byte[4096]; + while (bw.pos() < count) + { + int read = serial.readBytes(chunk, Math.min(chunk.length, count - bw.pos())); + if (read < 0) + throw new FluxEngineException("serial read failed"); + for (int i = 0; i < read; i++) + bw.write8(chunk[i] & 0xff); + } + return result; + } + + private void writeByte(int b) + { + writeBytes(new byte[] {(byte) b}); + } + + private void writeBytes(byte[] data) + { + int written = serial.writeBytes(data, data.length); + if (written != data.length) + throw new FluxEngineException("serial write failed"); + } + + private void writeBytes(Bytes data) + { + writeBytes(data.toByteArray()); + } +} diff --git a/java/com/cowlark/fluxengine/usb/BUILD.bazel b/java/com/cowlark/fluxengine/usb/BUILD.bazel index a89d43cf..7f530934 100644 --- a/java/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/java/com/cowlark/fluxengine/usb/BUILD.bazel @@ -25,6 +25,8 @@ java_library( "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", "//java/com/cowlark/fluxengine/external", "@maven//:com_fazecast_jSerialComm", "@maven//:com_google_guava_guava", diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 43e5185d..ca6c8701 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -50,6 +50,9 @@ public static UsbDevice connect(ConfigProto config) case GREASEWEAZLE -> new GreaseweazleUsbDevice( candidateDevice.serialPort, config.getUsb().getGreaseweazle()); + case APPLESAUCE -> new ApplesauceUsbDevice( + candidateDevice.serialPort, + config.getUsb().getApplesauce()); case FLUXENGINE -> new FluxEngineUsbDevice(candidateDevice.device); default -> throw new FluxEngineException("unsupported hardware device"); From 12f1e62cb14da11c795edf588f1738fba4ac8743 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 12:40:51 +0200 Subject: [PATCH 139/192] Format. --- .../cowlark/fluxengine/algorithms/Reader.java | 44 ++++---- java/com/cowlark/fluxengine/arch/Arch.java | 1 - .../fluxengine/arch/agat/AgatEncoder.java | 5 +- .../fluxengine/arch/apple2/Apple2Encoder.java | 38 ++++--- .../arch/brother/BrotherEncoder.java | 32 +++--- .../arch/c64/Commodore64Encoder.java | 35 +++--- .../fluxengine/arch/ibm/IbmEncoder.java | 21 ++-- .../arch/macintosh/MacintoshEncoder.java | 35 +++--- .../arch/micropolis/MicropolisEncoder.java | 21 ++-- .../arch/northstar/NorthstarEncoder.java | 19 ++-- .../fluxengine/arch/tartu/TartuEncoder.java | 4 +- .../arch/tids990/Tids990Encoder.java | 17 ++- .../arch/victor9k/Victor9kEncoder.java | 36 +++---- java/com/cowlark/fluxengine/cli/BUILD.bazel | 2 +- .../fluxengine/cli/RawwriteCommand.java | 4 +- .../cowlark/fluxengine/cli/ReadCommand.java | 2 +- .../cowlark/fluxengine/cli/WriteCommand.java | 7 +- .../fluxengine/config/ConfigBuilder.java | 73 +++++++------ .../cowlark/fluxengine/config/ProtoPath.java | 20 ++-- .../cowlark/fluxengine/core/LogMessage.java | 10 +- .../cowlark/fluxengine/core/LogRenderer.java | 6 +- java/com/cowlark/fluxengine/data/Disk.java | 6 +- .../cowlark/fluxengine/encoders/Encoder.java | 16 ++- .../fluxengine/fluxsink/A2RFluxSink.java | 14 ++- .../fluxengine/fluxsink/Fl2FluxSink.java | 30 +++--- .../fluxengine/fluxsink/FluxSinkFactory.java | 12 +-- .../fluxengine/fluxsink/HardwareFluxSink.java | 3 +- .../fluxengine/fluxsink/ScpFluxSink.java | 45 ++++---- .../fluxsink/ScpFluxSinkFactory.java | 5 +- java/com/cowlark/fluxengine/gui/Gui.java | 10 +- .../imagereader/D88ImageReader.java | 6 +- .../imagereader/DiskCopyImageReader.java | 32 +++--- .../imagereader/Jv3ImageReader.java | 16 ++- .../imagereader/NfdImageReader.java | 9 +- .../fluxengine/usb/ApplesauceUsbDevice.java | 102 +++++++++--------- .../fluxengine/usb/FluxEngineUsbDevice.java | 84 +++++++-------- .../cowlark/fluxengine/usb/UsbFactory.java | 4 +- 37 files changed, 394 insertions(+), 432 deletions(-) diff --git a/java/com/cowlark/fluxengine/algorithms/Reader.java b/java/com/cowlark/fluxengine/algorithms/Reader.java index 450614b2..8a85038e 100644 --- a/java/com/cowlark/fluxengine/algorithms/Reader.java +++ b/java/com/cowlark/fluxengine/algorithms/Reader.java @@ -36,16 +36,6 @@ */ public final class Reader { - static enum ReadResult - { - GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY - } - - static enum BadSectorsState - { - HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS - } - private Reader() { } @@ -277,12 +267,6 @@ private static Sector copySector(Sector sector) return s; } - static class CombinationResult - { - BadSectorsState result; - List sectors; - } - static CombinationResult combineRecordAndSectors(List tracks, Decoder decoder, LogicalTrackLayout ltl) @@ -320,12 +304,6 @@ static CombinationResult combineRecordAndSectors(List tracks, return cr; } - static class ReadGroupResult - { - ReadResult result; - List combinedSectors; - } - static ReadGroupResult readGroup(DiskLayout diskLayout, Common.FluxSourceIteratorHolder fluxSourceIteratorHolder, LogicalTrackLayout ltl, @@ -440,4 +418,26 @@ private static void readAndDecodeTrack(ConfigProto config, } } + static enum ReadResult + { + GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY + } + + static enum BadSectorsState + { + HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS + } + + static class CombinationResult + { + BadSectorsState result; + List sectors; + } + + static class ReadGroupResult + { + ReadResult result; + List combinedSectors; + } + } diff --git a/java/com/cowlark/fluxengine/arch/Arch.java b/java/com/cowlark/fluxengine/arch/Arch.java index e4d11106..cdd33bf4 100644 --- a/java/com/cowlark/fluxengine/arch/Arch.java +++ b/java/com/cowlark/fluxengine/arch/Arch.java @@ -36,7 +36,6 @@ import com.cowlark.fluxengine.decoders.Decoder; import com.cowlark.fluxengine.decoders.DecoderProto; import com.cowlark.fluxengine.encoders.Encoder; -import com.cowlark.fluxengine.encoders.EncoderProto; /** * The Arch class, ported from arch/arch.{h,cc}. diff --git a/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java b/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java index f190ec4f..40641450 100644 --- a/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java +++ b/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java @@ -103,12 +103,11 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) if (cursor.get() >= bits.size()) throw new FluxEngineException("track data overrun"); - bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( - bits, - (long) calculatePhysicalClockPeriod( + bits, (long) calculatePhysicalClockPeriod( fullConfig, config.getTargetClockPeriodUs() * 1e3, config.getTargetRotationalPeriodMs() * 1e6)); diff --git a/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java b/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java index 01748cc3..f1d386f9 100644 --- a/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java +++ b/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java @@ -86,15 +86,9 @@ public class Apple2Encoder extends Encoder ENCODE_DATA_GCR[0x3f] = 0xff; } - private static int encodeDataGcr(int data) - { - if (data < 0 || data >= ENCODE_DATA_GCR.length) - return -1; - return ENCODE_DATA_GCR[data]; - } - private final ConfigProto fullConfig; private final Apple2EncoderProto config; + private int volumeId = 254; public Apple2Encoder(ConfigProto config) { @@ -102,6 +96,13 @@ public Apple2Encoder(ConfigProto config) this.config = config.getEncoder().getApple2(); } + private static int encodeDataGcr(int data) + { + if (data < 0 || data >= ENCODE_DATA_GCR.length) + return -1; + return ENCODE_DATA_GCR[data]; + } + @Override public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) { @@ -117,25 +118,24 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) if (cursor.get() >= bits.size()) throw new FluxEngineException( "track data overrun by " + (cursor.get() - bits.size()) + " bits"); - bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( - bits, - (long) calculatePhysicalClockPeriod( + bits, (long) calculatePhysicalClockPeriod( fullConfig, config.getClockPeriodUs() * 1e3, config.getRotationalPeriodMs() * 1e6)); return fluxmap; } - private int volumeId = 254; - /* This is extremely inspired by the MESS implementation, written by Nathan * Woods and R. Belmont: - * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib/formats/ap2_dsk.cpp + * https://github.com/mamedev/mame/blob/7914a6083a3b3a8c243ae6c3b8cb50b023f21e0e/src/lib + * /formats/ap2_dsk.cpp * as well as Understanding the Apple II (1983) Chapter 9 - * https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230/mode/1up?view=theater + * https://archive.org/details/Understanding_the_Apple_II_1983_Quality_Software/page/n230 + * /mode/1up?view=theater */ private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) @@ -180,8 +180,7 @@ private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) // Convert the sector data to GCR, append the checksum, and write it // out - final int TWOBIT_COUNT = - 0x56; // Size of the 'twobit' area at the start of the GCR data + final int TWOBIT_COUNT = 0x56; // Size of the 'twobit' area at the start of the GCR data int checksum = 0; for (int i = 0; i < Apple2.APPLE2_ENCODED_SECTOR_LENGTH; i++) { @@ -189,8 +188,7 @@ private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) if (i >= TWOBIT_COUNT) { value = sector.data.getByte(i - TWOBIT_COUNT) >> 2; - } - else + } else { int tmp = sector.data.getByte(i); value = ((tmp & 1) << 1) | ((tmp & 2) >> 1); @@ -224,7 +222,7 @@ private void writeBit(Bits bits, Bits.Cursor cursor, boolean val) private void writeBits(Bits bits, Bits.Cursor cursor, int data, int width) { - for (int i = width; i-- != 0;) + for (int i = width; i-- != 0; ) writeBit(bits, cursor, (data & (1 << i)) != 0); } @@ -240,7 +238,7 @@ private void writeGcr6(Bits bits, Bits.Cursor cursor, int value) private void writeFf40(Bits bits, Bits.Cursor cursor, int n) { - for (; n-- != 0;) + for (; n-- != 0; ) writeBits(bits, cursor, 0xff << 2, 10); } } diff --git a/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java b/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java index 5b63b161..dff7a4fe 100644 --- a/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java +++ b/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java @@ -136,6 +136,15 @@ public class BrotherEncoder extends Encoder ENCODE_DATA_GCR[31] = 0xfb; } + private final ConfigProto fullConfig; + private final BrotherEncoderProto config; + + public BrotherEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getBrother(); + } + private static int encodeHeaderGcr(int word) { if (word < 0 || word >= ENCODE_HEADER_GCR.length) @@ -210,15 +219,6 @@ private static void writeSectorData(Bits bits, Bits.Cursor cursor, Bytes data) writeByte.accept(0); } - private final ConfigProto fullConfig; - private final BrotherEncoderProto config; - - public BrotherEncoder(ConfigProto config) - { - this.fullConfig = config; - this.config = config.getEncoder().getBrother(); - } - @Override public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) { @@ -229,15 +229,19 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) int sectorCount = 0; for (Sector sectorData : sectors) { - double headerMs = config.getPostIndexGapMs() + sectorCount * config.getSectorSpacingMs(); + double headerMs = + config.getPostIndexGapMs() + sectorCount * config.getSectorSpacingMs(); int headerCursor = (int) (headerMs * 1e3 / config.getClockRateUs()); double dataMs = headerMs + config.getPostHeaderSpacingMs(); int dataCursor = (int) (dataMs * 1e3 / config.getClockRateUs()); - bits.fillBitmapTo(cursor, headerCursor, new boolean[] {true, false}); + bits.fillBitmapTo(cursor, headerCursor, new boolean[]{true, false}); writeSectorHeader( - bits, cursor, sectorData.location.logicalCylinder(), sectorData.location.logicalSector()); - bits.fillBitmapTo(cursor, dataCursor, new boolean[] {true, false}); + bits, + cursor, + sectorData.location.logicalCylinder(), + sectorData.location.logicalSector()); + bits.fillBitmapTo(cursor, dataCursor, new boolean[]{true, false}); writeSectorData(bits, cursor, sectorData.data); sectorCount++; @@ -245,7 +249,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) if (cursor.get() >= bits.size()) throw new FluxEngineException("track data overrun"); - bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits(bits, (long) (config.getClockRateUs() * 1e3)); diff --git a/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java b/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java index a47ec715..0d930a93 100644 --- a/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java +++ b/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java @@ -40,6 +40,17 @@ public class Commodore64Encoder extends Encoder ENCODE_DATA_GCR[0xf] = 0x15; } + private final ConfigProto fullConfig; + private final Commodore64EncoderProto config; + private int formatByte1; + private int formatByte2; + + public Commodore64Encoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getC64(); + } + private static int encodeDataGcr(int data) { if (data < 0 || data >= ENCODE_DATA_GCR.length) @@ -89,8 +100,7 @@ private static boolean[] encodeData(int input) { output[4 - i] = (loGcr & 1) != 0; loGcr >>= 1; - } - else + } else { output[i + b] = (hiGcr & 1) != 0; hiGcr >>= 1; @@ -100,17 +110,6 @@ private static boolean[] encodeData(int input) return output; } - private final ConfigProto fullConfig; - private final Commodore64EncoderProto config; - private int formatByte1; - private int formatByte2; - - public Commodore64Encoder(ConfigProto config) - { - this.fullConfig = config; - this.config = config.getEncoder().getC64(); - } - @Override public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) { @@ -128,8 +127,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) br.seek(162); /* goto position of the first Disk ID Byte */ formatByte1 = br.read8(); formatByte2 = br.read8(); - } - else + } else { formatByte1 = formatByte2 = 0; } @@ -143,7 +141,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) bits.fillBitmapTo( cursor, (int) (config.getPostIndexGapUs() / clockRateUs), - new boolean[] {true, false}); + new boolean[]{true, false}); for (Sector sector : sectors) writeSector(bits, cursor, sector); @@ -151,7 +149,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) if (cursor.get() >= bits.size()) throw new FluxEngineException( "track data overrun by " + (cursor.get() - bits.size()) + " bits"); - bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( @@ -176,8 +174,7 @@ private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) // 2. Write Header info 10 GCR bytes int encodedTrack = sector.location.logicalCylinder() + 1; int encodedSector = sector.location.logicalSector(); - int headerChecksum = - (encodedTrack ^ encodedSector ^ formatByte1 ^ formatByte2); + int headerChecksum = (encodedTrack ^ encodedSector ^ formatByte1 ^ formatByte2); writeBits(bits, cursor, encodeData(C64.C64_HEADER_BLOCK_ID)); writeBits(bits, cursor, encodeData(headerChecksum)); writeBits(bits, cursor, encodeData(encodedSector)); diff --git a/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java b/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java index b2386178..86174d97 100644 --- a/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java +++ b/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java @@ -56,14 +56,6 @@ public class IbmEncoder extends Encoder */ private static final int MFM_RECORD_SEPARATOR = 0x4489; private static final int MFM_RECORD_SEPARATOR_BYTE = 0xa1; - - private static int decodeUint16(int raw) - { - Bytes b = new Bytes(2); - b.writer().writeBe16(raw); - return FmMfm.decodeFmMfm(b.toBits()).getByte(0) & 0xff; - } - private final ConfigProto fullConfig; private final IbmEncoderProto config; private final boolean[] lastBit = new boolean[1]; @@ -76,6 +68,13 @@ public IbmEncoder(ConfigProto config) this.config = config.getEncoder().getIbm(); } + private static int decodeUint16(int raw) + { + Bytes b = new Bytes(2); + b.writer().writeBe16(raw); + return FmMfm.decodeFmMfm(b.toBits()).getByte(0) & 0xff; + } + private void writeRawBits(int data, int width) { cursor.advance(width); @@ -195,8 +194,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) } bw.write8(idamUnencoded); bw.write8(sectorData.location.logicalCylinder()); - bw.write8( - sectorData.location.logicalHead() ^ + bw.write8(sectorData.location.logicalHead() ^ (trackdata.getInvertSideByte() ? 1 : 0)); bw.write8(sectorData.location.logicalSector()); bw.write8(sectorSize); @@ -256,8 +254,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( - bits, - (long) calculatePhysicalClockPeriod( + bits, (long) calculatePhysicalClockPeriod( fullConfig, clockRateUs * 1e3, trackdata.getTargetRotationalPeriodMs() * 1e6)); diff --git a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java index a2223e7f..4cba0d43 100644 --- a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java +++ b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java @@ -89,6 +89,15 @@ public class MacintoshEncoder extends Encoder ENCODE_DATA_GCR[0x3f] = 0xff; } + private final ConfigProto fullConfig; + private final MacintoshEncoderProto config; + + public MacintoshEncoder(ConfigProto config) + { + this.fullConfig = config; + this.config = config.getEncoder().getMacintosh(); + } + private static int encodeDataGcr(int data) { if (data < 0 || data >= ENCODE_DATA_GCR.length) @@ -125,7 +134,8 @@ private static int sectorsForTrack(int track) /* This is extremely inspired by the MESS implementation, written by Nathan * Woods and R. Belmont: - * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib/formats/ap_dsk35.cpp + * https://github.com/mamedev/mame/blob/4263a71e64377db11392c458b580c5ae83556bc7/src/lib + * /formats/ap_dsk35.cpp */ private static Bytes encodeCrazyData(Bytes input) { @@ -249,10 +259,10 @@ private static void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) int encodedTrack = sector.location.logicalCylinder() & 0x3f; int encodedSector = sector.location.logicalSector(); - int encodedSide = encodeSide(sector.location.logicalCylinder(), sector.location.logicalHead()); + int encodedSide = + encodeSide(sector.location.logicalCylinder(), sector.location.logicalHead()); int formatByte = Macintosh.MAC_FORMAT_BYTE; - int headerChecksum = - (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; + int headerChecksum = (encodedTrack ^ encodedSector ^ encodedSide ^ formatByte) & 0x3f; writeBits(bits, cursor, encodeDataGcr(encodedTrack), 1 * 8); writeBits(bits, cursor, encodeDataGcr(encodedSector), 1 * 8); @@ -265,9 +275,7 @@ private static void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) writeBits(bits, cursor, Macintosh.MAC_DATA_RECORD, 3 * 8); writeBits(bits, cursor, encodeDataGcr(sector.location.logicalSector()), 1 * 8); - Bytes wireData = sector.data - .slice(512, 12) - .concat(sector.data.slice(0, 512)); + Bytes wireData = sector.data.slice(512, 12).concat(sector.data.slice(0, 512)); Bytes crazy = encodeCrazyData(wireData); for (int i = 0; i < crazy.size(); i++) writeBits(bits, cursor, encodeDataGcr(crazy.getByte(i) & 0xff), 1 * 8); @@ -275,15 +283,6 @@ private static void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) writeBits(bits, cursor, 0xdeaaff, 3 * 8); } - private final ConfigProto fullConfig; - private final MacintoshEncoderProto config; - - public MacintoshEncoder(ConfigProto config) - { - this.fullConfig = config; - this.config = config.getEncoder().getMacintosh(); - } - @Override public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) { @@ -295,7 +294,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) bits.fillBitmapTo( cursor, (int) (config.getPostIndexGapUs() / clockRateUs), - new boolean[] {true, false}); + new boolean[]{true, false}); for (Sector sector : sectors) writeSector(bits, cursor, sector); @@ -303,7 +302,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) if (cursor.get() >= bits.size()) throw new FluxEngineException( "track data overrun by " + (cursor.get() - bits.size()) + " bits"); - bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( diff --git a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java index 5a533f42..e523eb3a 100644 --- a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java +++ b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java @@ -53,11 +53,10 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) throw new FluxEngineException("track data mismatched length"); Fluxmap fluxmap = new Fluxmap(); - long clockPeriod = - (long) calculatePhysicalClockPeriod( - fullConfig, - config.getClockPeriodUs() * 1e3, - config.getRotationalPeriodMs() * 1e6); + long clockPeriod = (long) calculatePhysicalClockPeriod( + fullConfig, + config.getClockPeriodUs() * 1e3, + config.getRotationalPeriodMs() * 1e6); int pos = 0; for (int i = 1; i < indexes.size(); i++) { @@ -70,11 +69,12 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) } private void writeSector(Bits bits, - Bits.Cursor cursor, - Sector sector, - MicropolisEncoderProto.EccType eccType) + Bits.Cursor cursor, + Sector sector, + MicropolisEncoderProto.EccType eccType) { - if ((sector.data.size() != 256) && (sector.data.size() != Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE)) + if ((sector.data.size() != 256) && + (sector.data.size() != Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE)) throw new FluxEngineException("unsupported sector size --- you must pick 256 or 275"); int fullSectorSize = 40 + Micropolis.MICROPOLIS_ENCODED_SECTOR_SIZE + 40 + 35; @@ -97,8 +97,7 @@ private void writeSector(Bits bits, System.err.println( "Warning: checksum incorrect. Sector: " + sector.location.logicalSector()); sectorData = sector.data; - } - else + } else { sectorData = new Bytes(0); ByteWriter writer = sectorData.writer(); diff --git a/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java b/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java index c53199e2..f3358f3b 100644 --- a/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java +++ b/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java @@ -49,22 +49,25 @@ private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) { case Northstar.NORTHSTAR_PAYLOAD_SIZE_SD: preambleSize = Northstar.NORTHSTAR_PREAMBLE_SIZE_SD; - encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_SD + - Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_SD + GAP_FILL_SIZE_SD; + encodedSectorSize = + PRE_HEADER_GAP_FILL_SIZE_SD + Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_SD + + GAP_FILL_SIZE_SD; gapFillSize = GAP_FILL_SIZE_SD; preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_SD; doubleDensity = false; break; case Northstar.NORTHSTAR_PAYLOAD_SIZE_DD: preambleSize = Northstar.NORTHSTAR_PREAMBLE_SIZE_DD; - encodedSectorSize = PRE_HEADER_GAP_FILL_SIZE_DD + - Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_DD + GAP_FILL_SIZE_DD; + encodedSectorSize = + PRE_HEADER_GAP_FILL_SIZE_DD + Northstar.NORTHSTAR_ENCODED_SECTOR_SIZE_DD + + GAP_FILL_SIZE_DD; gapFillSize = GAP_FILL_SIZE_DD; preHeaderGapFillSize = PRE_HEADER_GAP_FILL_SIZE_DD; doubleDensity = true; break; default: - throw new FluxEngineException("unsupported sector size --- you must pick 256 or 512"); + throw new FluxEngineException( + "unsupported sector size --- you must pick 256 or " + "512"); } int fullSectorSize = preambleSize + encodedSectorSize; @@ -95,8 +98,7 @@ private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) if (doubleDensity) { writer.write8(NorthstarDecoder.northstarChecksum(sectorData.slice(2))); - } - else + } else { writer.write8(NorthstarDecoder.northstarChecksum(sectorData.slice(1))); } @@ -123,8 +125,7 @@ private void writeSector(Bits bits, Bits.Cursor cursor, Sector sector) if (doubleDensity) { FmMfm.encodeMfm(bits, cursor, fullSector, lastBit); - } - else + } else { FmMfm.encodeFm(bits, cursor, fullSector); } diff --git a/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java b/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java index 42ae34d6..33d27be7 100644 --- a/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java +++ b/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java @@ -64,8 +64,8 @@ private void writeSector(Sector sectorData) { Bytes bytes = new Bytes(0); ByteWriter bw = bytes.writer(); - bw.write8( - (sectorData.location.logicalCylinder() << 1) | sectorData.location.logicalHead()); + bw.write8((sectorData.location.logicalCylinder() << 1) | + sectorData.location.logicalHead()); bw.write8(1); bw.write8(sectorData.location.logicalSector()); bw.write8(~Crc.sumBytes(bytes.slice(0, 3))); diff --git a/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java b/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java index 6ff536d4..b82ee196 100644 --- a/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java +++ b/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java @@ -20,13 +20,6 @@ */ public class Tids990Encoder extends Encoder { - private static int decodeUint16(int raw) - { - Bytes b = new Bytes(2); - b.writer().writeBe16(raw); - return FmMfm.decodeFmMfm(b.toBits()).getByte(0) & 0xff; - } - private final ConfigProto fullConfig; private final Tids990EncoderProto config; private final boolean[] lastBit = new boolean[1]; @@ -39,6 +32,13 @@ public Tids990Encoder(ConfigProto config) this.config = config.getEncoder().getTids990(); } + private static int decodeUint16(int raw) + { + Bytes b = new Bytes(2); + b.writer().writeBe16(raw); + return FmMfm.decodeFmMfm(b.toBits()).getByte(0) & 0xff; + } + private void writeRawBits(int data, int width) { cursor.advance(width); @@ -68,8 +68,7 @@ private void writeBytes(int count, int byte_) public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) { double clockRateUs = config.getClockPeriodUs() / 2.0; - int bitsPerRevolution = - (int) ((config.getRotationalPeriodMs() * 1000.0) / clockRateUs); + int bitsPerRevolution = (int) ((config.getRotationalPeriodMs() * 1000.0) / clockRateUs); bits = new Bits(bitsPerRevolution); cursor = new Bits.Cursor(0); diff --git a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java index 79107bf6..0e905b42 100644 --- a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java +++ b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java @@ -40,12 +40,6 @@ public class Victor9kEncoder extends Encoder ENCODE_DATA_GCR[0xf] = 0x15; } - private static int encodeDataGcr(int data) - { - data &= 0x0f; - return ENCODE_DATA_GCR[data]; - } - private final ConfigProto fullConfig; private final Victor9kEncoderProto config; private final boolean[] lastBit = new boolean[1]; @@ -56,6 +50,12 @@ public Victor9kEncoder(ConfigProto config) this.config = config.getEncoder().getVictor9K(); } + private static int encodeDataGcr(int data) + { + data &= 0x0f; + return ENCODE_DATA_GCR[data]; + } + private void writeZeroBits(Bits bits, Bits.Cursor cursor, int count) { while (count-- != 0) @@ -138,9 +138,9 @@ private void writeGap(Bits bits, Bits.Cursor cursor, int length) } private void writeSector(Bits bits, - Bits.Cursor cursor, - Victor9kEncoderProto.TrackdataProto trackdata, - Sector sector) + Bits.Cursor cursor, + Victor9kEncoderProto.TrackdataProto trackdata, + Sector sector) { writeOneBits(bits, cursor, trackdata.getPreHeaderSyncBits()); writeBits(bits, cursor, Victor9k.VICTOR9K_SECTOR_RECORD, 10); @@ -150,10 +150,7 @@ private void writeSector(Bits bits, writeBytes( bits, cursor, - Bytes.of( - encodedTrack, - encodedSector, - (encodedTrack + encodedSector) & 0xff)); + Bytes.of(encodedTrack, encodedSector, (encodedTrack + encodedSector) & 0xff)); writeGap(bits, cursor, trackdata.getPostHeaderGapBits()); @@ -195,17 +192,16 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) int bitsPerRevolution = (int) ((trackdata.getRotationalPeriodMs() * 1e3) / trackdata.getClockPeriodUs()); Bits bits = new Bits(bitsPerRevolution); - long clockPeriod = - (long) calculatePhysicalClockPeriod( - fullConfig, - trackdata.getClockPeriodUs() * 1e3, - trackdata.getRotationalPeriodMs() * 1e6); + long clockPeriod = (long) calculatePhysicalClockPeriod( + fullConfig, + trackdata.getClockPeriodUs() * 1e3, + trackdata.getRotationalPeriodMs() * 1e6); Bits.Cursor cursor = new Bits.Cursor(0); bits.fillBitmapTo( cursor, (int) (trackdata.getPostIndexGapUs() * 1e3 / clockPeriod), - new boolean[] {true, false}); + new boolean[]{true, false}); lastBit[0] = false; for (Sector sector : sectors) @@ -214,7 +210,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) if (cursor.get() >= bits.size()) throw new FluxEngineException( "track data overrun by " + (cursor.get() - bits.size()) + " bits"); - bits.fillBitmapTo(cursor, bits.size(), new boolean[] {true, false}); + bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits(bits, clockPeriod); diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 3344bbb8..41d1ce88 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -21,8 +21,8 @@ java_library( "//java/com/cowlark/fluxengine/fluxsink", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/gui", - "//java/com/cowlark/fluxengine/imagewriter", "//java/com/cowlark/fluxengine/imagereader", + "//java/com/cowlark/fluxengine/imagewriter", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_guava_guava", ], diff --git a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java index 6ae6a85b..ed2a7b18 100644 --- a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.cli; import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_ERASE; import com.cowlark.fluxengine.algorithms.Writer; import com.cowlark.fluxengine.config.ConfigBuilder; @@ -34,6 +33,7 @@ public class RawwriteCommand implements Command .setName("-d") .setHelpText("flux destination to write to") .build(); + private boolean erase = false; private ActionFlag eraseFlag = ActionFlag.builder() .setGroup(flags) .setName("--erase") @@ -41,8 +41,6 @@ public class RawwriteCommand implements Command .setVoidCallback(this::setErase) .build(); - private boolean erase = false; - @Override public String getHelp() { diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index 92a0e7f6..ee3e75d9 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -2,8 +2,8 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import com.cowlark.fluxengine.arch.Arch; import com.cowlark.fluxengine.algorithms.Reader; +import com.cowlark.fluxengine.arch.Arch; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; diff --git a/java/com/cowlark/fluxengine/cli/WriteCommand.java b/java/com/cowlark/fluxengine/cli/WriteCommand.java index 7fc35da4..491082d0 100644 --- a/java/com/cowlark/fluxengine/cli/WriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/WriteCommand.java @@ -35,6 +35,7 @@ public class WriteCommand implements Command .setName("-d") .setHelpText("flux destination to write to") .build(); + private boolean verify = true; private ActionFlag noVerifyFlag = ActionFlag.builder() .setGroup(flags) .setName("--no-verify") @@ -43,8 +44,6 @@ public class WriteCommand implements Command .setVoidCallback(() -> verify = false) .build(); - private boolean verify = true; - @Override public String getHelp() { @@ -65,9 +64,7 @@ public void run(ImmutableList args) ImageReader reader = ImageReader.create(config); Image image = reader.readImage(); - config = config.toBuilder() - .mergeFrom(reader.getExtraConfig()) - .build(); + config = config.toBuilder().mergeFrom(reader.getExtraConfig()).build(); DiskLayout diskLayout = new DiskLayout(config); Encoder encoder = Arch.createEncoder(config); diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index 0f7c04a2..ffae6114 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -43,12 +43,11 @@ */ public class ConfigBuilder { - private ConfigProto.Builder proto = ConfigProto.newBuilder() - .setFluxSource(FluxSourceProto.newBuilder().setType(FLUXTYPE_DRIVE).build()); - /* The groups which have had an option applied, so that applyDefaultOptions * knows not to apply their defaults. */ private final Set appliedOptions = new HashSet<>(); + private ConfigProto.Builder proto = ConfigProto.newBuilder() + .setFluxSource(FluxSourceProto.newBuilder().setType(FLUXTYPE_DRIVE).build()); public ConfigBuilder() { @@ -90,6 +89,32 @@ private static boolean isReadOnlyImage(String filename) filename.endsWith(".jv3") || filename.endsWith(".nfd") || filename.endsWith(".td0"); } + /* Quotes a string if it contains spaces or quote characters, ported from + * lib/core/utils.cc quote(). */ + private static String quote(String s) + { + boolean spaces = s.contains(" "); + if (!spaces && !s.contains("\\") && !s.contains("'") && !s.contains("\"")) + return s; + + StringBuilder ss = new StringBuilder(); + if (spaces) + ss.append('"'); + + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + if ((c == '\\') || (c == '"') || (c == '!')) + ss.append('\\'); + ss.append(c); + } + + if (spaces) + ss.append('"'); + + return ss.toString(); + } + public ConfigBuilder fromFlags(ImmutableList args, FlagGroup... group) { ImmutableList allGroups = ImmutableList.builder() @@ -255,12 +280,6 @@ public ConfigBuilder set(String key, String value) return this; } - /* The result of looking up an option, ported from - * lib/config/config.h Config::OptionInfo. */ - public record OptionInfo(OptionGroupProto group, OptionProto option, boolean usesValue) - { - } - /* Looks up an option by name, ported from Config::findOption. The group * value parameter of the C++ version is not needed here, so it takes a * key only. */ @@ -386,8 +405,8 @@ private void checkOptionValid(OptionProto optionProto) ss.append(']'); throw new InapplicableOptionException( - "option '%s' is inapplicable to this configuration " - + "because %s=%s could not be met", + "option '%s' is inapplicable to this configuration " + + "because %s=%s could not be met", optionProto.getName(), req.getKey(), ss.toString()); @@ -395,32 +414,6 @@ private void checkOptionValid(OptionProto optionProto) } } - /* Quotes a string if it contains spaces or quote characters, ported from - * lib/core/utils.cc quote(). */ - private static String quote(String s) - { - boolean spaces = s.contains(" "); - if (!spaces && !s.contains("\\") && !s.contains("'") && !s.contains("\"")) - return s; - - StringBuilder ss = new StringBuilder(); - if (spaces) - ss.append('"'); - - for (int i = 0; i < s.length(); i++) - { - char c = s.charAt(i); - if ((c == '\\') || (c == '"') || (c == '!')) - ss.append('\\'); - ss.append(c); - } - - if (spaces) - ss.append('"'); - - return ss.toString(); - } - public ConfigProto build() { applyDefaultOptions(); @@ -441,4 +434,10 @@ private void validateUsb() proto.getUsbBuilder().setSerial(UsbFinder.selectDevice(proto).serial); } + /* The result of looking up an option, ported from + * lib/config/config.h Config::OptionInfo. */ + public record OptionInfo(OptionGroupProto group, OptionProto option, boolean usesValue) + { + } + } diff --git a/java/com/cowlark/fluxengine/config/ProtoPath.java b/java/com/cowlark/fluxengine/config/ProtoPath.java index 457d77ec..f0389210 100644 --- a/java/com/cowlark/fluxengine/config/ProtoPath.java +++ b/java/com/cowlark/fluxengine/config/ProtoPath.java @@ -65,9 +65,8 @@ private static String getRecursive(Message.Builder builder, } else { if (component.index() >= 0) - throw new ProtoPathNotFoundException( - "config field '" + component.name() + - "' is not repeated but an index is provided"); + throw new ProtoPathNotFoundException("config field '" + component.name() + + "' is not repeated but an index is provided"); if (!builder.hasField(field)) throw new ProtoPathNotFoundException( "could not find config field '" + field.getName() + "'"); @@ -95,9 +94,8 @@ private static String getLeaf(Message.Builder builder, } else { if (component.index() >= 0) - throw new ProtoPathNotFoundException( - "config field '" + component.name() + - "' is not repeated but an index is provided"); + throw new ProtoPathNotFoundException("config field '" + component.name() + + "' is not repeated but an index is provided"); value = builder.getField(field); } return formatValue(field, value); @@ -166,9 +164,8 @@ private static void setRecursive(Message.Builder builder, } else { if (component.index() >= 0) - throw new ProtoPathNotFoundException( - "config field '" + component.name() + - "' is not repeated but an index is provided"); + throw new ProtoPathNotFoundException("config field '" + component.name() + + "' is not repeated but an index is provided"); Message.Builder elementBuilder; if (builder.hasField(field)) elementBuilder = ((Message) builder.getField(field)).toBuilder(); @@ -198,9 +195,8 @@ private static void setLeaf(Message.Builder builder, } else { if (component.index() >= 0) - throw new ProtoPathNotFoundException( - "config field '" + component.name() + - "' is not repeated but an index is provided"); + throw new ProtoPathNotFoundException("config field '" + component.name() + + "' is not repeated but an index is provided"); builder.setField(field, coerced); } } diff --git a/java/com/cowlark/fluxengine/core/LogMessage.java b/java/com/cowlark/fluxengine/core/LogMessage.java index 0220c769..b14d7c5c 100644 --- a/java/com/cowlark/fluxengine/core/LogMessage.java +++ b/java/com/cowlark/fluxengine/core/LogMessage.java @@ -50,12 +50,10 @@ record EndSpeedOperationLogMessage(double rotationalPeriodNs) implements LogMess @Override public void render(LogRenderer r) { - r.newline() - .add(String.format( - "Rotational period is %.1fms (%.1frpm)", - rotationalPeriodNs / 1e6, - 60e9 / rotationalPeriodNs)) - .newline(); + r.newline().add(String.format( + "Rotational period is %.1fms (%.1frpm)", + rotationalPeriodNs / 1e6, + 60e9 / rotationalPeriodNs)).newline(); } } diff --git a/java/com/cowlark/fluxengine/core/LogRenderer.java b/java/com/cowlark/fluxengine/core/LogRenderer.java index 234260f2..bfd884ba 100644 --- a/java/com/cowlark/fluxengine/core/LogRenderer.java +++ b/java/com/cowlark/fluxengine/core/LogRenderer.java @@ -68,7 +68,8 @@ public LogRenderer add(String message) indent(); } stream.print(message); - space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); + space = !message.isEmpty() && + Character.isWhitespace(message.charAt(message.length() - 1)); return this; } @@ -81,7 +82,8 @@ public LogRenderer header(String message) lineLen = message.length(); header = true; newline = true; - space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); + space = !message.isEmpty() && + Character.isWhitespace(message.charAt(message.length() - 1)); return this; } diff --git a/java/com/cowlark/fluxengine/data/Disk.java b/java/com/cowlark/fluxengine/data/Disk.java index bb0a3df3..b6959e10 100644 --- a/java/com/cowlark/fluxengine/data/Disk.java +++ b/java/com/cowlark/fluxengine/data/Disk.java @@ -29,8 +29,7 @@ public Disk(Image image, DiskLayout diskLayout) { this.image = image; - ListMultimap sectorsGroupedByTrack = - ArrayListMultimap.create(); + ListMultimap sectorsGroupedByTrack = ArrayListMultimap.create(); for (Sector sector : image) sectorsGroupedByTrack.put(sector.physicalLocation, sector); @@ -40,8 +39,7 @@ public Disk(Image image, DiskLayout diskLayout) for (CylinderHead physicalLocation : sectorLocations) { - PhysicalTrackLayout ptl = - diskLayout.layoutByPhysicalLocation.get(physicalLocation); + PhysicalTrackLayout ptl = diskLayout.layoutByPhysicalLocation.get(physicalLocation); LogicalTrackLayout ltl = ptl.logicalTrackLayout; Track decodedTrack = new Track(); diff --git a/java/com/cowlark/fluxengine/encoders/Encoder.java b/java/com/cowlark/fluxengine/encoders/Encoder.java index 1013db7f..3cf14fd7 100644 --- a/java/com/cowlark/fluxengine/encoders/Encoder.java +++ b/java/com/cowlark/fluxengine/encoders/Encoder.java @@ -8,7 +8,6 @@ import com.cowlark.fluxengine.data.LogicalTrackLayout; import com.cowlark.fluxengine.data.Sector; import com.google.common.collect.ImmutableList; -import java.util.ArrayList; import java.util.List; /** @@ -48,20 +47,17 @@ public ImmutableList collectSectors(LogicalTrackLayout ltl, Image image) return sectors.build(); } - public abstract Fluxmap encode( - LogicalTrackLayout ltl, List sectors, Image image); + public abstract Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image); public double calculatePhysicalClockPeriod(ConfigProto config, - double targetClockPeriod, double targetRotationalPeriod) + double targetClockPeriod, + double targetRotationalPeriod) { - double currentRotationalPeriod = - config.getDrive().getRotationalPeriodMs() * 1e6; + double currentRotationalPeriod = config.getDrive().getRotationalPeriodMs() * 1e6; if (currentRotationalPeriod == 0) throw new FluxEngineException( - "you must set --drive.rotational_period_ms as it can't be " - + "autodetected"); + "you must set --drive.rotational_period_ms as it can't be " + "autodetected"); - return targetClockPeriod * - (currentRotationalPeriod / targetRotationalPeriod); + return targetClockPeriod * (currentRotationalPeriod / targetRotationalPeriod); } } diff --git a/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java index 0c7c4e67..347af1c5 100644 --- a/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/A2RFluxSink.java @@ -5,8 +5,8 @@ import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.Logger; import com.cowlark.fluxengine.data.DiskLayout; @@ -31,12 +31,6 @@ public class A2RFluxSink extends FluxSink { private static final String VERSION_STRING = String.format("%-32s", "FluxEngine"); - - private static long ticksToA2r(long ticks) - { - return (long) (ticks * NS_PER_TICK / A2R.NS_PER_TICK); - } - private final String filename; private final ConfigProto config; private final Bytes bytes = new Bytes(0); @@ -44,7 +38,6 @@ private static long ticksToA2r(long ticks) private final Bytes strmBytes = new Bytes(0); private final ByteWriter strmWriter = strmBytes.writer(); private final Map metadata = new LinkedHashMap<>(); - private int minHead; private int maxHead; private int minCylinder; @@ -59,6 +52,11 @@ public A2RFluxSink(String filename, ConfigProto config) DateTimeFormatter.ISO_INSTANT.format(ZonedDateTime.now(ZoneOffset.UTC))); } + private static long ticksToA2r(long ticks) + { + return (long) (ticks * NS_PER_TICK / A2R.NS_PER_TICK); + } + private void writeChunkAndData(int chunkId, Bytes data) { writer.writeLe32(chunkId); diff --git a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java index 4ac452f9..66e44b4b 100644 --- a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java @@ -10,6 +10,7 @@ import com.cowlark.fluxengine.external.FluxMagic; import com.cowlark.fluxengine.external.TrackFluxProto; import com.google.protobuf.ByteString; +import org.apache.commons.lang3.tuple.Pair; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -17,7 +18,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.tuple.Pair; /** * A flux sink which writes an FL2 flux file, ported from @@ -45,6 +45,20 @@ public Fl2FluxSink(String filename, ConfigProto config) } } + private static void saveFl2File(String filename, FluxFileProto.Builder proto) + { + proto.setMagic(FluxMagic.MAGIC.getNumber()); + proto.setVersion(FluxFileVersion.VERSION_2); + + try + { + Files.write(Path.of(filename), proto.build().toByteArray()); + } catch (IOException e) + { + throw new FluxEngineException("unable to write output file '" + filename + "'"); + } + } + @Override public void addFlux(int track, int head, Fluxmap fluxmap) { @@ -73,18 +87,4 @@ public void close() saveFl2File(filename, proto); } - - private static void saveFl2File(String filename, FluxFileProto.Builder proto) - { - proto.setMagic(FluxMagic.MAGIC.getNumber()); - proto.setVersion(FluxFileVersion.VERSION_2); - - try - { - Files.write(Path.of(filename), proto.build().toByteArray()); - } catch (IOException e) - { - throw new FluxEngineException("unable to write output file '" + filename + "'"); - } - } } diff --git a/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java index b9ecc72f..920336c3 100644 --- a/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java +++ b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.fluxsink; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.config.FluxSourceSinkType; import com.cowlark.fluxengine.core.FluxEngineException; /** @@ -16,8 +15,7 @@ public static FluxSinkFactory create(ConfigProto config) return create(config, config.getFluxSink()); } - public static FluxSinkFactory create( - ConfigProto config, FluxSinkProto sinkConfig) + public static FluxSinkFactory create(ConfigProto config, FluxSinkProto sinkConfig) { switch (sinkConfig.getType()) { @@ -44,14 +42,14 @@ public static FluxSinkFactory create( } } - public static Fl2FluxSinkFactory createFl2FluxSinkFactory( - Fl2FluxSinkProto config, ConfigProto fullConfig) + public static Fl2FluxSinkFactory createFl2FluxSinkFactory(Fl2FluxSinkProto config, + ConfigProto fullConfig) { return new Fl2FluxSinkFactory(config.getFilename(), fullConfig); } - public static Fl2FluxSinkFactory createFl2FluxSinkFactory( - String filename, ConfigProto fullConfig) + public static Fl2FluxSinkFactory createFl2FluxSinkFactory(String filename, + ConfigProto fullConfig) { return new Fl2FluxSinkFactory(filename, fullConfig); } diff --git a/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java index bc34a6a6..6d513718 100644 --- a/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/HardwareFluxSink.java @@ -28,7 +28,8 @@ public HardwareFluxSink(ConfigProto config) @Override public void addFlux(int track, int side, Fluxmap fluxmap) { - device.setDrive(config.getDrive().getDrive(), + device.setDrive( + config.getDrive().getDrive(), config.getDrive().getHighDensity(), config.getDrive().getIndexMode().getNumber()); device.seek(track); diff --git a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java index 7c2f1ba3..0bc8d5e1 100644 --- a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java @@ -5,9 +5,9 @@ import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.ByteReader; import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.Logger; import com.cowlark.fluxengine.data.DiskLayout; @@ -26,32 +26,10 @@ */ public class ScpFluxSink extends FluxSink { - private static int strackno(int track, int side) - { - return (track << 1) | side; - } - - private static void writeLe32(byte[] dest, int offset, int v) - { - dest[offset] = (byte) v; - dest[offset + 1] = (byte) (v >> 8); - dest[offset + 2] = (byte) (v >> 16); - dest[offset + 3] = (byte) (v >> 24); - } - - private static int appendChecksum(int checksum, Bytes bytes) - { - ByteReader br = new ByteReader(bytes); - while (!br.eof()) - checksum += br.read8(); - return checksum; - } - private final String filename; private final int typeByte; private final boolean alignWithIndex; private final ConfigProto config; - /* The 688-byte file header. */ private final byte[] fileheader = new byte[Scp.SCP_HEADER_SIZE]; private final Bytes trackdata = new Bytes(0); @@ -96,6 +74,27 @@ else if ((minHead == 1) && (maxHead == 1)) (fileheader[7] - fileheader[6] + 1) + " tracks"); } + private static int strackno(int track, int side) + { + return (track << 1) | side; + } + + private static void writeLe32(byte[] dest, int offset, int v) + { + dest[offset] = (byte) v; + dest[offset + 1] = (byte) (v >> 8); + dest[offset + 2] = (byte) (v >> 16); + dest[offset + 3] = (byte) (v >> 24); + } + + private static int appendChecksum(int checksum, Bytes bytes) + { + ByteReader br = new ByteReader(bytes); + while (!br.eof()) + checksum += br.read8(); + return checksum; + } + @Override public void addFlux(int track, int head, Fluxmap fluxmap) { diff --git a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java index 94669e71..d7743749 100644 --- a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java +++ b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSinkFactory.java @@ -12,7 +12,10 @@ public class ScpFluxSinkFactory extends FluxSinkFactory private final boolean alignWithIndex; private final ConfigProto config; - public ScpFluxSinkFactory(String filename, int typeByte, boolean alignWithIndex, ConfigProto config) + public ScpFluxSinkFactory(String filename, + int typeByte, + boolean alignWithIndex, + ConfigProto config) { this.filename = filename; this.typeByte = typeByte; diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index f834a1d5..7e6a87e4 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -11,6 +11,11 @@ */ public class Gui extends Application { + public static void main(String[] args) + { + launch(Gui.class, args); + } + @Override public void start(Stage stage) { @@ -22,9 +27,4 @@ public void start(Stage stage) stage.setScene(scene); stage.show(); } - - public static void main(String[] args) - { - launch(Gui.class, args); - } } diff --git a/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java b/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java index 063c0985..72bf7142 100644 --- a/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/D88ImageReader.java @@ -8,8 +8,8 @@ import com.cowlark.fluxengine.data.Geometry; import com.cowlark.fluxengine.data.Image; import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.ibm.IbmEncoderProto; import com.cowlark.fluxengine.external.FormatType; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -120,8 +120,8 @@ public Image readImage() if (ddam != 0) throw new FluxEngineException("D88: nonzero ddam currently unsupported"); if (rpm != 0) - throw new FluxEngineException("D88: 1.44MB 300rpm formats currently " + - "unsupported"); + throw new FluxEngineException( + "D88: 1.44MB 300rpm formats currently " + "unsupported"); if (fddStatusCode != 0) throw new FluxEngineException( "D88: nonzero fdd status codes are currently unsupported"); diff --git a/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java b/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java index 17351532..01bc3ce5 100644 --- a/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/DiskCopyImageReader.java @@ -23,6 +23,22 @@ public DiskCopyImageReader(ImageReaderProto config) super(config); } + private static int sectorsPerTrack(int track, int numSectors, boolean mfm) + { + if (mfm) + return numSectors; + + if (track < 16) + return 12; + if (track < 32) + return 11; + if (track < 48) + return 10; + if (track < 64) + return 9; + return 8; + } + @Override public Image readImage() { @@ -118,20 +134,4 @@ public Image readImage() image.setGeometry(geometry); return image; } - - private static int sectorsPerTrack(int track, int numSectors, boolean mfm) - { - if (mfm) - return numSectors; - - if (track < 16) - return 12; - if (track < 32) - return 11; - if (track < 48) - return 10; - if (track < 64) - return 9; - return 8; - } } diff --git a/java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java b/java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java index 9813df14..9e4740b9 100644 --- a/java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/Jv3ImageReader.java @@ -23,13 +23,17 @@ public class Jv3ImageReader extends ImageReader private static final int JV3_SIDE = 0x10; /* 0=side 0, 1=side 1 */ private static final int JV3_ERROR = 0x08; /* 0=ok, 1=CRC error */ private static final int JV3_NONIBM = 0x04; /* 0=normal, 1=short */ - private static final int JV3_SIZE = - 0x03; /* in used sectors: 0=256,1=128,2=1024,3=512 + private static final int JV3_SIZE = 0x03; /* in used sectors: 0=256,1=128,2=1024,3=512 in free sectors: 0=512,1=1024,2=128,3=256 */ private static final int JV3_FREE = 0xFF; /* in track and sector fields of free sectors */ private static final int JV3_FREEF = 0xFC; /* in flags field, or'd with size code */ + public Jv3ImageReader(ImageReaderProto config) + { + super(config); + } + private static int getSectorSize(int flags) { if ((flags & JV3_FREEF) == JV3_FREEF) @@ -45,8 +49,7 @@ private static int getSectorSize(int flags) case 3: return 256; } - } - else + } else { switch (flags & JV3_SIZE) { @@ -63,11 +66,6 @@ private static int getSectorSize(int flags) throw new FluxEngineException("not reachable"); } - public Jv3ImageReader(ImageReaderProto config) - { - super(config); - } - @Override public Image readImage() { diff --git a/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java b/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java index 2a063467..2b5f7063 100644 --- a/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/NfdImageReader.java @@ -8,8 +8,8 @@ import com.cowlark.fluxengine.data.Geometry; import com.cowlark.fluxengine.data.Image; import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.ibm.IbmEncoderProto; import com.cowlark.fluxengine.external.FormatType; +import com.cowlark.fluxengine.ibm.IbmEncoderProto; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -81,9 +81,10 @@ public Image readImage() for (int sectorInTrack = 0; sectorInTrack < 26; sectorInTrack++) { - ByteReader sectorHeaderReader = new ByteReader(data.slice( - 0x120 + track * 26 * 16 + sectorInTrack * 16, - 16)); + ByteReader sectorHeaderReader = + new ByteReader(data.slice( + 0x120 + track * 26 * 16 + sectorInTrack * 16, + 16)); int cyl = sectorHeaderReader.seek(0).read8(); int head = sectorHeaderReader.seek(1).read8(); int sectorId = sectorHeaderReader.seek(2).read8(); diff --git a/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java b/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java index aab5f53f..fcfadfed 100644 --- a/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java @@ -20,14 +20,36 @@ */ class ApplesauceUsbDevice extends UsbDevice { + private final SerialPort serial; + private final ApplesauceProto config; + private boolean connected; + + ApplesauceUsbDevice(String port, ApplesauceProto config) + { + this.config = config; + this.serial = SerialPort.getCommPort(port); + serial.setBaudRate(38400); + serial.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); + if (!serial.openPort()) + throw new FluxEngineException("Unable to open serial port " + port); + + String s = sendrecv("?"); + if (!s.equals("Applesauce")) + throw new FluxEngineException(String.format("Applesauce device not responding " + + "(expected 'Applesauce', got '%s')", + s)); + + doCommand("client:v2"); + } + private static long ssRandNext(long x) { return (x & 1) != 0 ? (x >> 1) ^ 0x80000062L : x >> 1; } private static Bytes applesauceReadDataToFluxEngine(Bytes asdata, - double clock, - List indexMarks) + double clock, + List indexMarks) { ByteReader br = new ByteReader(asdata); Fluxmap fluxmap = new Fluxmap(); @@ -82,30 +104,27 @@ private static Bytes fluxEngineToApplesauceWriteData(Bytes fldata) return asdata; } - private final SerialPort serial; - private final ApplesauceProto config; - private boolean connected; - - ApplesauceUsbDevice(String port, ApplesauceProto config) + private static double getCurrentTime() { - this.config = config; - this.serial = SerialPort.getCommPort(port); - serial.setBaudRate(38400); - serial.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); - if (!serial.openPort()) - throw new FluxEngineException("Unable to open serial port " + port); - - String s = sendrecv("?"); - if (!s.equals("Applesauce")) - throw new FluxEngineException(String.format( - "Applesauce device not responding (expected 'Applesauce', got '%s')", s)); - - doCommand("client:v2"); + return System.nanoTime() / 1e9; } - private static double getCurrentTime() + private static List split(String s, char separator) { - return System.nanoTime() / 1e9; + List result = new ArrayList<>(); + StringBuilder current = new StringBuilder(); + for (int i = 0; i < s.length(); i++) + { + char c = s.charAt(i); + if (c == separator) + { + result.add(current.toString()); + current.setLength(0); + } else + current.append(c); + } + result.add(current.toString()); + return result; } private String sendrecv(String command) @@ -171,7 +190,7 @@ public double getRotationalPeriod(int hardSectorCount) { if (hardSectorCount != 0) throw new FluxEngineException( - "hard sectors are currently unsupported on the Applesauce"); + "hard sectors are currently unsupported on the " + "Applesauce"); connect(); try @@ -235,16 +254,14 @@ public void testBulkRead() } @Override - public Bytes read(int side, - boolean synced, - double readTimeNs, - double hardSectorThresholdNs) + public Bytes read(int side, boolean synced, double readTimeNs, double hardSectorThresholdNs) { if (hardSectorThresholdNs != 0.0) throw new FluxEngineException( - "hard sectors are currently unsupported on the Applesauce"); + "hard sectors are currently unsupported on the " + "Applesauce"); boolean shortRead = readTimeNs < 400e6; - Logger.logf("applesauce: timed reads not supported; using read of %s revolutions", + Logger.logf( + "applesauce: timed reads not supported; using read of %s revolutions", shortRead ? "1.25" : "2.25"); connect(); @@ -277,8 +294,7 @@ private void checkWritable() if (sendrecv("?safe").equals("+")) throw new FluxEngineException("cannot write --- Applesauce 'safe' switch is on"); if (sendrecv("?vers").compareTo("0300") < 0) - throw new FluxEngineException( - "cannot write --- need Applesauce firmware 2.0 or above"); + throw new FluxEngineException("cannot write --- need Applesauce firmware 2.0 or above"); } @Override @@ -286,7 +302,7 @@ public void write(int side, Bytes fldata, double hardSectorThresholdNs) { if (hardSectorThresholdNs != 0.0) throw new FluxEngineException( - "hard sectors are currently unsupported on the Applesauce"); + "hard sectors are currently unsupported on the " + "Applesauce"); checkWritable(); connect(); @@ -309,7 +325,7 @@ public void erase(int side, double hardSectorThresholdNs) { if (hardSectorThresholdNs != 0.0) throw new FluxEngineException( - "hard sectors are currently unsupported on the Applesauce"); + "hard sectors are currently unsupported on the " + "Applesauce"); checkWritable(); connect(); @@ -345,24 +361,6 @@ public void close() } } - private static List split(String s, char separator) - { - List result = new ArrayList<>(); - StringBuilder current = new StringBuilder(); - for (int i = 0; i < s.length(); i++) - { - char c = s.charAt(i); - if (c == separator) - { - result.add(current.toString()); - current.setLength(0); - } else - current.append(c); - } - result.add(current.toString()); - return result; - } - private void writeLine(String s) { writeBytes(s.getBytes()); @@ -406,7 +404,7 @@ private Bytes readBytes(int count) private void writeByte(int b) { - writeBytes(new byte[] {(byte) b}); + writeBytes(new byte[]{(byte) b}); } private void writeBytes(byte[] data) diff --git a/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java b/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java index 74223aad..44bd46ef 100644 --- a/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java @@ -1,6 +1,20 @@ package com.cowlark.fluxengine.usb; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_CMD_IN_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_CMD_OUT_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_DATA_IN_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_DATA_OUT_EP; +import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_PROTOCOL_VERSION; +import static com.cowlark.fluxengine.external.FluxEngine.FRAME_SIZE; +import static com.cowlark.fluxengine.external.FluxEngine.F_ERROR_BAD_COMMAND; +import static com.cowlark.fluxengine.external.FluxEngine.F_ERROR_UNDERRUN; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_READ_TEST_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_READ_TEST_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_WRITE_TEST_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_WRITE_TEST_REPLY; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_DEBUG; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERASE_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERASE_REPLY; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERROR; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_GET_VERSION_CMD; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_GET_VERSION_REPLY; @@ -8,43 +22,26 @@ import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_MEASURE_SPEED_REPLY; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_MEASURE_VOLTAGES_CMD; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_MEASURE_VOLTAGES_REPLY; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_READ_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_READ_REPLY; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_RECALIBRATE_CMD; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_RECALIBRATE_REPLY; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SEEK_CMD; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SEEK_REPLY; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_READ_TEST_CMD; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_READ_TEST_REPLY; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_WRITE_TEST_CMD; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_BULK_WRITE_TEST_REPLY; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_READ_CMD; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_READ_REPLY; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_WRITE_CMD; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_WRITE_REPLY; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERASE_CMD; -import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_ERASE_REPLY; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SET_DRIVE_CMD; import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_SET_DRIVE_REPLY; -import static com.cowlark.fluxengine.external.FluxEngine.F_ERROR_BAD_COMMAND; -import static com.cowlark.fluxengine.external.FluxEngine.F_ERROR_UNDERRUN; -import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_CMD_IN_EP; -import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_CMD_OUT_EP; -import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_DATA_IN_EP; -import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_DATA_OUT_EP; -import static com.cowlark.fluxengine.external.FluxEngine.FLUXENGINE_PROTOCOL_VERSION; -import static com.cowlark.fluxengine.external.FluxEngine.FRAME_SIZE; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_WRITE_CMD; +import static com.cowlark.fluxengine.external.FluxEngine.F_FRAME_WRITE_REPLY; -import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; -import java.util.ArrayList; -import java.util.List; import javax.usb.UsbConfiguration; -import javax.usb.UsbConst; - import javax.usb.UsbEndpoint; import javax.usb.UsbException; import javax.usb.UsbInterface; import javax.usb.UsbPipe; +import java.util.List; /** * FluxEngine floppy drive device, ported from lib/usb/fluxengineusb.cc. @@ -125,9 +122,7 @@ class FluxEngineUsbDevice extends UsbDevice if (version != FLUXENGINE_PROTOCOL_VERSION) throw new FluxEngineException(String.format( "your FluxEngine firmware is at version %d but the client is for version %d; " + - "please upgrade", - version, - FLUXENGINE_PROTOCOL_VERSION)); + "please upgrade", version, FLUXENGINE_PROTOCOL_VERSION)); } private static double getCurrentTime() @@ -135,6 +130,13 @@ private static double getCurrentTime() return System.nanoTime() / 1e9; } + private static Voltages readVoltages(byte[] r, int ptr) + { + int logic0 = (r[ptr] & 0xff) | ((r[ptr + 1] & 0xff) << 8); + int logic1 = (r[ptr + 2] & 0xff) | ((r[ptr + 3] & 0xff) << 8); + return new Voltages(logic0, logic1); + } + private void usbCmdSend(byte[] data) { try @@ -297,8 +299,8 @@ public void testBulkWrite() Bytes bulkBuffer = usbDataRecv(XSIZE * YSIZE * ZSIZE); double elapsedTime = getCurrentTime() - startTime; - System.out.println("transferred " + bulkBuffer.size() + - " bytes from device -> PC in " + (int) (elapsedTime * 1000.0) + " ms (" + + System.out.println("transferred " + bulkBuffer.size() + " bytes from device -> PC in " + + (int) (elapsedTime * 1000.0) + " ms (" + (int) ((bulkBuffer.size() / 1024.0) / elapsedTime) + " kB/s)"); for (int x = 0; x < XSIZE; x++) @@ -308,7 +310,7 @@ public void testBulkWrite() int offset = x * XSIZE * YSIZE + y * ZSIZE + z; if ((bulkBuffer.getByte(offset) & 0xff) != (x + y + z) % 256) throw new FluxEngineException(String.format( - "data transfer corrupted at 0x%x %d.%d.%d", + "data transfer corrupted at " + "0x%x %d.%d.%d", offset, x, y, @@ -344,18 +346,15 @@ public void testBulkRead() usbDataSend(bulkBuffer); double elapsedTime = getCurrentTime() - startTime; - System.out.println("transferred " + bulkBuffer.size() + - " bytes from PC -> device in " + (int) (elapsedTime * 1000.0) + " ms (" + + System.out.println("transferred " + bulkBuffer.size() + " bytes from PC -> device in " + + (int) (elapsedTime * 1000.0) + " ms (" + (int) ((bulkBuffer.size() / 1024.0) / elapsedTime) + " kB/s)"); awaitReply(F_FRAME_BULK_READ_TEST_REPLY); } @Override - public Bytes read(int side, - boolean synced, - double readTimeNs, - double hardSectorThresholdNs) + public Bytes read(int side, boolean synced, double readTimeNs, double hardSectorThresholdNs) { Bytes f = new Bytes(0); ByteWriter bw = f.writer(); @@ -414,9 +413,11 @@ public void erase(int side, double hardSectorThresholdNs) @Override public void setDrive(int drive, boolean highDensity, int indexMode) { - byte[] f = { - F_FRAME_SET_DRIVE_CMD, 5, (byte) drive, (byte) (highDensity ? 1 : 0), (byte) indexMode - }; + byte[] f = {F_FRAME_SET_DRIVE_CMD, + 5, + (byte) drive, + (byte) (highDensity ? 1 : 0), + (byte) indexMode}; usbCmdSend(f); awaitReply(F_FRAME_SET_DRIVE_REPLY); } @@ -453,13 +454,6 @@ public VoltageMeasurements measureVoltages() return measurements; } - private static Voltages readVoltages(byte[] r, int ptr) - { - int logic0 = (r[ptr] & 0xff) | ((r[ptr + 1] & 0xff) << 8); - int logic1 = (r[ptr + 2] & 0xff) | ((r[ptr + 3] & 0xff) << 8); - return new Voltages(logic0, logic1); - } - @Override public void close() { diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index ca6c8701..643a47eb 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -14,12 +14,12 @@ public final class UsbFactory { + private static final Cache cache = CacheBuilder.newBuilder().build(); + private UsbFactory() { } - private static final Cache cache = CacheBuilder.newBuilder().build(); - /* Connects a USB device, reusing a previously connected device for the * same configuration. This is the Java equivalent of the C++ global * getUsb(). If a different configuration requires a new device, the From 4f802df376cdf9e19c7fd88da84dc3bde2028266 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 13:37:17 +0200 Subject: [PATCH 140/192] Lots of serial device rearrangement. --- .../fluxengine/config/ConfigBuilder.java | 12 +- .../fluxengine/config/OptionLogMessage.java | 17 +++ java/com/cowlark/fluxengine/core/Bytes.java | 7 +- .../cowlark/fluxengine/core/LogMessage.java | 8 -- .../fluxengine/usb/ApplesauceUsbDevice.java | 95 +++---------- .../fluxengine/usb/GreaseweazleUsbDevice.java | 74 +++------- .../fluxengine/usb/RetryableUsbException.java | 11 ++ java/com/cowlark/fluxengine/usb/Serial.java | 114 +++++++++++++++ .../cowlark/fluxengine/usb/UsbFactory.java | 11 +- .../com/cowlark/fluxengine/core/BUILD.bazel | 2 + .../fluxengine/core/LogRendererTest.java | 10 +- .../com/cowlark/fluxengine/usb/BUILD.bazel | 1 + .../fluxengine/usb/UsbFactoryTest.java | 131 +++++++++++++++--- 13 files changed, 319 insertions(+), 174 deletions(-) create mode 100644 java/com/cowlark/fluxengine/config/OptionLogMessage.java create mode 100644 java/com/cowlark/fluxengine/usb/RetryableUsbException.java create mode 100644 java/com/cowlark/fluxengine/usb/Serial.java diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index ffae6114..65b4a42a 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -24,6 +24,7 @@ import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_NSI; import static com.cowlark.fluxengine.config.ImageReaderWriterType.IMAGETYPE_TD0; +import com.cowlark.fluxengine.core.Logger; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.Flags; import com.cowlark.fluxengine.data.Formats; @@ -351,6 +352,7 @@ public void applyOption(OptionInfo option, String value) checkOptionValid(optionProto); if (option.group() != null) appliedOptions.add(option.group()); + Logger.log(new OptionLogMessage("user option", optionProto)); proto.mergeFrom(optionProto.getConfig()); } @@ -362,13 +364,15 @@ private void applyDefaultOptions() { if (!appliedOptions.contains(group)) { - for (OptionProto option : group.getOptionList()) + for (OptionProto optionProto : group.getOptionList()) { - if (option.getSetByDefault()) + if (optionProto.getSetByDefault()) { - checkOptionValid(option); + checkOptionValid(optionProto); appliedOptions.add(group); - proto.mergeFrom(option.getConfig()); + + Logger.log(new OptionLogMessage("default option", optionProto)); + proto.mergeFrom(optionProto.getConfig()); } } } diff --git a/java/com/cowlark/fluxengine/config/OptionLogMessage.java b/java/com/cowlark/fluxengine/config/OptionLogMessage.java new file mode 100644 index 00000000..67c40a10 --- /dev/null +++ b/java/com/cowlark/fluxengine/config/OptionLogMessage.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.config; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; +import com.google.common.base.Strings; + +public record OptionLogMessage(String message, OptionProto option) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + r.newline().add("OPTION:"); + if (!Strings.isNullOrEmpty(message)) + r.add(message + ":"); + r.add(option.getComment()).newline(); + } +} diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 148530a2..9123f299 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -332,13 +332,16 @@ public ByteReader iterator() return new ByteReader(this); } - /* Returns a cursor for writing into this Bytes, ported from - * lib/core/bytes.h Bytes::writer(). */ public ByteWriter writer() { return new ByteWriter(this); } + public ByteReader reader() + { + return new ByteReader(this); + } + @Override public boolean add(Byte value) { diff --git a/java/com/cowlark/fluxengine/core/LogMessage.java b/java/com/cowlark/fluxengine/core/LogMessage.java index b14d7c5c..a86c5fd4 100644 --- a/java/com/cowlark/fluxengine/core/LogMessage.java +++ b/java/com/cowlark/fluxengine/core/LogMessage.java @@ -115,12 +115,4 @@ public void render(LogRenderer r) } } - record OptionLogMessage(String message) implements LogMessage - { - @Override - public void render(LogRenderer r) - { - r.newline().add("OPTION:").add(message).newline(); - } - } } diff --git a/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java b/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java index fcfadfed..ad1ba69c 100644 --- a/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/ApplesauceUsbDevice.java @@ -11,7 +11,6 @@ import com.cowlark.fluxengine.data.Fluxmap; import com.cowlark.fluxengine.data.FluxmapReader; import com.cowlark.fluxengine.decoders.DecoderProto; -import com.fazecast.jSerialComm.SerialPort; import java.util.ArrayList; import java.util.List; @@ -20,23 +19,19 @@ */ class ApplesauceUsbDevice extends UsbDevice { - private final SerialPort serial; + private final Serial serial; private final ApplesauceProto config; private boolean connected; ApplesauceUsbDevice(String port, ApplesauceProto config) { this.config = config; - this.serial = SerialPort.getCommPort(port); - serial.setBaudRate(38400); - serial.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); - if (!serial.openPort()) - throw new FluxEngineException("Unable to open serial port " + port); + this.serial = new Serial(port, 9600); String s = sendrecv("?"); if (!s.equals("Applesauce")) - throw new FluxEngineException(String.format("Applesauce device not responding " + - "(expected 'Applesauce', got '%s')", + throw new FluxEngineException(String.format( + "Applesauce device not responding " + "(expected 'Applesauce', got '%s')", s)); doCommand("client:v2"); @@ -131,8 +126,8 @@ private String sendrecv(String command) { if (config.getVerbose()) System.out.println("> " + command); - writeLine(command); - String r = readLine(); + serial.writeLine(command); + String r = serial.readLine(); if (config.getVerbose()) System.out.println("< " + r); return r; @@ -152,7 +147,7 @@ private void doCommand(String command) private String doCommandX(String command) { doCommand(command); - String r = readLine(); + String r = serial.readLine(); if (config.getVerbose()) System.out.println("<< " + r); return r; @@ -196,8 +191,8 @@ public double getRotationalPeriod(int hardSectorCount) try { double periodUs = Double.parseDouble(doCommandX("sync:?speed")); - writeByte('X'); - String r = readLine(); + serial.writeByte('X'); + String r = serial.readLine(); if (config.getVerbose()) System.out.println("<< " + r); return periodUs * 1e3; @@ -223,8 +218,8 @@ public void testBulkWrite() seed = ssRandNext(seed); } double startTime = getCurrentTime(); - writeBytes(junk); - readLine(); + serial.writeBytes(junk); + serial.readLine(); double elapsedTime = getCurrentTime() - startTime; System.out.printf( @@ -243,7 +238,7 @@ public void testBulkRead() doCommand(String.format("data:<%d", max)); double startTime = getCurrentTime(); - readBytes(max); + serial.readBytes(max); double elapsedTime = getCurrentTime() - startTime; System.out.printf( @@ -283,7 +278,7 @@ public Bytes read(int side, boolean synced, double readTimeNs, double hardSector doCommand(String.format("data:<%d", bufferSize)); - Bytes rawData = readBytes(bufferSize); + Bytes rawData = serial.readBytes(bufferSize); return applesauceReadDataToFluxEngine(rawData, tickSize, indexMarks); } @@ -314,8 +309,8 @@ public void write(int side, Bytes fldata, double hardSectorThresholdNs) Bytes asdata = fluxEngineToApplesauceWriteData(fldata); doCommand(String.format("data:>%d", asdata.size())); - writeBytes(asdata); - checkCommandResult(readLine()); + serial.writeBytes(asdata); + checkCommandResult(serial.readLine()); doCommand("disk:wcmd0,0"); doCommand("disk:write"); } @@ -357,65 +352,7 @@ public void close() sendrecv("disconnect"); } finally { - serial.closePort(); + serial.close(); } } - - private void writeLine(String s) - { - writeBytes(s.getBytes()); - writeByte('\n'); - } - - private String readLine() - { - StringBuilder sb = new StringBuilder(); - for (; ; ) - { - int b = readByte(); - if (b == '\r') - continue; - if (b == '\n') - return sb.toString(); - sb.append((char) b); - } - } - - private int readByte() - { - return readBytes(1).getByte(0) & 0xff; - } - - private Bytes readBytes(int count) - { - Bytes result = new Bytes(0); - ByteWriter bw = result.writer(); - byte[] chunk = new byte[4096]; - while (bw.pos() < count) - { - int read = serial.readBytes(chunk, Math.min(chunk.length, count - bw.pos())); - if (read < 0) - throw new FluxEngineException("serial read failed"); - for (int i = 0; i < read; i++) - bw.write8(chunk[i] & 0xff); - } - return result; - } - - private void writeByte(int b) - { - writeBytes(new byte[]{(byte) b}); - } - - private void writeBytes(byte[] data) - { - int written = serial.writeBytes(data, data.length); - if (written != data.length) - throw new FluxEngineException("serial write failed"); - } - - private void writeBytes(Bytes data) - { - writeBytes(data.toByteArray()); - } } diff --git a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java index 63cc64e6..6fac22f1 100644 --- a/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/GreaseweazleUsbDevice.java @@ -36,7 +36,6 @@ import com.cowlark.fluxengine.core.Bytes; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.external.GreaseweazleUtils; -import com.fazecast.jSerialComm.SerialPort; import com.google.common.util.concurrent.Uninterruptibles; import java.time.Duration; @@ -45,7 +44,7 @@ */ class GreaseweazleUsbDevice extends UsbDevice { - private final SerialPort serial; + private final Serial serial; private final GreaseweazleProto config; private Version version; private long clock; @@ -54,11 +53,7 @@ class GreaseweazleUsbDevice extends UsbDevice GreaseweazleUsbDevice(String port, GreaseweazleProto config) { this.config = config; - this.serial = SerialPort.getCommPort(port); - serial.setBaudRate(BAUD_NORMAL); - serial.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 0, 0); - if (!serial.openPort()) - throw new FluxEngineException("Unable to open serial port " + port); + this.serial = new Serial(port, BAUD_NORMAL); int version = getVersion(); if (version >= 29) @@ -129,7 +124,7 @@ private int getVersion() { doCommand(CMD_GET_INFO, GETINFO_FIRMWARE); - ByteReader response = new ByteReader(readBytes(32)); + ByteReader response = serial.readBytes(32).reader(); response.seek(4); long freq = response.readLe32() & 0xffffffffL; clock = 1000000000L / freq; @@ -140,7 +135,7 @@ private int getVersion() private long read28() { - ByteReader buffer = new ByteReader(readBytes(4)); + ByteReader buffer = new ByteReader(serial.readBytes(4)); return (long) ((buffer.read8() & 0xfe) >> 1) | (long) (buffer.read8() & 0xfe) << 6 | (long) (buffer.read8() & 0xfe) << 13 | (long) (buffer.read8() & 0xfe) << 20; } @@ -162,12 +157,12 @@ private void doCommand(Bytes command) private void doCommand(byte[] command) { - writeBytes(command); + serial.writeBytes(command); - Bytes buffer = readBytes(2); + Bytes buffer = serial.readBytes(2); if ((buffer.getByte(0) & 0xff) != (command[0] & 0xff)) - throw new FluxEngineException(String.format( + throw new RetryableUsbException(String.format( "command returned garbage (0x%x != 0x%x with status 0x%x)", buffer.getByte(0), command[0], @@ -216,13 +211,13 @@ public double getRotationalPeriod(int hardSectorCount) long secondIndex = -1; for (; ; ) { - int b = readByte(); + int b = serial.readByte(); if (b == 0) break; if (b == 255) { - switch (readByte()) + switch (serial.readByte()) { case FLUXOP_INDEX: { @@ -247,7 +242,7 @@ else if (secondIndex == -1) ticksGw += b; else { - long delta = 250 + (b - 250) * 255 + readByte() - 1; + long delta = 250 + (b - 250) * 255 + serial.readByte() - 1; ticksGw += delta; } } @@ -299,8 +294,8 @@ public void testBulkWrite() seed = ssRandNext(seed); } double startTime = getCurrentTime(); - writeBytes(junk); - readBytes(1); + serial.writeBytes(junk); + serial.readBytes(1); double elapsedTime = getCurrentTime() - startTime; System.out.printf( @@ -339,7 +334,7 @@ public void testBulkRead() doCommand(cmd); double startTime = getCurrentTime(); - readBytes(LEN); + serial.readBytes(LEN); double elapsedTime = getCurrentTime() - startTime; System.out.printf( @@ -389,7 +384,7 @@ public Bytes read(int side, boolean synced, double readTimeNs, double hardSector ByteWriter bw = new ByteWriter(buffer); for (; ; ) { - int b = readByte(); + int b = serial.readByte(); if (b == 0) break; bw.write8(b); @@ -423,8 +418,8 @@ public void write(int side, Bytes fldata, double hardSectorThresholdNs) break; } Bytes gwdata = GreaseweazleUtils.fluxEngineToGreaseweazle(fldata, clock); - writeBytes(gwdata); - readByte(); /* synchronise */ + serial.writeBytes(gwdata); + serial.readByte(); /* synchronise */ doCommand(CMD_GET_FLUX_STATUS); } @@ -444,7 +439,7 @@ public void erase(int side, double hardSectorThresholdNs) bw.write8(6); bw.writeLe32((int) (200e6 / clock)); doCommand(cmd); - readByte(); /* synchronise */ + serial.readByte(); /* synchronise */ doCommand(CMD_GET_FLUX_STATUS); } @@ -466,40 +461,7 @@ public VoltageMeasurements measureVoltages() @Override public void close() { - serial.closePort(); - } - - private int readByte() - { - return readBytes(1).get(0) & 0xff; - } - - private Bytes readBytes(int count) - { - Bytes result = new Bytes(0); - ByteWriter bw = new ByteWriter(result); - byte[] chunk = new byte[4096]; - while (bw.pos() < count) - { - int read = serial.readBytes(chunk, Math.min(chunk.length, count - bw.pos())); - if (read < 0) - throw new FluxEngineException("serial read failed"); - for (int i = 0; i < read; i++) - bw.write8(chunk[i] & 0xff); - } - return result; - } - - private void writeBytes(byte[] data) - { - int written = serial.writeBytes(data, data.length); - if (written != data.length) - throw new FluxEngineException("serial write failed"); - } - - private void writeBytes(Bytes data) - { - writeBytes(data.toByteArray()); + serial.close(); } private enum Version diff --git a/java/com/cowlark/fluxengine/usb/RetryableUsbException.java b/java/com/cowlark/fluxengine/usb/RetryableUsbException.java new file mode 100644 index 00000000..ac840963 --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/RetryableUsbException.java @@ -0,0 +1,11 @@ +package com.cowlark.fluxengine.usb; + +import com.cowlark.fluxengine.core.FluxEngineException; + +public class RetryableUsbException extends FluxEngineException +{ + public RetryableUsbException(String message) + { + super(message); + } +} diff --git a/java/com/cowlark/fluxengine/usb/Serial.java b/java/com/cowlark/fluxengine/usb/Serial.java new file mode 100644 index 00000000..d54c8bc3 --- /dev/null +++ b/java/com/cowlark/fluxengine/usb/Serial.java @@ -0,0 +1,114 @@ +package com.cowlark.fluxengine.usb; + +import static com.fazecast.jSerialComm.SerialPort.FLOW_CONTROL_DISABLED; +import static com.fazecast.jSerialComm.SerialPort.TIMEOUT_READ_BLOCKING; +import static com.fazecast.jSerialComm.SerialPort.TIMEOUT_WRITE_BLOCKING; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.fazecast.jSerialComm.SerialPort; +import com.google.common.util.concurrent.Uninterruptibles; +import java.time.Duration; + +/** + * A wrapper around a USB serial port which more closely matches the behaviour + * of the original lib/usb/serial.c than the raw jSerialComm interface: raw + * 8N1 mode with no flow control, DTR toggling to reset the device, flushing of + * pending input on open, and read/write loops which collect or transmit all of + * the requested bytes. + */ +public final class Serial +{ + private final SerialPort serial; + private final byte[] readBuffer = new byte[4096]; + private int readBufferPtr = 0; + private int readBufferFill = 0; + + public Serial(String path, int baudRate) + { + serial = SerialPort.getCommPort(path); + serial.setComPortParameters(baudRate, 8, 1, 0); /* raw 8N1 */ + serial.setFlowControl(FLOW_CONTROL_DISABLED); + serial.setComPortTimeouts(TIMEOUT_READ_BLOCKING | TIMEOUT_WRITE_BLOCKING, 0, 0); + if (!serial.openPort()) + throw new FluxEngineException("cannot open serial port '" + path + "'"); + + /* Toggle DTR to reset the device. */ + toggleDtr(); + + /* Flush pending input from a generic device. */ + readBufferPtr = 0; + readBufferFill = 0; + } + + /* Toggles the DTR line, which resets the attached device. The C++ clears + * DTR, sleeps, and sets it again. */ + public void toggleDtr() + { + boolean rts = serial.getRTS(); + serial.setDTRandRTS(false, rts); + Uninterruptibles.sleepUninterruptibly(Duration.ofMillis(200)); + serial.setDTRandRTS(true, rts); + } + + public void setBaudRate(int baudRate) + { + if (!serial.setBaudRate(baudRate)) + throw new FluxEngineException("cannot set baud rate on serial port"); + toggleDtr(); + } + + public Bytes readBytes(int count) + { + byte[] array = new byte[count]; + serial.readBytes(array, count); + return new Bytes(array); + } + + public int readByte() + { + return readBytes(1).getByte(0); + } + + public void writeBytes(byte[] data) + { + serial.writeBytes(data, data.length); + } + + public void writeBytes(Bytes data) + { + serial.writeBytes(data.toByteArray(), data.size()); + } + + public void writeByte(int b) + { + Bytes data = new Bytes(1); + data.setByte(0, (byte) b); + writeBytes(data); + } + + public void writeLine(String s) + { + writeBytes(new Bytes(s)); + writeByte('\n'); + } + + public String readLine() + { + StringBuilder sb = new StringBuilder(); + for (; ; ) + { + int b = readByte(); + if (b == '\r') + continue; + if (b == '\n') + return sb.toString(); + sb.append((char) b); + } + } + + public void close() + { + serial.closePort(); + } +} diff --git a/java/com/cowlark/fluxengine/usb/UsbFactory.java b/java/com/cowlark/fluxengine/usb/UsbFactory.java index 643a47eb..b9731aca 100644 --- a/java/com/cowlark/fluxengine/usb/UsbFactory.java +++ b/java/com/cowlark/fluxengine/usb/UsbFactory.java @@ -4,6 +4,7 @@ import com.cowlark.fluxengine.config.UsbFinder; import com.cowlark.fluxengine.config.UsbFinder.CandidateDevice; import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import java.util.Map; @@ -16,6 +17,10 @@ public final class UsbFactory private static final Cache cache = CacheBuilder.newBuilder().build(); + /* The device factory; replaceable from tests to avoid touching real + * hardware. */ + static java.util.function.Function deviceFactory = UsbFactory::connect; + private UsbFactory() { } @@ -36,7 +41,7 @@ public static synchronized UsbDevice reconnect(ConfigProto config) entry.getValue().close(); cache.invalidateAll(); - device = connect(config); + device = deviceFactory.apply(config); cache.put(config, device); } return device; @@ -45,6 +50,10 @@ public static synchronized UsbDevice reconnect(ConfigProto config) public static UsbDevice connect(ConfigProto config) { CandidateDevice candidateDevice = UsbFinder.selectDevice(config); + Logger.logf( + "using %s serial %s", + candidateDevice.type.getDeviceName(), + candidateDevice.serial); UsbDevice device = switch (candidateDevice.type) { case GREASEWEAZLE -> new GreaseweazleUsbDevice( diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 92cf3c99..19e3ef47 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -67,6 +67,8 @@ java_test( name = "LogRendererTest", srcs = ["LogRendererTest.java"], deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "@maven//:com_google_truth_truth", "@maven//:junit_junit", diff --git a/javatests/com/cowlark/fluxengine/core/LogRendererTest.java b/javatests/com/cowlark/fluxengine/core/LogRendererTest.java index 7b317e43..87e6f479 100644 --- a/javatests/com/cowlark/fluxengine/core/LogRendererTest.java +++ b/javatests/com/cowlark/fluxengine/core/LogRendererTest.java @@ -2,12 +2,13 @@ import static com.google.common.truth.Truth.assertThat; +import com.cowlark.fluxengine.config.OptionLogMessage; +import com.cowlark.fluxengine.config.OptionProto; import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.BeginWriteOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.EmergencyStopMessage; import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.ErrorLogMessage; -import com.cowlark.fluxengine.core.LogMessage.OptionLogMessage; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.function.Consumer; @@ -77,10 +78,13 @@ public void writeOperationHeader() @Test public void optionMessage() { + OptionProto option = OptionProto.newBuilder() + .setComment("high density") + .build(); String output = render( - r -> r.add(new OptionLogMessage("high density"))); + r -> r.add(new OptionLogMessage("user option", option))); - assertThat(output).isEqualTo("\n OPTION: high density\n"); + assertThat(output).isEqualTo("\n OPTION: user option: high density\n"); } @Test diff --git a/javatests/com/cowlark/fluxengine/usb/BUILD.bazel b/javatests/com/cowlark/fluxengine/usb/BUILD.bazel index 95db07f0..798df9ae 100644 --- a/javatests/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/usb/BUILD.bazel @@ -8,6 +8,7 @@ java_test( deps = [ "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/usb", "@maven//:com_google_truth_truth", "@maven//:junit_junit", diff --git a/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java b/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java index e0018f0a..e83f96a9 100644 --- a/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java +++ b/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java @@ -4,6 +4,7 @@ import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.Bytes; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -11,49 +12,137 @@ @RunWith(JUnit4.class) public class UsbFactoryTest { + private static class FakeUsbDevice extends UsbDevice + { + int closed = 0; + + @Override + public void seek(int track) + { + } + + @Override + public double getRotationalPeriod(int hardSectorCount) + { + return 0; + } + + @Override + public void testBulkWrite() + { + } + + @Override + public void testBulkRead() + { + } + + @Override + public Bytes read(int side, boolean synced, double readTimeNs, double hardSectorThresholdNs) + { + return new Bytes(); + } + + @Override + public void write(int side, Bytes bytes, double hardSectorThresholdNs) + { + } + + @Override + public void erase(int side, double hardSectorThresholdNs) + { + } + + @Override + public void setDrive(int drive, boolean highDensity, int indexMode) + { + } + + @Override + public VoltageMeasurements measureVoltages() + { + return null; + } + + @Override + public void close() + { + closed++; + } + } + private static ConfigProto config() { - /* No serial specified: with a single connected device, selectDevice - * returns it. */ - return new ConfigBuilder().build(); + return new ConfigBuilder() + .set("usb.serial", "test-serial") + .build(); + } + + private static void withFakeFactory(java.util.function.Function factory, + Runnable test) + { + java.util.function.Function saved = UsbFactory.deviceFactory; + UsbFactory.deviceFactory = factory; + try + { + UsbFactory.reconnect(config()); /* flush any cached device */ + test.run(); + } finally + { + UsbFactory.deviceFactory = saved; + } } @Test public void reconnectReturnsSameInstanceForSameConfig() { - ConfigProto config = config(); + withFakeFactory(c -> new FakeUsbDevice(), () -> + { + ConfigProto config = config(); - UsbDevice first = UsbFactory.reconnect(config); - UsbDevice second = UsbFactory.reconnect(config); + UsbDevice first = UsbFactory.reconnect(config); + UsbDevice second = UsbFactory.reconnect(config); - assertThat(second).isSameInstanceAs(first); + assertThat(second).isSameInstanceAs(first); + }); } @Test public void reconnectCachesByConfigValue() { - /* The cache is keyed by ConfigProto value equality, so a distinct but - * equal config object must hit the same cache entry. */ - ConfigProto first = config(); - ConfigProto second = config(); + withFakeFactory(c -> new FakeUsbDevice(), () -> + { + /* The cache is keyed by ConfigProto value equality, so a distinct + * but equal config object must hit the same cache entry. */ + ConfigProto first = config(); + ConfigProto second = config(); - UsbDevice a = UsbFactory.reconnect(first); - UsbDevice b = UsbFactory.reconnect(second); + UsbDevice a = UsbFactory.reconnect(first); + UsbDevice b = UsbFactory.reconnect(second); - assertThat(a).isNotNull(); - assertThat(b).isSameInstanceAs(a); + assertThat(a).isNotNull(); + assertThat(b).isSameInstanceAs(a); + }); } @Test public void reconnectWithDifferentConfigEvictsAndClosesOldDevice() { - ConfigProto first = config(); - ConfigProto second = new ConfigBuilder().set("drive.drive", "1").build(); + withFakeFactory(c -> new FakeUsbDevice(), () -> + { + ConfigProto first = config(); + ConfigProto second = new ConfigBuilder() + .set("usb.serial", "test-serial") + .set("drive.drive", "1") + .build(); - UsbDevice a = UsbFactory.reconnect(first); - UsbDevice b = UsbFactory.reconnect(second); + UsbDevice a = UsbFactory.reconnect(first); + FakeUsbDevice fakeA = (FakeUsbDevice) a; + UsbDevice b = UsbFactory.reconnect(second); - assertThat(a).isNotNull(); - assertThat(b).isNotSameInstanceAs(a); + assertThat(a).isNotNull(); + assertThat(b).isNotSameInstanceAs(a); + assertThat(fakeA.closed).isEqualTo(1); + }); } } From f5499d04b3cdea94eb3c1ac5d2f841277f2f94d9 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 19:50:01 +0200 Subject: [PATCH 141/192] rpm works again. --- .../cowlark/fluxengine/cli/RpmCommand.java | 14 +- .../cowlark/fluxengine/cli/SeekCommand.java | 11 +- .../fluxengine/config/ConfigBuilder.java | 6 +- .../cowlark/fluxengine/reflect-config.json | 6588 +++++++++++++++++ java/com/cowlark/fluxengine/usb/Serial.java | 2 +- src/formats/_global_options.textpb | 2 +- 6 files changed, 6616 insertions(+), 7 deletions(-) diff --git a/java/com/cowlark/fluxengine/cli/RpmCommand.java b/java/com/cowlark/fluxengine/cli/RpmCommand.java index d80bb937..185201af 100644 --- a/java/com/cowlark/fluxengine/cli/RpmCommand.java +++ b/java/com/cowlark/fluxengine/cli/RpmCommand.java @@ -5,6 +5,9 @@ import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; import com.google.common.collect.ImmutableList; @@ -14,6 +17,14 @@ */ public class RpmCommand implements Command { + private FlagGroup flags = new FlagGroup(); + private ValueFlag sourceFlag = StringFlag.builder() + .setGroup(flags) + .setName("--source") + .setName("-s") + .setHelpText("flux file to read from") + .build(); + @Override public String getHelp() { @@ -23,7 +34,8 @@ public String getHelp() @Override public void run(ImmutableList args) { - ConfigProto config = new ConfigBuilder().fromFlags(args).build(); + ConfigProto config = + new ConfigBuilder().fromFlags(args, flags).withFluxSource(sourceFlag.get()).build(); if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) throw new FluxEngineException("this only makes sense with a real disk drive"); diff --git a/java/com/cowlark/fluxengine/cli/SeekCommand.java b/java/com/cowlark/fluxengine/cli/SeekCommand.java index 3cadc37a..88021067 100644 --- a/java/com/cowlark/fluxengine/cli/SeekCommand.java +++ b/java/com/cowlark/fluxengine/cli/SeekCommand.java @@ -7,6 +7,8 @@ import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.IntFlag; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; import com.cowlark.fluxengine.usb.UsbDevice; import com.cowlark.fluxengine.usb.UsbFactory; import com.google.common.collect.ImmutableList; @@ -17,6 +19,12 @@ public class SeekCommand implements Command { private static FlagGroup flags = new FlagGroup(); + private ValueFlag sourceFlag = StringFlag.builder() + .setGroup(flags) + .setName("--source") + .setName("-s") + .setHelpText("flux file to read from") + .build(); private static IntFlag track = IntFlag.builder() .setGroup(flags) .setName("--cylinder") @@ -33,7 +41,8 @@ public String getHelp() @Override public void run(ImmutableList args) { - ConfigProto config = new ConfigBuilder().fromFlags(args, flags).build(); + ConfigProto config = + new ConfigBuilder().fromFlags(args, flags).withFluxSource(sourceFlag.get()).build(); if (config.getFluxSource().getType() != FLUXTYPE_DRIVE) throw new FluxEngineException("this only makes sense with a real disk drive"); diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index 65b4a42a..45235237 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -47,8 +47,7 @@ public class ConfigBuilder /* The groups which have had an option applied, so that applyDefaultOptions * knows not to apply their defaults. */ private final Set appliedOptions = new HashSet<>(); - private ConfigProto.Builder proto = ConfigProto.newBuilder() - .setFluxSource(FluxSourceProto.newBuilder().setType(FLUXTYPE_DRIVE).build()); + private ConfigProto.Builder proto = Formats.get("_global_options").toBuilder(); public ConfigBuilder() { @@ -371,8 +370,9 @@ private void applyDefaultOptions() checkOptionValid(optionProto); appliedOptions.add(group); + /* Default options should never override anything the user set. */ Logger.log(new OptionLogMessage("default option", optionProto)); - proto.mergeFrom(optionProto.getConfig()); + proto = optionProto.getConfig().toBuilder().mergeFrom(proto.build()); } } } diff --git a/java/com/cowlark/fluxengine/reflect-config.json b/java/com/cowlark/fluxengine/reflect-config.json index 1646797e..923d9b7b 100644 --- a/java/com/cowlark/fluxengine/reflect-config.json +++ b/java/com/cowlark/fluxengine/reflect-config.json @@ -276,5 +276,6593 @@ "parameterTypes": [] } ] + }, + { + "name": "com.cowlark.fluxengine.aeslanier.AesLanierDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.aeslanier.AesLanierDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.aeslanier.AesLanierDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.agat.AgatDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.agat.AgatDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.agat.AgatDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.agat.AgatEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.agat.AgatEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.agat.AgatEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.amiga.AmigaDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.amiga.AmigaDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.amiga.AmigaDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.amiga.AmigaEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.amiga.AmigaEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.amiga.AmigaEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.apple2.Apple2DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.apple2.Apple2DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.apple2.Apple2DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.apple2.Apple2EncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.apple2.Apple2EncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.apple2.Apple2EncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.BrotherDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.BrotherDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.BrotherDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.BrotherEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.BrotherEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.BrotherEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.c64.Commodore64DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.c64.Commodore64DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.c64.Commodore64DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.c64.Commodore64EncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.c64.Commodore64EncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.c64.Commodore64EncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ConfigProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ConfigProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ConfigProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.DriveProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.DriveProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.DriveProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.DriveProto$ErrorBehaviour", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.DriveProto$ErrorBehaviour$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto$LayoutdataProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto$LayoutdataProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto$LayoutdataProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto$Order", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.LayoutProto$Order$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionGroupProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionGroupProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionGroupProto$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionGroupProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionPrerequisiteProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionPrerequisiteProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionPrerequisiteProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionProto$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ProtoPath", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ProtoPath$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ProtoPath$PathComponent", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ProtoPathNotFoundException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.SectorListProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.SectorListProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.SectorListProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.DecoderProto$FormatCase", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.encoders.EncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.encoders.EncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.encoders.EncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.encoders.EncoderProto$FormatCase", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxFileProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxFileProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxFileProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.TrackFluxProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.TrackFluxProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.TrackFluxProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.f85.F85DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.f85.F85DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.f85.F85DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fb100.Fb100DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fb100.Fb100DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fb100.Fb100DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.A2RFluxSinkProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.A2RFluxSinkProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.A2RFluxSinkProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.AuFluxSinkProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.AuFluxSinkProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.AuFluxSinkProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.Fl2FluxSinkProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.Fl2FluxSinkProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.Fl2FluxSinkProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.FluxSinkProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.FluxSinkProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.FluxSinkProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.HardwareFluxSinkProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.HardwareFluxSinkProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.HardwareFluxSinkProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.ScpFluxSinkProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.ScpFluxSinkProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.ScpFluxSinkProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.VcdFluxSinkProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.VcdFluxSinkProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsink.VcdFluxSinkProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.A2rFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.A2rFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.A2rFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.CwfFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.CwfFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.CwfFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.DmkFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.DmkFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.DmkFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.EraseFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.EraseFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.EraseFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.Fl2FluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.Fl2FluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.Fl2FluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.FluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.FluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.FluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.FlxFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.FlxFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.FlxFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.HardwareFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.HardwareFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.HardwareFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.KryofluxFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.KryofluxFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.KryofluxFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.ScpFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.ScpFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.ScpFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.TestPatternFluxSourceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.TestPatternFluxSourceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.TestPatternFluxSourceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$TrackdataProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$TrackdataProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$TrackdataProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$TrackdataProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$TrackdataProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$TrackdataProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.D64InputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.D64InputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.D64InputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.D88InputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.D88InputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.D88InputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.DimInputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.DimInputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.DimInputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.DiskCopyInputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.DiskCopyInputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.DiskCopyInputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.FdiInputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.FdiInputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.FdiInputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImageReaderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImageReaderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImageReaderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImdInputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImdInputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImdInputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImgInputOutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImgInputOutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.ImgInputOutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.Jv3InputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.Jv3InputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.Jv3InputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.NfdInputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.NfdInputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.NfdInputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.NsiInputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.NsiInputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.NsiInputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.Td0InputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.Td0InputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagereader.Td0InputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.D64OutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.D64OutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.D64OutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.D88OutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.D88OutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.D88OutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.DiskCopyOutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.DiskCopyOutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.DiskCopyOutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImageWriterProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImageWriterProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImageWriterProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$DataRate", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$DataRate$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$RecordingMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$RecordingMode$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$DataRate", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$DataRate$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$RecordingMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$RecordingMode$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.NsiOutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.NsiOutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.NsiOutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.RawOutputProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.RawOutputProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.imagewriter.RawOutputProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.macintosh.MacintoshDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.macintosh.MacintoshDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.macintosh.MacintoshDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.macintosh.MacintoshEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.macintosh.MacintoshEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.macintosh.MacintoshEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$ChecksumType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$ChecksumType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$EccType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$EccType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$EccType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$EccType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.mx.MxDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.mx.MxDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.mx.MxDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.northstar.NorthstarDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.northstar.NorthstarDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.northstar.NorthstarDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.northstar.NorthstarEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.northstar.NorthstarEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.northstar.NorthstarEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.rolandd20.RolandD20DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.rolandd20.RolandD20DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.rolandd20.RolandD20DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.smaky6.Smaky6DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.smaky6.Smaky6DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.smaky6.Smaky6DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tartu.TartuDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tartu.TartuDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tartu.TartuDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tartu.TartuEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tartu.TartuEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tartu.TartuEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tids990.Tids990DecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tids990.Tids990DecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tids990.Tids990DecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tids990.Tids990EncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tids990.Tids990EncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tids990.Tids990EncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.ApplesauceProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.ApplesauceProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.ApplesauceProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.GreaseweazleProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$BusType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$BusType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.UsbProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.UsbProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.UsbProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AcornDfsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$Flavour", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$Flavour$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AmigaFfsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AmigaFfsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AmigaFfsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AppledosProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AppledosProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.AppledosProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.Brother120FsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.Brother120FsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.Brother120FsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CbmfsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CbmfsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CbmfsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Location", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Location$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Location$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Padding", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Padding$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Padding$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FatFsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FatFsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FatFsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FilesystemProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FilesystemProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FilesystemProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FilesystemProto$FilesystemType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.FilesystemProto$FilesystemType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.LifProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.LifProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.LifProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.MacHfsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.MacHfsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.MacHfsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.MicrodosProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.MicrodosProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.MicrodosProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.PhileProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.PhileProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.PhileProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ProdosProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ProdosProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ProdosProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.RolandFsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.RolandFsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.RolandFsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.Smaky6FsProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.Smaky6FsProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.Smaky6FsProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ZDosProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ZDosProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ZDosProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ZDosProto$Location", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ZDosProto$Location$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.ZDosProto$Location$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$TrackdataProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$TrackdataProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$TrackdataProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.zilogmcz.ZilogMczDecoderProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.zilogmcz.ZilogMczDecoderProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.zilogmcz.ZilogMczDecoderProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractMessage", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractMessage$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractMessage$BuilderParent", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractMessageLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractMessageLite$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractMessageLite$Builder$LimitedInputStream", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractMessageLite$InternalOneOfEnum", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractParser", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AbstractProtobufList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AllocatedBuffer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AllocatedBuffer$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AllocatedBuffer$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Android", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Any", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Any$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Any$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.AnyProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Api", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Api$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Api$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ApiProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ArrayDecoders", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ArrayDecoders$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ArrayDecoders$Registers", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryReader", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryReader$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryReader$SafeHeapReader", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryWriter$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryWriter$SafeDirectWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryWriter$SafeHeapWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryWriter$UnsafeDirectWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BinaryWriter$UnsafeHeapWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BlockingRpcChannel", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BlockingService", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BoolValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BoolValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BoolValue$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BooleanArrayList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BufferAllocator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BufferAllocator$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteBufferWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteOutput", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$AbstractByteIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$ArraysByteArrayCopier", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$BoundedByteString", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$ByteArrayCopier", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$ByteIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$CodedBuilder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$LeafByteString", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$LiteralByteString", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$NioByteString", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$NioByteString$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$Output", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ByteString$SystemByteArrayCopier", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BytesValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BytesValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.BytesValue$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CanIgnoreReturnValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CheckReturnValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream$ArrayDecoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream$IterableDirectByteBufferDecoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream$StreamDecoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream$StreamDecoder$RefillCallback", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream$StreamDecoder$SkippedDataSink", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStream$UnsafeDirectNioDecoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStreamReader", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedInputStreamReader$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$AbstractBufferedEncoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$ArrayEncoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$ByteOutputEncoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$HeapNioEncoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$OutOfSpaceException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$OutputStreamEncoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$SafeDirectNioEncoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStream$UnsafeDirectNioEncoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStreamWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CodedOutputStreamWriter$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.CompileTimeConstant", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DebugFormat", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DebugFormat$LazyDebugOutput", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory$3", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory$IsInitializedCheckAnalyzer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory$IsInitializedCheckAnalyzer$Node", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory$IsInitializedCheckAnalyzer$StronglyConnectedComponent", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorMessageInfoFactory$OneofState", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$Edition", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$Edition$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$VerificationState", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$VerificationState$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnforceNamingStyle", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnforceNamingStyle$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnumType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnumType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$FieldPresence", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$FieldPresence$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$JsonFormat", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$JsonFormat$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$MessageEncoding", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$MessageEncoding$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$RepeatedFieldEncoding", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$RepeatedFieldEncoding$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$Utf8Validation", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$Utf8Validation$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$DefaultSymbolVisibility", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$DefaultSymbolVisibility$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$FeatureSetEditionDefault", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$FeatureSetEditionDefault$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$FeatureSetEditionDefault$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Label", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Label$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Type", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Type$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$CType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$CType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$EditionDefault", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$EditionDefault$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$EditionDefault$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$FeatureSupport", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$FeatureSupport$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$FeatureSupport$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$JSType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$JSType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionRetention", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionRetention$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionTargetType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionTargetType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions$OptimizeMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$FileOptions$OptimizeMode$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Semantic", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Semantic$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MessageOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MessageOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MessageOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions$IdempotencyLevel", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$MethodOptions$IdempotencyLevel$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$OneofOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceOptions", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceOptions$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$ServiceOptions$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SymbolVisibility", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$SymbolVisibility$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$Descriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$DescriptorValidationException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$EnumDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$EnumDescriptor$UnknownEnumValueReference", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$EnumValueDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$EnumValueDescriptor$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FieldDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FieldDescriptor$JavaType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FieldDescriptor$RedactionState", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FieldDescriptor$Type", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FileDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FileDescriptor$InternalDescriptorAssigner", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FileDescriptorTables", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FileDescriptorTables$PackageDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$FileDescriptorTables$SearchFilter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$GenericDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$MethodDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$OneofDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Descriptors$ServiceDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DiscardUnknownFieldsParser", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DiscardUnknownFieldsParser$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DoubleArrayList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DoubleValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DoubleValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DoubleValue$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Duration", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Duration$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Duration$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DurationProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DynamicMessage", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DynamicMessage$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DynamicMessage$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.DynamicMessage$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Empty", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Empty$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Empty$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.EmptyProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Enum", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Enum$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Enum$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.EnumValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.EnumValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.EnumValue$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExperimentalApi", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Extension", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Extension$ExtensionType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Extension$MessageType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistry", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistry$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistry$DescriptorIntPair", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistry$ExtensionInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistryFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistryLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistryLite$ExtensionClassHolder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionRegistryLite$ObjectIntPair", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionSchemaFull", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionSchemaFull$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionSchemaLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionSchemaLite$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ExtensionSchemas", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Field", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Field$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Field$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Field$Cardinality", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Field$Cardinality$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Field$Kind", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Field$Kind$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldInfo$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldInfo$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldMask", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldMask$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldMask$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldMaskProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldSet", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldSet$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldSet$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldSet$FieldDescriptorLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FieldType$Collection", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FloatArrayList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FloatValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FloatValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.FloatValue$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Generated", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedFile", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$Builder$BuilderParentImpl", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$CachedDescriptorRetriever", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtendableBuilder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$ExtensionSerializer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$ExtensionWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$FieldEntry", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$FieldEntryIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$NoOpExtensionSerializer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$ExtensionDescriptorRetriever", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$FieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$MapFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$OneofAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RealOneofAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedEnumFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedFieldAccessor$MethodInvoker", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedFieldAccessor$ReflectionInvoker", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedMessageFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularEnumFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularFieldAccessor$MethodInvoker", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularFieldAccessor$ReflectionInvoker", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularMessageFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularStringFieldAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SyntheticOneofAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$GeneratedExtension", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$GeneratedExtension$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessage$UnusedPrivateParameter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageInfoFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$DefaultInstanceBasedParser", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$ExtendableBuilder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$ExtendableMessage", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$ExtendableMessage$ExtensionWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$ExtensionDescriptor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$GeneratedExtension", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$MethodToInvoke", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageLite$SerializedForm", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$Builder$BuilderParentImpl", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$BuilderParent", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$ExtendableBuilder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$ExtendableMessage", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$ExtendableMessage$ExtensionWriter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$FieldAccessorTable", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratedMessageV3$UnusedPrivateParameter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratorNames", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.GeneratorNames$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.InlineMe", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Int32Value", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Int32Value$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Int32Value$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Int64Value", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Int64Value$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Int64Value$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.IntArrayList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$BooleanList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$DoubleList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$EnumLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$EnumLiteMap", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$EnumVerifier", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$FloatList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$IntList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$IntListAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$IntListAdapter$IntConverter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$ListAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$ListAdapter$Converter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$LongList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$MapAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$MapAdapter$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$MapAdapter$Converter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$MapAdapter$EntryAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$MapAdapter$IteratorAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$MapAdapter$SetAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Internal$ProtobufList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.InvalidProtocolBufferException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.InvalidProtocolBufferException$InvalidWireTypeException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.IterableByteBufferInputStream", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Java8Compatibility", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaEditionDefaults", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$NestInFileClass", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$NestInFileClass$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$Utf8Validation", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$Utf8Validation$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.JavaType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyField", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyField$LazyEntry", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyField$LazyIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyFieldLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyStringArrayList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyStringArrayList$ByteArrayListView", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyStringArrayList$ByteStringListView", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LazyStringList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LegacyUnredactedTextFormat", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ListFieldSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ListFieldSchemaFull", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ListFieldSchemaLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ListFieldSchemas", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ListValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ListValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ListValue$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.LongArrayList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ManifestSchemaFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ManifestSchemaFactory$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ManifestSchemaFactory$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ManifestSchemaFactory$CompositeMessageInfoFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapEntry", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapEntry$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapEntry$Metadata", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapEntry$Metadata$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapEntryLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapEntryLite$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapEntryLite$Metadata", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField$Converter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField$ImmutableMessageConverter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField$MutabilityAwareMap", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField$MutabilityAwareMap$MutabilityAwareCollection", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField$MutabilityAwareMap$MutabilityAwareIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField$MutabilityAwareMap$MutabilityAwareSet", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapField$StorageMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldBuilder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldBuilder$Converter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldReflectionAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldSchemaFull", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldSchemaLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MapFieldSchemas", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Message", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Message$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageInfoFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageLite$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageLiteToString", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageReflection", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageReflection$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageReflection$BuilderAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageReflection$ExtensionAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageReflection$ExtensionBuilderAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageReflection$MergeTarget", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageReflection$MergeTarget$ContainerType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageSchema$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MessageSetSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Method", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Method$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Method$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Mixin", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Mixin$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Mixin$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MutabilityOracle", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.MutabilityOracle$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.NewInstanceSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.NewInstanceSchemaFull", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.NewInstanceSchemaLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.NewInstanceSchemas", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.NullValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.NullValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.OneofInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Option", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Option$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Option$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Parser", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.PrimitiveNonBoxingCollection", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ProtoSyntax", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Protobuf", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ProtobufArrayList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ProtobufToStringOutput", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ProtobufToStringOutput$OutputMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ProtocolMessageEnum", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ProtocolStringList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RawMessageInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Reader", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilder$BuilderExternalList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilder$MessageExternalList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilder$MessageOrBuilderExternalList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilderV3", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilderV3$BuilderExternalList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilderV3$MessageExternalList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RepeatedFieldBuilderV3$MessageOrBuilderExternalList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RopeByteString", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RopeByteString$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RopeByteString$Balancer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RopeByteString$PieceIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RopeByteString$RopeInputStream", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RpcCallback", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RpcChannel", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RpcController", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RpcUtil", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RpcUtil$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RpcUtil$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RpcUtil$AlreadyCalledException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RuntimeVersion", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RuntimeVersion$ProtobufRuntimeVersionException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.RuntimeVersion$RuntimeDomain", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Schema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SchemaFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SchemaUtil", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Service", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.ServiceException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SingleFieldBuilder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SingleFieldBuilderV3", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SmallSortedMap", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SmallSortedMap$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SmallSortedMap$DescendingEntryIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SmallSortedMap$DescendingEntrySet", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SmallSortedMap$Entry", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SmallSortedMap$EntryIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SmallSortedMap$EntrySet", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SourceContext", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SourceContext$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SourceContext$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.SourceContextProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.StringValue", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.StringValue$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.StringValue$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Struct", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Struct$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Struct$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Struct$Builder$FieldsConverter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Struct$FieldsDefaultEntryHolder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.StructProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.StructuralMessageInfo", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.StructuralMessageInfo$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Syntax", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Syntax$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$InvalidEscapeSequenceException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$ParseException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Parser", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Parser$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Parser$SingularOverwritePolicy", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Parser$UnknownField", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Parser$UnknownField$Type", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Printer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Printer$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Printer$FieldReporterLevel", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Printer$MapEntryAdapter", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$TextGenerator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$Tokenizer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormat$UnknownFieldParseException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormatEscaper", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormatEscaper$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormatEscaper$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormatEscaper$ByteSequence", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormatParseInfoTree", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormatParseInfoTree$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TextFormatParseLocation", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Timestamp", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Timestamp$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Timestamp$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TimestampProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Type", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Type$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Type$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TypeProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TypeRegistry", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TypeRegistry$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.TypeRegistry$EmptyTypeRegistryHolder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UInt32Value", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UInt32Value$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UInt32Value$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UInt64Value", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UInt64Value$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UInt64Value$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UninitializedMessageException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSet", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSet$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSet$Field", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSet$Field$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSet$Parser", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSetLite", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSetLiteSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnknownFieldSetSchema", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnmodifiableLazyStringList", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnmodifiableLazyStringList$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnmodifiableLazyStringList$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnredactedDebugFormatForTest", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnsafeByteOperations", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnsafeUtil", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnsafeUtil$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnsafeUtil$Android32MemoryAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnsafeUtil$Android64MemoryAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnsafeUtil$JvmMemoryAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.UnsafeUtil$MemoryAccessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Utf8", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Utf8$DecodeUtil", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Utf8$Processor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Utf8$SafeProcessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Utf8$UnpairedSurrogateException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Utf8$UnsafeProcessor", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Value", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Value$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Value$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Value$KindCase", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat$FieldType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat$FieldType$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat$FieldType$2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat$FieldType$3", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat$FieldType$4", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat$JavaType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WireFormat$Utf8Validation", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.WrappersProto", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Writer", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.Writer$FieldOrder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorRequest", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorRequest$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorRequest$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$Feature", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$Feature$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$File", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$File$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$File$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$Version", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$Version$1", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.google.protobuf.compiler.PluginProtos$Version$Builder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.aeslanier.Aeslanier", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.agat.Agat", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.amiga.Amiga", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.apple2.Apple2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.Brother", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.brother.BrotherFormat", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.c64.C64", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.Common", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.Config", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ConfigTools", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.Drive", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.FluxSourceSinkType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.ImageReaderWriterType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.IndexMode", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.Layout", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.OptionApplicabilityHint", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.SupportStatus", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.Decoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.Decoder$RecordType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.Decoders", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.decoders.FluxDecoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.encoders.Encoder", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.encoders.Encoders", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.A2R", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.Crc", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.DriveType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.Fl2", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$AnyFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$DebugFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$EraseFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$ErrorFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$FrameHeader", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$MeasureSpeedFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$ReadFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$SeekFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$SetDriveFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$SpeedFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$VersionFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$Voltages", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$VoltagesFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxEngine$WriteFrame", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxFileVersion", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FluxMagic", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FmMfm", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.FormatType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.GreaseweazleUtils", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.external.Scp", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.f85.F85", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fb100.Fb100", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.EmptyFluxSourceIterator", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.fluxsource.Fluxsource", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.ibm.Ibm", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.macintosh.Macintosh", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.micropolis.Micropolis", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.mx.Mx", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.northstar.Northstar", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.rolandd20.Rolandd20", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.smaky6.Smaky6", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tartu.Tartu", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.tids990.Tids990", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.ApplesauceUsbDevice", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.FluxEngineUsbDevice", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.GreaseweazleUsbDevice", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.GreaseweazleUsbDevice$Version", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.RetryableUsbException", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.Serial", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.Usb", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.UsbDevice", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.UsbFactory", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.VoltageMeasurements", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.usb.Voltages", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.vfs.Vfs", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.victor9k.Victor9K", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.zilogmcz.Zilogmcz", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true + }, + { + "name": "com.cowlark.fluxengine.config.UsbFinder$DeviceType", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "allDeclaredMethods": true } ] diff --git a/java/com/cowlark/fluxengine/usb/Serial.java b/java/com/cowlark/fluxengine/usb/Serial.java index d54c8bc3..06e87404 100644 --- a/java/com/cowlark/fluxengine/usb/Serial.java +++ b/java/com/cowlark/fluxengine/usb/Serial.java @@ -67,7 +67,7 @@ public Bytes readBytes(int count) public int readByte() { - return readBytes(1).getByte(0); + return readBytes(1).getByte(0) & 0xff; } public void writeBytes(byte[] data) diff --git a/src/formats/_global_options.textpb b/src/formats/_global_options.textpb index b64b9093..f4b5f77a 100644 --- a/src/formats/_global_options.textpb +++ b/src/formats/_global_options.textpb @@ -64,7 +64,7 @@ option_group { option { name: "auto" - comment: 'Autodetect from hardware' + comment: 'Autodetect rotational speed from hardware' set_by_default: true config { From dc47f1e590e3650062117908f8bbc3a602ef1b69 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 19:59:56 +0200 Subject: [PATCH 142/192] Rework Bytes not to return a `byte`, as for some reason they're signed (!). --- java/com/cowlark/fluxengine/core/Bytes.java | 24 ++++++++++--------- .../fluxengine/usb/FluxEngineUsbDevice.java | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/java/com/cowlark/fluxengine/core/Bytes.java b/java/com/cowlark/fluxengine/core/Bytes.java index 9123f299..ab6f6fb9 100644 --- a/java/com/cowlark/fluxengine/core/Bytes.java +++ b/java/com/cowlark/fluxengine/core/Bytes.java @@ -84,14 +84,15 @@ public boolean isEmpty() @Override public Byte get(int offset) { - return getByte(offset); + return (byte) getByte(offset); } - /* Fast, allocation-free byte access for hot paths (avoids Byte boxing). */ - public byte getByte(int offset) + /* Fast, allocation-free byte access for hot paths (avoids Byte boxing). + * Returns the value as an unsigned int (0..255). */ + public int getByte(int offset) { boundsCheck(offset); - return storage.data[low + offset]; + return storage.data[low + offset] & 0xff; } @Override @@ -104,12 +105,13 @@ public Byte set(int offset, Byte value) return old; } - /* Fast, allocation-free byte write for hot paths (avoids Byte boxing). */ - public void setByte(int offset, byte value) + /* Fast, allocation-free byte write for hot paths (avoids Byte boxing). + * Accepts an unsigned int (0..255). */ + public void setByte(int offset, int value) { boundsCheck(offset); detach(); - storage.data[low + offset] = value; + storage.data[low + offset] = (byte) value; } public byte[] toByteArray() @@ -138,7 +140,7 @@ public Object[] toArray() { Object[] result = new Object[size()]; for (int i = 0; i < size(); i++) - result[i] = getByte(i); + result[i] = (byte) getByte(i); return result; } @@ -150,7 +152,7 @@ public T[] toArray(T[] a) if (a.length < n) a = (T[]) Arrays.copyOf(a, n, a.getClass()); for (int i = 0; i < n; i++) - a[i] = (T) Byte.valueOf(getByte(i)); + a[i] = (T) Byte.valueOf((byte) getByte(i)); if (a.length > n) a[n] = null; return a; @@ -541,7 +543,7 @@ public boolean removeAll(Collection c) boolean changed = false; for (int i = size() - 1; i >= 0; i--) { - if (c.contains(getByte(i))) + if (c.contains((byte) getByte(i))) { remove(i); changed = true; @@ -556,7 +558,7 @@ public boolean retainAll(Collection c) boolean changed = false; for (int i = size() - 1; i >= 0; i--) { - if (!c.contains(getByte(i))) + if (!c.contains((byte) getByte(i))) { remove(i); changed = true; diff --git a/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java b/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java index 44bd46ef..b0993232 100644 --- a/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java +++ b/java/com/cowlark/fluxengine/usb/FluxEngineUsbDevice.java @@ -169,7 +169,7 @@ private void usbDataSend(Bytes bytes) int len = Math.min(bytes.size() - ptr, MAX_TRANSFER); byte[] data = new byte[len]; for (int i = 0; i < len; i++) - data[i] = bytes.getByte(ptr + i); + data[i] = (byte) bytes.getByte(ptr + i); try { dataOut.syncSubmit(data); From eab5e985e6d3854dcec6ce0e1fa185eed8174538 Mon Sep 17 00:00:00 2001 From: David Given Date: Sun, 9 Aug 2026 23:05:14 +0200 Subject: [PATCH 143/192] Refactor the algorithm code (still doesn't work). --- .../cowlark/fluxengine/algorithms/BUILD.bazel | 4 + .../cowlark/fluxengine/algorithms/Common.java | 61 --- .../fluxengine/algorithms/Operation.java | 146 +++++++ .../{Reader.java => ReadOperation.java} | 358 ++++++++---------- .../fluxengine/algorithms/WriteOperation.java | 208 ++++++++++ .../cowlark/fluxengine/algorithms/Writer.java | 253 ------------- java/com/cowlark/fluxengine/cli/Command.java | 10 +- .../fluxengine/cli/RawwriteCommand.java | 18 +- .../cowlark/fluxengine/cli/ReadCommand.java | 22 +- .../cowlark/fluxengine/cli/WriteCommand.java | 66 ++-- .../core/SupplierOfAutocloseable.java | 41 ++ java/com/cowlark/fluxengine/data/Disk.java | 2 +- .../fluxengine/fluxsink/FluxSinkFactory.java | 7 +- .../fluxengine/fluxsource/FluxSource.java | 7 +- .../fluxengine/imagereader/ImageReader.java | 7 +- .../fluxengine/imagewriter/ImageWriter.java | 8 +- .../cowlark/fluxengine/algorithms/BUILD.bazel | 22 +- .../fluxengine/algorithms/CommonTest.java | 92 +---- ...ReaderTest.java => ReadOperationTest.java} | 56 +-- .../algorithms/WriteOperationTest.java | 69 ++++ .../fluxengine/algorithms/WriterTest.java | 146 ------- .../com/cowlark/fluxengine/core/BUILD.bazel | 10 + .../core/SupplierOfAutocloseableTest.java | 131 +++++++ 23 files changed, 904 insertions(+), 840 deletions(-) create mode 100644 java/com/cowlark/fluxengine/algorithms/Operation.java rename java/com/cowlark/fluxengine/algorithms/{Reader.java => ReadOperation.java} (78%) create mode 100644 java/com/cowlark/fluxengine/algorithms/WriteOperation.java delete mode 100644 java/com/cowlark/fluxengine/algorithms/Writer.java create mode 100644 java/com/cowlark/fluxengine/core/SupplierOfAutocloseable.java rename javatests/com/cowlark/fluxengine/algorithms/{ReaderTest.java => ReadOperationTest.java} (71%) create mode 100644 javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java delete mode 100644 javatests/com/cowlark/fluxengine/algorithms/WriterTest.java create mode 100644 javatests/com/cowlark/fluxengine/core/SupplierOfAutocloseableTest.java diff --git a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel index f6445053..5dd64690 100644 --- a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -6,6 +6,7 @@ java_library( name = "algorithms", srcs = glob(["*.java"]), deps = [ + "//java/com/cowlark/fluxengine/arch", "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", @@ -15,8 +16,11 @@ java_library( "//java/com/cowlark/fluxengine/encoders", "//java/com/cowlark/fluxengine/fluxsink", "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/imagereader", "//java/com/cowlark/fluxengine/imagewriter", "//java/com/cowlark/fluxengine/usb", "@com_google_protobuf//java/core", + "@maven//:com_google_guava_guava", + "@maven//:org_apache_commons_commons_lang3", ], ) diff --git a/java/com/cowlark/fluxengine/algorithms/Common.java b/java/com/cowlark/fluxengine/algorithms/Common.java index 4394eeaa..199c8a8d 100644 --- a/java/com/cowlark/fluxengine/algorithms/Common.java +++ b/java/com/cowlark/fluxengine/algorithms/Common.java @@ -1,78 +1,17 @@ package com.cowlark.fluxengine.algorithms; -import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.FluxEngineException; -import com.cowlark.fluxengine.core.LogMessage; -import com.cowlark.fluxengine.core.Logger; import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.fluxsource.FluxSource; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; -import com.cowlark.fluxengine.usb.UsbDevice; -import com.cowlark.fluxengine.usb.UsbFactory; import java.util.HashMap; import java.util.Map; class Common { - static double getRotationalPeriodFromConfig(ConfigProto config) - { - return config.getDrive().getRotationalPeriodMs() * 1e6; - } - - static double measureDiskRotation(ConfigProto config) - { - Logger.log(new LogMessage.BeginSpeedOperationLogMessage()); - - double oneRevolution = getRotationalPeriodFromConfig(config); - if (oneRevolution == 0) - { - UsbDevice device = UsbFactory.reconnect(config); - device.setDrive( - config.getDrive().getDrive(), - config.getDrive().getHighDensity(), - config.getDrive().getIndexMode().getNumber()); - - Logger.log(new LogMessage.BeginOperationLogMessage("Measuring drive rotational speed")); - int retries = 5; - do - { - oneRevolution = device.getRotationalPeriod(config.getDrive().getHardSectorCount()); - retries--; - } while ((oneRevolution == 0) && (retries > 0)); - Logger.log(new LogMessage.EndOperationLogMessage("")); - } - - if (oneRevolution == 0) - throw new FluxEngineException("Failed\nIs a disk in the drive?"); - - Logger.log(new LogMessage.EndSpeedOperationLogMessage(oneRevolution)); - return oneRevolution; - } - static void testForEmergencyStop() { } - static void adjustTrackOnError(FluxSource fluxSource, int baseTrack, ConfigProto config) - { - switch (config.getDrive().getErrorBehaviour()) - { - case NOTHING: - break; - - case RECALIBRATE: - fluxSource.recalibrate(); - break; - - case JIGGLE: - if (baseTrack > 0) - fluxSource.seek(baseTrack - 1); - else - fluxSource.seek(baseTrack + 1); - break; - } - } - static class FluxSourceIteratorHolder { private final FluxSource fluxSource; diff --git a/java/com/cowlark/fluxengine/algorithms/Operation.java b/java/com/cowlark/fluxengine/algorithms/Operation.java new file mode 100644 index 00000000..61f5b581 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/Operation.java @@ -0,0 +1,146 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.arch.Arch; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.core.SupplierOfAutocloseable; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.imagereader.ImageReader; +import com.cowlark.fluxengine.imagewriter.ImageWriter; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; + +public abstract class Operation implements AutoCloseable +{ + private final ConfigProto configProto; + private double diskRotationalPeriodNs; + private Supplier diskLayoutSupplier; + private SupplierOfAutocloseable fluxSourceSupplier; + private SupplierOfAutocloseable fluxSinkFactorySupplier; + private SupplierOfAutocloseable usbDeviceSupplier; + private Supplier decoderSupplier; + private Supplier encoderSupplier; + private SupplierOfAutocloseable imageReaderSupplier; + private SupplierOfAutocloseable imageWriterSupplier; + + public Operation(ConfigProto configProto) + { + this.configProto = configProto; + diskLayoutSupplier = Suppliers.memoize(() -> new DiskLayout(configProto)); + fluxSourceSupplier = new SupplierOfAutocloseable(() -> FluxSource.create(configProto)); + fluxSinkFactorySupplier = + new SupplierOfAutocloseable(() -> FluxSinkFactory.create(configProto)); + usbDeviceSupplier = new SupplierOfAutocloseable(() -> UsbFactory.connect(configProto)); + decoderSupplier = Suppliers.memoize(() -> Arch.createDecoder(configProto)); + encoderSupplier = Suppliers.memoize(() -> Arch.createEncoder(configProto)); + imageWriterSupplier = new SupplierOfAutocloseable(() -> ImageWriter.create(configProto)); + imageReaderSupplier = new SupplierOfAutocloseable(() -> ImageReader.create(configProto)); + } + + @Override + public void close() throws Exception + { + fluxSourceSupplier.close(); + fluxSinkFactorySupplier.close(); + usbDeviceSupplier.close(); + imageWriterSupplier.close(); + imageReaderSupplier.close(); + } + + public ConfigProto getConfig() + { + return configProto; + } + + public DiskLayout getDiskLayout() + { + return diskLayoutSupplier.get(); + } + + public FluxSource getFluxSource() + { + return fluxSourceSupplier.get(); + } + + public FluxSinkFactory getFluxSinkFactory() + { + return fluxSinkFactorySupplier.get(); + } + + public Decoder getDecoder() + { + return decoderSupplier.get(); + } + + public Encoder getEncoder() + { + return encoderSupplier.get(); + } + + public ImageReader getImageReader() + { + return imageReaderSupplier.get(); + } + + public ImageWriter getImageWriter() + { + return imageWriterSupplier.get(); + } + + public double getDiskRotationalPeriodNs() + { + if (diskRotationalPeriodNs != 0) + return diskRotationalPeriodNs; + diskRotationalPeriodNs = configProto.getDrive().getRotationalPeriodMs() * 1e6; + if (diskRotationalPeriodNs == 0) + { + UsbDevice device = UsbFactory.reconnect(configProto); + + Logger.log(new LogMessage.BeginOperationLogMessage("Measuring drive rotational speed")); + Logger.log(new LogMessage.BeginSpeedOperationLogMessage()); + + int retries = 5; + do + { + diskRotationalPeriodNs = + device.getRotationalPeriod(configProto.getDrive().getHardSectorCount()); + retries--; + } while ((diskRotationalPeriodNs == 0) && (retries > 0)); + Logger.log(new LogMessage.EndOperationLogMessage("")); + } + + if (diskRotationalPeriodNs == 0) + throw new FluxEngineException("Failed\nIs a disk in the drive?"); + + Logger.log(new LogMessage.EndSpeedOperationLogMessage(diskRotationalPeriodNs)); + return diskRotationalPeriodNs; + } + + void adjustTrackOnError(int baseTrack) + { + switch (getConfig().getDrive().getErrorBehaviour()) + { + case NOTHING: + break; + + case RECALIBRATE: + getFluxSource().recalibrate(); + break; + + case JIGGLE: + if (baseTrack > 0) + getFluxSource().seek(baseTrack - 1); + else + getFluxSource().seek(baseTrack + 1); + break; + } + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/Reader.java b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java similarity index 78% rename from java/com/cowlark/fluxengine/algorithms/Reader.java rename to java/com/cowlark/fluxengine/algorithms/ReadOperation.java index 8a85038e..6eda40bf 100644 --- a/java/com/cowlark/fluxengine/algorithms/Reader.java +++ b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.algorithms; +import com.cowlark.fluxengine.algorithms.Common.FluxSourceIteratorHolder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.LogMessage.BeginOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; @@ -10,7 +11,6 @@ import com.cowlark.fluxengine.core.Utils; import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.data.Disk; -import com.cowlark.fluxengine.data.DiskLayout; import com.cowlark.fluxengine.data.Fluxmap; import com.cowlark.fluxengine.data.Image; import com.cowlark.fluxengine.data.LogicalLocation; @@ -18,10 +18,8 @@ import com.cowlark.fluxengine.data.PhysicalTrackLayout; import com.cowlark.fluxengine.data.Sector; import com.cowlark.fluxengine.data.Track; -import com.cowlark.fluxengine.decoders.Decoder; import com.cowlark.fluxengine.fluxsink.FluxSink; import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; -import com.cowlark.fluxengine.fluxsource.FluxSource; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import com.cowlark.fluxengine.imagewriter.ImageWriter; import java.util.ArrayList; @@ -34,38 +32,173 @@ /** * Disk read/write algorithms, ported from lib/algorithms/readerwriter.cc. */ -public final class Reader +public class ReadOperation extends Operation { - private Reader() + public ReadOperation(ConfigProto configProto) { + super(configProto); } - public static void readDiskCommand(ConfigProto config, - DiskLayout diskLayout, - FluxSource fluxSource, - Decoder decoder, - Disk disk) + static CombinationResult combineRecordAndSectors(List tracks, + LogicalTrackLayout ltl) + { + CombinationResult cr = new CombinationResult(); + cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; + List trackSectors = new ArrayList<>(); + + /* Add the sectors which were there. */ + + for (Track track : tracks) + trackSectors.addAll(track.allSectors); + + /* Add the sectors which should be there. */ + + for (int sectorId : ltl.diskSectorOrder) + { + Sector sector = + new Sector(new LogicalLocation(ltl.logicalCylinder, ltl.logicalHead, sectorId)); + + sector.status = Sector.Status.MISSING; + sector.physicalLocation = new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); + trackSectors.add(sector); + } + + /* Deduplicate. */ + + cr.sectors = collectSectors(trackSectors); + if (cr.sectors.isEmpty()) + cr.result = BadSectorsState.HAS_BAD_SECTORS; + for (Sector sector : cr.sectors) + if (sector.status != Sector.Status.OK) + cr.result = BadSectorsState.HAS_BAD_SECTORS; + + return cr; + } + + private ReadGroupResult readGroup(FluxSourceIteratorHolder fluxSourceIteratorHolder, + LogicalTrackLayout ltl, + List tracks) + { + ReadGroupResult rgr = new ReadGroupResult(); + rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; + + /* Before doing the read, look to see if we already have the necessary + * sectors. */ + + { + CombinationResult cr = combineRecordAndSectors(tracks, ltl); + rgr.combinedSectors = cr.sectors; + if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) + { + /* We have all necessary sectors, so can stop here. */ + rgr.result = ReadResult.GOOD_READ; + if (getConfig().getDecoder().getSkipUnnecessaryTracks()) + return rgr; + } + } + + for (int offset = 0; offset < ltl.groupSize; offset += getDiskLayout().headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + PhysicalTrackLayout ptl = getDiskLayout().layoutByPhysicalLocation.get(new CylinderHead( + physicalCylinder, + physicalHead)); + + /* Do the physical read. */ + + Logger.log(new BeginReadOperationLogMessage(physicalCylinder, physicalHead)); + + FluxSourceIterator fluxSourceIterator = + fluxSourceIteratorHolder.getIterator(physicalCylinder, physicalHead); + if (!fluxSourceIterator.hasNext()) + continue; + + Fluxmap fluxmap = fluxSourceIterator.next(); + Logger.log(new EndReadOperationLogMessage()); + Logger.logf("%d ms in %d bytes", (int) (fluxmap.duration() / 1e6), fluxmap.bytes()); + + Track flux = getDecoder().decodeToSectors(fluxmap, ptl); + flux.normalisedSectors = collectSectors(flux.allSectors); + tracks.add(flux); + + /* Decode what we've got so far. */ + + CombinationResult cr = combineRecordAndSectors(tracks, ltl); + rgr.combinedSectors = cr.sectors; + if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) + { + /* We have all necessary sectors, so can stop here. */ + rgr.result = ReadResult.GOOD_READ; + if (getConfig().getDecoder().getSkipUnnecessaryTracks()) + break; + } else if (fluxSourceIterator.hasNext()) + { + /* The flux source claims it can do more reads, so mark this + * group as being retryable. */ + rgr.result = ReadResult.BAD_AND_CAN_RETRY; + } + } + + return rgr; + } + + private void readAndDecodeTrack(LogicalTrackLayout ltl, + List tracks, + List combinedSectors) + { + FluxSourceIteratorHolder fluxSourceIteratorHolder = + new FluxSourceIteratorHolder(getFluxSource()); + int retriesRemaining = getConfig().getDecoder().getRetries(); + for (; ; ) + { + ReadGroupResult rgr = readGroup(fluxSourceIteratorHolder, ltl, tracks); + combinedSectors.clear(); + combinedSectors.addAll(rgr.combinedSectors); + if (rgr.result == ReadResult.GOOD_READ) + break; + if (rgr.result == ReadResult.BAD_AND_CAN_NOT_RETRY) + { + Logger.logf("no more data; giving up"); + break; + } + + if (retriesRemaining == 0) + { + Logger.logf("giving up"); + break; + } + + if (getFluxSource().isHardware()) + { + adjustTrackOnError(ltl.physicalCylinder); + Logger.logf("retrying; %d retries remaining", retriesRemaining); + retriesRemaining--; + } + } + } + + public void run(Disk disk) { FluxSinkFactory outputFluxSinkFactory = null; - if (config.getDecoder().hasCopyFluxTo()) + if (getConfig().getDecoder().hasCopyFluxTo()) outputFluxSinkFactory = - FluxSinkFactory.create(config, config.getDecoder().getCopyFluxTo()); + FluxSinkFactory.create(getConfig(), getConfig().getDecoder().getCopyFluxTo()); Map> tracksByLogicalLocation = new HashMap<>(); for (Map.Entry entry : disk.tracksByPhysicalLocation.entries()) { Track track = entry.getValue(); tracksByLogicalLocation.computeIfAbsent( - new CylinderHead(track.ltl.logicalCylinder, track.ltl.logicalHead), + new CylinderHead( + track.ltl.logicalCylinder, + track.ltl.logicalHead), k -> new ArrayList<>()).add(track); } Logger.log(new BeginOperationLogMessage("Reading and decoding disk")); - if (fluxSource.isHardware()) - disk.rotationalPeriod = Common.measureDiskRotation(config); - else - disk.rotationalPeriod = Common.getRotationalPeriodFromConfig(config); + disk.rotationalPeriodNs = getDiskRotationalPeriodNs(); try (FluxSink outputFluxSink = outputFluxSinkFactory != null ? outputFluxSinkFactory.create() : @@ -73,12 +206,12 @@ public static void readDiskCommand(ConfigProto config, { int index = 0; for (Map.Entry entry : - diskLayout.layoutByLogicalLocation.entrySet()) + getDiskLayout().layoutByLogicalLocation.entrySet()) { CylinderHead logicalLocation = entry.getKey(); LogicalTrackLayout ltl = entry.getValue(); Logger.log(new OperationProgressLogMessage( - index * 100 / diskLayout.layoutByLogicalLocation.size())); + index * 100 / getDiskLayout().layoutByLogicalLocation.size())); index++; Common.testForEmergencyStop(); @@ -87,14 +220,7 @@ public static void readDiskCommand(ConfigProto config, logicalLocation, k -> new ArrayList<>()); List trackSectors = new ArrayList<>(); - readAndDecodeTrack( - config, - diskLayout, - fluxSource, - decoder, - ltl, - trackFluxes, - trackSectors); + readAndDecodeTrack(ltl, trackFluxes, trackSectors); /* Replace all tracks on the disk by the new combined set. */ @@ -125,7 +251,7 @@ public static void readDiskCommand(ConfigProto config, data.fluxmap); } - if (config.getDecoder().getDumpRecords()) + if (getConfig().getDecoder().getDumpRecords()) { List sortedRecords = new ArrayList<>(); for (Track data : trackFluxes) @@ -144,7 +270,7 @@ public static void readDiskCommand(ConfigProto config, } } - if (config.getDecoder().getDumpSectors()) + if (getConfig().getDecoder().getDumpSectors()) { List sectors = collectSectors(trackSectors, false); sectors.sort(Comparator.comparing((Sector s) -> s.location.logicalCylinder()) @@ -183,21 +309,6 @@ public static void readDiskCommand(ConfigProto config, Logger.log(new EndOperationLogMessage("Read complete")); } - public static void readDiskCommand(ConfigProto config, - DiskLayout diskLayout, - FluxSource fluxSource, - Decoder decoder, - ImageWriter writer) - { - Disk disk = new Disk(); - readDiskCommand(config, diskLayout, fluxSource, decoder, disk); - - writer.printMap(disk.image); - if (config.getDecoder().hasWriteCsvTo()) - writer.writeCsv(disk.image, config.getDecoder().getWriteCsvTo()); - writer.writeImage(disk.image); - } - /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a * good and bad version of the same sector, the bad version is dropped). */ static List collectSectors(List trackSectors, boolean collapseConflicts) @@ -267,163 +378,26 @@ private static Sector copySector(Sector sector) return s; } - static CombinationResult combineRecordAndSectors(List tracks, - Decoder decoder, - LogicalTrackLayout ltl) - { - CombinationResult cr = new CombinationResult(); - cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; - List trackSectors = new ArrayList<>(); - - /* Add the sectors which were there. */ - - for (Track track : tracks) - trackSectors.addAll(track.allSectors); - - /* Add the sectors which should be there. */ - - for (int sectorId : ltl.diskSectorOrder) - { - Sector sector = - new Sector(new LogicalLocation(ltl.logicalCylinder, ltl.logicalHead, sectorId)); - - sector.status = Sector.Status.MISSING; - sector.physicalLocation = new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); - trackSectors.add(sector); - } - - /* Deduplicate. */ - - cr.sectors = collectSectors(trackSectors); - if (cr.sectors.isEmpty()) - cr.result = BadSectorsState.HAS_BAD_SECTORS; - for (Sector sector : cr.sectors) - if (sector.status != Sector.Status.OK) - cr.result = BadSectorsState.HAS_BAD_SECTORS; - - return cr; - } - - static ReadGroupResult readGroup(DiskLayout diskLayout, - Common.FluxSourceIteratorHolder fluxSourceIteratorHolder, - LogicalTrackLayout ltl, - List tracks, - Decoder decoder, - ConfigProto config) + public Disk run() { - ReadGroupResult rgr = new ReadGroupResult(); - rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; - - /* Before doing the read, look to see if we already have the necessary - * sectors. */ - - { - CombinationResult cr = combineRecordAndSectors(tracks, decoder, ltl); - rgr.combinedSectors = cr.sectors; - if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) - { - /* We have all necessary sectors, so can stop here. */ - rgr.result = ReadResult.GOOD_READ; - if (config.getDecoder().getSkipUnnecessaryTracks()) - return rgr; - } - } - - for (int offset = 0; offset < ltl.groupSize; offset += diskLayout.headWidth) - { - int physicalCylinder = ltl.physicalCylinder + offset; - int physicalHead = ltl.physicalHead; - PhysicalTrackLayout ptl = diskLayout.layoutByPhysicalLocation.get(new CylinderHead( - physicalCylinder, - physicalHead)); - - /* Do the physical read. */ - - Logger.log(new BeginReadOperationLogMessage(physicalCylinder, physicalHead)); - - FluxSourceIterator fluxSourceIterator = - fluxSourceIteratorHolder.getIterator(physicalCylinder, physicalHead); - if (!fluxSourceIterator.hasNext()) - continue; - - Fluxmap fluxmap = fluxSourceIterator.next(); - Logger.log(new EndReadOperationLogMessage()); - Logger.logf("%d ms in %d bytes", (int) (fluxmap.duration() / 1e6), fluxmap.bytes()); - - Track flux = decoder.decodeToSectors(fluxmap, ptl); - flux.normalisedSectors = collectSectors(flux.allSectors); - tracks.add(flux); - - /* Decode what we've got so far. */ - - CombinationResult cr = combineRecordAndSectors(tracks, decoder, ltl); - rgr.combinedSectors = cr.sectors; - if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) - { - /* We have all necessary sectors, so can stop here. */ - rgr.result = ReadResult.GOOD_READ; - if (config.getDecoder().getSkipUnnecessaryTracks()) - break; - } else if (fluxSourceIterator.hasNext()) - { - /* The flux source claims it can do more reads, so mark this - * group as being retryable. */ - rgr.result = ReadResult.BAD_AND_CAN_RETRY; - } - } - - return rgr; - } - - private static void readAndDecodeTrack(ConfigProto config, - DiskLayout diskLayout, - FluxSource fluxSource, - Decoder decoder, - LogicalTrackLayout ltl, - List tracks, - List combinedSectors) - { - if (fluxSource.isHardware()) - Common.measureDiskRotation(config); - - Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = - new Common.FluxSourceIteratorHolder(fluxSource); - int retriesRemaining = config.getDecoder().getRetries(); - for (; ; ) - { - ReadGroupResult rgr = - readGroup(diskLayout, fluxSourceIteratorHolder, ltl, tracks, decoder, config); - combinedSectors.clear(); - combinedSectors.addAll(rgr.combinedSectors); - if (rgr.result == ReadResult.GOOD_READ) - break; - if (rgr.result == ReadResult.BAD_AND_CAN_NOT_RETRY) - { - Logger.logf("no more data; giving up"); - break; - } + Disk disk = new Disk(); + run(disk); - if (retriesRemaining == 0) - { - Logger.logf("giving up"); - break; - } + ImageWriter writer = getImageWriter(); + writer.printMap(disk.image); + if (getConfig().getDecoder().hasWriteCsvTo()) + writer.writeCsv(disk.image, getConfig().getDecoder().getWriteCsvTo()); + writer.writeImage(disk.image); - if (fluxSource.isHardware()) - { - Common.adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); - Logger.logf("retrying; %d retries remaining", retriesRemaining); - retriesRemaining--; - } - } + return disk; } - static enum ReadResult + enum ReadResult { GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY } - static enum BadSectorsState + enum BadSectorsState { HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS } diff --git a/java/com/cowlark/fluxengine/algorithms/WriteOperation.java b/java/com/cowlark/fluxengine/algorithms/WriteOperation.java new file mode 100644 index 00000000..2590d8dc --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/WriteOperation.java @@ -0,0 +1,208 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import java.util.Collection; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public class WriteOperation extends ReadOperation +{ + public WriteOperation(ConfigProto configProto) + { + super(configProto); + } + + private void writeTracks(Function producer, + Predicate verifier, + List logicalLocations) + { + Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + + getDiskRotationalPeriodNs(); + try (FluxSink fluxSink = getFluxSinkFactory().create()) + { + int index = 0; + for (CylinderHead ch : logicalLocations) + { + Logger.log(new LogMessage.OperationProgressLogMessage( + index * 100 / logicalLocations.size())); + index++; + + Common.testForEmergencyStop(); + + LogicalTrackLayout ltl = getDiskLayout().layoutByLogicalLocation.get(ch); + int retriesRemaining = getConfig().getDecoder().getRetries(); + for (; ; ) + { + for (int offset = 0; offset < ltl.groupSize; + offset += getDiskLayout().headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + + Logger.log(new LogMessage.BeginWriteOperationLogMessage( + physicalCylinder, + ltl.physicalHead)); + + boolean erase = false; + if (offset == getConfig().getDrive().getGroupOffset()) + { + Fluxmap fluxmap = producer.apply(ltl); + if (fluxmap == null) + erase = true; + else + { + fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); + Logger.logf( + "writing %d ms in %d bytes", + (int) (fluxmap.duration() / 1e6), + fluxmap.bytes()); + } + } else + erase = true; + + if (erase) + { + /* Erase this track rather than writing. */ + + Fluxmap blank = new Fluxmap(); + fluxSink.addFlux(physicalCylinder, physicalHead, blank); + Logger.logf("erased"); + } + + Logger.log(new LogMessage.EndWriteOperationLogMessage()); + } + + if (verifier.test(ltl)) + break; + + if (retriesRemaining == 0) + throw new FluxEngineException("fatal error on write"); + + Logger.logf("retrying; %d retries remaining", retriesRemaining); + retriesRemaining--; + } + } + } + + Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); + } + + // private static void writeTracks(ImmutableList chs) + // { + // writeTracks( + // ltl -> { + // ImmutableList sectors = getEncoder().collectSectors(ltl, image); + // return encoder.encode(ltl, sectors, image); + // }, ltl -> true, chs); + // } + + // private void writeTracksAndVerify(ConfigProto config, + // DiskLayout diskLayout, + // FluxSinkFactory fluxSinkFactory, + // Encoder encoder, + // FluxSource fluxSource, + // Decoder decoder, + // Image image, + // List chs) + // { + // writeTracks( + // config, diskLayout, fluxSinkFactory, ltl -> { + // List sectors = encoder.collectSectors(ltl, image); + // return encoder.encode(ltl, sectors, image); + // }, ltl -> { + // Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = + // new Common.FluxSourceIteratorHolder(fluxSource); + // List tracks = new ArrayList<>(); + // ReadOperation.ReadGroupResult rgr = + // readGroup(fluxSourceIteratorHolder, ltl, tracks); + + // if (rgr.result != ReadOperation.ReadResult.GOOD_READ) + // { + // adjustTrackOnError(ltl.physicalCylinder); + // Logger.logf("bad read"); + // return false; + // } + + // Image wanted = new Image(); + // for (Sector sector : encoder.collectSectors(ltl, image)) + // wanted.put( + // sector.location.logicalCylinder(), + // sector.location.logicalHead(), + // sector.location.logicalSector()).data = sector.data; + + // for (Sector sector : rgr.combinedSectors) + // { + // Sector s = wanted.get( + // sector.location.logicalCylinder(), + // sector.location.logicalHead(), + // sector.location.logicalSector()); + // if (s == null) + // { + // Logger.logf("spurious sector on verify"); + // return false; + // } + // if (!s.data.equals(sector.data.slice(0, s.data.size()))) + // { + // Logger.logf("data mismatch on verify"); + // return false; + // } + // wanted.erase( + // sector.location.logicalCylinder(), + // sector.location.logicalHead(), + // sector.location.logicalSector()); + // } + // if (!wanted.empty()) + // { + // Logger.logf("missing sector on verify"); + // return false; + // } + // return true; + // }, chs); + // } + + public void writeDiskCommand(Image image, Collection physicalLocations) + { + // ImmutableSet chs = getDiskLayout().layoutByLogicalLocation + // .keySet(); + // if (fluxSource != null && decoder != null) + // writeTracksAndVerify( + // config, + // diskLayout, + // fluxSinkFactory, + // encoder, + // fluxSource, + // decoder, + // image, + // chs); + // else + // writeTracks(config, diskLayout, fluxSinkFactory, encoder, image, chs); + } + + public void writeDiskCommand(Image image) + { + writeDiskCommand(image, getDiskLayout().layoutByLogicalLocation.keySet()); + } + + public void writeRawDiskCommand() + { + writeTracks( + ltl -> { + FluxSourceIterator iterator = + getFluxSource().readFlux(ltl.physicalCylinder, ltl.physicalHead); + if (!iterator.hasNext()) + return null; + return iterator.next(); + }, ltl -> true, getDiskLayout().logicalLocations); + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/Writer.java b/java/com/cowlark/fluxengine/algorithms/Writer.java deleted file mode 100644 index c535d2ba..00000000 --- a/java/com/cowlark/fluxengine/algorithms/Writer.java +++ /dev/null @@ -1,253 +0,0 @@ -package com.cowlark.fluxengine.algorithms; - -import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.FluxEngineException; -import com.cowlark.fluxengine.core.LogMessage; -import com.cowlark.fluxengine.core.Logger; -import com.cowlark.fluxengine.data.CylinderHead; -import com.cowlark.fluxengine.data.DiskLayout; -import com.cowlark.fluxengine.data.Fluxmap; -import com.cowlark.fluxengine.data.Image; -import com.cowlark.fluxengine.data.LogicalTrackLayout; -import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.data.Track; -import com.cowlark.fluxengine.decoders.Decoder; -import com.cowlark.fluxengine.encoders.Encoder; -import com.cowlark.fluxengine.fluxsink.FluxSink; -import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; -import com.cowlark.fluxengine.fluxsource.FluxSource; -import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; - -/** - * Writes images to disks, ported from lib/algorithms/readerwriter.cc. - */ -public final class Writer -{ - private Writer() - { - } - - private static void writeTracks(ConfigProto config, - DiskLayout diskLayout, - FluxSinkFactory fluxSinkFactory, - Function producer, - Predicate verifier, - List logicalLocations) - { - Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); - - if (fluxSinkFactory.isHardware()) - Common.measureDiskRotation(config); - try (FluxSink fluxSink = fluxSinkFactory.create()) - { - int index = 0; - for (CylinderHead ch : logicalLocations) - { - Logger.log(new LogMessage.OperationProgressLogMessage( - index * 100 / logicalLocations.size())); - index++; - - Common.testForEmergencyStop(); - - LogicalTrackLayout ltl = diskLayout.layoutByLogicalLocation.get(ch); - int retriesRemaining = config.getDecoder().getRetries(); - for (; ; ) - { - for (int offset = 0; offset < ltl.groupSize; offset += diskLayout.headWidth) - { - int physicalCylinder = ltl.physicalCylinder + offset; - int physicalHead = ltl.physicalHead; - - Logger.log(new LogMessage.BeginWriteOperationLogMessage( - physicalCylinder, - ltl.physicalHead)); - - boolean erase = false; - if (offset == config.getDrive().getGroupOffset()) - { - Fluxmap fluxmap = producer.apply(ltl); - if (fluxmap == null) - erase = true; - else - { - fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); - Logger.logf( - "writing %d ms in %d bytes", - (int) (fluxmap.duration() / 1e6), - fluxmap.bytes()); - } - } else - erase = true; - - if (erase) - { - /* Erase this track rather than writing. */ - - Fluxmap blank = new Fluxmap(); - fluxSink.addFlux(physicalCylinder, physicalHead, blank); - Logger.logf("erased"); - } - - Logger.log(new LogMessage.EndWriteOperationLogMessage()); - } - - if (verifier.test(ltl)) - break; - - if (retriesRemaining == 0) - throw new FluxEngineException("fatal error on write"); - - Logger.logf("retrying; %d retries remaining", retriesRemaining); - retriesRemaining--; - } - } - } - - Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); - } - - private static void writeTracks(ConfigProto config, - DiskLayout diskLayout, - FluxSinkFactory fluxSinkFactory, - Encoder encoder, - Image image, - List chs) - { - writeTracks( - config, diskLayout, fluxSinkFactory, ltl -> { - List sectors = encoder.collectSectors(ltl, image); - return encoder.encode(ltl, sectors, image); - }, ltl -> true, chs); - } - - private static void writeTracksAndVerify(ConfigProto config, - DiskLayout diskLayout, - FluxSinkFactory fluxSinkFactory, - Encoder encoder, - FluxSource fluxSource, - Decoder decoder, - Image image, - List chs) - { - writeTracks( - config, diskLayout, fluxSinkFactory, ltl -> { - List sectors = encoder.collectSectors(ltl, image); - return encoder.encode(ltl, sectors, image); - }, ltl -> { - Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = - new Common.FluxSourceIteratorHolder(fluxSource); - List tracks = new ArrayList<>(); - Reader.ReadGroupResult rgr = Reader.readGroup( - diskLayout, - fluxSourceIteratorHolder, - ltl, - tracks, - decoder, - config); - - if (rgr.result != Reader.ReadResult.GOOD_READ) - { - Common.adjustTrackOnError(fluxSource, ltl.physicalCylinder, config); - Logger.logf("bad read"); - return false; - } - - Image wanted = new Image(); - for (Sector sector : encoder.collectSectors(ltl, image)) - wanted.put( - sector.location.logicalCylinder(), - sector.location.logicalHead(), - sector.location.logicalSector()).data = sector.data; - - for (Sector sector : rgr.combinedSectors) - { - Sector s = wanted.get( - sector.location.logicalCylinder(), - sector.location.logicalHead(), - sector.location.logicalSector()); - if (s == null) - { - Logger.logf("spurious sector on verify"); - return false; - } - if (!s.data.equals(sector.data.slice(0, s.data.size()))) - { - Logger.logf("data mismatch on verify"); - return false; - } - wanted.erase( - sector.location.logicalCylinder(), - sector.location.logicalHead(), - sector.location.logicalSector()); - } - if (!wanted.empty()) - { - Logger.logf("missing sector on verify"); - return false; - } - return true; - }, chs); - } - - public static void writeDiskCommand(ConfigProto config, - DiskLayout diskLayout, - Image image, - Encoder encoder, - FluxSinkFactory fluxSinkFactory, - Decoder decoder, - FluxSource fluxSource, - List physicalLocations) - { - List chs = new ArrayList<>(diskLayout.layoutByLogicalLocation.keySet()); - if (fluxSource != null && decoder != null) - writeTracksAndVerify( - config, - diskLayout, - fluxSinkFactory, - encoder, - fluxSource, - decoder, - image, - chs); - else - writeTracks(config, diskLayout, fluxSinkFactory, encoder, image, chs); - } - - public static void writeDiskCommand(ConfigProto config, - DiskLayout diskLayout, - Image image, - Encoder encoder, - FluxSinkFactory fluxSinkFactory, - Decoder decoder, - FluxSource fluxSource) - { - writeDiskCommand( - config, - diskLayout, - image, - encoder, - fluxSinkFactory, - decoder, - fluxSource, - new ArrayList<>(diskLayout.layoutByLogicalLocation.keySet())); - } - - public static void writeRawDiskCommand(ConfigProto config, - DiskLayout diskLayout, - FluxSource fluxSource, - FluxSinkFactory fluxSinkFactory) - { - writeTracks( - config, diskLayout, fluxSinkFactory, ltl -> { - FluxSourceIterator iterator = - fluxSource.readFlux(ltl.physicalCylinder, ltl.physicalHead); - if (!iterator.hasNext()) - return null; - return iterator.next(); - }, ltl -> true, diskLayout.logicalLocations); - } -} diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 1995d811..46b26904 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -81,7 +81,13 @@ static boolean dispatch(Map> commands, Supplier supplier = commands.get(args.get(index)); if (supplier != null) { - supplier.get().run(ImmutableList.copyOf(args.subList(index + 1, args.size()))); + try + { + supplier.get().run(ImmutableList.copyOf(args.subList(index + 1, args.size()))); + } catch (Exception e) + { + throw new RuntimeException(e); + } return true; } } @@ -96,6 +102,6 @@ static Supplier stub(String name, String help) String getHelp(); - void run(ImmutableList args); + void run(ImmutableList args) throws Exception; } diff --git a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java index ed2a7b18..c8527400 100644 --- a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java @@ -2,7 +2,7 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import com.cowlark.fluxengine.algorithms.Writer; +import com.cowlark.fluxengine.algorithms.WriteOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; @@ -10,9 +10,6 @@ import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; -import com.cowlark.fluxengine.data.DiskLayout; -import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; -import com.cowlark.fluxengine.fluxsource.FluxSource; import com.google.common.collect.ImmutableList; /** @@ -53,7 +50,7 @@ private void setErase() } @Override - public void run(ImmutableList args) + public void run(ImmutableList args) throws Exception { ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); if (sourceFluxFlag.isSet()) @@ -67,10 +64,9 @@ public void run(ImmutableList args) if (config.getFluxSource().getType() == FLUXTYPE_DRIVE) throw new FluxEngineException("you can't use rawwrite to read from hardware"); - FluxSource fluxSource = FluxSource.create(config); - FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); - DiskLayout diskLayout = new DiskLayout(config); - - Writer.writeRawDiskCommand(config, diskLayout, fluxSource, fluxSinkFactory); + try (WriteOperation operation = new WriteOperation(config)) + { + operation.writeRawDiskCommand(); + } } -} +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index ee3e75d9..614647cf 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -2,18 +2,13 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import com.cowlark.fluxengine.algorithms.Reader; -import com.cowlark.fluxengine.arch.Arch; +import com.cowlark.fluxengine.algorithms.ReadOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; -import com.cowlark.fluxengine.data.DiskLayout; -import com.cowlark.fluxengine.decoders.Decoder; -import com.cowlark.fluxengine.fluxsource.FluxSource; -import com.cowlark.fluxengine.imagewriter.ImageWriter; import com.google.common.collect.ImmutableList; /** @@ -61,10 +56,15 @@ public void run(ImmutableList args) if (config.getDecoder().getCopyFluxTo().getType() == FLUXTYPE_DRIVE) throw new FluxEngineException("you cannot copy flux to a hardware device"); - DiskLayout diskLayout = new DiskLayout(config); - FluxSource fluxSource = FluxSource.create(config); - Decoder decoder = Arch.createDecoder(config); - ImageWriter writer = ImageWriter.create(config); - Reader.readDiskCommand(config, diskLayout, fluxSource, decoder, writer); + try + { + try (ReadOperation operation = new ReadOperation(config)) + { + operation.run(); + } + } catch (Exception e) + { + throw new RuntimeException(e); + } } } diff --git a/java/com/cowlark/fluxengine/cli/WriteCommand.java b/java/com/cowlark/fluxengine/cli/WriteCommand.java index 491082d0..e3db93a5 100644 --- a/java/com/cowlark/fluxengine/cli/WriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/WriteCommand.java @@ -1,20 +1,11 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.algorithms.Writer; -import com.cowlark.fluxengine.arch.Arch; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.flags.ActionFlag; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; -import com.cowlark.fluxengine.data.DiskLayout; -import com.cowlark.fluxengine.data.Image; -import com.cowlark.fluxengine.decoders.Decoder; -import com.cowlark.fluxengine.encoders.Encoder; -import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; -import com.cowlark.fluxengine.fluxsource.FluxSource; -import com.cowlark.fluxengine.imagereader.ImageReader; import com.google.common.collect.ImmutableList; /** @@ -51,7 +42,7 @@ public String getHelp() } @Override - public void run(ImmutableList args) + public void run(ImmutableList args) throws Exception { ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); if (sourceImageFlag.isSet()) @@ -61,32 +52,33 @@ public void run(ImmutableList args) builder.withFluxSink(dest); ConfigProto config = builder.build(); - ImageReader reader = ImageReader.create(config); - Image image = reader.readImage(); - - config = config.toBuilder().mergeFrom(reader.getExtraConfig()).build(); - - DiskLayout diskLayout = new DiskLayout(config); - Encoder encoder = Arch.createEncoder(config); - FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); - - Decoder decoder = null; - FluxSource verificationFluxSource = null; - if (config.hasDecoder() && fluxSinkFactory.isHardware() && verify) - { - decoder = Arch.createDecoder(config); - ConfigBuilder verifyBuilder = new ConfigBuilder().fromFlags(args, flags); - verifyBuilder.withFluxSource(dest); - verificationFluxSource = FluxSource.create(verifyBuilder.build()); - } - - Writer.writeDiskCommand( - config, - diskLayout, - image, - encoder, - fluxSinkFactory, - decoder, - verificationFluxSource); + // try (var operation = new WriteOperation(config)){ + // ImageReader reader = operation.getImageReader(); + // Image image = reader.readImage(); + // + // config = config.toBuilder().mergeFrom(reader.getExtraConfig()).build(); + // + // DiskLayout diskLayout = new DiskLayout(config); + // Encoder encoder = Arch.createEncoder(config); + // FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); + // + // Decoder decoder = null; + // FluxSource verificationFluxSource = null; + // if (config.hasDecoder() && fluxSinkFactory.isHardware() && verify) + // { + // decoder = Arch.createDecoder(config); + // ConfigBuilder verifyBuilder = new ConfigBuilder().fromFlags(args, flags); + // verifyBuilder.withFluxSource(dest); + // verificationFluxSource = FluxSource.create(verifyBuilder.build()); + // } + // + // Writer.writeDiskCommand( + // config, + // diskLayout, + // image, + // encoder, + // fluxSinkFactory, + // decoder, + // verificationFluxSource); } } diff --git a/java/com/cowlark/fluxengine/core/SupplierOfAutocloseable.java b/java/com/cowlark/fluxengine/core/SupplierOfAutocloseable.java new file mode 100644 index 00000000..5230f98d --- /dev/null +++ b/java/com/cowlark/fluxengine/core/SupplierOfAutocloseable.java @@ -0,0 +1,41 @@ +package com.cowlark.fluxengine.core; + +import java.util.function.Supplier; + +public class SupplierOfAutocloseable implements Supplier, AutoCloseable +{ + private final Supplier delegate; + public T instance; + private boolean closed = false; + + public SupplierOfAutocloseable(Supplier delegate) + { + if (delegate == null) + throw new IllegalArgumentException("Delegate supplier cannot be null"); + this.delegate = delegate; + } + + @Override + public T get() + { + synchronized (this) + { + if (closed) + throw new IllegalStateException("Supplier has already been closed"); + if (instance == null) + instance = delegate.get(); + return instance; + } + } + + @Override + public void close() throws Exception + { + synchronized (this) + { + if ((instance != null) && !closed) + instance.close(); + closed = true; + } + } +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/data/Disk.java b/java/com/cowlark/fluxengine/data/Disk.java index b6959e10..400fcdec 100644 --- a/java/com/cowlark/fluxengine/data/Disk.java +++ b/java/com/cowlark/fluxengine/data/Disk.java @@ -18,7 +18,7 @@ public class Disk public Image image = null; /* 0 if the period is unknown (e.g. if this Disk was made from an image). */ - public double rotationalPeriod = 0; + public double rotationalPeriodNs = 0; public Disk() { diff --git a/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java index 920336c3..61344636 100644 --- a/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java +++ b/java/com/cowlark/fluxengine/fluxsink/FluxSinkFactory.java @@ -6,7 +6,7 @@ /** * Factory for creating flux sinks, ported from lib/fluxsink/fluxsink.h. */ -public abstract class FluxSinkFactory +public abstract class FluxSinkFactory implements AutoCloseable { public static FluxSinkFactory create(ConfigProto config) { @@ -54,6 +54,11 @@ public static Fl2FluxSinkFactory createFl2FluxSinkFactory(String filename, return new Fl2FluxSinkFactory(filename, fullConfig); } + @Override + public void close() throws Exception + { + } + /* Creates a writer object. */ public abstract FluxSink create(); diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index 02248935..f28c4782 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -8,7 +8,7 @@ /** * A source of flux data, ported from lib/fluxsource/fluxsource.{h,cc}. */ -public abstract class FluxSource +public abstract class FluxSource implements AutoCloseable { public static FluxSource create(ConfigProto config) { @@ -51,6 +51,11 @@ private static FluxSource notImplemented(String name) throw new FluxEngineException(name + " flux source is not implemented yet"); } + @Override + public void close() throws Exception + { + } + /* Adjusts the current configuration based on the contents of this flux source. */ public void adjustConfig(ConfigBuilder configBuilder) { diff --git a/java/com/cowlark/fluxengine/imagereader/ImageReader.java b/java/com/cowlark/fluxengine/imagereader/ImageReader.java index 6c02640a..2b4d0924 100644 --- a/java/com/cowlark/fluxengine/imagereader/ImageReader.java +++ b/java/com/cowlark/fluxengine/imagereader/ImageReader.java @@ -8,7 +8,7 @@ * Reads sector images from disk, ported from * lib/imagereader/imagereader.{h,cc}. */ -public abstract class ImageReader +public abstract class ImageReader implements AutoCloseable { protected final ImageReaderProto config; protected final ConfigProto fullConfig; @@ -68,6 +68,11 @@ public static ImageReader create(ConfigProto fullConfig, ImageReaderProto config } } + @Override + public void close() throws Exception + { + } + /* Returns any extra config the image might want to contribute. */ public ConfigProto getExtraConfig() { diff --git a/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java index 12d1ecc8..ce060be1 100644 --- a/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java +++ b/java/com/cowlark/fluxengine/imagewriter/ImageWriter.java @@ -15,7 +15,7 @@ * Writes sector images to disk, ported from * lib/imagewriter/imagewriter.{h,cc}. */ -public abstract class ImageWriter +public abstract class ImageWriter implements AutoCloseable { protected final ImageWriterProto config; @@ -24,6 +24,12 @@ public ImageWriter(ImageWriterProto config) this.config = config; } + @Override + public void close() throws Exception + { + + } + public static ImageWriter create(ConfigProto config) { if (!config.hasImageWriter()) diff --git a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel index 2b337efc..1c9e2302 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -3,47 +3,43 @@ load("@rules_java//java:defs.bzl", "java_test") package(default_visibility = ["//visibility:public"]) java_test( - name = "WriterTest", - srcs = ["WriterTest.java"], + name = "ReadOperationTest", + srcs = ["ReadOperationTest.java"], deps = [ "//java/com/cowlark/fluxengine/algorithms", - "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", - "//java/com/cowlark/fluxengine/encoders", - "//java/com/cowlark/fluxengine/fluxsink", - "//java/com/cowlark/fluxengine/fluxsource", + "@maven//:com_google_guava_guava", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], ) java_test( - name = "CommonTest", - srcs = ["CommonTest.java"], + name = "WriteOperationTest", + srcs = ["WriteOperationTest.java"], deps = [ "//java/com/cowlark/fluxengine/algorithms", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", - "//java/com/cowlark/fluxengine/fluxsource", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], ) java_test( - name = "ReaderTest", - srcs = ["ReaderTest.java"], + name = "CommonTest", + srcs = ["CommonTest.java"], deps = [ "//java/com/cowlark/fluxengine/algorithms", + "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", - "//java/com/cowlark/fluxengine/decoders", - "@maven//:com_google_guava_guava", + "//java/com/cowlark/fluxengine/fluxsource", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], diff --git a/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java b/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java index f0458470..15337821 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java @@ -2,13 +2,9 @@ import static com.google.common.truth.Truth.assertThat; -import com.cowlark.fluxengine.config.ConfigBuilder; -import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.fluxsource.FluxSource; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; -import java.util.ArrayList; -import java.util.List; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -16,31 +12,8 @@ @RunWith(JUnit4.class) public class CommonTest { - private static ConfigProto makeConfig() - { - return new ConfigBuilder() - .set("usb.serial", "test-serial") - .set("drive.rotational_period_ms", "200") - .build(); - } - private static class RecordingFluxSource extends FluxSource { - final List seeks = new ArrayList<>(); - int recalibrations = 0; - - @Override - public void recalibrate() - { - recalibrations++; - } - - @Override - public void seek(int cylinder) - { - seeks.add(cylinder); - } - @Override public FluxSourceIterator readFlux(int cylinder, int head) { @@ -48,65 +21,6 @@ public FluxSourceIterator readFlux(int cylinder, int head) } } - @Test - public void getRotationalPeriodFromConfig() - { - assertThat(Common.getRotationalPeriodFromConfig(makeConfig())).isEqualTo(200e6); - } - - @Test - public void measureDiskRotationUsesConfigPeriod() - { - /* The period is set in the config, so no hardware access happens. */ - assertThat(Common.measureDiskRotation(makeConfig())).isEqualTo(200e6); - } - - @Test - public void adjustTrackOnErrorNothing() - { - ConfigProto config = new ConfigBuilder() - .set("usb.serial", "test-serial") - .set("drive.error_behaviour", "NOTHING") - .build(); - RecordingFluxSource fluxSource = new RecordingFluxSource(); - - Common.adjustTrackOnError(fluxSource, 5, config); - - assertThat(fluxSource.recalibrations).isEqualTo(0); - assertThat(fluxSource.seeks).isEmpty(); - } - - @Test - public void adjustTrackOnErrorRecalibrate() - { - ConfigProto config = new ConfigBuilder() - .set("usb.serial", "test-serial") - .set("drive.error_behaviour", "RECALIBRATE") - .build(); - RecordingFluxSource fluxSource = new RecordingFluxSource(); - - Common.adjustTrackOnError(fluxSource, 5, config); - - assertThat(fluxSource.recalibrations).isEqualTo(1); - assertThat(fluxSource.seeks).isEmpty(); - } - - @Test - public void adjustTrackOnErrorJiggle() - { - ConfigProto config = new ConfigBuilder() - .set("usb.serial", "test-serial") - .set("drive.error_behaviour", "JIGGLE") - .build(); - RecordingFluxSource fluxSource = new RecordingFluxSource(); - - Common.adjustTrackOnError(fluxSource, 5, config); - assertThat(fluxSource.seeks).containsExactly(4); - - Common.adjustTrackOnError(fluxSource, 0, config); - assertThat(fluxSource.seeks).containsExactly(4, 1); - } - @Test public void fluxSourceIteratorHolderCaches() { @@ -145,4 +59,10 @@ public com.cowlark.fluxengine.data.Fluxmap next() assertThat(it3).isNotSameInstanceAs(it1); assertThat(new CylinderHead(1, 0)).isEqualTo(new CylinderHead(1, 0)); } + + @Test + public void testForEmergencyStopDoesNotThrow() + { + Common.testForEmergencyStop(); + } } diff --git a/javatests/com/cowlark/fluxengine/algorithms/ReaderTest.java b/javatests/com/cowlark/fluxengine/algorithms/ReadOperationTest.java similarity index 71% rename from javatests/com/cowlark/fluxengine/algorithms/ReaderTest.java rename to javatests/com/cowlark/fluxengine/algorithms/ReadOperationTest.java index b7d83af5..4aa2c9f8 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/ReaderTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/ReadOperationTest.java @@ -16,14 +16,24 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) -public class ReaderTest +public class ReadOperationTest { private static LogicalTrackLayout makeLtl() { ImmutableList order = ImmutableList.of(0, 1, 2); return new LogicalTrackLayout( - 0, 0, 1, 0, 0, 3, 256, order, order, order, - ImmutableMap.of(0, 0, 1, 1, 2, 2), ImmutableMap.of(0, 0, 1, 1, 2, 2)); + 0, + 0, + 1, + 0, + 0, + 3, + 256, + order, + order, + order, + ImmutableMap.of(0, 0, 1, 1, 2, 2), + ImmutableMap.of(0, 0, 1, 1, 2, 2)); } private static Sector makeSector(int sectorId, Sector.Status status) @@ -43,7 +53,7 @@ public void collectSectorsDeduplicatesOkAndBad() sectors.add(makeSector(1, Sector.Status.OK)); sectors.add(makeSector(2, Sector.Status.BAD_CHECKSUM)); - List result = Reader.collectSectors(sectors, true); + List result = ReadOperation.collectSectors(sectors, true); assertThat(result).hasSize(3); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -58,7 +68,7 @@ public void collectSectorsPrefersOkOverMissing() sectors.add(makeSector(0, Sector.Status.MISSING)); sectors.add(makeSector(0, Sector.Status.OK)); - List result = Reader.collectSectors(sectors); + List result = ReadOperation.collectSectors(sectors); assertThat(result).hasSize(1); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -73,13 +83,13 @@ public void collectSectorsConflictWhenBothOkDifferentData() b.data = Bytes.of(2); /* collapseConflicts=false keeps both as CONFLICT. */ - List result = Reader.collectSectors(List.of(a, b), false); + List result = ReadOperation.collectSectors(List.of(a, b), false); assertThat(result).hasSize(2); assertThat(result.get(0).status).isEqualTo(Sector.Status.CONFLICT); assertThat(result.get(1).status).isEqualTo(Sector.Status.CONFLICT); /* collapseConflicts=true collapses to a single CONFLICT. */ - List collapsed = Reader.collectSectors(List.of(a, b), true); + List collapsed = ReadOperation.collectSectors(List.of(a, b), true); assertThat(collapsed).hasSize(1); assertThat(collapsed.get(0).status).isEqualTo(Sector.Status.CONFLICT); } @@ -92,7 +102,7 @@ public void collectSectorsOkDataSameCollapses() Sector b = makeSector(0, Sector.Status.OK); b.data = Bytes.of(1); - List result = Reader.collectSectors(List.of(a, b), false); + List result = ReadOperation.collectSectors(List.of(a, b), false); assertThat(result).hasSize(1); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -106,18 +116,18 @@ public void combineRecordAndSectorsFillsMissing() track.allSectors = new ArrayList<>(); track.allSectors.add(makeSector(0, Sector.Status.OK)); - Reader.CombinationResult cr = Reader.combineRecordAndSectors( - List.of(track), null, makeLtl()); + ReadOperation.CombinationResult cr = + ReadOperation.combineRecordAndSectors(List.of(track), makeLtl()); - assertThat(cr.result).isEqualTo(Reader.BadSectorsState.HAS_BAD_SECTORS); + assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); - Sector s0 = cr.sectors.stream() - .filter(s -> s.location.logicalSector() == 0).findFirst().get(); - Sector s1 = cr.sectors.stream() - .filter(s -> s.location.logicalSector() == 1).findFirst().get(); - Sector s2 = cr.sectors.stream() - .filter(s -> s.location.logicalSector() == 2).findFirst().get(); + Sector s0 = + cr.sectors.stream().filter(s -> s.location.logicalSector() == 0).findFirst().get(); + Sector s1 = + cr.sectors.stream().filter(s -> s.location.logicalSector() == 1).findFirst().get(); + Sector s2 = + cr.sectors.stream().filter(s -> s.location.logicalSector() == 2).findFirst().get(); assertThat(s0.status).isEqualTo(Sector.Status.OK); assertThat(s1.status).isEqualTo(Sector.Status.MISSING); assertThat(s2.status).isEqualTo(Sector.Status.MISSING); @@ -132,10 +142,10 @@ public void combineRecordAndSectorsNoBadWhenAllPresent() track.allSectors.add(makeSector(1, Sector.Status.OK)); track.allSectors.add(makeSector(2, Sector.Status.OK)); - Reader.CombinationResult cr = Reader.combineRecordAndSectors( - List.of(track), null, makeLtl()); + ReadOperation.CombinationResult cr = + ReadOperation.combineRecordAndSectors(List.of(track), makeLtl()); - assertThat(cr.result).isEqualTo(Reader.BadSectorsState.HAS_NO_BAD_SECTORS); + assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_NO_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); for (Sector sector : cr.sectors) assertThat(sector.status).isEqualTo(Sector.Status.OK); @@ -144,10 +154,10 @@ public void combineRecordAndSectorsNoBadWhenAllPresent() @Test public void combineRecordAndSectorsEmptyTrackIsBad() { - Reader.CombinationResult cr = Reader.combineRecordAndSectors( - List.of(), null, makeLtl()); + ReadOperation.CombinationResult cr = + ReadOperation.combineRecordAndSectors(List.of(), makeLtl()); - assertThat(cr.result).isEqualTo(Reader.BadSectorsState.HAS_BAD_SECTORS); + assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); for (Sector sector : cr.sectors) assertThat(sector.status).isEqualTo(Sector.Status.MISSING); diff --git a/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java b/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java new file mode 100644 index 00000000..421d200b --- /dev/null +++ b/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java @@ -0,0 +1,69 @@ +package com.cowlark.fluxengine.algorithms; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.data.DiskLayout; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class WriteOperationTest +{ + private static ConfigProto makeConfig() + { + return new ConfigBuilder().set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("layout.tracks", "1") + .set("layout.sides", "1") + .set("layout.layoutdata[0].sector_size", "256") + .set("layout.layoutdata[0].physical.start_sector", "0") + .set("layout.layoutdata[0].physical.count", "8") + .build(); + } + + @Test + public void getConfigReturnsConfiguredConfig() + { + ConfigProto config = makeConfig(); + WriteOperation operation = new WriteOperation(config); + + assertThat(operation.getConfig()).isSameInstanceAs(config); + } + + @Test + public void getDiskLayoutBuildsFromConfig() + { + WriteOperation operation = new WriteOperation(makeConfig()); + + DiskLayout diskLayout = operation.getDiskLayout(); + + assertThat(diskLayout).isNotNull(); + assertThat(diskLayout.logicalLocations).isNotEmpty(); + assertThat(diskLayout.layoutByLogicalLocation.size()).isEqualTo(1); + } + + @Test + public void getDiskLayoutIsMemoized() + { + WriteOperation operation = new WriteOperation(makeConfig()); + + assertThat(operation.getDiskLayout()).isSameInstanceAs(operation.getDiskLayout()); + } + + @Test + public void closeDoesNotThrowWhenNothingCreated() + { + WriteOperation operation = new WriteOperation(makeConfig()); + + try + { + operation.close(); + } catch (Exception e) + { + throw new AssertionError("close should not throw", e); + } + } +} diff --git a/javatests/com/cowlark/fluxengine/algorithms/WriterTest.java b/javatests/com/cowlark/fluxengine/algorithms/WriterTest.java deleted file mode 100644 index 335ea5f1..00000000 --- a/javatests/com/cowlark/fluxengine/algorithms/WriterTest.java +++ /dev/null @@ -1,146 +0,0 @@ -package com.cowlark.fluxengine.algorithms; - -import static com.google.common.truth.Truth.assertThat; - -import com.cowlark.fluxengine.config.ConfigBuilder; -import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.Bytes; -import com.cowlark.fluxengine.data.CylinderHead; -import com.cowlark.fluxengine.data.DiskLayout; -import com.cowlark.fluxengine.data.Fluxmap; -import com.cowlark.fluxengine.data.Image; -import com.cowlark.fluxengine.data.LogicalTrackLayout; -import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.encoders.Encoder; -import com.cowlark.fluxengine.fluxsink.FluxSink; -import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -@RunWith(JUnit4.class) -public class WriterTest -{ - private static class RecordingFluxSink extends FluxSink - { - final Map written = new HashMap<>(); - - @Override - public void addFlux(int track, int head, Fluxmap fluxmap) - { - written.put(new CylinderHead(track, head), fluxmap); - } - } - - private static class RecordingFluxSinkFactory extends FluxSinkFactory - { - final RecordingFluxSink sink = new RecordingFluxSink(); - - @Override - public FluxSink create() - { - return sink; - } - } - - private static class TestEncoder extends Encoder - { - final List encoded = new ArrayList<>(); - - @Override - public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) - { - encoded.add(ltl); - Fluxmap fluxmap = new Fluxmap(); - fluxmap.appendInterval(100); - fluxmap.appendPulse(); - return fluxmap; - } - } - - private static ConfigProto makeConfig() - { - return new ConfigBuilder().set("usb.serial", "test-serial") - .set("drive.rotational_period_ms", "200") - .set("layout.tracks", "1") - .set("layout.sides", "1") - .set("layout.layoutdata[0].sector_size", "256") - .set("layout.layoutdata[0].physical.start_sector", "0") - .set("layout.layoutdata[0].physical.count", "8") - .build(); - } - - private static Image makeImage() - { - Image image = new Image(); - for (int sectorId = 0; sectorId < 8; sectorId++) - { - Sector sector = image.put(0, 0, sectorId); - sector.status = Sector.Status.OK; - sector.data = Bytes.of(sectorId); - } - return image; - } - - @Test - public void writesAllLogicalLocations() - { - ConfigProto config = makeConfig(); - DiskLayout diskLayout = new DiskLayout(config); - Image image = makeImage(); - - RecordingFluxSinkFactory factory = new RecordingFluxSinkFactory(); - TestEncoder encoder = new TestEncoder(); - - Writer.writeDiskCommand(config, diskLayout, image, encoder, factory, null, null); - - assertThat(encoder.encoded).hasSize(1); - assertThat(encoder.encoded.get(0).logicalCylinder).isEqualTo(0); - assertThat(factory.sink.written.keySet()).containsExactly(new CylinderHead(0, 0)); - assertThat(factory.sink.written.get(new CylinderHead(0, 0)).bytes()).isGreaterThan(0); - } - - @Test - public void writesWithoutVerifyWhenNoSource() - { - ConfigProto config = makeConfig(); - DiskLayout diskLayout = new DiskLayout(config); - Image image = makeImage(); - - RecordingFluxSinkFactory factory = new RecordingFluxSinkFactory(); - TestEncoder encoder = new TestEncoder(); - - /* decoder/fluxSource are null, so no verification happens. */ - Writer.writeDiskCommand(config, diskLayout, image, encoder, factory, null, null); - - assertThat(factory.sink.written).hasSize(1); - } - - @Test - public void emptyImageThrows() - { - ConfigProto config = makeConfig(); - DiskLayout diskLayout = new DiskLayout(config); - Image image = new Image(); - - RecordingFluxSinkFactory factory = new RecordingFluxSinkFactory(); - TestEncoder encoder = new TestEncoder(); - - /* The encoder needs all sectors present in the image, so an empty - * image is an error. */ - org.junit.Assert.assertThrows( - com.cowlark.fluxengine.core.FluxEngineException.class, - () -> Writer.writeDiskCommand( - config, - diskLayout, - image, - encoder, - factory, - null, - null)); - } -} diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 19e3ef47..4087f905 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -84,3 +84,13 @@ java_test( "@maven//:junit_junit", ], ) + +java_test( + name = "SupplierOfAutocloseableTest", + srcs = ["SupplierOfAutocloseableTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) diff --git a/javatests/com/cowlark/fluxengine/core/SupplierOfAutocloseableTest.java b/javatests/com/cowlark/fluxengine/core/SupplierOfAutocloseableTest.java new file mode 100644 index 00000000..c1c16ef1 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/core/SupplierOfAutocloseableTest.java @@ -0,0 +1,131 @@ +package com.cowlark.fluxengine.core; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; + +import java.util.concurrent.atomic.AtomicInteger; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class SupplierOfAutocloseableTest +{ + private static final class TestCloseable implements AutoCloseable + { + final AtomicInteger closes = new AtomicInteger(); + + @Override + public void close() + { + closes.incrementAndGet(); + } + } + + @Test + public void nullDelegateThrows() + { + assertThrows(IllegalArgumentException.class, + () -> new SupplierOfAutocloseable(null)); + } + + @Test + public void getReturnsInstance() + { + TestCloseable delegate = new TestCloseable(); + SupplierOfAutocloseable supplier = + new SupplierOfAutocloseable<>(() -> delegate); + + assertThat(supplier.get()).isSameInstanceAs(delegate); + assertThat(supplier.instance).isSameInstanceAs(delegate); + } + + @Test + public void getMemoizesInstance() + { + AtomicInteger calls = new AtomicInteger(); + SupplierOfAutocloseable supplier = new SupplierOfAutocloseable<>(() -> + { + calls.incrementAndGet(); + return new TestCloseable(); + }); + + TestCloseable first = supplier.get(); + TestCloseable second = supplier.get(); + + assertThat(calls.get()).isEqualTo(1); + assertThat(second).isSameInstanceAs(first); + } + + @Test + public void getAfterCloseThrows() + { + SupplierOfAutocloseable supplier = + new SupplierOfAutocloseable<>(TestCloseable::new); + + assertThrows(Exception.class, () -> + { + supplier.close(); + supplier.get(); + }); + } + + @Test + public void closeClosesCreatedInstance() + { + TestCloseable delegate = new TestCloseable(); + SupplierOfAutocloseable supplier = + new SupplierOfAutocloseable<>(() -> delegate); + + supplier.get(); + + assertThat(delegate.closes.get()).isEqualTo(0); + try + { + supplier.close(); + } catch (Exception e) + { + throw new AssertionError("close should not throw", e); + } + assertThat(delegate.closes.get()).isEqualTo(1); + } + + @Test + public void closeDoesNotCloseNeverCreatedInstance() + { + TestCloseable delegate = new TestCloseable(); + SupplierOfAutocloseable supplier = + new SupplierOfAutocloseable<>(() -> delegate); + + try + { + supplier.close(); + } catch (Exception e) + { + throw new AssertionError("close should not throw", e); + } + + assertThat(delegate.closes.get()).isEqualTo(0); + } + + @Test + public void closeIsIdempotent() + { + TestCloseable delegate = new TestCloseable(); + SupplierOfAutocloseable supplier = + new SupplierOfAutocloseable<>(() -> delegate); + supplier.get(); + + try + { + supplier.close(); + supplier.close(); + } catch (Exception e) + { + throw new AssertionError("close should not throw", e); + } + + /* The instance is only closed once. */ + assertThat(delegate.closes.get()).isEqualTo(1); + } +} From 92529ac2b427696b143dc5f8b42bd6d3f12470df Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 00:54:56 +0200 Subject: [PATCH 144/192] Okay, the Greaseweazle is now sort-of trying to read disks. --- .../cowlark/fluxengine/algorithms/Common.java | 7 ++- .../fluxengine/algorithms/ReadOperation.java | 60 +++++++++++-------- .../fluxengine/algorithms/WriteOperation.java | 6 +- .../fluxengine/cli/ConvertCommand.java | 6 +- .../fluxengine/cli/InspectCommand.java | 7 ++- .../fluxengine/fluxsource/A2RFluxSource.java | 5 +- .../cowlark/fluxengine/fluxsource/BUILD.bazel | 11 +++- .../fluxsource/EraseFluxSource.java | 2 +- .../fluxengine/fluxsource/Fl2FluxSource.java | 5 +- .../fluxsource/FluxReadParameters.java | 14 +++++ .../fluxengine/fluxsource/FluxSource.java | 2 +- .../fluxsource/HardwareFluxSource.java | 13 ++-- .../fluxsource/KryofluxFluxSource.java | 4 +- .../fluxsource/TrivialFluxSource.java | 6 +- .../fluxsource/TrivialFluxSourceIterator.java | 10 ++-- .../fluxengine/algorithms/CommonTest.java | 14 +++-- .../fluxsource/A2RFluxSourceTest.java | 6 +- .../fluxsource/Fl2FluxSourceTest.java | 9 ++- .../fluxengine/fluxsource/FluxSourceTest.java | 6 +- .../fluxsource/HardwareFluxSourceTest.java | 11 +++- .../fluxsource/KryofluxFluxSourceTest.java | 7 ++- 21 files changed, 135 insertions(+), 76 deletions(-) create mode 100644 java/com/cowlark/fluxengine/fluxsource/FluxReadParameters.java diff --git a/java/com/cowlark/fluxengine/algorithms/Common.java b/java/com/cowlark/fluxengine/algorithms/Common.java index 199c8a8d..dc461102 100644 --- a/java/com/cowlark/fluxengine/algorithms/Common.java +++ b/java/com/cowlark/fluxengine/algorithms/Common.java @@ -2,6 +2,7 @@ import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import java.util.HashMap; import java.util.Map; @@ -22,13 +23,13 @@ static class FluxSourceIteratorHolder this.fluxSource = fluxSource; } - FluxSourceIterator getIterator(int physicalCylinder, int head) + FluxSourceIterator getIterator(FluxReadParameters parameters) { - CylinderHead key = new CylinderHead(physicalCylinder, head); + CylinderHead key = new CylinderHead(parameters.cylinder(), parameters.head()); FluxSourceIterator it = cache.get(key); if (it == null) { - it = fluxSource.readFlux(physicalCylinder, head); + it = fluxSource.readFlux(parameters); cache.put(key, it); } return it; diff --git a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java index 6eda40bf..b9906db5 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java @@ -20,6 +20,7 @@ import com.cowlark.fluxengine.data.Track; import com.cowlark.fluxengine.fluxsink.FluxSink; import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import com.cowlark.fluxengine.imagewriter.ImageWriter; import java.util.ArrayList; @@ -39,8 +40,30 @@ public ReadOperation(ConfigProto configProto) super(configProto); } - static CombinationResult combineRecordAndSectors(List tracks, - LogicalTrackLayout ltl) + + enum ReadResult + { + GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY + } + + enum BadSectorsState + { + HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS + } + + static class CombinationResult + { + BadSectorsState result; + List sectors; + } + + static class ReadGroupResult + { + ReadResult result; + List combinedSectors; + } + + static CombinationResult combineRecordAndSectors(List tracks, LogicalTrackLayout ltl) { CombinationResult cr = new CombinationResult(); cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; @@ -110,7 +133,15 @@ private ReadGroupResult readGroup(FluxSourceIteratorHolder fluxSourceIteratorHol Logger.log(new BeginReadOperationLogMessage(physicalCylinder, physicalHead)); FluxSourceIterator fluxSourceIterator = - fluxSourceIteratorHolder.getIterator(physicalCylinder, physicalHead); + fluxSourceIteratorHolder.getIterator(FluxReadParameters.builder() + .setCylinder(physicalCylinder) + .setHead(physicalHead) + .setSyncWithIndex(getConfig().getDrive().getSyncWithIndex()) + .setReadTimeNs(getConfig().getDrive().getRevolutions() * + getDiskRotationalPeriodNs()) + .setHardSectorThresholdNs(getConfig().getDrive() + .getHardSectorThresholdNs()) + .build()); if (!fluxSourceIterator.hasNext()) continue; @@ -391,27 +422,4 @@ public Disk run() return disk; } - - enum ReadResult - { - GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY - } - - enum BadSectorsState - { - HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS - } - - static class CombinationResult - { - BadSectorsState result; - List sectors; - } - - static class ReadGroupResult - { - ReadResult result; - List combinedSectors; - } - } diff --git a/java/com/cowlark/fluxengine/algorithms/WriteOperation.java b/java/com/cowlark/fluxengine/algorithms/WriteOperation.java index 2590d8dc..cb450210 100644 --- a/java/com/cowlark/fluxengine/algorithms/WriteOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/WriteOperation.java @@ -9,6 +9,7 @@ import com.cowlark.fluxengine.data.Image; import com.cowlark.fluxengine.data.LogicalTrackLayout; import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import java.util.Collection; import java.util.List; @@ -199,7 +200,10 @@ public void writeRawDiskCommand() writeTracks( ltl -> { FluxSourceIterator iterator = - getFluxSource().readFlux(ltl.physicalCylinder, ltl.physicalHead); + getFluxSource().readFlux(FluxReadParameters.builder() + .setCylinder(ltl.physicalCylinder) + .setHead(ltl.physicalHead) + .build()); if (!iterator.hasNext()) return null; return iterator.next(); diff --git a/java/com/cowlark/fluxengine/cli/ConvertCommand.java b/java/com/cowlark/fluxengine/cli/ConvertCommand.java index d4c449eb..b261c156 100644 --- a/java/com/cowlark/fluxengine/cli/ConvertCommand.java +++ b/java/com/cowlark/fluxengine/cli/ConvertCommand.java @@ -14,6 +14,7 @@ import com.cowlark.fluxengine.data.DiskLayout; import com.cowlark.fluxengine.fluxsink.FluxSink; import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; import com.cowlark.fluxengine.fluxsource.FluxSource; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import com.google.common.collect.ImmutableList; @@ -82,7 +83,10 @@ public void run(ImmutableList args) for (CylinderHead physicalLocation : diskLayout.physicalLocations) { FluxSourceIterator fi = - fluxSource.readFlux(physicalLocation.cylinder(), physicalLocation.head()); + fluxSource.readFlux(FluxReadParameters.builder() + .setCylinder(physicalLocation.cylinder()) + .setHead(physicalLocation.head()) + .build()); while (fi.hasNext()) fluxSink.addFlux(physicalLocation, fi.next()); } diff --git a/java/com/cowlark/fluxengine/cli/InspectCommand.java b/java/com/cowlark/fluxengine/cli/InspectCommand.java index 1968677d..c04ca3bc 100644 --- a/java/com/cowlark/fluxengine/cli/InspectCommand.java +++ b/java/com/cowlark/fluxengine/cli/InspectCommand.java @@ -22,6 +22,7 @@ import com.cowlark.fluxengine.decoders.DecoderProto; import com.cowlark.fluxengine.decoders.FluxDecoder; import com.cowlark.fluxengine.external.FmMfm; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; import com.cowlark.fluxengine.fluxsource.FluxSource; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import com.google.common.collect.ImmutableList; @@ -179,7 +180,11 @@ public void run(ImmutableList args) if (tracks.size() != 1) throw new FluxEngineException("you must specify exactly one track"); CylinderHead ch = tracks.get(0); - FluxSourceIterator iterator = fluxSource.readFlux(ch.cylinder(), ch.head()); + FluxSourceIterator iterator = + fluxSource.readFlux(FluxReadParameters.builder() + .setCylinder(ch.cylinder()) + .setHead(ch.head()) + .build()); Fluxmap fluxmap = iterator.next(); System.out.printf( diff --git a/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java index 877eedba..08298fc7 100644 --- a/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/A2RFluxSource.java @@ -114,13 +114,14 @@ public void adjustConfig(ConfigBuilder configBuilder) } @Override - public FluxSourceIterator readFlux(int track, int head) + public FluxSourceIterator readFlux(FluxReadParameters parameters) { switch (version) { case 2: { - A2Rv2Flux entry = v2data.get(new CylinderHead(track, head)); + A2Rv2Flux entry = + v2data.get(new CylinderHead(parameters.cylinder(), parameters.head())); if (entry != null) return new A2RFluxSourceIterator(entry.flux, entry.index); else diff --git a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel index bb6fc984..8e07aa8c 100644 --- a/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/java/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -1,9 +1,16 @@ load("@com_google_protobuf//bazel:java_proto_library.bzl", "java_proto_library") -load("@rules_java//java:defs.bzl", "java_library") +load("@rules_java//java:defs.bzl", "java_library", "java_plugin") load("@rules_proto//proto:defs.bzl", "proto_library") package(default_visibility = ["//visibility:public"]) +java_plugin( + name = "lombok_plugin", + generates_api = True, + processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + deps = ["@maven//:org_projectlombok_lombok"], +) + proto_library( name = "fluxsource_proto", srcs = ["fluxsource.proto"], @@ -19,6 +26,7 @@ java_proto_library( java_library( name = "fluxsource", srcs = glob(["*.java"]), + plugins = [":lombok_plugin"], deps = [ ":fluxsource_java_proto", "//java/com/cowlark/fluxengine/config", @@ -30,5 +38,6 @@ java_library( "//java/com/cowlark/fluxengine/external:fl2_java_proto", "//java/com/cowlark/fluxengine/usb", "@com_google_protobuf//java/core", + "@maven//:org_projectlombok_lombok", ], ) diff --git a/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java index c5cbb5b1..82901aa8 100644 --- a/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/EraseFluxSource.java @@ -26,7 +26,7 @@ public void adjustConfig(ConfigBuilder configBuilder) } @Override - public Fluxmap readSingleFlux(int cylinder, int head) + public Fluxmap readSingleFlux(FluxReadParameters parameters) { return null; } diff --git a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java index 15ef525b..73667438 100644 --- a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java @@ -114,11 +114,12 @@ public void adjustConfig(ConfigBuilder configBuilder) } @Override - public FluxSourceIterator readFlux(int track, int head) + public FluxSourceIterator readFlux(FluxReadParameters parameters) { for (TrackFluxProto trackFlux : proto.getTrackList()) { - if (trackFlux.getTrack() == track && trackFlux.getHead() == head) + if (trackFlux.getTrack() == parameters.cylinder() && + trackFlux.getHead() == parameters.head()) return new Fl2FluxSourceIterator(trackFlux); } diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxReadParameters.java b/java/com/cowlark/fluxengine/fluxsource/FluxReadParameters.java new file mode 100644 index 00000000..fcfd89e7 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/FluxReadParameters.java @@ -0,0 +1,14 @@ +package com.cowlark.fluxengine.fluxsource; + +import lombok.Builder; + +/** + * The parameters for reading flux from a track, passed to + * {@link FluxSource#readFlux}. + */ +@Builder(setterPrefix = "set") +public record FluxReadParameters + (int cylinder, int head, boolean syncWithIndex, double readTimeNs, + double hardSectorThresholdNs) +{ +} diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index f28c4782..b87a658f 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -62,7 +62,7 @@ public void adjustConfig(ConfigBuilder configBuilder) } /* Read flux from a given cylinder and head. */ - public abstract FluxSourceIterator readFlux(int cylinder, int head); + public abstract FluxSourceIterator readFlux(FluxReadParameters parameters); /* Recalibrates; seeks to cylinder 0 and ensures the head is in the right * place. */ diff --git a/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java index 0d66d687..f48d61b5 100644 --- a/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/HardwareFluxSource.java @@ -28,7 +28,7 @@ public HardwareFluxSource(ConfigProto config) } @Override - public FluxSourceIterator readFlux(int track, int head) + public FluxSourceIterator readFlux(FluxReadParameters parameters) { return new FluxSourceIterator() { @@ -41,14 +41,13 @@ public boolean hasNext() @Override public Fluxmap next() { - device.seek(track); + device.seek(parameters.cylinder()); Bytes data = device.read( - head, - config.getDrive().getSyncWithIndex(), - config.getDrive().getRevolutions() * - config.getDrive().getRotationalPeriodMs() * 1e6, - config.getDrive().getHardSectorThresholdNs()); + parameters.head(), + parameters.syncWithIndex(), + parameters.readTimeNs(), + parameters.hardSectorThresholdNs()); Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBytes(data); return fluxmap; diff --git a/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java index d2636235..5535c7cf 100644 --- a/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/KryofluxFluxSource.java @@ -54,9 +54,9 @@ public void adjustConfig(ConfigBuilder configBuilder) } @Override - public Fluxmap readSingleFlux(int cylinder, int head) + public Fluxmap readSingleFlux(FluxReadParameters parameters) { - return Kryoflux.readStream(path, cylinder, head); + return Kryoflux.readStream(path, parameters.cylinder(), parameters.head()); } @Override diff --git a/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java index da639388..2ce393a7 100644 --- a/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSource.java @@ -9,10 +9,10 @@ public abstract class TrivialFluxSource extends FluxSource { @Override - public FluxSourceIterator readFlux(int cylinder, int head) + public FluxSourceIterator readFlux(FluxReadParameters parameters) { - return new TrivialFluxSourceIterator(this, cylinder, head); + return new TrivialFluxSourceIterator(this, parameters); } - public abstract Fluxmap readSingleFlux(int cylinder, int head); + public abstract Fluxmap readSingleFlux(FluxReadParameters parameters); } diff --git a/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java index 64f94f1a..c008f1e3 100644 --- a/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java +++ b/java/com/cowlark/fluxengine/fluxsource/TrivialFluxSourceIterator.java @@ -9,15 +9,13 @@ public class TrivialFluxSourceIterator implements FluxSourceIterator { private final TrivialFluxSource fluxSource; - private final int cylinder; - private final int head; + private final FluxReadParameters parameters; private boolean done; - public TrivialFluxSourceIterator(TrivialFluxSource fluxSource, int cylinder, int head) + public TrivialFluxSourceIterator(TrivialFluxSource fluxSource, FluxReadParameters parameters) { this.fluxSource = fluxSource; - this.cylinder = cylinder; - this.head = head; + this.parameters = parameters; } @Override @@ -30,6 +28,6 @@ public boolean hasNext() public Fluxmap next() { done = true; - return fluxSource.readSingleFlux(cylinder, head); + return fluxSource.readSingleFlux(parameters); } } diff --git a/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java b/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java index 15337821..afb6a0ea 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/CommonTest.java @@ -3,6 +3,7 @@ import static com.google.common.truth.Truth.assertThat; import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; import com.cowlark.fluxengine.fluxsource.FluxSource; import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; import org.junit.Test; @@ -15,7 +16,7 @@ public class CommonTest private static class RecordingFluxSource extends FluxSource { @Override - public FluxSourceIterator readFlux(int cylinder, int head) + public FluxSourceIterator readFlux(FluxReadParameters parameters) { return null; } @@ -28,7 +29,7 @@ public void fluxSourceIteratorHolderCaches() FluxSource fluxSource = new FluxSource() { @Override - public FluxSourceIterator readFlux(int cylinder, int head) + public FluxSourceIterator readFlux(FluxReadParameters parameters) { reads[0]++; return new FluxSourceIterator() @@ -50,9 +51,12 @@ public com.cowlark.fluxengine.data.Fluxmap next() Common.FluxSourceIteratorHolder holder = new Common.FluxSourceIteratorHolder(fluxSource); - FluxSourceIterator it1 = holder.getIterator(1, 0); - FluxSourceIterator it2 = holder.getIterator(1, 0); - FluxSourceIterator it3 = holder.getIterator(2, 1); + FluxSourceIterator it1 = holder.getIterator(FluxReadParameters.builder() + .setCylinder(1).setHead(0).build()); + FluxSourceIterator it2 = holder.getIterator(FluxReadParameters.builder() + .setCylinder(1).setHead(0).build()); + FluxSourceIterator it3 = holder.getIterator(FluxReadParameters.builder() + .setCylinder(2).setHead(1).build()); assertThat(reads[0]).isEqualTo(2); assertThat(it1).isSameInstanceAs(it2); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java index b023014f..599a3741 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java @@ -82,12 +82,14 @@ public void readsTracks() throws IOException .setFilename(path.toString()) .build()); - FluxSourceIterator iterator = source.readFlux(0, 0); + FluxSourceIterator iterator = + source.readFlux(FluxReadParameters.builder().setCylinder(0).setHead(0).build()); assertThat(iterator.hasNext()).isTrue(); Bytes expected = Bytes.of(0x40, 0xad, 0xad, 0xad); assertThat(iterator.next().rawBytes().toByteArray()).isEqualTo(expected.toByteArray()); assertThat(iterator.hasNext()).isFalse(); - assertThat(source.readFlux(1, 0)).isInstanceOf(EmptyFluxSourceIterator.class); + assertThat(source.readFlux(FluxReadParameters.builder().setCylinder(1).setHead(0).build())).isInstanceOf( + EmptyFluxSourceIterator.class); ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); source.adjustConfig(configBuilder); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java index bb270e56..6579961c 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java @@ -43,11 +43,13 @@ public void readsTracks() throws IOException .setFilename(path.toString()) .build()); - FluxSourceIterator iterator = source.readFlux(0, 0); + FluxSourceIterator iterator = + source.readFlux(FluxReadParameters.builder().setCylinder(0).setHead(0).build()); assertThat(iterator.hasNext()).isTrue(); assertThat(iterator.next()).isNotNull(); assertThat(iterator.hasNext()).isFalse(); - assertThat(source.readFlux(1, 0)).isInstanceOf(EmptyFluxSourceIterator.class); + assertThat(source.readFlux(FluxReadParameters.builder().setCylinder(1).setHead(0).build())).isInstanceOf( + EmptyFluxSourceIterator.class); ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); source.adjustConfig(configBuilder); @@ -75,7 +77,8 @@ public void upgradesVersion1() throws IOException .setFilename(path.toString()) .build()); - FluxSourceIterator iterator = source.readFlux(0, 0); + FluxSourceIterator iterator = + source.readFlux(FluxReadParameters.builder().setCylinder(0).setHead(0).build()); assertThat(iterator.hasNext()).isTrue(); iterator.next(); assertThat(iterator.hasNext()).isTrue(); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java index 35b0272f..da4f299c 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/FluxSourceTest.java @@ -42,7 +42,7 @@ public void createEraseFluxSource() FluxSource source = FluxSource.create(config); assertThat(source).isInstanceOf(EraseFluxSource.class); - assertThat(source.readFlux(0, 0).next()).isNull(); + assertThat(source.readFlux(new FluxReadParameters(0, 0)).next()).isNull(); ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); source.adjustConfig(configBuilder); @@ -55,13 +55,13 @@ public void trivialFluxSourceIteratorYieldsOneMap() TrivialFluxSource source = new TrivialFluxSource() { @Override - public Fluxmap readSingleFlux(int cylinder, int head) + public Fluxmap readSingleFlux(FluxReadParameters parameters) { return new Fluxmap(); } }; - FluxSourceIterator iterator = source.readFlux(0, 0); + FluxSourceIterator iterator = source.readFlux(new FluxReadParameters(0, 0)); assertThat(iterator.hasNext()).isTrue(); iterator.next(); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java index 8c2059e9..c65f24c2 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java @@ -93,8 +93,7 @@ public void close() private static ConfigProto config() { - return new ConfigBuilder() - .set("usb.serial", "test-serial") + return new ConfigBuilder().set("usb.serial", "test-serial") .set("drive.sync_with_index", "true") .set("drive.revolutions", "3") .set("drive.rotational_period_ms", "200") @@ -139,7 +138,13 @@ public void readFluxReadsAndWrapsFluxmap() device.readResult = Bytes.of(0x01, 0x02, 0x03, 0x04); HardwareFluxSource source = new HardwareFluxSource(config(), device); - FluxSourceIterator iterator = source.readFlux(17, 1); + FluxSourceIterator iterator = source.readFlux(FluxReadParameters.builder() + .setCylinder(17) + .setHead(1) + .setSyncWithIndex(true) + .setReadTimeNs(3 * 200 * 1e6) + .setHardSectorThresholdNs(1000) + .build()); assertThat(iterator.hasNext()).isTrue(); Fluxmap fluxmap = iterator.next(); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java index b3240c73..ed1a97fe 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java @@ -29,9 +29,10 @@ public void readsSingleFluxFromDirectory() throws Exception KryofluxFluxSourceProto.newBuilder().setDirectory(dir.toString()).build(); KryofluxFluxSource source = new KryofluxFluxSource(config); - assertThat(source.readSingleFlux(80, 0) - .rawBytes() - .toByteArray()).isEqualTo(new byte[]{(byte) 0x8f}); + assertThat(source.readSingleFlux(FluxReadParameters.builder() + .setCylinder(80) + .setHead(0) + .build()).rawBytes().toByteArray()).isEqualTo(new byte[]{(byte) 0x8f}); ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); source.adjustConfig(configBuilder); From db48f88cd585c6bd280f87028e43be8e108f4135 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 14:05:16 +0200 Subject: [PATCH 145/192] Replace the existing makefile with a simple one which invokes bazel. --- Makefile | 133 ++----------------------------------------------------- 1 file changed, 3 insertions(+), 130 deletions(-) diff --git a/Makefile b/Makefile index 83149592..998507d0 100644 --- a/Makefile +++ b/Makefile @@ -1,132 +1,5 @@ -ifeq ($(BUILDTYPE),) - # On MSYS2 uname -s produces something like: MINGW64_NT-10.0-19045 - # Strip the suffix off - OS := $(patsubst MINGW%,MINGW,$(shell uname -s)) - buildtype_Darwin = osx - buildtype_Haiku = haiku - buildtype_MINGW = windows - BUILDTYPE := $(buildtype_$(OS)) - ifeq ($(BUILDTYPE),) - BUILDTYPE := unix - endif -endif -export BUILDTYPE - -OPTFLAGS = -g -O3 - -ifeq ($(BUILDTYPE),windows) - MINGW = x86_64-w64-mingw32- - CC = $(MINGW)gcc - CXX = $(MINGW)g++ - CFLAGS += \ - $(OPTFLAGS) \ - -ffunction-sections \ - -fdata-sections \ - -Wno-attributes \ - -Wa,-mbig-obj \ - -static - CXXFLAGS += \ - $(OPTFLAGS) \ - -std=c++23 \ - -Wno-deprecated-enum-float-conversion \ - -Wno-deprecated-enum-enum-conversion \ - -Wno-attributes \ - -Wa,-mbig-obj \ - -static - LDFLAGS += -Wl,--gc-sections -static - AR = $(MINGW)gcc-ar - PKG_CONFIG = $(MINGW)pkg-config --static - WINDRES = $(MINGW)windres - WX_CONFIG = /usr/i686-w64-mingw32/sys-root/mingw/bin/wx-config-3.0 --static=yes - NINJA = /bin/ninja - PROTOC = /mingw64/bin/protoc - PROTOC_SEPARATOR = ; - EXT = .exe - - AB_SANDBOX = no -else - CFLAGS += \ - $(OPTFLAGS) \ - -I/opt/homebrew/include -I/usr/local/include \ - -Wno-unknown-warning-option - CXXFLAGS += \ - $(OPTFLAGS) \ - -std=c++23 \ - -I/opt/homebrew/include -I/usr/local/include \ - -Wformat \ - -Wformat-security \ - -Wno-deprecated-enum-float-conversion \ - -Wno-deprecated-enum-enum-conversion - LDFLAGS += - AR = ar - PKG_CONFIG = pkg-config - ifeq ($(BUILDTYPE),osx) - CXXFLAGS += -fexperimental-library - else - LDFLAGS += -pthread - endif -endif - -HOSTCC = gcc -HOSTCXX = g++ -std=c++20 -HOSTCFLAGS += -g -O3 -HOSTLDFLAGS = - -REALOBJ = .obj -OBJ = $(REALOBJ)/$(BUILDTYPE) -DESTDIR ?= -PREFIX ?= /usr/local -BINDIR ?= $(PREFIX)/bin - -# Special Windows settings. - -#ifeq ($(OS), Windows_NT) -# EXT ?= .exe -# MINGWBIN = /mingw32/bin -# CCPREFIX = $(MINGWBIN)/ -# PKG_CONFIG = $(MINGWBIN)/pkg-config -# WX_CONFIG = /usr/bin/sh $(MINGWBIN)/wx-config --static=yes -# PROTOC = $(MINGWBIN)/protoc -# WINDRES = windres -# LDFLAGS += \ -# -static -# CXXFLAGS += \ -# -fext-numeric-literals \ -# -Wno-deprecated-enum-float-conversion \ -# -Wno-deprecated-enum-enum-conversion -# -# # Required to get the gcc run - time libraries on the path. -# export PATH := $(PATH):$(MINGWBIN) -#endif - -# Special OSX settings. - -ifeq ($(shell uname),Darwin) - LDFLAGS += \ - -framework IOKit \ - -framework AppKit \ - -framework UniformTypeIdentifiers \ - -framework UserNotifications -endif - .PHONY: all -all: +all README.md - -.PHONY: binaries tests -binaries: all -tests: all - -README.md: $(OBJ)/scripts/+mkdocindex/mkdocindex$(EXT) - @echo $(PROGRESSINFO)MKDOC $@ - @csplit -s -f$(OBJ)/README. README.md '//' '%%' - @(cat $(OBJ)/README.00 && $< && cat $(OBJ)/README.01) > README.md - -.PHONY: tests - -clean:: - $(hide) rm -rf $(REALOBJ) - -include build/ab.mk +all: + bazel test //javatests/... + bazel build //:fluxengine //:fluxengine_native -docker-%: tests/docker/Dockerfile.% - docker build --progress=plain -t $* -f $< . From 8940f07f6a7136a4548f7c560c855fbbd8dfc39a Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 14:40:03 +0000 Subject: [PATCH 146/192] Fix the bazelrc to use Java 21 throughout (caused build failures in places). --- .bazelrc | 1 + 1 file changed, 1 insertion(+) diff --git a/.bazelrc b/.bazelrc index 794186e6..abdeacb4 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,4 +1,5 @@ common --java_language_version=21 +common --java_runtime_version=remotejdk_21 # Tools built in the exec configuration also need Java 21 (for records etc.). common --tool_java_language_version=21 From 3d5c748f49e0eb4517ebecc83a8e34b94d121bfc Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 14:41:08 +0000 Subject: [PATCH 147/192] Switch the GUI skeleton to Swing. --- MODULE.bazel | 7 - MODULE.bazel.lock | 298 +++++--------------- java/com/cowlark/fluxengine/gui/BUILD.bazel | 8 - java/com/cowlark/fluxengine/gui/Gui.java | 29 +- 4 files changed, 77 insertions(+), 265 deletions(-) diff --git a/MODULE.bazel b/MODULE.bazel index 9d1e9097..9e28af5e 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -19,13 +19,6 @@ maven.install( "com.jayway.jsonpath:json-path:3.0.0", "javax.usb:usb-api:1.0.2", "junit:junit:4.13.2", - "org.openjfx:javafx-controls:23.0.2", - "org.openjfx:javafx-fxml:23.0.2", - "org.openjfx:javafx-graphics:23.0.2", - "org.openjfx:javafx-base:23.0.2", - "org.openjfx:javafx-graphics:23.0.2:linux", - "org.openjfx:javafx-graphics:23.0.2:mac", - "org.openjfx:javafx-graphics:23.0.2:win", "org.usb4java:usb4java:1.3.0", "org.usb4java:usb4java-javax:1.3.0", ], diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 86e94867..0c192e40 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 28, + "lockFileVersion": 24, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -16,12 +16,9 @@ "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", - "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", - "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", - "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", + "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", + "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", - "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", @@ -34,9 +31,8 @@ "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", + "https://bcr.bazel.build/modules/bazel_features/1.33.0/source.json": "13617db3930328c2cd2807a0f13d52ca870ac05f96db9668655113265147b2a6", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", - "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", - "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", @@ -50,10 +46,9 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", - "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", - "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -79,8 +74,10 @@ "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", @@ -96,11 +93,11 @@ "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", - "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", @@ -109,22 +106,22 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", - "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", - "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", - "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", - "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", + "https://bcr.bazel.build/modules/rules_cc/0.2.13/source.json": "f872e892c5265c5532e526857532f4868708f88d64e5ebe517ea72e09da61bdb", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", @@ -133,9 +130,12 @@ "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", @@ -149,6 +149,7 @@ "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", @@ -158,28 +159,25 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", + "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", - "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", - "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_python/1.6.0/source.json": "e980f654cf66ec4928672f41fc66c4102b5ea54286acf4aecd23256c84211be6", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", - "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", - "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", - "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", @@ -192,44 +190,25 @@ "general": { "bzlTransitiveDigest": "j3Qz7w1ruIOY8oFfCFAgWWjr/ev5O+1F8Jebvo5QHGo=", "usagesDigest": "iUXd/3jCJaegO0Dllj33xgb8pqISEwNiOB/aoc6/sBQ=", - "recordedInputs": [], + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, "generatedRepoSpecs": { "graalvm": { "repoRuleId": "@@//:graalvm_repository.bzl%graalvm_repository", "attributes": {} } - } - } - }, - "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { - "general": { - "bzlTransitiveDigest": "NRXra7941UfmNUyIxnLt82V5hULluVGL2nBsijTl4j4=", - "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", - "recordedInputs": [ - "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools", - "FILE:@@pybind11_bazel+//MODULE.bazel e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" - ], - "generatedRepoSpecs": { - "pybind11": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", - "strip_prefix": "pybind11-2.12.0", - "urls": [ - "https://github.com/pybind/pybind11/archive/v2.12.0.zip" - ] - } - } - } + }, + "recordedRepoMappingEntries": [] } }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "+Kp6j204mBZ3mxlIDDR0gBoP45BZ4jYRhRAcB8sU0qc=", + "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedInputs": [ - "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" - ], + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, "generatedRepoSpecs": { "com_github_jetbrains_kotlin_git": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", @@ -277,185 +256,23 @@ ] } } - } - } - }, - "@@rules_python+//python/extensions:config.bzl%config": { - "general": { - "bzlTransitiveDigest": "dzD8Q2YmrP3fz8saWLHPmlwPLO91ImtTmP/c9JKTStM=", - "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", - "recordedInputs": [ - "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", - "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", - "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", - "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", - "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", - "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", - "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", - "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", - "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", - "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", - "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", - "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", - "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", - "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", - "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" - ], - "generatedRepoSpecs": { - "rules_python_internal": { - "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", - "attributes": { - "transition_setting_generators": {}, - "transition_settings": [] - } - }, - "pypi__build": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", - "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__click": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", - "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__colorama": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", - "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__importlib_metadata": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", - "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__installer": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", - "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__more_itertools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", - "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__packaging": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", - "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pep517": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", - "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", - "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pip_tools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", - "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__pyproject_hooks": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", - "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__setuptools": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", - "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__tomli": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", - "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__wheel": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", - "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - }, - "pypi__zipp": { - "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", - "attributes": { - "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", - "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", - "type": "zip", - "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" - } - } - } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin+", + "bazel_tools", + "bazel_tools" + ] + ] } }, "@@rules_python+//python/uv:uv.bzl%uv": { "general": { - "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", - "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", - "recordedInputs": [ - "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", - "REPO_MAPPING:rules_python+,platforms platforms" - ], + "bzlTransitiveDigest": "xfNZ/WmfkC9N/pNH0cmucTOrqBa966d9iMmmX54m1UM=", + "usagesDigest": "p80sy6cYQuWxx5jhV3fOTu+N9EyIUFG9+F7UC/nhXic=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, "generatedRepoSpecs": { "uv": { "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", @@ -475,10 +292,21 @@ "toolchain_target_settings": {} } } - } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python+", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_python+", + "platforms", + "platforms" + ] + ] } } }, - "facts": {}, - "factsVersions": {} + "facts": {} } diff --git a/java/com/cowlark/fluxengine/gui/BUILD.bazel b/java/com/cowlark/fluxengine/gui/BUILD.bazel index cabc03b8..7fbfd2a6 100644 --- a/java/com/cowlark/fluxengine/gui/BUILD.bazel +++ b/java/com/cowlark/fluxengine/gui/BUILD.bazel @@ -5,12 +5,4 @@ package(default_visibility = ["//visibility:public"]) java_library( name = "gui", srcs = glob(["*.java"]), - deps = [ - "@maven//:org_openjfx_javafx_base", - "@maven//:org_openjfx_javafx_base_linux", - "@maven//:org_openjfx_javafx_controls", - "@maven//:org_openjfx_javafx_controls_linux", - "@maven//:org_openjfx_javafx_graphics", - "@maven//:org_openjfx_javafx_graphics_linux", - ], ) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index 7e6a87e4..aa97e4c3 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -1,30 +1,29 @@ package com.cowlark.fluxengine.gui; -import javafx.application.Application; -import javafx.scene.Scene; -import javafx.scene.control.Label; -import javafx.scene.layout.StackPane; -import javafx.stage.Stage; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.WindowConstants; /** * The FluxEngine GUI, ported from src/gui/main.cc. */ -public class Gui extends Application +public class Gui { public static void main(String[] args) { - launch(Gui.class, args); + SwingUtilities.invokeLater(Gui::createAndShowGui); } - @Override - public void start(Stage stage) + private static void createAndShowGui() { - Label label = new Label("FluxEngine"); - StackPane root = new StackPane(label); - Scene scene = new Scene(root, 800, 600); + JFrame frame = new JFrame("FluxEngine"); + frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - stage.setTitle("FluxEngine"); - stage.setScene(scene); - stage.show(); + JLabel label = new JLabel("FluxEngine", JLabel.CENTER); + frame.getContentPane().add(label); + frame.setSize(800, 600); + frame.setLocationRelativeTo(null); + frame.setVisible(true); } } From 4de94184e703e5787a3c2ff748e4c0d1b281ea8b Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 14:46:08 +0000 Subject: [PATCH 148/192] Integrate flatlaf. --- MODULE.bazel | 1 + java/com/cowlark/fluxengine/gui/BUILD.bazel | 3 +++ java/com/cowlark/fluxengine/gui/Gui.java | 16 +++++++++++++++- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/MODULE.bazel b/MODULE.bazel index 9e28af5e..163fc2d8 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -21,6 +21,7 @@ maven.install( "junit:junit:4.13.2", "org.usb4java:usb4java:1.3.0", "org.usb4java:usb4java-javax:1.3.0", + "com.formdev:flatlaf:3.0", ], repositories = [ "https://repo1.maven.org/maven2", diff --git a/java/com/cowlark/fluxengine/gui/BUILD.bazel b/java/com/cowlark/fluxengine/gui/BUILD.bazel index 7fbfd2a6..5e9a8b77 100644 --- a/java/com/cowlark/fluxengine/gui/BUILD.bazel +++ b/java/com/cowlark/fluxengine/gui/BUILD.bazel @@ -5,4 +5,7 @@ package(default_visibility = ["//visibility:public"]) java_library( name = "gui", srcs = glob(["*.java"]), + deps = [ + "@maven//:com_formdev_flatlaf", + ], ) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index aa97e4c3..89e2018c 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -1,8 +1,10 @@ package com.cowlark.fluxengine.gui; +import com.formdev.flatlaf.FlatLightLaf; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; +import javax.swing.UIManager; import javax.swing.WindowConstants; /** @@ -12,7 +14,19 @@ public class Gui { public static void main(String[] args) { - SwingUtilities.invokeLater(Gui::createAndShowGui); + SwingUtilities.invokeLater(() -> { + setupLookAndFeel(); + createAndShowGui(); + }); + } + + private static void setupLookAndFeel() + { + try { + UIManager.setLookAndFeel(new FlatLightLaf()); + } catch (Exception e) { + e.printStackTrace(); + } } private static void createAndShowGui() From 026122879dba8e0beeb9b1fc1347a317792a9175 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 15:25:14 +0000 Subject: [PATCH 149/192] Try integrating swing and flatlaf into the build. --- java/com/cowlark/fluxengine/BUILD.bazel | 1 + java/com/cowlark/fluxengine/reflect-config.json | 12 ++++++++++++ java/com/cowlark/fluxengine/resource-config.json | 6 ++++++ 3 files changed, 19 insertions(+) diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 9ac102b3..21ff52a8 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -16,6 +16,7 @@ native_image( "--no-fallback", "-O2", "-H:IncludeResources=(javax.usb.properties|org/usb4java/.*/libusb4java\\..*|.*/libjSerialComm.*|.*/jSerialComm.dll)", + "-Djava.awt.headless=false", ], jar = ":fluxengine_deploy.jar", jni_config = ["jni-config.json"], diff --git a/java/com/cowlark/fluxengine/reflect-config.json b/java/com/cowlark/fluxengine/reflect-config.json index 923d9b7b..868d0b8d 100644 --- a/java/com/cowlark/fluxengine/reflect-config.json +++ b/java/com/cowlark/fluxengine/reflect-config.json @@ -10,6 +10,18 @@ "allDeclaredFields": true, "queryAllDeclaredMethods": true }, + { + "name": "com.formdev.flatlaf.FlatLightLaf", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, + { + "name": "com.formdev.flatlaf.UIDefaultsLoader", + "allDeclaredConstructors": true, + "allDeclaredFields": true, + "queryAllDeclaredMethods": true + }, { "name": "com.sun.javafx.tk.quantum.QuantumToolkit", "allDeclaredConstructors": true, diff --git a/java/com/cowlark/fluxengine/resource-config.json b/java/com/cowlark/fluxengine/resource-config.json index 7fb9fbea..e9eaa197 100644 --- a/java/com/cowlark/fluxengine/resource-config.json +++ b/java/com/cowlark/fluxengine/resource-config.json @@ -10,6 +10,12 @@ { "pattern": "\\Qorg/usb4java/linux-x86-64/libusb4java.so\\E" }, + { + "pattern": "\\Qcom/formdev/flatlaf/\\E.*" + }, + { + "pattern": "\\QMETA-INF/services/\\E.*" + }, { "pattern": "\\Qformats/\\E.*" } From 30408bb0bc40bca7d6d74c92151fa448cff724bf Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 19:33:58 +0200 Subject: [PATCH 150/192] Build each textpb seperately. --- MODULE.bazel.lock | 298 +++++++++++++++++++++++++++++++--------- src/formats/BUILD.bazel | 23 ++-- 2 files changed, 250 insertions(+), 71 deletions(-) diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 0c192e40..86e94867 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -1,5 +1,5 @@ { - "lockFileVersion": 24, + "lockFileVersion": 28, "registryFileHashes": { "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", @@ -16,9 +16,12 @@ "https://bcr.bazel.build/modules/abseil-cpp/20250814.1/source.json": "cea3901d7e299da7320700abbaafe57a65d039f10d0d7ea601c4a66938ea4b0c", "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", - "https://bcr.bazel.build/modules/apple_support/1.23.1/MODULE.bazel": "53763fed456a968cf919b3240427cf3a9d5481ec5466abc9d5dc51bc70087442", - "https://bcr.bazel.build/modules/apple_support/1.23.1/source.json": "d888b44312eb0ad2c21a91d026753f330caa48a25c9b2102fae75eb2b0dcfdd2", + "https://bcr.bazel.build/modules/apple_support/1.21.0/MODULE.bazel": "ac1824ed5edf17dee2fdd4927ada30c9f8c3b520be1b5fd02a5da15bc10bff3e", + "https://bcr.bazel.build/modules/apple_support/1.21.1/MODULE.bazel": "5809fa3efab15d1f3c3c635af6974044bac8a4919c62238cce06acee8a8c11f1", + "https://bcr.bazel.build/modules/apple_support/1.24.2/MODULE.bazel": "0e62471818affb9f0b26f128831d5c40b074d32e6dda5a0d3852847215a41ca4", + "https://bcr.bazel.build/modules/apple_support/1.24.2/source.json": "2c22c9827093250406c5568da6c54e6fdf0ef06238def3d99c71b12feb057a8d", "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.10.0/MODULE.bazel": "f75e8807570484a99be90abcd52b5e1f390362c258bcb73106f4544957a48101", "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", @@ -31,8 +34,9 @@ "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", "https://bcr.bazel.build/modules/bazel_features/1.33.0/MODULE.bazel": "8b8dc9d2a4c88609409c3191165bccec0e4cb044cd7a72ccbe826583303459f6", - "https://bcr.bazel.build/modules/bazel_features/1.33.0/source.json": "13617db3930328c2cd2807a0f13d52ca870ac05f96db9668655113265147b2a6", "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/MODULE.bazel": "275a59b5406ff18c01739860aa70ad7ccb3cfb474579411decca11c93b951080", + "https://bcr.bazel.build/modules/bazel_features/1.42.1/source.json": "fcd4396b2df85f64f2b3bb436ad870793ecf39180f1d796f913cc9276d355309", "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", @@ -46,9 +50,10 @@ "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/MODULE.bazel": "88ade7293becda963e0e3ea33e7d54d3425127e0a326e0d17da085a5f1f03ff6", - "https://bcr.bazel.build/modules/bazel_skylib/1.8.1/source.json": "7ebaefba0b03efe59cac88ed5bbc67bcf59a3eff33af937345ede2a38b2d368a", - "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", - "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/source.json": "34a3c8bcf233b835eb74be9d628899bb32999d3e0eadef1947a0a562a2b16ffb", + "https://bcr.bazel.build/modules/buildozer/8.5.1/MODULE.bazel": "a35d9561b3fc5b18797c330793e99e3b834a473d5fbd3d7d7634aafc9bdb6f8f", + "https://bcr.bazel.build/modules/buildozer/8.5.1/source.json": "e3386e6ff4529f2442800dee47ad28d3e6487f36a1f75ae39ae56c70f0cd2fbd", "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", @@ -74,10 +79,8 @@ "https://bcr.bazel.build/modules/platforms/1.0.0/source.json": "f4ff1fd412e0246fd38c82328eb209130ead81d62dcd5a9e40910f867f733d96", "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", - "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", - "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", "https://bcr.bazel.build/modules/protobuf/29.1/MODULE.bazel": "557c3457560ff49e122ed76c0bc3397a64af9574691cb8201b4e46d4ab2ecb95", "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", "https://bcr.bazel.build/modules/protobuf/32.1/MODULE.bazel": "89cd2866a9cb07fee9ff74c41ceace11554f32e0d849de4e23ac55515cfada4d", @@ -93,11 +96,11 @@ "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", - "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/MODULE.bazel": "76e10fd4a48038d3fc7c5dc6e63b7063bbf5304a2e3bd42edda6ec660eebea68", + "https://bcr.bazel.build/modules/rules_apple/4.1.0/source.json": "8ee81e1708756f81b343a5eb2b2f0b953f1d25c4ab3d4a68dc02754872e80715", "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", - "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", @@ -106,22 +109,22 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.2/MODULE.bazel": "557ddc3a96858ec0d465a87c0a931054d7dcfd6583af2c7ed3baf494407fd8d0", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", "https://bcr.bazel.build/modules/rules_cc/0.2.0/MODULE.bazel": "b5c17f90458caae90d2ccd114c81970062946f49f355610ed89bebf954f5783c", "https://bcr.bazel.build/modules/rules_cc/0.2.13/MODULE.bazel": "eecdd666eda6be16a8d9dc15e44b5c75133405e820f620a234acc4b1fdc5aa37", - "https://bcr.bazel.build/modules/rules_cc/0.2.13/source.json": "f872e892c5265c5532e526857532f4868708f88d64e5ebe517ea72e09da61bdb", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/MODULE.bazel": "1849602c86cb60da8613d2de887f9566a6d354a6df6d7009f9d04a14402f9a84", + "https://bcr.bazel.build/modules/rules_cc/0.2.17/source.json": "3832f45d145354049137c0090df04629d9c2b5493dc5c2bf46f1834040133a07", + "https://bcr.bazel.build/modules/rules_cc/0.2.8/MODULE.bazel": "f1df20f0bf22c28192a794f29b501ee2018fa37a3862a1a2132ae2940a23a642", "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", - "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", - "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", - "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/8.14.0/MODULE.bazel": "717717ed40cc69994596a45aec6ea78135ea434b8402fb91b009b9151dd65615", "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", @@ -130,12 +133,9 @@ "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", - "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", - "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", "https://bcr.bazel.build/modules/rules_jvm_external/6.7/source.json": "5426f412d0a7fc6b611643376c7e4a82dec991491b9ce5cb1cfdd25fe2e92be4", - "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", @@ -149,7 +149,6 @@ "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", - "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", "https://bcr.bazel.build/modules/rules_proto/7.1.0/MODULE.bazel": "002d62d9108f75bb807cd56245d45648f38275cb3a99dcd45dfb864c5d74cb96", "https://bcr.bazel.build/modules/rules_proto/7.1.0/source.json": "39f89066c12c24097854e8f57ab8558929f9c8d474d34b2c00ac04630ad8940e", "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", @@ -159,25 +158,28 @@ "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", - "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://bcr.bazel.build/modules/rules_python/1.3.0/MODULE.bazel": "8361d57eafb67c09b75bf4bbe6be360e1b8f4f18118ab48037f2bd50aa2ccb13", "https://bcr.bazel.build/modules/rules_python/1.4.1/MODULE.bazel": "8991ad45bdc25018301d6b7e1d3626afc3c8af8aaf4bc04f23d0b99c938b73a6", "https://bcr.bazel.build/modules/rules_python/1.6.0/MODULE.bazel": "7e04ad8f8d5bea40451cf80b1bd8262552aa73f841415d20db96b7241bd027d8", - "https://bcr.bazel.build/modules/rules_python/1.6.0/source.json": "e980f654cf66ec4928672f41fc66c4102b5ea54286acf4aecd23256c84211be6", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", - "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/MODULE.bazel": "72e76b0eea4e81611ef5452aa82b3da34caca0c8b7b5c0c9584338aa93bae26b", + "https://bcr.bazel.build/modules/rules_shell/0.6.1/source.json": "20ec05cd5e592055e214b2da8ccb283c7f2a421ea0dc2acbf1aa792e11c03d0c", "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", - "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", + "https://bcr.bazel.build/modules/rules_swift/2.4.0/MODULE.bazel": "1639617eb1ede28d774d967a738b4a68b0accb40650beadb57c21846beab5efd", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/MODULE.bazel": "72c8f5cf9d26427cee6c76c8e3853eb46ce6b0412a081b2b6db6e8ad56267400", + "https://bcr.bazel.build/modules/rules_swift/3.1.2/source.json": "e85761f3098a6faf40b8187695e3de6d97944e98abd0d8ce579cb2daf6319a66", "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", - "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", - "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", - "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/MODULE.bazel": "75aab2373a4bbe2a1260b9bf2a1ebbdbf872d3bd36f80bff058dccd82e89422f", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.2/source.json": "5fba48bbe0ba48761f9e9f75f92876cafb5d07c0ce059cc7a8027416de94a05b", "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", @@ -190,25 +192,44 @@ "general": { "bzlTransitiveDigest": "j3Qz7w1ruIOY8oFfCFAgWWjr/ev5O+1F8Jebvo5QHGo=", "usagesDigest": "iUXd/3jCJaegO0Dllj33xgb8pqISEwNiOB/aoc6/sBQ=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "recordedInputs": [], "generatedRepoSpecs": { "graalvm": { "repoRuleId": "@@//:graalvm_repository.bzl%graalvm_repository", "attributes": {} } - }, - "recordedRepoMappingEntries": [] + } + } + }, + "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { + "general": { + "bzlTransitiveDigest": "NRXra7941UfmNUyIxnLt82V5hULluVGL2nBsijTl4j4=", + "usagesDigest": "D1r3lfzMuUBFxgG8V6o0bQTLMk3GkaGOaPzw53wrwyw=", + "recordedInputs": [ + "REPO_MAPPING:pybind11_bazel+,bazel_tools bazel_tools", + "FILE:@@pybind11_bazel+//MODULE.bazel e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" + ], + "generatedRepoSpecs": { + "pybind11": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "build_file": "@@pybind11_bazel+//:pybind11-BUILD.bazel", + "strip_prefix": "pybind11-2.12.0", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.12.0.zip" + ] + } + } + } } }, "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { "general": { - "bzlTransitiveDigest": "03Qju4tW0vE+0RBuZGuV2A4Hx6AiSkdNahYvworx2aM=", + "bzlTransitiveDigest": "+Kp6j204mBZ3mxlIDDR0gBoP45BZ4jYRhRAcB8sU0qc=", "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "recordedInputs": [ + "REPO_MAPPING:rules_kotlin+,bazel_tools bazel_tools" + ], "generatedRepoSpecs": { "com_github_jetbrains_kotlin_git": { "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", @@ -256,23 +277,185 @@ ] } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_kotlin+", - "bazel_tools", - "bazel_tools" - ] - ] + } + } + }, + "@@rules_python+//python/extensions:config.bzl%config": { + "general": { + "bzlTransitiveDigest": "dzD8Q2YmrP3fz8saWLHPmlwPLO91ImtTmP/c9JKTStM=", + "usagesDigest": "ZVSXMAGpD+xzVNPuvF1IoLBkty7TROO0+akMapt1pAg=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,pypi__build rules_python++config+pypi__build", + "REPO_MAPPING:rules_python+,pypi__click rules_python++config+pypi__click", + "REPO_MAPPING:rules_python+,pypi__colorama rules_python++config+pypi__colorama", + "REPO_MAPPING:rules_python+,pypi__importlib_metadata rules_python++config+pypi__importlib_metadata", + "REPO_MAPPING:rules_python+,pypi__installer rules_python++config+pypi__installer", + "REPO_MAPPING:rules_python+,pypi__more_itertools rules_python++config+pypi__more_itertools", + "REPO_MAPPING:rules_python+,pypi__packaging rules_python++config+pypi__packaging", + "REPO_MAPPING:rules_python+,pypi__pep517 rules_python++config+pypi__pep517", + "REPO_MAPPING:rules_python+,pypi__pip rules_python++config+pypi__pip", + "REPO_MAPPING:rules_python+,pypi__pip_tools rules_python++config+pypi__pip_tools", + "REPO_MAPPING:rules_python+,pypi__pyproject_hooks rules_python++config+pypi__pyproject_hooks", + "REPO_MAPPING:rules_python+,pypi__setuptools rules_python++config+pypi__setuptools", + "REPO_MAPPING:rules_python+,pypi__tomli rules_python++config+pypi__tomli", + "REPO_MAPPING:rules_python+,pypi__wheel rules_python++config+pypi__wheel", + "REPO_MAPPING:rules_python+,pypi__zipp rules_python++config+pypi__zipp" + ], + "generatedRepoSpecs": { + "rules_python_internal": { + "repoRuleId": "@@rules_python+//python/private:internal_config_repo.bzl%internal_config_repo", + "attributes": { + "transition_setting_generators": {}, + "transition_settings": [] + } + }, + "pypi__build": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__click": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__colorama": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__packaging": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + } } }, "@@rules_python+//python/uv:uv.bzl%uv": { "general": { - "bzlTransitiveDigest": "xfNZ/WmfkC9N/pNH0cmucTOrqBa966d9iMmmX54m1UM=", - "usagesDigest": "p80sy6cYQuWxx5jhV3fOTu+N9EyIUFG9+F7UC/nhXic=", - "recordedFileInputs": {}, - "recordedDirentsInputs": {}, - "envVariables": {}, + "bzlTransitiveDigest": "ijW9KS7qsIY+yBVvJ+Nr1mzwQox09j13DnE3iIwaeTM=", + "usagesDigest": "H8dQoNZcoqP+Mu0tHZTi4KHATzvNkM5ePuEqoQdklIU=", + "recordedInputs": [ + "REPO_MAPPING:rules_python+,bazel_tools bazel_tools", + "REPO_MAPPING:rules_python+,platforms platforms" + ], "generatedRepoSpecs": { "uv": { "repoRuleId": "@@rules_python+//python/uv/private:uv_toolchains_repo.bzl%uv_toolchains_repo", @@ -292,21 +475,10 @@ "toolchain_target_settings": {} } } - }, - "recordedRepoMappingEntries": [ - [ - "rules_python+", - "bazel_tools", - "bazel_tools" - ], - [ - "rules_python+", - "platforms", - "platforms" - ] - ] + } } } }, - "facts": {} + "facts": {}, + "factsVersions": {} } diff --git a/src/formats/BUILD.bazel b/src/formats/BUILD.bazel index 1c006045..e1c9c046 100644 --- a/src/formats/BUILD.bazel +++ b/src/formats/BUILD.bazel @@ -39,16 +39,23 @@ FORMATS = [ "zilogmcz", ] +[ + genrule( + name = "%s_bin" % f, + srcs = ["%s.textpb" % f], + outs = ["formats/%s.bin" % f], + tools = ["//java/com/cowlark/fluxengine/buildtools:protoencode"], + cmd = "$(location //java/com/cowlark/fluxengine/buildtools:protoencode) " + + "$(location %s.textpb) $(location formats/%s.bin)" % (f, f), + ) + for f in FORMATS +] + genrule( - name = "formats", + name = "names", srcs = ["%s.textpb" % f for f in FORMATS], - outs = ["formats/%s.bin" % f for f in FORMATS] + ["formats/names.txt"], - tools = ["//java/com/cowlark/fluxengine/buildtools:protoencode"], - cmd = " && ".join([ - "$(location //java/com/cowlark/fluxengine/buildtools:protoencode) " + - "$(location %s.textpb) $(location formats/%s.bin)" % (f, f) - for f in FORMATS - ]) + " && printf '%%s\\n' %s > $(location formats/names.txt)" % " ".join(FORMATS), + outs = ["formats/names.txt"], + cmd = "printf '%%s\\n' %s > $(location formats/names.txt)" % " ".join(FORMATS), ) filegroup( From 07498a2ed75058c0eb16cb0cdef62ab7ca1d9088 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 19:36:08 +0200 Subject: [PATCH 151/192] ProtoEncode doesn't need to depend on core. --- java/com/cowlark/fluxengine/buildtools/BUILD.bazel | 1 - java/com/cowlark/fluxengine/buildtools/ProtoEncode.java | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel index 95aa4081..83f2e0d5 100644 --- a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel +++ b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel @@ -7,7 +7,6 @@ java_library( srcs = glob(["*.java"]), deps = [ "//java/com/cowlark/fluxengine/config:config_java_proto", - "//java/com/cowlark/fluxengine/core", "@com_google_protobuf//java/core", ], ) diff --git a/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java index be69223e..a5269f77 100644 --- a/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java +++ b/java/com/cowlark/fluxengine/buildtools/ProtoEncode.java @@ -1,6 +1,5 @@ package com.cowlark.fluxengine.buildtools; -import com.cowlark.fluxengine.core.FluxEngineException; import com.google.protobuf.Message; import com.google.protobuf.TextFormat; import java.io.IOException; @@ -46,7 +45,7 @@ public static void main(String[] args) { System.err.println("couldn't open file: " + e.getMessage()); System.exit(1); - } catch (FluxEngineException e) + } catch (RuntimeException e) { System.err.println(e.getMessage()); System.exit(1); @@ -64,7 +63,7 @@ public static byte[] encodeToBytes(String contents, String protoClass) TextFormat.merge(processed, builder); } catch (TextFormat.ParseException e) { - throw new FluxEngineException("cannot parse text proto: " + e.getMessage()); + throw new RuntimeException("cannot parse text proto: " + e.getMessage()); } return builder.build().toByteArray(); } @@ -133,7 +132,7 @@ private static Message.Builder newBuilder(String protoClass) } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException | ClassCastException e) { - throw new FluxEngineException("cannot create builder for " + protoClass + ": " + e); + throw new RuntimeException("cannot create builder for " + protoClass + ": " + e); } } } \ No newline at end of file From 32c0af389b79f8babf159a27f3888ef3c36e25ec Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 19:46:18 +0200 Subject: [PATCH 152/192] Switch to darklaf. --- java/com/cowlark/fluxengine/cli/GuiCommand.java | 4 ++-- java/com/cowlark/fluxengine/gui/BUILD.bazel | 1 + java/com/cowlark/fluxengine/gui/Gui.java | 16 ++++------------ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/java/com/cowlark/fluxengine/cli/GuiCommand.java b/java/com/cowlark/fluxengine/cli/GuiCommand.java index 33c0cdf1..17ca2e00 100644 --- a/java/com/cowlark/fluxengine/cli/GuiCommand.java +++ b/java/com/cowlark/fluxengine/cli/GuiCommand.java @@ -12,8 +12,8 @@ public String getHelp() } @Override - public void run(ImmutableList args) + public void run(ImmutableList args) throws Exception { - Gui.main(args.toArray(new String[0])); + new Gui().run(args); } } diff --git a/java/com/cowlark/fluxengine/gui/BUILD.bazel b/java/com/cowlark/fluxengine/gui/BUILD.bazel index 5e9a8b77..7fe9fd4a 100644 --- a/java/com/cowlark/fluxengine/gui/BUILD.bazel +++ b/java/com/cowlark/fluxengine/gui/BUILD.bazel @@ -7,5 +7,6 @@ java_library( srcs = glob(["*.java"]), deps = [ "@maven//:com_formdev_flatlaf", + "@maven//:com_google_guava_guava", ], ) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index 89e2018c..b67d4e21 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.gui; -import com.formdev.flatlaf.FlatLightLaf; +import com.formdev.flatlaf.FlatDarkLaf; +import com.google.common.collect.ImmutableList; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.SwingUtilities; @@ -12,23 +13,14 @@ */ public class Gui { - public static void main(String[] args) + public void run(ImmutableList args) throws Exception { + UIManager.setLookAndFeel(new FlatDarkLaf()); SwingUtilities.invokeLater(() -> { - setupLookAndFeel(); createAndShowGui(); }); } - private static void setupLookAndFeel() - { - try { - UIManager.setLookAndFeel(new FlatLightLaf()); - } catch (Exception e) { - e.printStackTrace(); - } - } - private static void createAndShowGui() { JFrame frame = new JFrame("FluxEngine"); From 176dab18ca519b3c3cad240ef7693e4e8fdaaa0f Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 21:27:30 +0200 Subject: [PATCH 153/192] You can write disks! Or at least, erase them. --- .../algorithms/RawWriteOperation.java | 114 ++++++++++++++++ .../fluxengine/algorithms/ReadOperation.java | 128 ++++++++---------- .../fluxengine/cli/RawwriteCommand.java | 32 ++--- .../cowlark/fluxengine/cli/ReadCommand.java | 2 +- .../cowlark/fluxengine/cli/WriteCommand.java | 81 ++++++----- java/com/cowlark/fluxengine/data/Sector.java | 15 ++ 6 files changed, 241 insertions(+), 131 deletions(-) create mode 100644 java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java diff --git a/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java b/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java new file mode 100644 index 00000000..251fb491 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java @@ -0,0 +1,114 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +public class RawWriteOperation extends ReadOperation +{ + public RawWriteOperation(ConfigProto configProto) + { + super(configProto); + } + + private void writeTracks(Function producer, + Predicate verifier, + List logicalLocations) + { + Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + + getDiskRotationalPeriodNs(); + try (FluxSink fluxSink = getFluxSinkFactory().create()) + { + int index = 0; + for (CylinderHead ch : logicalLocations) + { + Logger.log(new LogMessage.OperationProgressLogMessage( + index * 100 / logicalLocations.size())); + index++; + + Common.testForEmergencyStop(); + + LogicalTrackLayout ltl = getDiskLayout().layoutByLogicalLocation.get(ch); + int retriesRemaining = getConfig().getDecoder().getRetries(); + for (; ; ) + { + for (int offset = 0; offset < ltl.groupSize; + offset += getDiskLayout().headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + + Logger.log(new LogMessage.BeginWriteOperationLogMessage( + physicalCylinder, + ltl.physicalHead)); + + boolean erase = false; + if (offset == getConfig().getDrive().getGroupOffset()) + { + Fluxmap fluxmap = producer.apply(ltl); + if (fluxmap == null) + erase = true; + else + { + fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); + Logger.logf( + "writing %d ms in %d bytes", + (int) (fluxmap.duration() / 1e6), + fluxmap.bytes()); + } + } else + erase = true; + + if (erase) + { + /* Erase this track rather than writing. */ + + Fluxmap blank = new Fluxmap(); + fluxSink.addFlux(physicalCylinder, physicalHead, blank); + Logger.logf("erased"); + } + + Logger.log(new LogMessage.EndWriteOperationLogMessage()); + } + + if (verifier.test(ltl)) + break; + + if (retriesRemaining == 0) + throw new FluxEngineException("fatal error on write"); + + Logger.logf("retrying; %d retries remaining", retriesRemaining); + retriesRemaining--; + } + } + } + + Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); + } + + public void rawWrite() + { + writeTracks( + ltl -> { + FluxSourceIterator iterator = + getFluxSource().readFlux(FluxReadParameters.builder() + .setCylinder(ltl.physicalCylinder) + .setHead(ltl.physicalHead) + .build()); + if (!iterator.hasNext()) + return null; + return iterator.next(); + }, ltl -> true, getDiskLayout().logicalLocations); + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java index b9906db5..1f287bbb 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java @@ -209,7 +209,60 @@ private void readAndDecodeTrack(LogicalTrackLayout ltl, } } - public void run(Disk disk) + /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a + * good and bad version of the same sector, the bad version is dropped). */ + static List collectSectors(List trackSectors, boolean collapseConflicts) + { + Map> sectors = new LinkedHashMap<>(); + for (Sector sector : trackSectors) + sectors.computeIfAbsent(sector.location, k -> new ArrayList<>()).add(sector); + + List sectorSet = new ArrayList<>(); + for (Map.Entry> entry : sectors.entrySet()) + { + List bucket = entry.getValue(); + Sector newSector = bucket.get(0); + for (int i = 1; i < bucket.size(); i++) + { + Sector right = bucket.get(i); + if ((newSector.status == Sector.Status.OK) && (right.status == Sector.Status.OK) && + (!newSector.data.equals(right.data))) + { + if (!collapseConflicts) + { + Sector s = new Sector(right); + s.status = Sector.Status.CONFLICT; + sectorSet.add(s); + } + Sector s = new Sector(newSector); + s.status = Sector.Status.CONFLICT; + newSector = s; + continue; + } + if (newSector.status == Sector.Status.CONFLICT) + continue; + if (right.status == Sector.Status.CONFLICT) + { + newSector = right; + continue; + } + if (newSector.status == Sector.Status.OK) + continue; + if (right.status == Sector.Status.OK) + newSector = right; + } + sectorSet.add(newSector); + } + + return sectorSet; + } + + static List collectSectors(List trackSectors) + { + return collectSectors(trackSectors, true); + } + + public void read(Disk disk) { FluxSinkFactory outputFluxSinkFactory = null; if (getConfig().getDecoder().hasCopyFluxTo()) @@ -340,79 +393,10 @@ public void run(Disk disk) Logger.log(new EndOperationLogMessage("Read complete")); } - /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a - * good and bad version of the same sector, the bad version is dropped). */ - static List collectSectors(List trackSectors, boolean collapseConflicts) - { - Map> sectors = new LinkedHashMap<>(); - for (Sector sector : trackSectors) - sectors.computeIfAbsent(sector.location, k -> new ArrayList<>()).add(sector); - - List sectorSet = new ArrayList<>(); - for (Map.Entry> entry : sectors.entrySet()) - { - List bucket = entry.getValue(); - Sector newSector = bucket.get(0); - for (int i = 1; i < bucket.size(); i++) - { - Sector right = bucket.get(i); - if ((newSector.status == Sector.Status.OK) && (right.status == Sector.Status.OK) && - (!newSector.data.equals(right.data))) - { - if (!collapseConflicts) - { - Sector s = copySector(right); - s.status = Sector.Status.CONFLICT; - sectorSet.add(s); - } - Sector s = copySector(newSector); - s.status = Sector.Status.CONFLICT; - newSector = s; - continue; - } - if (newSector.status == Sector.Status.CONFLICT) - continue; - if (right.status == Sector.Status.CONFLICT) - { - newSector = right; - continue; - } - if (newSector.status == Sector.Status.OK) - continue; - if (right.status == Sector.Status.OK) - newSector = right; - } - sectorSet.add(newSector); - } - - return sectorSet; - } - - static List collectSectors(List trackSectors) - { - return collectSectors(trackSectors, true); - } - - private static Sector copySector(Sector sector) - { - Sector s = new Sector(sector.location); - s.status = sector.status; - s.position = sector.position; - s.clockNs = sector.clockNs; - s.headerStartTimeNs = sector.headerStartTimeNs; - s.headerEndTimeNs = sector.headerEndTimeNs; - s.dataStartTimeNs = sector.dataStartTimeNs; - s.dataEndTimeNs = sector.dataEndTimeNs; - s.physicalLocation = sector.physicalLocation; - s.data = sector.data; - s.records = sector.records; - return s; - } - - public Disk run() + public Disk read() { Disk disk = new Disk(); - run(disk); + read(disk); ImageWriter writer = getImageWriter(); writer.printMap(disk.image); diff --git a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java index c8527400..9fea734f 100644 --- a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java @@ -2,9 +2,11 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; +import com.cowlark.fluxengine.algorithms.RawWriteOperation; import com.cowlark.fluxengine.algorithms.WriteOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.config.ConfigProtoOrBuilder; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.flags.ActionFlag; import com.cowlark.fluxengine.core.flags.FlagGroup; @@ -30,13 +32,6 @@ public class RawwriteCommand implements Command .setName("-d") .setHelpText("flux destination to write to") .build(); - private boolean erase = false; - private ActionFlag eraseFlag = ActionFlag.builder() - .setGroup(flags) - .setName("--erase") - .setHelpText("erases the destination") - .setVoidCallback(this::setErase) - .build(); @Override public String getHelp() @@ -44,29 +39,20 @@ public String getHelp() return "Writes a flux file to a disk. Warning: you can't use this to copy disks."; } - private void setErase() - { - erase = true; - } - @Override public void run(ImmutableList args) throws Exception { - ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); - if (sourceFluxFlag.isSet()) - builder.withFluxSource(sourceFluxFlag.get()); - String dest = destFluxFlag.isSet() ? destFluxFlag.get() : "drive:0"; - builder.withFluxSink(dest); - if (erase) - builder.withFluxSource("erase:"); - ConfigProto config = builder.build(); + ConfigProto configProto = new ConfigBuilder().fromFlags(args, flags) + .withFluxSource(sourceFluxFlag.get()) + .withFluxSink(destFluxFlag.get()) + .build(); - if (config.getFluxSource().getType() == FLUXTYPE_DRIVE) + if (configProto.getFluxSource().getType() == FLUXTYPE_DRIVE) throw new FluxEngineException("you can't use rawwrite to read from hardware"); - try (WriteOperation operation = new WriteOperation(config)) + try (RawWriteOperation operation = new RawWriteOperation(configProto)) { - operation.writeRawDiskCommand(); + operation.rawWrite(); } } } \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index 614647cf..9d100572 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -60,7 +60,7 @@ public void run(ImmutableList args) { try (ReadOperation operation = new ReadOperation(config)) { - operation.run(); + operation.read(); } } catch (Exception e) { diff --git a/java/com/cowlark/fluxengine/cli/WriteCommand.java b/java/com/cowlark/fluxengine/cli/WriteCommand.java index e3db93a5..dbf3b39a 100644 --- a/java/com/cowlark/fluxengine/cli/WriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/WriteCommand.java @@ -1,11 +1,14 @@ package com.cowlark.fluxengine.cli; +import com.cowlark.fluxengine.algorithms.WriteOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.flags.ActionFlag; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.fluxsource.FluxSource; import com.google.common.collect.ImmutableList; /** @@ -44,41 +47,49 @@ public String getHelp() @Override public void run(ImmutableList args) throws Exception { - ConfigBuilder builder = new ConfigBuilder().fromFlags(args, flags); - if (sourceImageFlag.isSet()) - builder.withImageReader(sourceImageFlag.get()); + var configProto = new ConfigBuilder().fromFlags(args, flags) + .withImageReader(sourceImageFlag.get()) + .withFluxSink(destFluxFlag.get()) + .build(); - String dest = destFluxFlag.isSet() ? destFluxFlag.get() : "drive:0"; - builder.withFluxSink(dest); - ConfigProto config = builder.build(); - - // try (var operation = new WriteOperation(config)){ - // ImageReader reader = operation.getImageReader(); - // Image image = reader.readImage(); - // - // config = config.toBuilder().mergeFrom(reader.getExtraConfig()).build(); - // - // DiskLayout diskLayout = new DiskLayout(config); - // Encoder encoder = Arch.createEncoder(config); - // FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); - // - // Decoder decoder = null; - // FluxSource verificationFluxSource = null; - // if (config.hasDecoder() && fluxSinkFactory.isHardware() && verify) - // { - // decoder = Arch.createDecoder(config); - // ConfigBuilder verifyBuilder = new ConfigBuilder().fromFlags(args, flags); - // verifyBuilder.withFluxSource(dest); - // verificationFluxSource = FluxSource.create(verifyBuilder.build()); - // } - // - // Writer.writeDiskCommand( - // config, - // diskLayout, - // image, - // encoder, - // fluxSinkFactory, - // decoder, - // verificationFluxSource); + try (WriteOperation operation = new WriteOperation(configProto)) + { + // Image image = operation.getImageReader().readImage(); + // + // FluxSource verificationFluxSource = null; + // if (configProto.hasDecoder() && operation.getFluxSinkFactory() + // .isHardware() && verify) + // { + // verificationFluxSource = FluxSource.create(operation + // .getVerificationFluxSource()); + // } + // ImageReader reader = operation.getImageReader(); + // Image image = reader.readImage(); + // + // config = config.toBuilder().mergeFrom(reader.getExtraConfig()).build(); + // + // DiskLayout diskLayout = new DiskLayout(config); + // Encoder encoder = Arch.createEncoder(config); + // FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); + // + // Decoder decoder = null; + // FluxSource verificationFluxSource = null; + // if (config.hasDecoder() && fluxSinkFactory.isHardware() && verify) + // { + // decoder = Arch.createDecoder(config); + // ConfigBuilder verifyBuilder = new ConfigBuilder().fromFlags(args, flags); + // verifyBuilder.withFluxSource(dest); + // verificationFluxSource = FluxSource.create(verifyBuilder.build()); + // } + // + // Writer.writeDiskCommand( + // config, + // diskLayout, + // image, + // encoder, + // fluxSinkFactory, + // decoder, + // verificationFluxSource); + } } } diff --git a/java/com/cowlark/fluxengine/data/Sector.java b/java/com/cowlark/fluxengine/data/Sector.java index 2ab03526..cc3a7b32 100644 --- a/java/com/cowlark/fluxengine/data/Sector.java +++ b/java/com/cowlark/fluxengine/data/Sector.java @@ -28,6 +28,21 @@ public Sector(LogicalLocation location) this.location = location; } + public Sector(Sector other) + { + this.location = other.location; + this.status = other.status; + this.position = other.position; + this.clockNs = other.clockNs; + this.headerStartTimeNs = other.headerStartTimeNs; + this.headerEndTimeNs = other.headerEndTimeNs; + this.dataStartTimeNs = other.dataStartTimeNs; + this.dataEndTimeNs = other.dataEndTimeNs; + this.physicalLocation = other.physicalLocation; + this.data = other.data; + this.records = other.records; + } + public static String statusToString(Status status) { switch (status) From e478c774d908aeea6a9e98a0198c517fcd405b57 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 21:55:59 +0200 Subject: [PATCH 154/192] Remove ab, as we're not needing it any more and it makes NetBeans unhappy. --- build/_objectify.py | 19 -- build/_sandbox.py | 49 --- build/_zip.py | 25 -- build/ab.mk | 126 ------- build/ab.ninja | 2 - build/ab.py | 798 -------------------------------------------- build/c.py | 597 --------------------------------- build/git.py | 26 -- build/pkg.py | 87 ----- build/protobuf.py | 192 ----------- build/toolchain.py | 12 - build/utils.py | 181 ---------- build/zip.py | 27 -- 13 files changed, 2141 deletions(-) delete mode 100644 build/_objectify.py delete mode 100644 build/_sandbox.py delete mode 100755 build/_zip.py delete mode 100644 build/ab.mk delete mode 100644 build/ab.ninja delete mode 100644 build/ab.py delete mode 100644 build/c.py delete mode 100644 build/git.py delete mode 100644 build/pkg.py delete mode 100644 build/protobuf.py delete mode 100644 build/toolchain.py delete mode 100644 build/utils.py delete mode 100644 build/zip.py diff --git a/build/_objectify.py b/build/_objectify.py deleted file mode 100644 index 17148954..00000000 --- a/build/_objectify.py +++ /dev/null @@ -1,19 +0,0 @@ -import sys -from functools import partial - -if len(sys.argv) != 3: - sys.exit("Usage: %s " % sys.argv[0]) -filename = sys.argv[1] -symbol = sys.argv[2] - -print("const uint8_t " + symbol + "[] = {") -n = 0 -with open(filename, "rb") as in_file: - for c in iter(partial(in_file.read, 1), b""): - print("0x%02X," % ord(c), end="") - n += 1 - if n % 16 == 0: - print() -print("};") - -print("const size_t " + symbol + "_len = sizeof(" + symbol + ");") diff --git a/build/_sandbox.py b/build/_sandbox.py deleted file mode 100644 index f7667a68..00000000 --- a/build/_sandbox.py +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/python3 - -from os.path import * -import argparse -import os -import shutil - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("-s", "--sandbox") - parser.add_argument("-v", "--verbose", action="store_true") - parser.add_argument("-l", "--link", action="store_true") - parser.add_argument("-e", "--export", action="store_true") - parser.add_argument("files", nargs="*") - args = parser.parse_args() - - assert args.sandbox, "You must specify a sandbox directory" - assert args.link ^ args.export, "You can't link and export at the same time" - - if args.link: - os.makedirs(args.sandbox, exist_ok=True) - for f in args.files: - sf = join(args.sandbox, f) - if args.verbose: - print("link", sf) - os.makedirs(dirname(sf), exist_ok=True) - try: - os.symlink(abspath(f), sf) - except PermissionError: - shutil.copy(f, sf) - - if args.export: - for f in args.files: - sf = join(args.sandbox, f) - if args.verbose: - print("export", sf) - df = dirname(f) - if df: - os.makedirs(df, exist_ok=True) - - try: - os.remove(f) - except FileNotFoundError: - pass - os.rename(sf, f) - - -main() diff --git a/build/_zip.py b/build/_zip.py deleted file mode 100755 index f5a49d09..00000000 --- a/build/_zip.py +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/python3 - -from os.path import * -import argparse -import os -from zipfile import ZipFile - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("-z", "--zipfile") - parser.add_argument("-v", "--verbose", action="store_true") - parser.add_argument("-f", "--file", nargs=2, action="append") - args = parser.parse_args() - - assert args.zipfile, "You must specify a zipfile to create" - - with ZipFile(args.zipfile, mode="w") as zf: - for zipname, filename in args.file: - if args.verbose: - print(filename, "->", zipname) - zf.write(filename, arcname=zipname) - - -main() diff --git a/build/ab.mk b/build/ab.mk deleted file mode 100644 index e8a9aa50..00000000 --- a/build/ab.mk +++ /dev/null @@ -1,126 +0,0 @@ -MAKENOT4 := $(if $(findstring 3.9999, $(lastword $(sort 3.9999 $(MAKE_VERSION)))),yes,no) - -ifeq ($(MAKENOT4),yes) -$(error You need GNU Make 4.x for this (if you're on OSX, use gmake).) -endif - -OBJ ?= .obj -PYTHON ?= python3 -PKG_CONFIG ?= pkg-config -HOST_PKG_CONFIG ?= $(PKG_CONFIG) -ECHO ?= echo -CP ?= cp - -HOSTCC ?= gcc -HOSTCXX ?= g++ -HOSTAR ?= ar -HOSTCFLAGS ?= -g -Og -HOSTCXXFLAGS ?= $(HOSTCFLAGS) -HOSTLDFLAGS ?= -g - -CC ?= $(HOSTCC) -CXX ?= $(HOSTCXX) -AR ?= $(HOSTAR) -CFLAGS ?= $(HOSTCFLAGS) -CXXFLAGS ?= $(CFLAGS) -LDFLAGS ?= $(HOSTLDFLAGS) - -NINJA ?= ninja - -ifdef VERBOSE - hide = -else - ifdef V - hide = - else - hide = @ - endif -endif - -# If enabled, shows a nice display of how far through the build you are. This -# doubles Make startup time. Also, on Make 4.3 and above, rebuilds don't show -# correct progress information. -AB_ENABLE_PROGRESS_INFO ?= true - -WINDOWS := no -OSX := no -LINUX := no -ifeq ($(OS),Windows_NT) - WINDOWS := yes -else - UNAME_S := $(shell uname -s) - ifeq ($(UNAME_S),Linux) - LINUX := yes - endif - ifeq ($(UNAME_S),Darwin) - OSX := yes - endif -endif - -ifeq ($(OS), Windows_NT) - EXT ?= .exe -endif -EXT ?= - -CWD=$(shell pwd) - -define newline - - -endef - -define check_for_command - $(shell command -v $1 >/dev/null || (echo "Required command '$1' missing" >&2 && kill $$PPID)) -endef - -$(call check_for_command,ninja) -$(call check_for_command,cmp) -$(call check_for_command,$(PYTHON)) - -pkg-config-hash = $(shell ($(PKG_CONFIG) --list-all && $(HOST_PKG_CONFIG) --list-all) | md5sum) -build-files = $(shell find . -name .obj -prune -o \( -name 'build.py' -a -type f \) -print) $(wildcard build/*.py) $(wildcard config.py) -build-file-timestamps = $(shell ls -l $(build-files) | md5sum) - -# Wipe the build file (forcing a regeneration) if the make environment is different. -# (Conveniently, this includes the pkg-config hash calculated above.) - -ignored-variables = MAKE_RESTARTS .VARIABLES MAKECMDGOALS MAKEFLAGS MFLAGS PAGER _ \ - DESKTOP_STARTUP_ID XAUTHORITY ICEAUTHORITY SSH_AUTH_SOCK SESSION_MANAGER \ - INVOCATION_ID SYSTEMD_EXEC_PID MANAGER_PID SSH_AGENT_PID JOURNAL_STREAM \ - GPG_TTY WINDOWID MANAGERPID MAKE_TERMOUT MAKE_TERMERR OLDPWD -$(shell mkdir -p $(OBJ)) -$(file >$(OBJ)/newvars.txt,$(foreach v,$(filter-out $(ignored-variables),$(.VARIABLES)),$(v)=$($(v))$(newline))) -$(shell touch $(OBJ)/vars.txt) -#$(shell diff -u $(OBJ)/vars.txt $(OBJ)/newvars.txt >&2) -$(shell cmp -s $(OBJ)/newvars.txt $(OBJ)/vars.txt || (rm -f $(OBJ)/build.ninja && echo "Environment changed --- regenerating" >&2)) -$(shell mv $(OBJ)/newvars.txt $(OBJ)/vars.txt) - -.PHONY: update-ab -update-ab: - @echo "Press RETURN to update ab from the repository, or CTRL+C to cancel." \ - && read a \ - && (curl -L https://github.com/davidgiven/ab/releases/download/dev/distribution.tar.xz | tar xvJf -) \ - && echo "Done." - -.PHONY: clean -clean:: - @echo CLEAN - $(hide) rm -rf $(OBJ) - -compile_commands.json: $(OBJ)/build.ninja - +$(hide) $(NINJA) -f $(OBJ)/build.ninja -t compdb > $@ - -export PYTHONHASHSEED = 1 -$(OBJ)/build.ninja $(OBJ)/build.targets &: - @echo "AB" - $(hide) $(PYTHON) -X pycache_prefix=$(OBJ)/__pycache__ build/ab.py \ - -o $(OBJ) build.py \ - -v $(OBJ)/vars.txt \ - || (rm -f $@ && false) - $(hide) cp $(OBJ)/compile_commands.json compile_commands.json - -include $(OBJ)/build.targets -.PHONY: $(ninja-targets) -.NOTPARALLEL: -$(ninja-targets): $(OBJ)/build.ninja - +$(hide) $(NINJA) -f $(OBJ)/build.ninja $@ diff --git a/build/ab.ninja b/build/ab.ninja deleted file mode 100644 index 98599f85..00000000 --- a/build/ab.ninja +++ /dev/null @@ -1,2 +0,0 @@ -rule rule - command = $command diff --git a/build/ab.py b/build/ab.py deleted file mode 100644 index 655f9e2d..00000000 --- a/build/ab.py +++ /dev/null @@ -1,798 +0,0 @@ -from collections import namedtuple -from copy import copy -from importlib.machinery import SourceFileLoader, PathFinder, ModuleSpec -from os.path import * -from pathlib import Path -from typing import Iterable -import argparse -import ast -import builtins -import functools -import hashlib -import importlib -import importlib.util -import inspect -import json -import os -import re -import string -import sys -import types - -VERBOSE_NINJA_FILE = False - -quiet = False -cwdStack = [""] -targets = {} -unmaterialisedTargets = {} # dict, not set, to get consistent ordering -materialisingStack = [] -defaultGlobals = {} -outputTargets = set() -commandsDb = [] -belatedErrors = [] -atexits = [] - -RE_FORMAT_SPEC = re.compile( - r"(?:(?P[\s\S])?(?P[<>=^]))?" - r"(?P[- +])?" - r"(?Pz)?" - r"(?P#)?" - r"(?P0)?" - r"(?P\d+)?" - r"(?P[_,])?" - r"(?:(?P\.)(?P\d+))?" - r"(?P[bcdeEfFgGnosxX%])?" -) - -CommandFormatSpec = namedtuple( - "CommandFormatSpec", RE_FORMAT_SPEC.groupindex.keys() -) - -sys.path += ["."] -old_import = builtins.__import__ - - -class Environment(types.SimpleNamespace): - def setdefault(self, name, value): - if not hasattr(self, name): - setattr(self, name, value) - - -G = Environment() - - -class PathFinderImpl(PathFinder): - def find_spec(self, fullname, path, target=None): - # The second test here is needed for Python 3.9. - if not path or not path[0]: - path = ["."] - if len(path) != 1: - return None - - try: - path = relpath(path[0]) - except ValueError: - return None - - realpath = fullname.replace(".", "/") - buildpath = realpath + ".py" - if isfile(buildpath): - spec = importlib.util.spec_from_file_location( - name=fullname, - location=buildpath, - loader=BuildFileLoaderImpl(fullname=fullname, path=buildpath), - submodule_search_locations=[], - ) - return spec - if isdir(realpath): - return ModuleSpec(fullname, None, origin=realpath, is_package=True) - return None - - -class BuildFileLoaderImpl(SourceFileLoader): - def exec_module(self, module): - sourcepath = relpath(module.__file__) - - if not quiet: - print("loading", sourcepath) - cwdStack.append(dirname(sourcepath)) - super(SourceFileLoader, self).exec_module(module) - cwdStack.pop() - - -sys.meta_path.insert(0, PathFinderImpl()) - - -class ABException(BaseException): - pass - - -def error(message): - raise ABException(message) - - -def _undo_escaped_dollar(s, op): - return s.replace(f"$${op}", f"${op}") - - -class BracketedFormatter(string.Formatter): - def parse(self, format_string): - while format_string: - m = re.search(f"(?:[^$]|^)()\\$\\[()", format_string) - if not m: - yield ( - _undo_escaped_dollar(format_string, "["), - None, - None, - None, - ) - break - left = format_string[: m.start(1)] - right = format_string[m.end(2) :] - - offset = len(right) + 1 - try: - ast.parse(right) - except SyntaxError as e: - if not str(e).startswith(f"unmatched ']'"): - raise e - offset = e.offset - - expr = right[0 : offset - 1] - format_string = right[offset:] - - yield ( - _undo_escaped_dollar(left, "[") if left else None, - expr, - None, - None, - ) - - -class GlobalFormatter(string.Formatter): - def parse(self, format_string): - while format_string: - m = re.search(f"(?:[^$]|^)()\\$\\(([^)]*)\\)()", format_string) - if not m: - yield ( - format_string, - None, - None, - None, - ) - break - left = format_string[: m.start(1)] - var = m[2] - format_string = format_string[m.end(3) :] - - yield ( - left if left else None, - var, - None, - None, - ) - - def get_field(self, name, a1, a2): - return ( - getattr(G, name), - False, - ) - - def format_field(self, value, format_spec): - if not value: - return "" - return str(value) - - -globalFormatter = GlobalFormatter() - - -def substituteGlobalVariables(value): - while True: - oldValue = value - value = globalFormatter.format(value) - if value == oldValue: - return _undo_escaped_dollar(value, "(") - - -def Rule(func): - sig = inspect.signature(func) - - @functools.wraps(func) - def wrapper(*, name=None, replaces=None, **kwargs): - cwd = None - if "cwd" in kwargs: - cwd = kwargs["cwd"] - del kwargs["cwd"] - - if not cwd: - if replaces: - cwd = replaces.cwd - else: - cwd = cwdStack[-1] - - if name: - if name[0] != "+": - name = "+" + name - t = Target(cwd, join(cwd, name)) - - assert ( - t.name not in targets - ), f"target {t.name} has already been defined" - targets[t.name] = t - elif replaces: - t = replaces - else: - raise ABException("you must supply either 'name' or 'replaces'") - - t.cwd = cwd - t.types = func.__annotations__ - t.callback = func - t.traits.add(func.__name__) - if "args" in kwargs: - t.explicit_args = kwargs["args"] - t.args.update(t.explicit_args) - del kwargs["args"] - if "traits" in kwargs: - t.traits |= kwargs["traits"] - del kwargs["traits"] - - t.binding = sig.bind(name=name, self=t, **kwargs) - t.binding.apply_defaults() - - unmaterialisedTargets[t] = None - if replaces: - t.materialise(replacing=True) - return t - - defaultGlobals[func.__name__] = wrapper - return wrapper - - -def _isiterable(xs): - return isinstance(xs, Iterable) and not isinstance( - xs, (str, bytes, bytearray) - ) - - -class Target: - def __init__(self, cwd, name): - self.name = name - self.localname = self.name.rsplit("+")[-1] - self.traits = set() - self.dir = join(G.OBJ, name) - self.ins = [] - self.outs = [] - self.deps = [] - self.materialised = False - self.args = {} - - def __eq__(self, other): - return self.name is other.name - - def __lt__(self, other): - return self.name < other.name - - def __hash__(self): - return id(self) - - def __repr__(self): - return f"Target('{self.name}')" - - def templateexpand(selfi, s): - class Formatter(BracketedFormatter): - def get_field(self, name, a1, a2): - return ( - eval(name, selfi.callback.__globals__, selfi.args), - False, - ) - - def format_field(self, value, format_spec): - if not value: - return "" - if type(value) == str: - return value - if _isiterable(value): - value = list(value) - if type(value) != list: - value = [value] - return " ".join( - [selfi.templateexpand(f) for f in filenamesof(value)] - ) - - s = Formatter().format(s) - return substituteGlobalVariables(s) - - def materialise(self, replacing=False): - if self not in unmaterialisedTargets: - return - - if not replacing and self in materialisingStack: - print("Found dependency cycle:") - for i in materialisingStack: - print(f" {i.name}") - print(f" {self.name}") - sys.exit(1) - materialisingStack.append(self) - - # Perform type conversion to the declared rule parameter types. - - try: - for k, v in self.binding.arguments.items(): - if k != "kwargs": - t = self.types.get(k, None) - if t: - v = t.convert(v, self) - self.args[k] = copy(v) - else: - for kk, vv in v.items(): - t = self.types.get(kk, None) - if t: - vv = t.convert(v, self) - self.args[kk] = copy(vv) - self.args["name"] = self.name - self.args["dir"] = self.dir - self.args["self"] = self - - # Actually call the callback. - - cwdStack.append(self.cwd) - if "kwargs" in self.binding.arguments.keys(): - # If the caller wants kwargs, return all arguments except the standard ones. - cbargs = { - k: v for k, v in self.args.items() if k not in {"dir"} - } - else: - # Otherwise, just call the callback with the ones it asks for. - cbargs = {} - for k in self.binding.arguments.keys(): - if k != "kwargs": - try: - cbargs[k] = self.args[k] - except KeyError: - error( - f"invocation of {self} failed because {k} isn't an argument" - ) - self.callback(**cbargs) - cwdStack.pop() - except BaseException as e: - print(f"Error materialising {self}: {self.callback}") - print(f"Arguments: {self.args}") - raise e - - if self.outs is None: - raise ABException(f"{self.name} didn't set self.outs") - - if self in unmaterialisedTargets: - del unmaterialisedTargets[self] - materialisingStack.pop() - self.materialised = True - - def convert(value, target): - if not value: - return None - return target.targetof(value) - - def targetof(self, value): - if isinstance(value, str) and (value[0] == "="): - value = join(self.dir, value[1:]) - - return targetof(value, self.cwd) - - -def _filetarget(value, cwd): - if value in targets: - return targets[value] - - t = Target(cwd, value) - t.outs = [value] - targets[value] = t - return t - - -def getcwd(): - return cwdStack[-1] - - -def targetof(value, cwd=None): - if not cwd: - cwd = cwdStack[-1] - if isinstance(value, Path): - value = value.as_posix() - if isinstance(value, Target): - t = value - else: - assert ( - value[0] != "=" - ), "can only use = for targets associated with another target" - - if value.startswith("."): - # Check for local rule. - if value.startswith(".+"): - value = normpath(join(cwd, value[1:])) - # Check for local path. - elif value.startswith("./"): - value = normpath(join(cwd, value)) - # Explicit directories are always raw files. - if value.endswith("/"): - return _filetarget(value, cwd) - # Anything in .obj is a raw file. - elif value.startswith(outputdir) or value.startswith(G.OBJ): - return _filetarget(value, cwd) - - # If this is not a rule lookup... - if "+" not in value: - # ...and if the value is pointing at a directory without a trailing /, - # it's a shorthand rule lookup. - if isdir(value): - value = value + "+" + basename(value) - # Otherwise it's an absolute file. - else: - return _filetarget(value, cwd) - - # At this point we have the fully qualified name of a rule. - - (path, target) = value.rsplit("+", 1) - value = join(path, "+" + target) - if value not in targets: - # Load the new build file. - - path = join(path, "build.py") - try: - loadbuildfile(path) - except ModuleNotFoundError: - error( - f"no such build file '{path}' while trying to resolve '{value}'" - ) - assert ( - value in targets - ), f"build file at '{path}' doesn't contain '+{target}' when trying to resolve '{value}'" - - t = targets[value] - - t.materialise() - return t - - -class Targets: - def convert(value, target): - if not value: - return [] - assert _isiterable(value), "cannot convert non-list to Targets" - return [target.targetof(x) for x in flatten(value)] - - -class TargetsMap: - def convert(value, target): - if not value: - return {} - output = {k: target.targetof(v) for k, v in value.items()} - for k, v in output.items(): - assert ( - len(filenamesof([v])) == 1 - ), f"targets of a TargetsMap used as an argument of {target} with key '{k}' must contain precisely one output file, but was {filenamesof([v])}" - return output - - -def _removesuffix(self, suffix): - # suffix='' should not call self[:-0]. - if suffix and self.endswith(suffix): - return self[: -len(suffix)] - else: - return self[:] - - -def loadbuildfile(filename): - modulename = _removesuffix(filename.replace("/", "."), ".py") - if modulename not in sys.modules: - spec = importlib.util.spec_from_file_location( - name=modulename, - location=filename, - loader=BuildFileLoaderImpl(fullname=modulename, path=filename), - submodule_search_locations=[], - ) - module = importlib.util.module_from_spec(spec) - sys.modules[modulename] = module - spec.loader.exec_module(module) - - -def flatten(items): - def generate(xs): - for x in xs: - if _isiterable(x): - yield from generate(x) - else: - yield x - - return list(generate(items)) - - -def targetnamesof(items): - assert _isiterable(items), "argument of filenamesof is not a collection" - - return [t.name for t in items] - - -def filenamesof(items): - assert _isiterable(items), "argument of filenamesof is not a collection" - - def generate(xs): - for x in xs: - if isinstance(x, Target): - x.materialise() - yield from generate(x.outs) - else: - yield x - - return list(generate(items)) - - -def filenameof(x): - xs = filenamesof(x.outs) - assert ( - len(xs) == 1 - ), f"tried to use filenameof() on {x} which does not have exactly one output: {x.outs}" - return xs[0] - - -def emit(*args, into=None): - s = " ".join(args) + "\n" - if into is not None: - into += [s] - else: - ninjaFp.write(s) - - -def shell(*args): - s = "".join(args) + "\n" - shellFp.write(s) - - -def add_commanddb_entry(commands, file): - global commandsDb - commandsDb += [ - { - "directory": os.getcwd(), - "command": (" && ".join(commands)), - "file": file, - } - ] - - -def add_belated_error(msg): - global belatedErrors - belatedErrors += [msg] - - -def add_atexit(cb): - global atexits - atexits += [cb] - - -def emit_rule( - self, ins, outs, cmds=[], label=None, sandbox=True, generator=False -): - name = self.name - fins = [self.templateexpand(f) for f in set(filenamesof(ins))] - fouts = [self.templateexpand(f) for f in filenamesof(outs)] - - global outputTargets - outputTargets.update(fouts) - outputTargets.add(name) - - emit("") - if VERBOSE_NINJA_FILE: - for k, v in self.args.items(): - emit(f"# {k} = {v}") - - if outs: - os.makedirs(self.dir, exist_ok=True) - rule = [] - - sandbox = sandbox and (G.AB_SANDBOX == "yes") - if sandbox: - sandbox = join(self.dir, "sandbox") - emit(f"rm -rf {sandbox}", into=rule) - emit( - f"{G.PYTHON} build/_sandbox.py --link -s", - sandbox, - *fins, - into=rule, - ) - for c in cmds: - emit(f"(cd {sandbox} &&", c, ")", into=rule) - emit( - f"{G.PYTHON} build/_sandbox.py --export -s", - sandbox, - *fouts, - into=rule, - ) - else: - for c in cmds: - emit(c, into=rule) - - ruletext = "".join(rule) - if len(ruletext) > 7000: - rulehash = hashlib.sha1(ruletext.encode()).hexdigest() - - rulef = join(self.dir, f"rule-{rulehash}.sh") - with open(rulef, "wt") as fp: - fp.write("set -e\n") - fp.write(ruletext) - - emit("build", *fouts, ":rule", *fins) - emit(" command=sh", rulef) - else: - emit("build", *fouts, ":rule", *fins) - emit( - " command=", - "&&".join([s.strip() for s in rule]).replace("$", "$$"), - ) - if label: - emit(" description=", label) - if generator: - emit(" generator=true") - - emit("build", name, ":phony", *fouts) - else: - assert len(cmds) == 0, "rules with no outputs cannot have commands" - emit("build", name, ":phony", *fins) - - emit("") - - -@Rule -def simplerule( - self, - name, - ins: Targets = [], - outs: Targets = [], - deps: Targets = [], - commands=[], - add_to_commanddb=False, - sandbox=True, - generator=False, - label="RULE", -): - self.ins = ins - self.outs = outs - self.deps = deps - - dirs = [] - cs = [] - for out in filenamesof(outs): - dir = dirname(out) - if dir and dir not in dirs: - dirs += [dir] - - cs = [("mkdir -p %s" % dir) for dir in dirs] - - coreCommands = [] - for c in commands: - coreCommands += [self.templateexpand(c)] - cs += coreCommands - - if add_to_commanddb: - infiles = filenamesof(ins) - if len(infiles) > 0: - global commandsDb - commandsDb += [ - { - "directory": os.getcwd(), - "command": (" && ".join(coreCommands)), - "file": infiles[0], - } - ] - - emit_rule( - self=self, - ins=ins + deps, - outs=outs, - label=self.templateexpand("$[label] $[name]") if label else None, - cmds=cs, - sandbox=sandbox, - generator=generator, - ) - - -@Rule -def export(self, name=None, items: TargetsMap = {}, deps: Targets = []): - ins = [] - outs = [] - for dest, src in items.items(): - dest = self.targetof(dest) - outs += [dest] - - destf = self.templateexpand(filenameof(dest)) - outputTargets.update([destf]) - - srcs = filenamesof([src]) - assert ( - len(srcs) == 1 - ), "a dependency of an exported file must have exactly one output file" - srcf = self.templateexpand(srcs[0]) - - subrule = simplerule( - name=f"{self.localname}/{destf}", - cwd=self.cwd, - ins=[srcs[0]], - outs=[destf], - commands=["$(CP) -H %s %s" % (srcf, destf)], - label="EXPORT", - ) - subrule.materialise() - - self.ins = [] - self.outs = deps + outs - outputTargets.add(name) - - emit("") - emit( - "build", - name, - ":phony", - *[self.templateexpand(f) for f in filenamesof(outs + deps)], - ) - - -def main(): - parser = argparse.ArgumentParser() - parser.add_argument("-q", "--quiet", action="store_true") - parser.add_argument("-v", "--varfile") - parser.add_argument("-o", "--outputdir") - parser.add_argument("-D", "--define", action="append", default=[]) - parser.add_argument("files", nargs="+") - args = parser.parse_args() - - global quiet - quiet = args.quiet - - vardefs = args.define - if args.varfile: - with open(args.varfile, "rt") as fp: - vardefs = vardefs + list(fp) - - for line in vardefs: - if "=" in line: - name, value = line.split("=", 1) - G.setdefault(name.strip(), value.strip()) - G.setdefault("AB_SANDBOX", "yes") - - global ninjaFp, shellFp, jsonFp, outputdir - outputdir = args.outputdir - G.setdefault("OBJ", outputdir) - ninjaFp = open(outputdir + "/build.ninja", "wt") - ninjaFp.write(f"include build/ab.ninja\n") - - for k in ["Rule"]: - defaultGlobals[k] = globals()[k] - - global __name__ - sys.modules["build.ab"] = sys.modules[__name__] - __name__ = "build.ab" - - for f in args.files: - loadbuildfile(f) - - while unmaterialisedTargets: - t = next(iter(unmaterialisedTargets)) - t.materialise() - - if belatedErrors: - print("FAILED:") - for s in belatedErrors: - print(s) - sys.exit(1) - - for cb in atexits: - cb() - - with open(outputdir + "/build.targets", "wt") as fp: - fp.write("ninja-targets =") - fp.write(substituteGlobalVariables(" ".join(outputTargets))) - - with open(outputdir + "/compile_commands.json", "wt") as fp: - json.dump(commandsDb, fp) - - -main() diff --git a/build/c.py b/build/c.py deleted file mode 100644 index 67df0dce..00000000 --- a/build/c.py +++ /dev/null @@ -1,597 +0,0 @@ -from build.ab import ( - Rule, - Targets, - TargetsMap, - filenameof, - filenamesof, - flatten, - simplerule, - add_commanddb_entry, - G, -) -from build.utils import stripext, collectattrs -from build.toolchain import Toolchain, HostToolchain -from os.path import * - -if G.OSX != "yes": - G.STARTGROUP = "-Wl,--start-group" - G.ENDGROUP = "-Wl,--end-group" -else: - G.STARTGROUP = "" - G.ENDGROUP = "" - -Toolchain.CC = ["$(CC) -c -o $[outs[0]] $[ins[0]] $(CFLAGS) $[cflags]"] -Toolchain.CPP = ["$(CC) -E -P -o $[outs] $[cflags] -x c $[ins]"] -Toolchain.CXX = ["$(CXX) -c -o $[outs[0]] $[ins[0]] $(CXXFLAGS) $[cflags]"] -Toolchain.AR = ["$(AR) cqs $[outs[0]] $[ins]"] -Toolchain.ARXX = ["$(AR) cqs $[outs[0]] $[ins]"] -Toolchain.CLINK = [ - "$(CC) -o $[outs[0]] $(STARTGROUP) $[ins] $[ldflags] $(LDFLAGS) $(ENDGROUP)" -] -Toolchain.CXXLINK = [ - "$(CXX) -o $[outs[0]] $(STARTGROUP) $[ins] $[ldflags] $(LDFLAGS) $(ENDGROUP)" -] - -Toolchain.is_source_file = ( - lambda f: f.endswith(".c") - or f.endswith(".cc") - or f.endswith(".cpp") - or f.endswith(".S") - or f.endswith(".s") - or f.endswith(".m") - or f.endswith(".mm") -) - - -# Given a set of dependencies, finds the set of relevant library targets (i.e. -# contributes *.a files) for compiling C programs. The actual list of libraries -# is in dep.clibrary_files. -def _toolchain_find_library_targets(deps): - lib_deps = [] - for d in deps: - lib_deps = _combine(lib_deps, d.args.get("clibrary_deps", [])) - return lib_deps - - -Toolchain.find_c_library_targets = _toolchain_find_library_targets - - -# Given a set of dependencies, finds the set of relevant header targets (i.e. -# contributes *.h files) for compiling C programs. The actual list of libraries -# is in dep.cheader_files. -def _toolchain_find_header_targets(deps, initial=[]): - hdr_deps = initial - for d in deps: - hdr_deps = _combine(hdr_deps, d.args.get("cheader_deps", [])) - return hdr_deps - - -Toolchain.find_c_header_targets = _toolchain_find_header_targets - - -HostToolchain.CC = [ - "$(HOSTCC) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]" -] -HostToolchain.CPP = ["$(HOSTCC) -E -P -o $[outs] $[cflags] -x c $[ins]"] -HostToolchain.CXX = [ - "$(HOSTCXX) -c -o $[outs[0]] $[ins[0]] $(HOSTCFLAGS) $[cflags]" -] -HostToolchain.AR = ["$(HOSTAR) cqs $[outs[0]] $[ins]"] -HostToolchain.ARXX = ["$(HOSTAR) cqs $[outs[0]] $[ins]"] -HostToolchain.CLINK = [ - "$(HOSTCC) -o $[outs[0]] $(STARTGROUP) $[ins] $[ldflags] $(HOSTLDFLAGS) $(ENDGROUP)" -] -HostToolchain.CXXLINK = [ - "$(HOSTCXX) -o $[outs[0]] $(STARTGROUP) $[ins] $[ldflags] $(HOSTLDFLAGS) $(ENDGROUP)" -] - - -def _combine(list1, list2): - r = list(list1) - for i in list2: - if i not in r: - r.append(i) - return r - - -def _indirect(deps, name): - r = [] - for d in deps: - r = _combine(r, d.args.get(name, [d])) - return r - - -def cfileimpl( - self, name, srcs, deps, suffix, commands, label, toolchain, cflags -): - outleaf = "=" + stripext(basename(filenameof(srcs[0]))) + suffix - - hdr_deps = toolchain.find_c_header_targets(deps) - other_deps = [ - d - for d in deps - if ("cheader_deps" not in d.args) and ("clibrary_deps" not in d.args) - ] - hdr_files = collectattrs(targets=hdr_deps, name="cheader_files") - cflags = collectattrs( - targets=hdr_deps, name="caller_cflags", initial=cflags - ) - - t = simplerule( - replaces=self, - ins=srcs, - deps=other_deps + hdr_files, - outs=[outleaf], - label=label, - commands=commands, - add_to_commanddb=True, - args={"cflags": cflags}, - ) - - -@Rule -def cfile( - self, - name, - srcs: Targets = None, - deps: Targets = None, - cflags=[], - suffix=".o", - toolchain=Toolchain, - label="CC", -): - cfileimpl( - self, - name, - srcs, - deps, - suffix, - toolchain.CC, - toolchain.PREFIX + label, - toolchain, - cflags, - ) - - -@Rule -def cxxfile( - self, - name, - srcs: Targets = None, - deps: Targets = None, - cflags=[], - suffix=".o", - toolchain=Toolchain, - label="CXX", -): - cfileimpl( - self, - name, - srcs, - deps, - suffix, - toolchain.CXX, - toolchain.PREFIX + label, - toolchain, - cflags, - ) - - -def _removeprefix(self, prefix): - if self.startswith(prefix): - return self[len(prefix) :] - else: - return self[:] - - -def findsources(self, srcs, deps, cflags, filerule, toolchain, cwd): - for f in filenamesof(srcs): - if not toolchain.is_source_file(f): - cflags = cflags + [f"-I{dirname(f)}"] - deps = deps + [f] - - objs = [] - for s in flatten(srcs): - objs += [ - filerule( - name=join(self.localname, _removeprefix(f, G.OBJ + "/")), - srcs=[f], - deps=deps, - cflags=sorted(set(cflags)), - toolchain=toolchain, - cwd=cwd, - args=getattr(self, "explicit_args", {}), - ) - for f in filenamesof([s]) - if toolchain.is_source_file(f) - ] - if any(f.endswith(".o") for f in filenamesof([s])): - objs += [s] - - return objs - - -def libraryimpl( - self, - name, - srcs, - deps, - hdrs, - caller_cflags, - caller_ldflags, - cflags, - ldflags, - toolchain, - commands, - label, - filerule, -): - hdr_deps = toolchain.find_c_header_targets(deps) + [self] - lib_deps = toolchain.find_c_library_targets(deps) + [self] - - hr = None - hf = [] - ar = None - if hdrs: - cs = [] - ins = hdrs.values() - outs = [] - i = 0 - for dest, src in hdrs.items(): - s = filenamesof([src]) - assert ( - len(s) == 1 - ), "the target of a header must return exactly one file" - - cs += [f"$(CP) $[ins[{i}]] $[outs[{i}]]"] - outs += ["=" + dest] - i = i + 1 - - hr = simplerule( - name=f"{self.localname}_hdr", - ins=ins, - outs=outs, - commands=cs, - label=toolchain.PREFIX + "CHEADERS", - ) - hr.args["cheader_deps"] = [hr] - hr.args["cheader_files"] = [hr] - hf = [f"-I{hr.dir}"] - - if srcs: - # Can't depend on the current target to get the library headers, because - # if we do it'll cause a dependency loop. - objs = findsources( - self, - srcs, - deps + ([hr] if hr else []), - cflags + hf, - filerule, - toolchain, - self.cwd, - ) - - ar = simplerule( - name=f"{self.localname}_lib", - ins=objs, - outs=[f"={self.localname}.a"], - deps=deps, - label=label, - commands=commands, - ) - ar.materialise() - - self.outs = ([hr] if hr else []) + ([ar] if ar else []) - self.deps = self.outs - self.args["cheader_deps"] = hdr_deps - self.args["clibrary_deps"] = lib_deps - self.args["cheader_files"] = [hr] if hr else [] - self.args["clibrary_files"] = [ar] if ar else [] - self.args["caller_cflags"] = caller_cflags + hf - self.args["caller_ldflags"] = caller_ldflags - - -@Rule -def clibrary( - self, - name, - srcs: Targets = None, - deps: Targets = None, - hdrs: TargetsMap = None, - caller_cflags=[], - caller_ldflags=[], - cflags=[], - ldflags=[], - toolchain=Toolchain, - label="LIB", - cfilerule=cfile, -): - libraryimpl( - self, - name, - srcs, - deps, - hdrs, - caller_cflags, - caller_ldflags, - cflags, - ldflags, - toolchain, - toolchain.AR, - toolchain.PREFIX + label, - cfilerule, - ) - - -@Rule -def hostclibrary( - self, - name, - srcs: Targets = None, - deps: Targets = None, - hdrs: TargetsMap = None, - caller_cflags=[], - caller_ldflags=[], - cflags=[], - ldflags=[], - toolchain=HostToolchain, - label="LIB", - cfilerule=cfile, -): - libraryimpl( - self, - name, - srcs, - deps, - hdrs, - caller_cflags, - caller_ldflags, - cflags, - ldflags, - toolchain, - toolchain.AR, - toolchain.PREFIX + label, - cfilerule, - ) - - -@Rule -def cxxlibrary( - self, - name, - srcs: Targets = None, - deps: Targets = None, - hdrs: TargetsMap = None, - caller_cflags=[], - caller_ldflags=[], - cflags=[], - ldflags=[], - toolchain=Toolchain, - label="CXXLIB", - cxxfilerule=cxxfile, -): - libraryimpl( - self, - name, - srcs, - deps, - hdrs, - caller_cflags, - caller_ldflags, - cflags, - ldflags, - toolchain, - toolchain.ARXX, - toolchain.PREFIX + label, - cxxfilerule, - ) - - -@Rule -def hostcxxlibrary( - self, - name, - srcs: Targets = None, - deps: Targets = None, - hdrs: TargetsMap = None, - caller_cflags=[], - caller_ldflags=[], - cflags=[], - ldflags=[], - toolchain=HostToolchain, - label="CXXLIB", - cxxfilerule=cxxfile, -): - libraryimpl( - self, - name, - srcs, - deps, - hdrs, - caller_cflags, - caller_ldflags, - cflags, - ldflags, - toolchain, - toolchain.ARXX, - toolchain.PREFIX + label, - cxxfilerule, - ) - - -def programimpl( - self, - name, - srcs, - deps, - cflags, - ldflags, - toolchain, - commands, - label, - filerule, -): - cfiles = findsources( - self, srcs, deps, cflags, filerule, toolchain, self.cwd - ) - - lib_deps = toolchain.find_c_library_targets(deps) - libs = collectattrs(targets=lib_deps, name="clibrary_files") - ldflags = collectattrs( - targets=lib_deps, name="caller_ldflags", initial=ldflags - ) - - simplerule( - replaces=self, - ins=cfiles + libs, - outs=[f"={self.localname}{toolchain.EXE}"], - deps=deps, - label=label, - commands=commands, - args={"ldflags": ldflags}, - ) - - -@Rule -def cprogram( - self, - name, - srcs: Targets = None, - deps: Targets = None, - cflags=[], - ldflags=[], - toolchain=Toolchain, - label="CLINK", - cfilerule=cfile, -): - programimpl( - self, - name, - srcs, - deps, - cflags, - ldflags, - toolchain, - toolchain.CLINK, - toolchain.PREFIX + label, - cfilerule, - ) - - -@Rule -def hostcprogram( - self, - name, - srcs: Targets = None, - deps: Targets = None, - cflags=[], - ldflags=[], - toolchain=HostToolchain, - label="CLINK", - cfilerule=cfile, -): - programimpl( - self, - name, - srcs, - deps, - cflags, - ldflags, - toolchain, - toolchain.CLINK, - toolchain.PREFIX + label, - cfilerule, - ) - - -@Rule -def cxxprogram( - self, - name, - srcs: Targets = None, - deps: Targets = None, - cflags=[], - ldflags=[], - toolchain=Toolchain, - label="CXXLINK", - cxxfilerule=cxxfile, -): - programimpl( - self, - name, - srcs, - deps, - cflags, - ldflags, - toolchain, - toolchain.CXXLINK, - toolchain.PREFIX + label, - cxxfilerule, - ) - - -@Rule -def hostcxxprogram( - self, - name, - srcs: Targets = None, - deps: Targets = None, - cflags=[], - ldflags=[], - toolchain=HostToolchain, - label="CXXLINK", - cxxfilerule=cxxfile, -): - programimpl( - self, - name, - srcs, - deps, - cflags, - ldflags, - toolchain, - toolchain.CXXLINK, - toolchain.PREFIX + label, - cxxfilerule, - ) - - -def _cppfileimpl(self, name, srcs, deps, cflags, toolchain): - hdr_deps = _indirect(deps, "cheader_deps") - cflags = collectattrs( - targets=hdr_deps, name="caller_cflags", initial=cflags - ) - - simplerule( - replaces=self, - ins=srcs, - outs=[f"={self.localname}"], - deps=deps, - commands=toolchain.CPP, - args={"cflags": cflags}, - label=toolchain.PREFIX + "CPPFILE", - ) - - -@Rule -def cppfile( - self, - name, - srcs: Targets = [], - deps: Targets = [], - cflags=[], - toolchain=Toolchain, -): - _cppfileimpl(self, name, srcs, deps, cflags, toolchain) - - -@Rule -def hostcppfile( - self, - name, - srcs: Targets = [], - deps: Targets = [], - cflags=[], - toolchain=HostToolchain, -): - _cppfileimpl(self, name, srcs, deps, cflags, toolchain) diff --git a/build/git.py b/build/git.py deleted file mode 100644 index cb7ec267..00000000 --- a/build/git.py +++ /dev/null @@ -1,26 +0,0 @@ -from build.ab import Rule, simplerule -from build.utils import add_wildcard_dependency - - -@Rule -def git_repository(self, name, url, branch, path, commit=None): - simplerule( - replaces=self, - outs=[f"{path}/.git/config"], - commands=[ - f"rmdir {path}/.git", - f"git clone -q {url} --depth=1 -c advice.detachedHead=false -b {branch} {path}", - ] - + ( - [ - f"cd {path} && git fetch --depth=1 origin {commit} && git checkout {commit}" - ] - if commit - else [] - ), - sandbox=False, - generator=True, - label="GITREPOSITORY", - ) - - add_wildcard_dependency(self, f"{path}/**/*", exclude="**/.git/**") diff --git a/build/pkg.py b/build/pkg.py deleted file mode 100644 index f948d7ae..00000000 --- a/build/pkg.py +++ /dev/null @@ -1,87 +0,0 @@ -from build.ab import Rule, Target, G, add_belated_error -import subprocess - - -class _PkgConfig: - package_present = set() - package_properties = {} - pkgconfig = None - - def __init__(self, cmd): - assert cmd, "no pkg-config environment variable supplied" - self.pkgconfig = cmd - - r = subprocess.run(f"{cmd} --list-all", shell=True, capture_output=True) - ps = r.stdout.decode("utf-8") - self.package_present = {l.split(" ", 1)[0] for l in ps.splitlines()} - - def has_package(self, name): - return name in self.package_present - - def get_property(self, name, flag): - p = f"{name}.{flag}" - if p not in self.package_properties: - r = subprocess.run( - f"{self.pkgconfig} {flag} {name}", - shell=True, - capture_output=True, - ) - self.package_properties[p] = r.stdout.decode("utf-8").strip() - return self.package_properties[p] - - -TargetPkgConfig = _PkgConfig(G.PKG_CONFIG) -HostPkgConfig = _PkgConfig(G.HOST_PKG_CONFIG) - - -def _package(self, name, package, fallback, pkgconfig): - if pkgconfig.has_package(package): - print(f"package '{package}' found") - cflags = pkgconfig.get_property(package, "--cflags") - ldflags = pkgconfig.get_property(package, "--libs") - - if cflags: - self.args["caller_cflags"] = [cflags] - if ldflags: - self.args["caller_ldflags"] = [ldflags] - self.args["clibrary_deps"] = [self] - self.args["cheader_deps"] = [self] - self.traits.update({"clibrary", "cxxlibrary"}) - return - - if not fallback: - add_belated_error(f"Required package '{package}' not installed") - return - - print(f"package '{package}' not found; using fallback") - - if "cheader_deps" in fallback.args: - self.args["cheader_deps"] = fallback.args["cheader_deps"] - if "clibrary_deps" in fallback.args: - self.args["clibrary_deps"] = fallback.args["clibrary_deps"] - if "cheader_files" in fallback.args: - self.args["cheader_files"] = fallback.args["cheader_files"] - if "clibrary_files" in fallback.args: - self.args["clibrary_files"] = fallback.args["clibrary_files"] - self.ins = fallback.ins - self.outs = fallback.outs - self.deps = fallback.deps - self.traits = fallback.traits - - -@Rule -def package(self, name, package=None, fallback: Target = None): - _package(self, name, package, fallback, TargetPkgConfig) - - -@Rule -def hostpackage(self, name, package=None, fallback: Target = None): - _package(self, name, package, fallback, HostPkgConfig) - - -def has_package(name): - return TargetPkgConfig.has_package(name) - - -def has_host_package(name): - return HostPkgConfig.has_package(name) diff --git a/build/protobuf.py b/build/protobuf.py deleted file mode 100644 index b6674b5e..00000000 --- a/build/protobuf.py +++ /dev/null @@ -1,192 +0,0 @@ -from build.ab import ( - Rule, - Targets, - emit, - simplerule, - filenamesof, - G, - add_belated_error, -) -from build.utils import filenamesmatchingof, collectattrs -from os.path import join, abspath, dirname, relpath -from build.pkg import has_package, TargetPkgConfig -import platform - -G.setdefault("PROTOC", "protoc") -G.setdefault("HOSTPROTOC", "hostprotoc") - -if not has_package("protobuf"): - add_belated_error("Required package 'protobuf' not installed") - -PROTO_SEPARATOR = ";" if (platform.system() == "Windows") else ":" - - -def _getprotodeps(deps): - r = set() - for d in deps: - r.update(d.args.get("protodeps", {d})) - return sorted(r) - - -@Rule -def proto(self, name, srcs: Targets = [], deps: Targets = []): - protodeps = _getprotodeps(deps) - descriptorlist = PROTO_SEPARATOR.join( - [ - relpath(f, start=self.dir) - for f in filenamesmatchingof(protodeps, "*.descriptor") - ] - ) - - dirs = sorted({"$[dir]/" + dirname(f) for f in filenamesof(srcs)}) - simplerule( - replaces=self, - ins=srcs, - outs=[f"={self.localname}.descriptor"], - deps=protodeps, - commands=( - ["mkdir -p " + (" ".join(dirs))] - + [f"$(CP) {f} $[dir]/{f}" for f in filenamesof(srcs)] - + [ - "cd $[dir] && " - + ( - " ".join( - [ - "$(PROTOC)", - "--proto_path=.", - "--include_source_info", - f"--descriptor_set_out={self.localname}.descriptor", - ] - + ( - [f"--descriptor_set_in='{descriptorlist}'"] - if descriptorlist - else [] - ) - + ["$[ins]"] - ) - ) - ] - ), - label="PROTO", - args={ - "protosrcs": filenamesof(srcs), - "protodeps": set(protodeps) | {self}, - }, - ) - - -@Rule -def protolib(self, name, srcs: Targets = []): - simplerule( - replaces=self, - label="PROTOLIB", - args={ - "protosrcs": collectattrs(targets=srcs, name="protosrcs"), - "protodeps": set(_getprotodeps(srcs)), - }, - ) - - -@Rule -def protocc(self, name, srcs: Targets = [], deps: Targets = []): - outs = [] - protos = [] - - allsrcs = collectattrs(targets=srcs, name="protosrcs") - assert allsrcs, "no sources provided" - for f in filenamesmatchingof(allsrcs, "*.proto"): - cc = f.replace(".proto", ".pb.cc") - h = f.replace(".proto", ".pb.h") - protos += [f] - outs += ["=" + cc, "=" + h] - - protodeps = _getprotodeps(deps + srcs) - descriptorlist = PROTO_SEPARATOR.join( - [ - relpath(f, start=self.dir) - for f in filenamesmatchingof(protodeps, "*.descriptor") - ] - ) - - r = simplerule( - name=f"{self.localname}_srcs", - cwd=self.cwd, - ins=srcs, - outs=outs, - deps=protodeps, - commands=[ - "cd $[dir] && " - + ( - " ".join( - [ - "$(PROTOC)", - "--proto_path=.", - "--cpp_out=.", - f"--descriptor_set_in='{descriptorlist}'", - ] - + protos - ) - ) - ], - label="PROTOCC", - ) - - headers = {f[1:]: join(r.dir, f[1:]) for f in outs if f.endswith(".pb.h")} - - from build.c import cxxlibrary - - cxxlibrary( - replaces=self, - srcs=[r], - deps=deps, - hdrs=headers, - ) - - -@Rule -def protojava(self, name, srcs: Targets = [], deps: Targets = []): - outs = [] - - allsrcs = collectattrs(targets=srcs, name="protosrcs") - assert allsrcs, "no sources provided" - protos = [] - for f in filenamesmatchingof(allsrcs, "*.proto"): - protos += [f] - srcs += [f] - - descriptorlist = PROTO_SEPARATOR.join( - [abspath(f) for f in filenamesmatchingof(srcs + deps, "*.descriptor")] - ) - - r = simplerule( - name=f"{self.localname}_srcs", - cwd=self.cwd, - ins=protos, - outs=[f"={self.localname}.srcjar"], - deps=srcs + deps, - commands=[ - "mkdir -p $[dir]/srcs", - "cd $[dir]/srcs && " - + ( - " ".join( - [ - "$(PROTOC)", - "--proto_path=.", - "--java_out=.", - f"--descriptor_set_in='{descriptorlist}'", - ] - + protos - ) - ), - "$(JAR) cf $[outs[0]] -C $[dir]/srcs .", - ], - traits={"srcjar"}, - label="PROTOJAVA", - ) - - from build.java import javalibrary - - javalibrary( - replaces=self, - deps=[r] + deps, - ) diff --git a/build/toolchain.py b/build/toolchain.py deleted file mode 100644 index e728ef87..00000000 --- a/build/toolchain.py +++ /dev/null @@ -1,12 +0,0 @@ -import platform - -_is_windows = platform.system() == "Windows" - - -class Toolchain: - PREFIX = "" - EXE = ".exe" if _is_windows else "" - - -class HostToolchain(Toolchain): - PREFIX = "HOST" diff --git a/build/utils.py b/build/utils.py deleted file mode 100644 index a793ad9f..00000000 --- a/build/utils.py +++ /dev/null @@ -1,181 +0,0 @@ -from build.ab import ( - Rule, - Target, - Targets, - filenameof, - filenamesof, - getcwd, - error, - simplerule, - add_atexit, - targets, - emit, - G, -) -from os.path import relpath, splitext, join, basename, isfile, normpath -from os import walk -from glob import iglob -import fnmatch -import subprocess -import shutil -import re -import functools - - -def filenamesmatchingof(xs, pattern): - return fnmatch.filter(filenamesof(xs), pattern) - - -def stripext(path): - return splitext(path)[0] - - -def targetswithtraitsof(xs, trait): - return [t for t in xs if trait in t.traits] - - -def collectattrs(*, targets, name, initial=[]): - s = set(initial) - for a in [t.args.get(name, []) for t in targets]: - s.update(a) - return sorted(s) - - -@functools.cache -def _glob_to_re(glob_str): - if glob_str.startswith("./"): - glob_str = normpath(join(getcwd(), glob_str)) - - opts = re.compile("([.]|[*][*]/|[*]|[?])|(.)") - out = "" - for pattern_match, literal_text in opts.findall(glob_str): - if pattern_match == ".": - out += "[.]" - elif pattern_match == "**/": - out += "(?:.*/)?" - elif pattern_match == "*": - out += "[^/]*" - elif pattern_match == "?": - out += "." - elif literal_text: - out += literal_text - return re.compile(out) - - -def _glob_filter(paths, pattern): - r = _glob_to_re(pattern) - for f in paths: - if r.match(f): - yield f - - -def _glob_matches(path, pattern): - r = _glob_to_re(pattern) - return r.match(path) - - -def glob(include=["*"], exclude=[], dir=None, relative_to="."): - if not dir: - dir = getcwd() - if dir.startswith("./"): - dir = normpath(join(getcwd(), dir)) - if relative_to.startswith("./"): - relative_to = normpath(join(getcwd(), relative_to)) - - def iterate(): - for dirpath, dirnames, filenames in walk( - dir, topdown=True, followlinks=True - ): - dirpath = relpath(dirpath, relative_to) - filenames = [normpath(join(dirpath, f)) for f in filenames] - matching = set() - for p in include: - matching.update([f for f in _glob_filter(filenames, p)]) - for p in exclude: - matching = [n for n in matching if not _glob_matches(n, p)] - for f in matching: - yield f - - return list(iterate()) - - -def itemsof(pattern, root=None, cwd=None): - if not cwd: - cwd = getcwd() - if not root: - root = "." - - pattern = join(cwd, pattern) - root = join(cwd, root) - - result = {} - for f in iglob(pattern, recursive=True): - try: - if isfile(f): - result[relpath(f, root)] = f - except ValueError: - error(f"file '{f}' is not in root '{root}'") - return result - - -def does_command_exist(cmd): - basecmd = cmd.strip().split()[0] - return shutil.which(basecmd) - - -def shell(cmd): - r = subprocess.check_output([G.SHELL, "-c", cmd]) - return r.decode("utf-8").strip() - - -def add_wildcard_dependency(dep, pattern, exclude="."): - def cb(): - yesre = _glob_to_re(pattern) - nore = _glob_to_re(exclude) - for t in targets.values(): - for o in t.outs: - if (type(o) == str) and yesre.match(o) and not nore.match(o): - emit("build", o, ":phony", dep.name) - - add_atexit(cb) - - -@Rule -def objectify(self, name, src: Target, symbol): - simplerule( - replaces=self, - ins=["build/_objectify.py", src], - outs=[f"={basename(filenameof(src))}.h"], - commands=["$(PYTHON) $[ins[0]] $[ins[1]] " + symbol + " > $[outs]"], - label="OBJECTIFY", - ) - - -@Rule -def test( - self, - name, - command: Target = None, - commands=None, - ins: Targets = None, - deps: Targets = None, - label="TEST", -): - if command: - simplerule( - replaces=self, - ins=[command], - outs=["=sentinel"], - commands=["$[ins[0]]", "touch $[outs[0]]"], - deps=deps, - label=label, - ) - else: - simplerule( - replaces=self, - ins=ins, - outs=["=sentinel"], - commands=commands + ["touch $[outs[0]]"], - deps=deps, - label=label, - ) diff --git a/build/zip.py b/build/zip.py deleted file mode 100644 index 2b631c69..00000000 --- a/build/zip.py +++ /dev/null @@ -1,27 +0,0 @@ -from build.ab import ( - Rule, - simplerule, - TargetsMap, - filenameof, -) - - -@Rule -def zip( - self, name, flags="", items: TargetsMap = {}, extension="zip", label="ZIP" -): - cs = ["$(PYTHON) build/_zip.py -z $[outs]"] - - ins = [] - for k, v in items.items(): - cs += [f"-f {k} {filenameof(v)}"] - ins += [v] - - simplerule( - replaces=self, - ins=ins, - deps=["build/_zip.py"], - outs=[f"={self.localname}." + extension], - commands=[" ".join(cs)], - label=label, - ) From 4bbe306b59b63ab6248db30586abe229e710a2e7 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 21:56:17 +0200 Subject: [PATCH 155/192] Update .gitignore to ignore all netbeans artifacts. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index ddf22a9d..c6effad0 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,10 @@ bazel-bin .project .idea/ .ijwb/ +nbproject/ +build.xml +.classpath +manifest.mf /.ninja* /brother120tool /brother120tool-* @@ -17,3 +21,4 @@ bazel-bin /compile_commands.json /doc/disk-_global_options.md +/nbproject/private/ From f97e42b2c4d0419aa5799ce1ebd6f481e514f7c8 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 21:57:43 +0200 Subject: [PATCH 156/192] Get netbeans' matisse to display a window. --- java/com/cowlark/fluxengine/gui/Gui.java | 6 +- .../com/cowlark/fluxengine/gui/NewJFrame.form | 84 ++++++++++++++ .../com/cowlark/fluxengine/gui/NewJFrame.java | 103 ++++++++++++++++++ 3 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 java/com/cowlark/fluxengine/gui/NewJFrame.form create mode 100644 java/com/cowlark/fluxengine/gui/NewJFrame.java diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index b67d4e21..1a283479 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -23,12 +23,8 @@ public void run(ImmutableList args) throws Exception private static void createAndShowGui() { - JFrame frame = new JFrame("FluxEngine"); + JFrame frame = new NewJFrame(); frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - - JLabel label = new JLabel("FluxEngine", JLabel.CENTER); - frame.getContentPane().add(label); - frame.setSize(800, 600); frame.setLocationRelativeTo(null); frame.setVisible(true); } diff --git a/java/com/cowlark/fluxengine/gui/NewJFrame.form b/java/com/cowlark/fluxengine/gui/NewJFrame.form new file mode 100644 index 00000000..3e6ca105 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/NewJFrame.form @@ -0,0 +1,84 @@ + + +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com/cowlark/fluxengine/gui/NewJFrame.java b/java/com/cowlark/fluxengine/gui/NewJFrame.java new file mode 100644 index 00000000..8631fc91 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/NewJFrame.java @@ -0,0 +1,103 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JFrame.java to edit this template + */ +package com.cowlark.fluxengine.gui; + +/** + * + * @author dg + */ +public class NewJFrame extends javax.swing.JFrame { + + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(NewJFrame.class.getName()); + + /** + * Creates new form NewJFrame + */ + public NewJFrame() { + initComponents(); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + java.awt.GridBagConstraints gridBagConstraints; + + jLabel1 = new javax.swing.JLabel(); + jButton1 = new javax.swing.JButton(); + jTextField1 = new javax.swing.JTextField(); + jRadioButton1 = new javax.swing.JRadioButton(); + jMenuBar1 = new javax.swing.JMenuBar(); + jMenu1 = new javax.swing.JMenu(); + jMenu2 = new javax.swing.JMenu(); + + setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); + getContentPane().setLayout(new java.awt.GridBagLayout()); + + jLabel1.setText("jLabel1"); + getContentPane().add(jLabel1, new java.awt.GridBagConstraints()); + + jButton1.setText("jButton1"); + getContentPane().add(jButton1, new java.awt.GridBagConstraints()); + + jTextField1.setText("jTextField1"); + getContentPane().add(jTextField1, new java.awt.GridBagConstraints()); + + jRadioButton1.setText("jRadioButton1"); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 1; + gridBagConstraints.gridy = 1; + getContentPane().add(jRadioButton1, gridBagConstraints); + + jMenu1.setText("File"); + jMenuBar1.add(jMenu1); + + jMenu2.setText("Edit"); + jMenuBar1.add(jMenu2); + + setJMenuBar(jMenuBar1); + + pack(); + }// //GEN-END:initComponents + + /** + * @param args the command line arguments + */ + public static void main(String args[]) { + /* Set the Nimbus look and feel */ + // + /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. + * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html + */ + try { + for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { + if ("Nimbus".equals(info.getName())) { + javax.swing.UIManager.setLookAndFeel(info.getClassName()); + break; + } + } + } catch (ReflectiveOperationException | javax.swing.UnsupportedLookAndFeelException ex) { + logger.log(java.util.logging.Level.SEVERE, null, ex); + } + // + + /* Create and display the form */ + java.awt.EventQueue.invokeLater(() -> new NewJFrame().setVisible(true)); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton jButton1; + private javax.swing.JLabel jLabel1; + private javax.swing.JMenu jMenu1; + private javax.swing.JMenu jMenu2; + private javax.swing.JMenuBar jMenuBar1; + private javax.swing.JRadioButton jRadioButton1; + private javax.swing.JTextField jTextField1; + // End of variables declaration//GEN-END:variables +} From 09a8f4253f8329127a8fc133cced6e36ac149618 Mon Sep 17 00:00:00 2001 From: David Given Date: Mon, 10 Aug 2026 22:38:53 +0200 Subject: [PATCH 157/192] We have successfully written a disk, with verification! --- .../fluxengine/algorithms/Operation.java | 3 +- .../algorithms/RawWriteOperation.java | 2 +- .../fluxengine/algorithms/ReadOperation.java | 12 +- .../fluxengine/algorithms/WriteOperation.java | 186 ++++++++---------- java/com/cowlark/fluxengine/arch/Arch.java | 29 +-- .../fluxengine/arch/agat/AgatEncoder.java | 8 +- .../fluxengine/arch/amiga/AmigaEncoder.java | 10 +- .../fluxengine/arch/apple2/Apple2Encoder.java | 9 +- .../arch/brother/BrotherEncoder.java | 5 +- .../arch/c64/Commodore64Encoder.java | 9 +- .../fluxengine/arch/ibm/IbmEncoder.java | 17 +- .../arch/macintosh/MacintoshEncoder.java | 9 +- .../arch/micropolis/MicropolisEncoder.java | 8 +- .../arch/northstar/NorthstarEncoder.java | 8 +- .../fluxengine/arch/tartu/TartuEncoder.java | 8 +- .../arch/tids990/Tids990Encoder.java | 8 +- .../arch/victor9k/Victor9kEncoder.java | 8 +- .../fluxengine/cli/InspectCommand.java | 13 +- .../cowlark/fluxengine/cli/WriteCommand.java | 10 +- .../cowlark/fluxengine/config/config.proto | 3 +- java/com/cowlark/fluxengine/data/Fluxmap.java | 11 +- .../cowlark/fluxengine/encoders/Encoder.java | 19 +- .../arch/amiga/AmigaEncoderTest.java | 4 +- .../fluxengine/decoders/FluxDecoderTest.java | 9 +- .../fluxengine/encoders/EncoderTest.java | 51 +++-- 25 files changed, 206 insertions(+), 253 deletions(-) diff --git a/java/com/cowlark/fluxengine/algorithms/Operation.java b/java/com/cowlark/fluxengine/algorithms/Operation.java index 61f5b581..db920ae4 100644 --- a/java/com/cowlark/fluxengine/algorithms/Operation.java +++ b/java/com/cowlark/fluxengine/algorithms/Operation.java @@ -40,7 +40,8 @@ public Operation(ConfigProto configProto) new SupplierOfAutocloseable(() -> FluxSinkFactory.create(configProto)); usbDeviceSupplier = new SupplierOfAutocloseable(() -> UsbFactory.connect(configProto)); decoderSupplier = Suppliers.memoize(() -> Arch.createDecoder(configProto)); - encoderSupplier = Suppliers.memoize(() -> Arch.createEncoder(configProto)); + encoderSupplier = Suppliers.memoize(() -> Arch.createEncoder( + configProto, getDiskRotationalPeriodNs())); imageWriterSupplier = new SupplierOfAutocloseable(() -> ImageWriter.create(configProto)); imageReaderSupplier = new SupplierOfAutocloseable(() -> ImageReader.create(configProto)); } diff --git a/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java b/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java index 251fb491..583ea193 100644 --- a/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java @@ -64,7 +64,7 @@ private void writeTracks(Function producer, fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); Logger.logf( "writing %d ms in %d bytes", - (int) (fluxmap.duration() / 1e6), + (int) (fluxmap.durationNs() / 1e6), fluxmap.bytes()); } } else diff --git a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java index 1f287bbb..b68b5d49 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java @@ -98,9 +98,9 @@ static CombinationResult combineRecordAndSectors(List tracks, LogicalTrac return cr; } - private ReadGroupResult readGroup(FluxSourceIteratorHolder fluxSourceIteratorHolder, - LogicalTrackLayout ltl, - List tracks) + protected ReadGroupResult readGroup(FluxSourceIteratorHolder fluxSourceIteratorHolder, + LogicalTrackLayout ltl, + List tracks) { ReadGroupResult rgr = new ReadGroupResult(); rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; @@ -147,7 +147,7 @@ private ReadGroupResult readGroup(FluxSourceIteratorHolder fluxSourceIteratorHol Fluxmap fluxmap = fluxSourceIterator.next(); Logger.log(new EndReadOperationLogMessage()); - Logger.logf("%d ms in %d bytes", (int) (fluxmap.duration() / 1e6), fluxmap.bytes()); + Logger.logf("%d ms in %d bytes", (int) (fluxmap.durationNs() / 1e6), fluxmap.bytes()); Track flux = getDecoder().decodeToSectors(fluxmap, ptl); flux.normalisedSectors = collectSectors(flux.allSectors); @@ -274,9 +274,7 @@ public void read(Disk disk) { Track track = entry.getValue(); tracksByLogicalLocation.computeIfAbsent( - new CylinderHead( - track.ltl.logicalCylinder, - track.ltl.logicalHead), + new CylinderHead(track.ltl.logicalCylinder, track.ltl.logicalHead), k -> new ArrayList<>()).add(track); } diff --git a/java/com/cowlark/fluxengine/algorithms/WriteOperation.java b/java/com/cowlark/fluxengine/algorithms/WriteOperation.java index cb450210..721f8391 100644 --- a/java/com/cowlark/fluxengine/algorithms/WriteOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/WriteOperation.java @@ -3,20 +3,24 @@ import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogMessage.BeginOperationLogMessage; import com.cowlark.fluxengine.core.Logger; import com.cowlark.fluxengine.data.CylinderHead; import com.cowlark.fluxengine.data.Fluxmap; import com.cowlark.fluxengine.data.Image; import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.data.Track; import com.cowlark.fluxengine.fluxsink.FluxSink; -import com.cowlark.fluxengine.fluxsource.FluxReadParameters; -import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.function.Function; import java.util.function.Predicate; -public class WriteOperation extends ReadOperation +public class WriteOperation extends RawWriteOperation { public WriteOperation(ConfigProto configProto) { @@ -25,9 +29,9 @@ public WriteOperation(ConfigProto configProto) private void writeTracks(Function producer, Predicate verifier, - List logicalLocations) + ImmutableSet logicalLocations) { - Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + Logger.log(new BeginOperationLogMessage("Encoding and writing to disk")); getDiskRotationalPeriodNs(); try (FluxSink fluxSink = getFluxSinkFactory().create()) @@ -66,7 +70,7 @@ private void writeTracks(Function producer, fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); Logger.logf( "writing %d ms in %d bytes", - (int) (fluxmap.duration() / 1e6), + (int) (fluxmap.durationNs() / 1e6), fluxmap.bytes()); } } else @@ -99,114 +103,82 @@ private void writeTracks(Function producer, Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); } - // private static void writeTracks(ImmutableList chs) - // { - // writeTracks( - // ltl -> { - // ImmutableList sectors = getEncoder().collectSectors(ltl, image); - // return encoder.encode(ltl, sectors, image); - // }, ltl -> true, chs); - // } - - // private void writeTracksAndVerify(ConfigProto config, - // DiskLayout diskLayout, - // FluxSinkFactory fluxSinkFactory, - // Encoder encoder, - // FluxSource fluxSource, - // Decoder decoder, - // Image image, - // List chs) - // { - // writeTracks( - // config, diskLayout, fluxSinkFactory, ltl -> { - // List sectors = encoder.collectSectors(ltl, image); - // return encoder.encode(ltl, sectors, image); - // }, ltl -> { - // Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = - // new Common.FluxSourceIteratorHolder(fluxSource); - // List tracks = new ArrayList<>(); - // ReadOperation.ReadGroupResult rgr = - // readGroup(fluxSourceIteratorHolder, ltl, tracks); - - // if (rgr.result != ReadOperation.ReadResult.GOOD_READ) - // { - // adjustTrackOnError(ltl.physicalCylinder); - // Logger.logf("bad read"); - // return false; - // } - - // Image wanted = new Image(); - // for (Sector sector : encoder.collectSectors(ltl, image)) - // wanted.put( - // sector.location.logicalCylinder(), - // sector.location.logicalHead(), - // sector.location.logicalSector()).data = sector.data; - - // for (Sector sector : rgr.combinedSectors) - // { - // Sector s = wanted.get( - // sector.location.logicalCylinder(), - // sector.location.logicalHead(), - // sector.location.logicalSector()); - // if (s == null) - // { - // Logger.logf("spurious sector on verify"); - // return false; - // } - // if (!s.data.equals(sector.data.slice(0, s.data.size()))) - // { - // Logger.logf("data mismatch on verify"); - // return false; - // } - // wanted.erase( - // sector.location.logicalCylinder(), - // sector.location.logicalHead(), - // sector.location.logicalSector()); - // } - // if (!wanted.empty()) - // { - // Logger.logf("missing sector on verify"); - // return false; - // } - // return true; - // }, chs); - // } + private void writeTracks(Image image, ImmutableSet chs) + { + writeTracks( + ltl -> { + ImmutableList sectors = getEncoder().collectSectors(ltl, image); + return getEncoder().encode(ltl, sectors, image); + }, ltl -> true, chs); + } - public void writeDiskCommand(Image image, Collection physicalLocations) + private void writeTracksAndVerify(Image image, ImmutableSet chs) { - // ImmutableSet chs = getDiskLayout().layoutByLogicalLocation - // .keySet(); - // if (fluxSource != null && decoder != null) - // writeTracksAndVerify( - // config, - // diskLayout, - // fluxSinkFactory, - // encoder, - // fluxSource, - // decoder, - // image, - // chs); - // else - // writeTracks(config, diskLayout, fluxSinkFactory, encoder, image, chs); + writeTracks( + ltl -> { + List sectors = getEncoder().collectSectors(ltl, image); + return getEncoder().encode(ltl, sectors, image); + }, ltl -> { + Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = + new Common.FluxSourceIteratorHolder(getFluxSource()); + List tracks = new ArrayList<>(); + ReadGroupResult rgr = readGroup(fluxSourceIteratorHolder, ltl, tracks); + + if (rgr.result != ReadOperation.ReadResult.GOOD_READ) + { + adjustTrackOnError(ltl.physicalCylinder); + Logger.logf("bad read"); + return false; + } + + Image wanted = new Image(); + for (Sector sector : getEncoder().collectSectors(ltl, image)) + wanted.put( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()).data = sector.data; + + for (Sector sector : rgr.combinedSectors) + { + Sector s = wanted.get( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()); + if (s == null) + { + Logger.logf("spurious sector on verify"); + return false; + } + if (!s.data.equals(sector.data.slice(0, s.data.size()))) + { + Logger.logf("data mismatch on verify"); + return false; + } + wanted.erase( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()); + } + if (!wanted.empty()) + { + Logger.logf("missing sector on verify"); + return false; + } + return true; + }, chs); } - public void writeDiskCommand(Image image) + public void writeDiskCommand(Image image, Collection physicalLocations) { - writeDiskCommand(image, getDiskLayout().layoutByLogicalLocation.keySet()); + ImmutableSet chs = getDiskLayout().layoutByLogicalLocation.keySet(); + if (getConfig().getVerifyWrites()) + writeTracksAndVerify(image, chs); + else + writeTracks(image, chs); } - public void writeRawDiskCommand() + public void writeDiskCommand(Image image) { - writeTracks( - ltl -> { - FluxSourceIterator iterator = - getFluxSource().readFlux(FluxReadParameters.builder() - .setCylinder(ltl.physicalCylinder) - .setHead(ltl.physicalHead) - .build()); - if (!iterator.hasNext()) - return null; - return iterator.next(); - }, ltl -> true, getDiskLayout().logicalLocations); + writeDiskCommand(image, getDiskLayout().layoutByLogicalLocation.keySet()); } } diff --git a/java/com/cowlark/fluxengine/arch/Arch.java b/java/com/cowlark/fluxengine/arch/Arch.java index cdd33bf4..6475a248 100644 --- a/java/com/cowlark/fluxengine/arch/Arch.java +++ b/java/com/cowlark/fluxengine/arch/Arch.java @@ -101,6 +101,11 @@ public static Decoder createDecoder(DecoderProto config) } public static Encoder createEncoder(ConfigProto config) + { + return createEncoder(config, config.getDrive().getRotationalPeriodMs() * 1e6); + } + + public static Encoder createEncoder(ConfigProto config, double diskRotationalPeriodNs) { if (!config.hasEncoder()) throw new FluxEngineException("no encoder configured"); @@ -108,29 +113,29 @@ public static Encoder createEncoder(ConfigProto config) switch (config.getEncoder().getFormatCase()) { case AGAT: - return new AgatEncoder(config); + return new AgatEncoder(config, diskRotationalPeriodNs); case AMIGA: - return new AmigaEncoder(config); + return new AmigaEncoder(config, diskRotationalPeriodNs); case APPLE2: - return new Apple2Encoder(config); + return new Apple2Encoder(config, diskRotationalPeriodNs); case BROTHER: - return new BrotherEncoder(config); + return new BrotherEncoder(config, diskRotationalPeriodNs); case C64: - return new Commodore64Encoder(config); + return new Commodore64Encoder(config, diskRotationalPeriodNs); case IBM: - return new IbmEncoder(config); + return new IbmEncoder(config, diskRotationalPeriodNs); case MACINTOSH: - return new MacintoshEncoder(config); + return new MacintoshEncoder(config, diskRotationalPeriodNs); case MICROPOLIS: - return new MicropolisEncoder(config); + return new MicropolisEncoder(config, diskRotationalPeriodNs); case NORTHSTAR: - return new NorthstarEncoder(config); + return new NorthstarEncoder(config, diskRotationalPeriodNs); case TARTU: - return new TartuEncoder(config); + return new TartuEncoder(config, diskRotationalPeriodNs); case TIDS990: - return new Tids990Encoder(config); + return new Tids990Encoder(config, diskRotationalPeriodNs); case VICTOR9K: - return new Victor9kEncoder(config); + return new Victor9kEncoder(config, diskRotationalPeriodNs); default: throw new FluxEngineException("no encoder specified"); } diff --git a/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java b/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java index 40641450..5dd0bca7 100644 --- a/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java +++ b/java/com/cowlark/fluxengine/arch/agat/AgatEncoder.java @@ -18,15 +18,14 @@ */ public class AgatEncoder extends Encoder { - private final ConfigProto fullConfig; private final AgatEncoderProto config; private final boolean[] lastBit = new boolean[1]; private Bits bits; private Bits.Cursor cursor; - public AgatEncoder(ConfigProto config) + public AgatEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getAgat(); } @@ -107,8 +106,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( - bits, (long) calculatePhysicalClockPeriod( - fullConfig, + bits, (long) calculatePhysicalClockPeriodNs( config.getTargetClockPeriodUs() * 1e3, config.getTargetRotationalPeriodMs() * 1e6)); return fluxmap; diff --git a/java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java b/java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java index 13cfeddc..18bbc736 100644 --- a/java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java +++ b/java/com/cowlark/fluxengine/arch/amiga/AmigaEncoder.java @@ -18,13 +18,12 @@ */ public class AmigaEncoder extends Encoder { - private final ConfigProto fullConfig; private final AmigaEncoderProto config; private final boolean[] lastBit = new boolean[1]; - public AmigaEncoder(ConfigProto config) + public AmigaEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getAmiga(); } @@ -139,10 +138,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( bits, - (long) calculatePhysicalClockPeriod( - fullConfig, - config.getClockRateUs() * 1e3, - 200e6)); + (long) calculatePhysicalClockPeriodNs(config.getClockRateUs() * 1e3, 200e6)); return fluxmap; } } diff --git a/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java b/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java index f1d386f9..178fcb9b 100644 --- a/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java +++ b/java/com/cowlark/fluxengine/arch/apple2/Apple2Encoder.java @@ -86,13 +86,12 @@ public class Apple2Encoder extends Encoder ENCODE_DATA_GCR[0x3f] = 0xff; } - private final ConfigProto fullConfig; private final Apple2EncoderProto config; private int volumeId = 254; - public Apple2Encoder(ConfigProto config) + public Apple2Encoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getApple2(); } @@ -122,8 +121,8 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( - bits, (long) calculatePhysicalClockPeriod( - fullConfig, + bits, + (long) calculatePhysicalClockPeriodNs( config.getClockPeriodUs() * 1e3, config.getRotationalPeriodMs() * 1e6)); return fluxmap; diff --git a/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java b/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java index dff7a4fe..acfb195d 100644 --- a/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java +++ b/java/com/cowlark/fluxengine/arch/brother/BrotherEncoder.java @@ -136,12 +136,11 @@ public class BrotherEncoder extends Encoder ENCODE_DATA_GCR[31] = 0xfb; } - private final ConfigProto fullConfig; private final BrotherEncoderProto config; - public BrotherEncoder(ConfigProto config) + public BrotherEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getBrother(); } diff --git a/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java b/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java index 0d930a93..85c0d63e 100644 --- a/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java +++ b/java/com/cowlark/fluxengine/arch/c64/Commodore64Encoder.java @@ -40,14 +40,13 @@ public class Commodore64Encoder extends Encoder ENCODE_DATA_GCR[0xf] = 0x15; } - private final ConfigProto fullConfig; private final Commodore64EncoderProto config; private int formatByte1; private int formatByte2; - public Commodore64Encoder(ConfigProto config) + public Commodore64Encoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getC64(); } @@ -152,9 +151,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); - fluxmap.appendBits( - bits, - (long) calculatePhysicalClockPeriod(fullConfig, clockRateUs * 1e3, 200e6)); + fluxmap.appendBits(bits, (long) calculatePhysicalClockPeriodNs(clockRateUs * 1e3, 200e6)); return fluxmap; } diff --git a/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java b/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java index 86174d97..2a8b0230 100644 --- a/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java +++ b/java/com/cowlark/fluxengine/arch/ibm/IbmEncoder.java @@ -56,15 +56,14 @@ public class IbmEncoder extends Encoder */ private static final int MFM_RECORD_SEPARATOR = 0x4489; private static final int MFM_RECORD_SEPARATOR_BYTE = 0xa1; - private final ConfigProto fullConfig; private final IbmEncoderProto config; private final boolean[] lastBit = new boolean[1]; private Bits bits; private Bits.Cursor cursor; - public IbmEncoder(ConfigProto config) + public IbmEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getIbm(); } @@ -131,11 +130,11 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) IbmEncoderProto.TrackdataProto trackdata = getEncoderTrackData(ltl.logicalCylinder, ltl.logicalHead); - double clockRateUs = trackdata.getTargetClockPeriodUs(); + double clockRateNs = trackdata.getTargetClockPeriodUs() * 1000.0; if (!trackdata.getUseFm()) - clockRateUs /= 2.0; + clockRateNs /= 2.0; int bitsPerRevolution = - (int) ((trackdata.getTargetRotationalPeriodMs() * 1000.0) / clockRateUs); + (int) ((trackdata.getTargetRotationalPeriodMs() * 1e6) / clockRateNs); bits = new Bits(bitsPerRevolution); cursor = new Bits.Cursor(0); @@ -254,9 +253,9 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( - bits, (long) calculatePhysicalClockPeriod( - fullConfig, - clockRateUs * 1e3, + bits, + (long) calculatePhysicalClockPeriodNs( + clockRateNs, trackdata.getTargetRotationalPeriodMs() * 1e6)); return fluxmap; } diff --git a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java index 4cba0d43..57c76314 100644 --- a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java +++ b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshEncoder.java @@ -89,12 +89,11 @@ public class MacintoshEncoder extends Encoder ENCODE_DATA_GCR[0x3f] = 0xff; } - private final ConfigProto fullConfig; private final MacintoshEncoderProto config; - public MacintoshEncoder(ConfigProto config) + public MacintoshEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getMacintosh(); } @@ -305,9 +304,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) bits.fillBitmapTo(cursor, bits.size(), new boolean[]{true, false}); Fluxmap fluxmap = new Fluxmap(); - fluxmap.appendBits( - bits, - (long) calculatePhysicalClockPeriod(fullConfig, clockRateUs * 1e3, 200e6)); + fluxmap.appendBits(bits, (long) calculatePhysicalClockPeriodNs(clockRateUs * 1e3, 200e6)); return fluxmap; } } diff --git a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java index e523eb3a..f136db6f 100644 --- a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java +++ b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java @@ -20,12 +20,11 @@ */ public class MicropolisEncoder extends Encoder { - private final ConfigProto fullConfig; private final MicropolisEncoderProto config; - public MicropolisEncoder(ConfigProto config) + public MicropolisEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getMicropolis(); } @@ -53,8 +52,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) throw new FluxEngineException("track data mismatched length"); Fluxmap fluxmap = new Fluxmap(); - long clockPeriod = (long) calculatePhysicalClockPeriod( - fullConfig, + long clockPeriod = (long) calculatePhysicalClockPeriodNs( config.getClockPeriodUs() * 1e3, config.getRotationalPeriodMs() * 1e6); int pos = 0; diff --git a/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java b/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java index f3358f3b..f224803d 100644 --- a/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java +++ b/java/com/cowlark/fluxengine/arch/northstar/NorthstarEncoder.java @@ -27,12 +27,11 @@ public class NorthstarEncoder extends Encoder private static final int GAP1_FILL_BYTE = 0x4F; private static final int GAP2_FILL_BYTE = 0x4F; - private final ConfigProto fullConfig; private final NorthstarEncoderProto config; - public NorthstarEncoder(ConfigProto config) + public NorthstarEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getNorthstar(); } @@ -155,8 +154,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( bits, - (long) calculatePhysicalClockPeriod( - fullConfig, + (long) calculatePhysicalClockPeriodNs( clockRateUs * 1e3, config.getRotationalPeriodMs() * 1e6)); return fluxmap; diff --git a/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java b/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java index 33d27be7..192c8b95 100644 --- a/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java +++ b/java/com/cowlark/fluxengine/arch/tartu/TartuEncoder.java @@ -20,16 +20,15 @@ */ public class TartuEncoder extends Encoder { - private final ConfigProto fullConfig; private final TartuEncoderProto config; private final boolean[] lastBit = new boolean[1]; private double clockRateUs; private Bits bits; private Bits.Cursor cursor; - public TartuEncoder(ConfigProto config) + public TartuEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getTartu(); } @@ -110,8 +109,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( bits, - (long) calculatePhysicalClockPeriod( - fullConfig, + (long) calculatePhysicalClockPeriodNs( clockRateUs * 1e3, config.getTargetRotationalPeriodMs() * 1e6)); return fluxmap; diff --git a/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java b/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java index b82ee196..48432960 100644 --- a/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java +++ b/java/com/cowlark/fluxengine/arch/tids990/Tids990Encoder.java @@ -20,15 +20,14 @@ */ public class Tids990Encoder extends Encoder { - private final ConfigProto fullConfig; private final Tids990EncoderProto config; private final boolean[] lastBit = new boolean[1]; private Bits bits; private Bits.Cursor cursor; - public Tids990Encoder(ConfigProto config) + public Tids990Encoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getTids990(); } @@ -136,8 +135,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) Fluxmap fluxmap = new Fluxmap(); fluxmap.appendBits( bits, - (long) calculatePhysicalClockPeriod( - fullConfig, + (long) calculatePhysicalClockPeriodNs( clockRateUs * 1e3, config.getRotationalPeriodMs() * 1e6)); return fluxmap; diff --git a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java index 0e905b42..f084e212 100644 --- a/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java +++ b/java/com/cowlark/fluxengine/arch/victor9k/Victor9kEncoder.java @@ -40,13 +40,12 @@ public class Victor9kEncoder extends Encoder ENCODE_DATA_GCR[0xf] = 0x15; } - private final ConfigProto fullConfig; private final Victor9kEncoderProto config; private final boolean[] lastBit = new boolean[1]; - public Victor9kEncoder(ConfigProto config) + public Victor9kEncoder(ConfigProto config, double diskRotationalPeriodNs) { - this.fullConfig = config; + super(diskRotationalPeriodNs); this.config = config.getEncoder().getVictor9K(); } @@ -192,8 +191,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) int bitsPerRevolution = (int) ((trackdata.getRotationalPeriodMs() * 1e3) / trackdata.getClockPeriodUs()); Bits bits = new Bits(bitsPerRevolution); - long clockPeriod = (long) calculatePhysicalClockPeriod( - fullConfig, + long clockPeriod = (long) calculatePhysicalClockPeriodNs( trackdata.getClockPeriodUs() * 1e3, trackdata.getRotationalPeriodMs() * 1e6); Bits.Cursor cursor = new Bits.Cursor(0); diff --git a/java/com/cowlark/fluxengine/cli/InspectCommand.java b/java/com/cowlark/fluxengine/cli/InspectCommand.java index c04ca3bc..1eb1e281 100644 --- a/java/com/cowlark/fluxengine/cli/InspectCommand.java +++ b/java/com/cowlark/fluxengine/cli/InspectCommand.java @@ -180,20 +180,19 @@ public void run(ImmutableList args) if (tracks.size() != 1) throw new FluxEngineException("you must specify exactly one track"); CylinderHead ch = tracks.get(0); - FluxSourceIterator iterator = - fluxSource.readFlux(FluxReadParameters.builder() - .setCylinder(ch.cylinder()) - .setHead(ch.head()) - .build()); + FluxSourceIterator iterator = fluxSource.readFlux(FluxReadParameters.builder() + .setCylinder(ch.cylinder()) + .setHead(ch.head()) + .build()); Fluxmap fluxmap = iterator.next(); System.out.printf( "0x%x bytes of data in %.3fms%n", fluxmap.bytes(), - fluxmap.duration() / 1e6); + fluxmap.durationNs() / 1e6); System.out.printf( "Required USB bandwidth: %dkB/s%n", - (int) (fluxmap.bytes() / 1024.0 / (fluxmap.duration() / 1e9))); + (int) (fluxmap.bytes() / 1024.0 / (fluxmap.durationNs() / 1e9))); FluxmapReader fmr = new FluxmapReader(fluxmap, DecoderProto.getDefaultInstance()); double clockPeriod = guessClock(fluxmap, fmr); diff --git a/java/com/cowlark/fluxengine/cli/WriteCommand.java b/java/com/cowlark/fluxengine/cli/WriteCommand.java index dbf3b39a..71e9ff22 100644 --- a/java/com/cowlark/fluxengine/cli/WriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/WriteCommand.java @@ -8,7 +8,6 @@ import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; import com.cowlark.fluxengine.data.Image; -import com.cowlark.fluxengine.fluxsource.FluxSource; import com.google.common.collect.ImmutableList; /** @@ -47,15 +46,18 @@ public String getHelp() @Override public void run(ImmutableList args) throws Exception { - var configProto = new ConfigBuilder().fromFlags(args, flags) + ConfigProto configProto = new ConfigBuilder().fromFlags(args, flags) .withImageReader(sourceImageFlag.get()) .withFluxSink(destFluxFlag.get()) + .withFluxSource(destFluxFlag.get()) /* for verification */.set( + "verify_writes", + Boolean.toString(verify)) .build(); try (WriteOperation operation = new WriteOperation(configProto)) { - // Image image = operation.getImageReader().readImage(); - // + Image image = operation.getImageReader().readImage(); + operation.writeDiskCommand(image); // FluxSource verificationFluxSource = null; // if (configProto.hasDecoder() && operation.getFluxSinkFactory() // .isHardware() && verify) diff --git a/java/com/cowlark/fluxengine/config/config.proto b/java/com/cowlark/fluxengine/config/config.proto index 28818dfa..d2e64b17 100644 --- a/java/com/cowlark/fluxengine/config/config.proto +++ b/java/com/cowlark/fluxengine/config/config.proto @@ -20,7 +20,7 @@ enum SupportStatus UNSUPPORTED = 0; DINOSAUR = 1; UNICORN = 2; } -// NEXT_TAG: 27 +// NEXT_TAG: 28 message ConfigProto { option(recurse) = false; @@ -45,6 +45,7 @@ message ConfigProto optional UsbProto usb = 15; optional string tracks = 16; + optional bool verify_writes = 27 [(help) = "verify writes where possible", default = true]; optional FilesystemProto filesystem = 18; diff --git a/java/com/cowlark/fluxengine/data/Fluxmap.java b/java/com/cowlark/fluxengine/data/Fluxmap.java index c0b70c0b..85bf7956 100644 --- a/java/com/cowlark/fluxengine/data/Fluxmap.java +++ b/java/com/cowlark/fluxengine/data/Fluxmap.java @@ -44,7 +44,7 @@ public int ticks() /* The duration of the fluxmap in nanoseconds, ported from * lib/data/fluxmap.h Fluxmap::duration(). */ - public double duration() + public double durationNs() { return ticks * NS_PER_TICK; } @@ -117,20 +117,21 @@ public Fluxmap appendByte(int b) return appendBytes(Bytes.of(b)); } - public Fluxmap appendBits(List bits, long clockTicks) + public Fluxmap appendBits(List bits, double clockNs) { - long nowTicks = ticks; + double nowTicks = durationNs() / NS_PER_TICK; + double clockTicks = clockNs / NS_PER_TICK; for (boolean bit : bits) { nowTicks += clockTicks; if (bit) { - int deltaTicks = (int) (nowTicks - ticks); + int deltaTicks = (int) nowTicks - ticks; appendInterval(deltaTicks); appendPulse(); } } - int deltaTicks = (int) (nowTicks - ticks); + int deltaTicks = (int) nowTicks - ticks; if (deltaTicks != 0) appendInterval(deltaTicks); return this; diff --git a/java/com/cowlark/fluxengine/encoders/Encoder.java b/java/com/cowlark/fluxengine/encoders/Encoder.java index 3cf14fd7..6aa52c17 100644 --- a/java/com/cowlark/fluxengine/encoders/Encoder.java +++ b/java/com/cowlark/fluxengine/encoders/Encoder.java @@ -15,6 +15,13 @@ */ public abstract class Encoder { + private final double diskRotationalPeriodNs; + + public Encoder(double diskRotationalPeriodNs) + { + this.diskRotationalPeriodNs = diskRotationalPeriodNs; + } + public static Encoder create(ConfigProto config) { throw new FluxEngineException("encoders are not implemented yet"); @@ -49,15 +56,13 @@ public ImmutableList collectSectors(LogicalTrackLayout ltl, Image image) public abstract Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image); - public double calculatePhysicalClockPeriod(ConfigProto config, - double targetClockPeriod, - double targetRotationalPeriod) + public double calculatePhysicalClockPeriodNs(double targetClockPeriodNs, + double targetRotationalPeriodNs) { - double currentRotationalPeriod = config.getDrive().getRotationalPeriodMs() * 1e6; - if (currentRotationalPeriod == 0) + if (diskRotationalPeriodNs == 0) throw new FluxEngineException( - "you must set --drive.rotational_period_ms as it can't be " + "autodetected"); + "you must set --drive.rotational_period_ms as it can't be autodetected"); - return targetClockPeriod * (currentRotationalPeriod / targetRotationalPeriod); + return targetClockPeriodNs * (diskRotationalPeriodNs / targetRotationalPeriodNs); } } diff --git a/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java index a255f834..42be4a3a 100644 --- a/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java +++ b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java @@ -32,7 +32,7 @@ private ConfigProto makeConfig() public void encodeProducesPulses() { ConfigProto config = makeConfig(); - AmigaEncoder encoder = new AmigaEncoder(config); + AmigaEncoder encoder = new AmigaEncoder(config, 200 * 1e6); Image image = new Image(); Sector sector = image.put(0, 0, 0); @@ -50,7 +50,7 @@ public void encodeProducesPulses() public void encodeRejectsBadSectorSize() { ConfigProto config = makeConfig(); - AmigaEncoder encoder = new AmigaEncoder(config); + AmigaEncoder encoder = new AmigaEncoder(config, 200 * 1e6); Image image = new Image(); Sector sector = image.put(0, 0, 0); diff --git a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java index 7404bcc7..5cdc1ced 100644 --- a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java +++ b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java @@ -15,8 +15,7 @@ public class FluxDecoderTest { private static final int CLOCK_TICKS = 1000; - private static final double CLOCK_NS = - CLOCK_TICKS * 1000000000.0 / 12000000.0; + private static final double CLOCK_NS = CLOCK_TICKS * 1000000000.0 / 12000000.0; private static Bytes roundTrip(Bytes data) { @@ -25,7 +24,7 @@ private static Bytes roundTrip(Bytes data) /* ...write it out as flux... */ Fluxmap map = new Fluxmap(); - map.appendBits(encoded, CLOCK_TICKS); + map.appendBits(encoded, CLOCK_NS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); @@ -53,7 +52,7 @@ public void emitsAClockForEveryFluxTransition() Fluxmap map = new Fluxmap(); map.appendBits( java.util.Arrays.asList(true, true, true, true, true, true, true, true), - CLOCK_TICKS); + CLOCK_NS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); @@ -72,7 +71,7 @@ public void firstBitIsAlwaysTrue() /* The initial leading-zeroes state (tell().zeroes() == 0) makes the * first readBit return true. */ Fluxmap map = new Fluxmap(); - map.appendBits(java.util.Arrays.asList(true), CLOCK_TICKS); + map.appendBits(java.util.Arrays.asList(true), CLOCK_NS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); diff --git a/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java b/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java index 9251a975..bbfd5d79 100644 --- a/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java +++ b/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java @@ -23,6 +23,11 @@ public class EncoderTest { private static final class TestEncoder extends Encoder { + TestEncoder(double diskRotationalPeriodNs) + { + super(diskRotationalPeriodNs); + } + @Override public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) { @@ -33,9 +38,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) @Test public void createThrowsNotImplemented() { - ConfigProto config = new ConfigBuilder() - .set("usb.serial", "test-serial") - .build(); + ConfigProto config = new ConfigBuilder().set("usb.serial", "test-serial").build(); assertThrows(FluxEngineException.class, () -> Encoder.create(config)); } @@ -45,15 +48,17 @@ public void collectSectorsCollectsInDiskOrder() { /* A single-track, single-side disk with sectors 0 and 1. */ DiskLayout layout = new DiskLayout(1, 1, 2, 256); - LogicalTrackLayout ltl = layout.layoutByLogicalLocation.get( - new com.cowlark.fluxengine.data.CylinderHead(0, 0)); + LogicalTrackLayout ltl = + layout.layoutByLogicalLocation.get(new com.cowlark.fluxengine.data.CylinderHead( + 0, + 0)); assertThat(ltl).isNotNull(); Image image = new Image(); image.put(0, 0, 0); image.put(0, 0, 1); - TestEncoder encoder = new TestEncoder(); + TestEncoder encoder = new TestEncoder(200 * 1e6); ImmutableList sectors = encoder.collectSectors(ltl, image); @@ -66,44 +71,34 @@ public void collectSectorsCollectsInDiskOrder() public void collectSectorsMissingSectorThrows() { DiskLayout layout = new DiskLayout(1, 1, 2, 256); - LogicalTrackLayout ltl = layout.layoutByLogicalLocation.get( - new com.cowlark.fluxengine.data.CylinderHead(0, 0)); + LogicalTrackLayout ltl = + layout.layoutByLogicalLocation.get(new com.cowlark.fluxengine.data.CylinderHead( + 0, + 0)); Image image = new Image(); image.put(0, 0, 0); /* sector 1 missing */ - TestEncoder encoder = new TestEncoder(); + TestEncoder encoder = new TestEncoder(200 * 1e6); - assertThrows( - FluxEngineException.class, - () -> encoder.collectSectors(ltl, image)); + assertThrows(FluxEngineException.class, () -> encoder.collectSectors(ltl, image)); } @Test - public void calculatePhysicalClockPeriod() + public void calculatePhysicalClockPeriodNs() { - ConfigProto config = new ConfigBuilder() - .set("usb.serial", "test-serial") - .set("drive.rotational_period_ms", "200") - .build(); - - TestEncoder encoder = new TestEncoder(); + TestEncoder encoder = new TestEncoder(200 * 1e6); - assertThat(encoder.calculatePhysicalClockPeriod(config, 4000, 200e6)) - .isEqualTo(4000.0); + assertThat(encoder.calculatePhysicalClockPeriodNs(4000, 200e6)).isEqualTo(4000.0); } @Test - public void calculatePhysicalClockPeriodUnsetThrows() + public void calculatePhysicalClockPeriodNsUnsetThrows() { - ConfigProto config = new ConfigBuilder() - .set("usb.serial", "test-serial") - .build(); - - TestEncoder encoder = new TestEncoder(); + TestEncoder encoder = new TestEncoder(0); assertThrows( FluxEngineException.class, - () -> encoder.calculatePhysicalClockPeriod(config, 4000, 200e6)); + () -> encoder.calculatePhysicalClockPeriodNs(4000, 200e6)); } } From 3040c766cd753a6b98c1a674cd17d314eb6652cf Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 00:26:59 +0200 Subject: [PATCH 158/192] Typo fix: the Atari config didn't set a default image writer. --- src/formats/atarist.textpb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/formats/atarist.textpb b/src/formats/atarist.textpb index 260ea6b9..9207c3a5 100644 --- a/src/formats/atarist.textpb +++ b/src/formats/atarist.textpb @@ -50,6 +50,11 @@ decoder { } } +image_writer { + filename: "atarist.st" + type: IMAGETYPE_IMG +} + layout { format_type: FORMATTYPE_80TRACK } From e2885a019b756182051c4a93aa68246ee02ac18e Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 00:34:35 +0200 Subject: [PATCH 159/192] Use Bits in various places rather than lists of boolean. --- .../fluxengine/arch/micropolis/MicropolisEncoder.java | 2 +- java/com/cowlark/fluxengine/core/Bits.java | 10 ++++++++++ java/com/cowlark/fluxengine/data/Fluxmap.java | 4 ++-- .../cowlark/fluxengine/decoders/FluxDecoderTest.java | 11 +++++++---- 4 files changed, 20 insertions(+), 7 deletions(-) diff --git a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java index f136db6f..c7a269ae 100644 --- a/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java +++ b/java/com/cowlark/fluxengine/arch/micropolis/MicropolisEncoder.java @@ -59,7 +59,7 @@ public Fluxmap encode(LogicalTrackLayout ltl, List sectors, Image image) for (int i = 1; i < indexes.size(); i++) { int end = indexes.get(i); - fluxmap.appendBits(bits.subList(pos, end), clockPeriod); + fluxmap.appendBits(bits.subBits(pos, end), clockPeriod); fluxmap.appendIndex(); pos = end; } diff --git a/java/com/cowlark/fluxengine/core/Bits.java b/java/com/cowlark/fluxengine/core/Bits.java index 445ee373..2b697e87 100644 --- a/java/com/cowlark/fluxengine/core/Bits.java +++ b/java/com/cowlark/fluxengine/core/Bits.java @@ -98,6 +98,16 @@ public void clear() modCount++; } + /* Returns a new Bits containing the bits from fromIndex (inclusive) to + * toIndex (exclusive). */ + public Bits subBits(int fromIndex, int toIndex) + { + Bits result = new Bits(toIndex - fromIndex); + for (int i = fromIndex; i < toIndex; i++) + result.setBit(i - fromIndex, getBit(i)); + return result; + } + /* Returns a new Bits with the bits in reverse order. */ public Bits reverseBits() { diff --git a/java/com/cowlark/fluxengine/data/Fluxmap.java b/java/com/cowlark/fluxengine/data/Fluxmap.java index 85bf7956..0087a0aa 100644 --- a/java/com/cowlark/fluxengine/data/Fluxmap.java +++ b/java/com/cowlark/fluxengine/data/Fluxmap.java @@ -5,10 +5,10 @@ import static com.cowlark.fluxengine.external.FluxEngine.F_DESYNC; import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; +import com.cowlark.fluxengine.core.Bits; import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; import com.google.common.collect.ImmutableList; -import java.util.List; /** * A stream of flux transitions, ported from lib/data/fluxmap.{h,cc}. @@ -117,7 +117,7 @@ public Fluxmap appendByte(int b) return appendBytes(Bytes.of(b)); } - public Fluxmap appendBits(List bits, double clockNs) + public Fluxmap appendBits(Bits bits, double clockNs) { double nowTicks = durationNs() / NS_PER_TICK; double clockTicks = clockNs / NS_PER_TICK; diff --git a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java index 5cdc1ced..09e684e9 100644 --- a/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java +++ b/javatests/com/cowlark/fluxengine/decoders/FluxDecoderTest.java @@ -49,10 +49,11 @@ public void emitsAClockForEveryFluxTransition() { /* A pulse at every cell boundary reads back as an unbroken run of * trues. */ + Bits inputBits = new Bits(); + for (int i = 0; i < 8; i++) + inputBits.add(true); Fluxmap map = new Fluxmap(); - map.appendBits( - java.util.Arrays.asList(true, true, true, true, true, true, true, true), - CLOCK_NS); + map.appendBits(inputBits, CLOCK_NS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); @@ -70,8 +71,10 @@ public void firstBitIsAlwaysTrue() { /* The initial leading-zeroes state (tell().zeroes() == 0) makes the * first readBit return true. */ + Bits inputBits = new Bits(); + inputBits.add(true); Fluxmap map = new Fluxmap(); - map.appendBits(java.util.Arrays.asList(true), CLOCK_NS); + map.appendBits(inputBits, CLOCK_NS); FluxmapReader reader = new FluxmapReader(map, DecoderProto.getDefaultInstance()); FluxDecoder decoder = new FluxDecoder(reader, CLOCK_NS, DecoderProto.getDefaultInstance()); From 9507b69254e5dacef09fe7e620046de749ec91fe Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 01:39:42 +0200 Subject: [PATCH 160/192] Bolt on the corpus tests, half of which fail because I haven't ported the scp code. --- BUILD.bazel | 10 ++ Makefile | 5 +- corpus.bzl | 88 +++++++++++++++++ .../cowlark/fluxengine/buildtools/BUILD.bazel | 14 ++- .../buildtools/EncodeDecodeTest.java | 99 +++++++++++++++++++ 5 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 corpus.bzl create mode 100644 java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java diff --git a/BUILD.bazel b/BUILD.bazel index f9c721c5..e816d5f3 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,3 +1,5 @@ +load("//:corpus.bzl", "define_corpus_tests") + package(default_visibility = ["//visibility:public"]) # Root aliases for running/building the application @@ -10,3 +12,11 @@ alias( name = "fluxengine_native", actual = "//java/com/cowlark/fluxengine:fluxengine_native", ) + +# Encode/decode round-trip tests, ported from the corpus tests in build.py. +CORPUS_TESTS = define_corpus_tests() + +test_suite( + name = "corpus", + tests = CORPUS_TESTS, +) diff --git a/Makefile b/Makefile index 998507d0..7c71111a 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,8 @@ -.PHONY: all +.PHONY: all corpus all: bazel test //javatests/... bazel build //:fluxengine //:fluxengine_native +corpus: + bazel test //:corpus + diff --git a/corpus.bzl b/corpus.bzl new file mode 100644 index 00000000..48e396e8 --- /dev/null +++ b/corpus.bzl @@ -0,0 +1,88 @@ +load("@rules_java//java:defs.bzl", "java_test") + +# Encode/decode round-trip tests, ported from the corpus tests in build.py. +# Each test generates a random sector image, writes it to a flux file, reads it +# back, and checks the result matches, using the EncodeDecodeTest tool. + +CORPUS = [ + ("acorndfs", "", "--200"), + ("agat", "", ""), + ("amiga", "", ""), + ("apple2", "", "--140 --drivetype=40"), + ("atarist", "", "--360"), + ("atarist", "", "--370"), + ("atarist", "", "--400"), + ("atarist", "", "--410"), + ("atarist", "", "--720"), + ("atarist", "", "--740"), + ("atarist", "", "--800"), + ("atarist", "", "--820"), + ("bk", "", ""), + ("brother", "", "--120 --drivetype=40"), + ("brother", "", "--240"), + ( + "commodore", + "scripts/commodore1541_test.textpb", + "--171 --drivetype=40", + ), + ( + "commodore", + "scripts/commodore1541_test.textpb", + "--192 --drivetype=40", + ), + ("commodore", "", "--800"), + ("commodore", "", "--1620"), + ("hplif", "", "--264"), + ("hplif", "", "--608"), + ("hplif", "", "--616"), + ("hplif", "", "--770"), + ("ibm", "", "--1200"), + ("ibm", "", "--1232"), + ("ibm", "", "--1440"), + ("ibm", "", "--1680"), + ("ibm", "", "--180 --drivetype=40"), + ("ibm", "", "--160 --drivetype=40"), + ("ibm", "", "--320 --drivetype=40"), + ("ibm", "", "--360 --drivetype=40"), + ("ibm", "", "--720_96"), + ("ibm", "", "--720_135"), + ("mac", "scripts/mac400_test.textpb", "--400"), + ("mac", "scripts/mac800_test.textpb", "--800"), + ("n88basic", "", ""), + ("rx50", "", ""), + ("tartu", "", "--390 --drivetype=40"), + ("tartu", "", "--780"), + ("tids990", "", ""), + ("victor9k", "", "--612"), + ("victor9k", "", "--1224"), +] + +def _sanitize(s): + result = "" + for ch in s.elems(): + result += ch if ch.isalnum() else "_" + return result + +def define_corpus_tests(): + tests = [] + for entry in CORPUS: + format = entry[0] + script = entry[1] + flags = entry[2] + name = _sanitize(format + script + flags) + for ext in ["scp", "flux"]: + test_name = "corpustest_%s_%s" % (name, ext) + args = [format, ext] + if flags: + args += flags.split(" ") + java_test( + name = test_name, + main_class = "com.cowlark.fluxengine.buildtools.EncodeDecodeTest", + use_testrunner = False, + args = args, + runtime_deps = ["//java/com/cowlark/fluxengine/buildtools:encodedecodetest"], + size = "small", + timeout = "moderate", + ) + tests.append(test_name) + return tests diff --git a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel index 83f2e0d5..7c5c7573 100644 --- a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel +++ b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel @@ -4,7 +4,10 @@ package(default_visibility = ["//visibility:public"]) java_library( name = "buildtools", - srcs = glob(["*.java"]), + srcs = glob( + ["*.java"], + exclude = ["EncodeDecodeTest.java"], + ), deps = [ "//java/com/cowlark/fluxengine/config:config_java_proto", "@com_google_protobuf//java/core", @@ -16,3 +19,12 @@ java_binary( main_class = "com.cowlark.fluxengine.buildtools.ProtoEncode", runtime_deps = [":buildtools"], ) + +java_library( + name = "encodedecodetest", + srcs = ["EncodeDecodeTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/cli", + "@maven//:com_google_guava_guava", + ], +) diff --git a/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java b/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java new file mode 100644 index 00000000..2253e3da --- /dev/null +++ b/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java @@ -0,0 +1,99 @@ +package com.cowlark.fluxengine.buildtools; + +import com.cowlark.fluxengine.cli.Command; +import com.cowlark.fluxengine.cli.ReadCommand; +import com.cowlark.fluxengine.cli.WriteCommand; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Random; + +/** + * A round-trip encode/decode test for a single format, ported from + * scripts/encodedecodetest.sh. Generates a random sector image, writes it out + * as flux with the WriteCommand, reads it back with the ReadCommand, and checks + * that the two images match. + * + *

Arguments: {@code format ext [flags...]}, where {@code ext} is the flux + * file extension ({@code scp} or {@code flux}) and the flags are the extra + * format-specific options (e.g. {@code --360}). The {@code -c} config flag, + * {@code --drive.rotational_period_ms=200}, and the file names are supplied by + * this program. + */ +public class EncodeDecodeTest +{ + public static void main(String[] args) throws Exception + { + String format = args[0]; + String ext = args[1]; + ImmutableList flags = ImmutableList.copyOf( + java.util.Arrays.asList(args).subList(2, args.length)); + + Path dir = Files.createTempDirectory("encodedecodetest"); + Path srcFile = dir.resolve("src.img"); + Path fluxFile = dir.resolve("flux." + ext); + Path destFile = dir.resolve("dest.img"); + + writeRandomImage(srcFile); + + run(new WriteCommand(), + ImmutableList.builder() + .add("-c", format, "-i", srcFile.toString(), + "-d", fluxFile.toString()) + .add("--drive.rotational_period_ms=200") + .add("--no-verify") + .addAll(flags) + .build()); + + run(new ReadCommand(), + ImmutableList.builder() + .add("-c", format, "-s", fluxFile.toString(), + "-o", destFile.toString()) + .add("--drive.rotational_period_ms=200") + .addAll(flags) + .build()); + + long destSize = Files.size(destFile); + if (destSize == 0) + { + System.err.println("Zero length output file!"); + System.exit(1); + } + + /* Make the source file the same length as the destination, ported from + * the script's `truncate -r $destfile $srcfile`. */ + try (RandomAccessFile raf = new RandomAccessFile(srcFile.toFile(), "rw")) + { + raf.setLength(destSize); + } + + if (Files.mismatch(srcFile, destFile) != -1) + { + System.err.println("Comparison failed!"); + System.err.println("Run this to repeat:"); + System.err.println("bazel run //java/com/cowlark/fluxengine/buildtools:encodedecodetest_bin -- " + + String.join(" ", args)); + System.exit(1); + } + } + + private static void run(Command command, + ImmutableList args) throws Exception + { + System.out.printf("fluxengine %s %s%n", + command instanceof WriteCommand ? "write" : "read", + String.join(" ", args)); + command.run(args); + } + + private static void writeRandomImage(Path path) throws IOException + { + /* The data is of no value, so a cheap PRNG is fine. */ + Random random = new Random(); + byte[] data = new byte[2 * 1024 * 1024]; + random.nextBytes(data); + Files.write(path, data); + } +} From a084a49f9d54a68fe394f38ae564457518898fed Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 01:58:45 +0200 Subject: [PATCH 161/192] Run the tests as part of the build. --- .github/workflows/ccpp.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index a88e41e2..574269ac 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -31,6 +31,7 @@ jobs: - name: Build with Bazel run: | cd fluxengine + bazel test //... bazel build //:all - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -58,6 +59,7 @@ jobs: - name: Build with Bazel run: | cd fluxengine + bazel test //... bazel build //:all - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -91,6 +93,11 @@ jobs: - name: Build with Bazel run: | cd fluxengine + bazel test //... ` + --action_env=PATH ` + --action_env=INCLUDE ` + --action_env=LIB ` + --action_env=LIBPATH bazel build //:all ` --action_env=PATH ` --action_env=INCLUDE ` From ea23c36bbea6010970573177d513b088c8648b46 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 19:39:09 +0200 Subject: [PATCH 162/192] Update AGENTS which was stale. --- AGENTS.md | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index f10dad81..c242d5f7 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -90,17 +90,6 @@ Useful commands: value (int/uint/long/float/double/bool/enum) as needed. Unknown paths and bad values throw `ConfigException`. -## Dependency injection (Dagger) - -- The Dagger annotation processor lives in the `wiring` package - (`java/com/cowlark/fluxengine/wiring/BUILD.bazel`), which defines `dagger_plugin` and - exports it via `exported_plugins` on the `wiring`/`dagger` targets. Any library that - depends on `//java/com/cowlark/fluxengine/wiring` gets Dagger annotation processing - automatically. -- `FluxEngineComponent` is the single `@Component`. It exposes accessors for the CLI - commands. Classes are injectable via `@Inject` constructors; there are no module - bindings for own classes unless needed. - ## CLI - Commands live in `com.cowlark.fluxengine.cli` and implement the `Command` interface From f2f6d025ae00ab52712c818ff2597b32c57e7584 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 20:13:23 +0200 Subject: [PATCH 163/192] Remember to port ScpFluxSource. --- java/com/cowlark/fluxengine/external/Scp.java | 15 ++ .../fluxengine/fluxsink/ScpFluxSink.java | 11 +- .../fluxengine/fluxsource/FluxSource.java | 2 +- .../fluxengine/fluxsource/ScpFluxSource.java | 163 ++++++++++++++++++ .../cowlark/fluxengine/fluxsource/BUILD.bazel | 18 ++ .../fluxsource/ScpFluxSourceTest.java | 102 +++++++++++ 6 files changed, 302 insertions(+), 9 deletions(-) create mode 100644 java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java create mode 100644 javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java diff --git a/java/com/cowlark/fluxengine/external/Scp.java b/java/com/cowlark/fluxengine/external/Scp.java index d970165f..eefd1dd7 100644 --- a/java/com/cowlark/fluxengine/external/Scp.java +++ b/java/com/cowlark/fluxengine/external/Scp.java @@ -19,6 +19,21 @@ public final class Scp /* Size of a track header (the 'TRK' id plus 5 revolution records). */ public static final int SCP_TRACK_SIZE = 4 + 5 * 12; + public static int trackno(int strack) + { + return strack >> 1; + } + + public static int headno(int strack) + { + return strack & 1; + } + + public static int strackno(int track, int side) + { + return (track << 1) | side; + } + private Scp() { } diff --git a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java index 0bc8d5e1..132fbf9d 100644 --- a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java @@ -53,8 +53,8 @@ public ScpFluxSink(String filename, int typeByte, boolean alignWithIndex, Config fileheader[2] = 'P'; fileheader[3] = 0x18; /* Version 1.8 of the spec */ fileheader[4] = (byte) typeByte; - fileheader[6] = (byte) strackno(minCylinder, minHead); - fileheader[7] = (byte) strackno(maxCylinder, maxHead); + fileheader[6] = (byte) Scp.strackno(minCylinder, minHead); + fileheader[7] = (byte) Scp.strackno(maxCylinder, maxHead); int flags = Scp.SCP_FLAG_INDEXED; if (config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) throw new FluxEngineException("you can't write Apple II flux images to SCP files yet"); @@ -74,11 +74,6 @@ else if ((minHead == 1) && (maxHead == 1)) (fileheader[7] - fileheader[6] + 1) + " tracks"); } - private static int strackno(int track, int side) - { - return (track << 1) | side; - } - private static void writeLe32(byte[] dest, int offset, int v) { dest[offset] = (byte) v; @@ -100,7 +95,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) { ByteWriter trackdataWriter = trackdata.writer(); trackdataWriter.seekToEnd(); - int strack = strackno(track, head); + int strack = Scp.strackno(track, head); if (strack >= 168) { diff --git a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java index b87a658f..e83b7804 100644 --- a/java/com/cowlark/fluxengine/fluxsource/FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/FluxSource.java @@ -30,7 +30,7 @@ public static FluxSource create(FluxSourceProto config) case FLUXTYPE_TEST_PATTERN: return notImplemented("test pattern"); case FLUXTYPE_SCP: - return notImplemented("scp"); + return new ScpFluxSource(config.getScp()); case FLUXTYPE_A2R: return new A2RFluxSource(config.getA2R()); case FLUXTYPE_CWF: diff --git a/java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java new file mode 100644 index 00000000..24e59e0d --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java @@ -0,0 +1,163 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteReader; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Locations; +import com.cowlark.fluxengine.external.DriveType; +import com.cowlark.fluxengine.external.Scp; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +/** + * A flux source which reads an SCP flux file, ported from + * lib/fluxsource/scpfluxsource.cc. + */ +public class ScpFluxSource extends TrivialFluxSource +{ + private final Bytes data; + protected ConfigProto extraConfig; + private final double resolution; + private final int heads; + private final int startTrack; + private final int endTrack; + private final int flags; + private final int revolutions; + private final int[] trackOffsets = new int[168]; + + public ScpFluxSource(ScpFluxSourceProto config) + { + data = readFile(config.getFilename()); + + ByteReader br = new ByteReader(data); + byte[] fileId = br.read(3).toByteArray(); + if ((fileId[0] != 'S') || (fileId[1] != 'C') || (fileId[2] != 'P')) + throw new FluxEngineException("input not a SCP file"); + + br.read8(); /* version */ + br.read8(); /* type */ + revolutions = br.read8(); + startTrack = Scp.trackno(br.read8()); + endTrack = Scp.trackno(br.read8()); + flags = br.read8(); + int cellWidth = br.read8(); + heads = br.read8(); + int resolutionByte = br.read8(); + br.skip(4); /* checksum */ + + for (int i = 0; i < 168; i++) + trackOffsets[i] = br.readLe32(); + + if ((cellWidth != 0) && (cellWidth != 16)) + throw new FluxEngineException("currently only 16-bit cells in SCP files are supported"); + + resolution = 25.0 * (resolutionByte + 1); + + int startSide = (heads == 2) ? 1 : 0; + int endSide = (heads == 1) ? 0 : 1; + + List chs = new ArrayList<>(); + for (int cylinder = startTrack; cylinder <= endTrack; cylinder++) + for (int head = startSide; head <= endSide; head++) + chs.add(new CylinderHead(cylinder, head)); + + ConfigProto.Builder builder = ConfigProto.newBuilder(); + builder.getDriveBuilder().setDriveType( + (flags & Scp.SCP_FLAG_96TPI) != 0 ? + DriveType.DRIVETYPE_80TRACK : + DriveType.DRIVETYPE_40TRACK); + builder.getDriveBuilder().setTracks(Locations.convertCylinderHeadsToString(chs)); + extraConfig = builder.build(); + + Logger.logf("SCP tracks %d-%d, heads %d-%d", + startTrack, endTrack, startSide, endSide); + Logger.logf("SCP sample resolution: %d ns", (int) resolution); + } + + private static Bytes readFile(String filename) + { + try + { + return new Bytes(Files.readAllBytes(Path.of(filename))); + } catch (IOException e) + { + throw new FluxEngineException( + "cannot open input file '" + filename + "': " + e.getMessage()); + } + } + + @Override + public void adjustConfig(ConfigBuilder configBuilder) + { + configBuilder.mergeConfig(extraConfig); + } + + @Override + public Fluxmap readSingleFlux(FluxReadParameters parameters) + { + int strack = Scp.strackno(parameters.cylinder(), parameters.head()); + if (strack >= 168) + return new Fluxmap(); + int offset = trackOffsets[strack]; + if (offset == 0) + return new Fluxmap(); + + ByteReader br = new ByteReader(data); + br.seek(offset); + byte[] trackId = br.read(3).toByteArray(); + if ((trackId[0] != 'T') || (trackId[1] != 'R') || (trackId[2] != 'K')) + throw new FluxEngineException("corrupt SCP file"); + br.read8(); /* strack */ + + int[] revsLength = new int[revolutions]; + int[] revsOffset = new int[revolutions]; + for (int revolution = 0; revolution < revolutions; revolution++) + { + br.skip(4); /* index */ + revsLength[revolution] = br.readLe32(); + revsOffset[revolution] = br.readLe32(); + } + + Fluxmap fluxmap = new Fluxmap(); + long pending = 0; + for (int revolution = 0; revolution < revolutions; revolution++) + { + if (revolution != 0) + fluxmap.appendIndex(); + + int dataLength = revsLength[revolution]; + int dataOffset = revsOffset[revolution]; + + ByteReader dbr = new ByteReader(data); + dbr.seek(dataOffset + offset); + for (int cell = 0; cell < dataLength; cell++) + { + int interval = dbr.readBe16(); + if (interval != 0) + { + fluxmap.appendInterval((int) ((interval + pending) * resolution / NS_PER_TICK)); + fluxmap.appendPulse(); + pending = 0; + } else + pending += 0x10000; + } + } + + return fluxmap; + } + + @Override + public void recalibrate() + { + } +} diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 9d4da107..3c247b53 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -36,6 +36,24 @@ java_test( ], ) +java_test( + name = "ScpFluxSourceTest", + srcs = ["ScpFluxSourceTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/config", + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/core", + "//java/com/cowlark/fluxengine/data", + "//java/com/cowlark/fluxengine/external", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", + "//java/com/cowlark/fluxengine/fluxsource", + "//java/com/cowlark/fluxengine/fluxsource:fluxsource_java_proto", + "@com_google_protobuf//java/core", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "HardwareFluxSourceTest", srcs = ["HardwareFluxSourceTest.java"], diff --git a/javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java new file mode 100644 index 00000000..a8527d55 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java @@ -0,0 +1,102 @@ +package com.cowlark.fluxengine.fluxsource; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.ByteWriter; +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.external.DriveType; +import com.cowlark.fluxengine.external.Scp; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +@RunWith(JUnit4.class) +public class ScpFluxSourceTest +{ + /* Builds an SCP file containing a single track 0/0 (strack 0), encoded + * with two intervals of 100 and 200 at a 25ns resolution. */ + private static Path writeTempFile() throws IOException + { + Bytes result = new Bytes(Scp.SCP_HEADER_SIZE + 4 + 12 + 4); + ByteWriter bw = new ByteWriter(result); + + bw.write8('S'); + bw.write8('C'); + bw.write8('P'); + bw.write8(0x18); /* version 1.8 */ + bw.write8(0xff); /* type */ + bw.write8(1); /* revolutions */ + bw.write8(Scp.strackno(0, 0)); /* start track */ + bw.write8(Scp.strackno(0, 0)); /* end track */ + bw.write8(0); /* flags: not 96tpi */ + bw.write8(0); /* cell width: 16-bit cells */ + bw.write8(1); /* heads: side 0 only */ + bw.write8(0); /* resolution: 25ns */ + bw.writeLe32(0); /* checksum */ + + /* Track offset table; only strack 0 is present. */ + int trackOffset = Scp.SCP_HEADER_SIZE; + for (int i = 0; i < 168; i++) + bw.writeLe32(i == 0 ? trackOffset : 0); + + /* Track header: 'TRK' + strack, then one revolution record. */ + bw.write8('T'); + bw.write8('R'); + bw.write8('K'); + bw.write8(0); /* strack */ + bw.writeLe32(0); /* index */ + bw.writeLe32(2); /* length: two cells */ + bw.writeLe32(16); /* offset to cell data, relative to track header */ + + /* Cell data: two big-endian intervals. */ + bw.writeBe16(100); + bw.writeBe16(200); + + Path path = Files.createTempFile("flux", ".scp"); + Files.write(path, result.toByteArray()); + return path; + } + + @Test + public void readsTracks() throws IOException + { + Path path = writeTempFile(); + + ScpFluxSource source = new ScpFluxSource(ScpFluxSourceProto.newBuilder() + .setFilename(path.toString()) + .build()); + + FluxSourceIterator iterator = + source.readFlux(FluxReadParameters.builder().setCylinder(0).setHead(0).build()); + assertThat(iterator.hasNext()).isTrue(); + Bytes expected = Bytes.of(0x9e, 0xbc); + assertThat(iterator.next().rawBytes().toByteArray()).isEqualTo(expected.toByteArray()); + assertThat(iterator.hasNext()).isFalse(); + + ConfigBuilder configBuilder = new ConfigBuilder().set("usb.serial", "test-serial"); + source.adjustConfig(configBuilder); + ConfigProto config = configBuilder.build(); + assertThat(config.getDrive().getTracks()).isEqualTo("c0h0"); + assertThat(config.getDrive().getDriveType()).isEqualTo(DriveType.DRIVETYPE_40TRACK); + } + + @Test + public void missingTrackReturnsEmptyFluxmap() throws IOException + { + Path path = writeTempFile(); + + ScpFluxSource source = new ScpFluxSource(ScpFluxSourceProto.newBuilder() + .setFilename(path.toString()) + .build()); + + FluxSourceIterator iterator = + source.readFlux(FluxReadParameters.builder().setCylinder(1).setHead(0).build()); + assertThat(iterator.hasNext()).isTrue(); + assertThat(iterator.next().ticks()).isEqualTo(0); + } +} From 04d44be35f77183865a02c200c9b7af4f33bb36b Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 20:16:39 +0200 Subject: [PATCH 164/192] A little cleanup. --- .../fluxengine/fluxsink/ScpFluxSink.java | 18 +++++++++++------- .../fluxengine/fluxsource/ScpFluxSource.java | 8 ++++---- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java index 132fbf9d..b843bace 100644 --- a/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/ScpFluxSink.java @@ -3,6 +3,10 @@ import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_INDEX; import static com.cowlark.fluxengine.external.FluxEngine.F_BIT_PULSE; import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; +import static com.cowlark.fluxengine.external.Scp.SCP_FLAG_96TPI; +import static com.cowlark.fluxengine.external.Scp.SCP_FLAG_INDEXED; +import static com.cowlark.fluxengine.external.Scp.SCP_HEADER_SIZE; +import static com.cowlark.fluxengine.external.Scp.SCP_TRACK_SIZE; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.ByteReader; @@ -31,7 +35,7 @@ public class ScpFluxSink extends FluxSink private final boolean alignWithIndex; private final ConfigProto config; /* The 688-byte file header. */ - private final byte[] fileheader = new byte[Scp.SCP_HEADER_SIZE]; + private final byte[] fileheader = new byte[SCP_HEADER_SIZE]; private final Bytes trackdata = new Bytes(0); public ScpFluxSink(String filename, int typeByte, boolean alignWithIndex, ConfigProto config) @@ -55,11 +59,11 @@ public ScpFluxSink(String filename, int typeByte, boolean alignWithIndex, Config fileheader[4] = (byte) typeByte; fileheader[6] = (byte) Scp.strackno(minCylinder, minHead); fileheader[7] = (byte) Scp.strackno(maxCylinder, maxHead); - int flags = Scp.SCP_FLAG_INDEXED; + int flags = SCP_FLAG_INDEXED; if (config.getDrive().getDriveType() == DriveType.DRIVETYPE_APPLE2) throw new FluxEngineException("you can't write Apple II flux images to SCP files yet"); if (config.getDrive().getDriveType() != DriveType.DRIVETYPE_40TRACK) - flags |= Scp.SCP_FLAG_96TPI; + flags |= SCP_FLAG_96TPI; fileheader[8] = (byte) flags; fileheader[9] = 0; /* cell width */ if ((minHead == 0) && (maxHead == 0)) @@ -69,7 +73,7 @@ else if ((minHead == 1) && (maxHead == 1)) else fileheader[10] = 0; - Logger.logf("SCP: writing " + (((flags & Scp.SCP_FLAG_96TPI) != 0) ? 96 : 48) + " tpi " + + Logger.logf("SCP: writing " + (((flags & SCP_FLAG_96TPI) != 0) ? 96 : 48) + " tpi " + ((minHead == maxHead) ? "single sided" : "double sided") + " file containing " + (fileheader[7] - fileheader[6] + 1) + " tracks"); } @@ -104,7 +108,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) return; } /* ScpTrack: 'TRK' id, strack, then 5 revolution records. */ - byte[] trackHeader = new byte[Scp.SCP_TRACK_SIZE]; + byte[] trackHeader = new byte[SCP_TRACK_SIZE]; trackHeader[0] = 'T'; trackHeader[1] = 'R'; trackHeader[2] = 'K'; @@ -144,7 +148,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) if (revolution >= 0) { int revOffset = 4 + revolution * 12; - writeLe32(trackHeader, revOffset + 8, startOffset + Scp.SCP_TRACK_SIZE); + writeLe32(trackHeader, revOffset + 8, startOffset + SCP_TRACK_SIZE); writeLe32(trackHeader, revOffset + 4, (fluxdataWriter.pos() - startOffset) / 2); writeLe32(trackHeader, revOffset, (int) (revTicks * NS_PER_TICK / 25)); } @@ -169,7 +173,7 @@ public void addFlux(int track, int head, Fluxmap fluxmap) } fileheader[5] = (byte) revolution; - writeLe32(fileheader, 16 + strack * 4, trackdataWriter.pos() + Scp.SCP_HEADER_SIZE); + writeLe32(fileheader, 16 + strack * 4, trackdataWriter.pos() + SCP_HEADER_SIZE); trackdataWriter.write(trackHeader); trackdataWriter.write(fluxdata); } diff --git a/java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java b/java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java index 24e59e0d..d2022b68 100644 --- a/java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/ScpFluxSource.java @@ -1,6 +1,7 @@ package com.cowlark.fluxengine.fluxsource; import static com.cowlark.fluxengine.external.FluxEngine.NS_PER_TICK; +import static com.cowlark.fluxengine.external.Scp.SCP_FLAG_96TPI; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; @@ -72,15 +73,14 @@ public ScpFluxSource(ScpFluxSourceProto config) chs.add(new CylinderHead(cylinder, head)); ConfigProto.Builder builder = ConfigProto.newBuilder(); - builder.getDriveBuilder().setDriveType( - (flags & Scp.SCP_FLAG_96TPI) != 0 ? + builder.getDriveBuilder() + .setDriveType((flags & SCP_FLAG_96TPI) != 0 ? DriveType.DRIVETYPE_80TRACK : DriveType.DRIVETYPE_40TRACK); builder.getDriveBuilder().setTracks(Locations.convertCylinderHeadsToString(chs)); extraConfig = builder.build(); - Logger.logf("SCP tracks %d-%d, heads %d-%d", - startTrack, endTrack, startSide, endSide); + Logger.logf("SCP tracks %d-%d, heads %d-%d", startTrack, endTrack, startSide, endSide); Logger.logf("SCP sample resolution: %d ns", (int) resolution); } From 858803950d11b454fddeb62b57f252ad69973915 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 20:58:44 +0200 Subject: [PATCH 165/192] Port TrackReadLogMessage; fix the macintosh decoder. All corpus tests are now green! --- .../fluxengine/algorithms/ReadOperation.java | 1 + .../algorithms/TrackReadLogMessage.java | 61 +++++++++++++++++++ .../arch/macintosh/MacintoshDecoder.java | 39 ++++++------ .../cowlark/fluxengine/buildtools/BUILD.bazel | 6 ++ .../buildtools/EncodeDecodeTest.java | 34 +++++------ .../cowlark/fluxengine/core/ByteReader.java | 2 +- 6 files changed, 107 insertions(+), 36 deletions(-) create mode 100644 java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java diff --git a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java index b68b5d49..d2492d36 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java @@ -376,6 +376,7 @@ public void read(Disk disk) } /* track can't be modified below this point. */ + Logger.log(new TrackReadLogMessage(trackFluxes, trackSectors)); List allSectors = new ArrayList<>(); for (Sector sector : disk.sectorsByPhysicalLocation.values()) diff --git a/java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java b/java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java new file mode 100644 index 00000000..a18462da --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java @@ -0,0 +1,61 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; +import com.cowlark.fluxengine.data.Record; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.data.Track; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Set; + +/** + * We've just read a track (we might reread it if there are errors), ported + * from lib/algorithms/readerwriter.cc. + */ +public record TrackReadLogMessage(List tracks, List sectors) + implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + /* The C++ dedupes these by pointer, so use identity. */ + Set rawSectors = Collections.newSetFromMap(new IdentityHashMap<>()); + Set rawRecords = Collections.newSetFromMap(new IdentityHashMap<>()); + for (Track track : tracks) + { + rawSectors.addAll(track.allSectors); + rawRecords.addAll(track.records); + } + + double clock = 0; + for (Sector sector : rawSectors) + clock += sector.clockNs; + if (!rawSectors.isEmpty()) + clock /= rawSectors.size(); + + r.comma().add(String.format("%d raw records, %d raw sectors", + rawRecords.size(), + rawSectors.size())); + if (clock != 0) + r.comma().add(String.format("%.2fus clock (%.0fkHz)", + clock / 1000.0, + 1000000.0 / clock)); + + r.newline().add("sectors:"); + + for (Sector sector : rawSectors) + r.add(String.format("%d.%d.%d%s", + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector(), + Sector.statusToChar(sector.status))); + + int size = 0; + for (Sector sector : sectors) + size += sector.data.size(); + + r.newline().add(String.format("%d bytes decoded\n", size)); + } +} diff --git a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java index fe7a99b2..15b576b9 100644 --- a/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java +++ b/java/com/cowlark/fluxengine/arch/macintosh/MacintoshDecoder.java @@ -1,5 +1,10 @@ package com.cowlark.fluxengine.arch.macintosh; +import static com.cowlark.fluxengine.arch.macintosh.Macintosh.MAC_DATA_RECORD; +import static com.cowlark.fluxengine.arch.macintosh.Macintosh.MAC_ENCODED_SECTOR_LENGTH; +import static com.cowlark.fluxengine.arch.macintosh.Macintosh.MAC_SECTOR_LENGTH; +import static com.cowlark.fluxengine.arch.macintosh.Macintosh.MAC_SECTOR_RECORD; + import com.cowlark.fluxengine.core.ByteReader; import com.cowlark.fluxengine.core.ByteWriter; import com.cowlark.fluxengine.core.Bytes; @@ -15,10 +20,8 @@ */ public class MacintoshDecoder extends Decoder { - private static final FluxPattern SECTOR_RECORD_PATTERN = - new FluxPattern(24, Macintosh.MAC_SECTOR_RECORD); - private static final FluxPattern DATA_RECORD_PATTERN = - new FluxPattern(24, Macintosh.MAC_DATA_RECORD); + private static final FluxPattern SECTOR_RECORD_PATTERN = new FluxPattern(24, MAC_SECTOR_RECORD); + private static final FluxPattern DATA_RECORD_PATTERN = new FluxPattern(24, MAC_DATA_RECORD); private static final FluxMatchers ANY_RECORD_PATTERN = FluxMatchers.of(SECTOR_RECORD_PATTERN, DATA_RECORD_PATTERN); @@ -175,7 +178,7 @@ private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) ByteWriter bw = new ByteWriter(output); ByteReader br = input.iterator(); - int lookupLen = Macintosh.MAC_SECTOR_LENGTH / 3; + int lookupLen = MAC_SECTOR_LENGTH / 3; int[] b1 = new int[lookupLen + 1]; int[] b2 = new int[lookupLen + 1]; @@ -206,7 +209,7 @@ private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) if ((c1 & 0x0100) != 0) c1++; - int val = b1[count] ^ c1; + int val = (b1[count] ^ c1) & 0xFF; c3 += val; if ((c1 & 0x0100) != 0) { @@ -215,7 +218,7 @@ private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) } bw.write8(val); - val = b2[count] ^ c3; + val = (b2[count] ^ c3) & 0xFF; c2 += val; if (c3 > 0xFF) { @@ -227,7 +230,7 @@ private static Bytes decodeCrazyData(Bytes input, Sector.Status[] status) if (output.size() == 524) break; - val = b3[count] ^ c2; + val = (b3[count] ^ c2) & 0xFF; c1 += val; if (c2 > 0xFF) { @@ -270,21 +273,21 @@ protected double advanceToNextRecord() @Override protected void decodeSectorRecord() { - if (readRaw24() != Macintosh.MAC_SECTOR_RECORD) + if (readRaw24() != MAC_SECTOR_RECORD) return; /* Read header. */ Bytes header = readRawBits(7 * 8).toBytes().slice(0, 7); - int encodedTrack = decodeDataGcr(header.getByte(0) & 0xff); + int encodedTrack = decodeDataGcr(header.getByte(0)); if (encodedTrack != (ltl.logicalCylinder & 0x3f)) return; - int encodedSector = decodeDataGcr(header.getByte(1) & 0xff); - int encodedSide = decodeDataGcr(header.getByte(2) & 0xff); - int formatByte = decodeDataGcr(header.getByte(3) & 0xff); - int wantedsum = decodeDataGcr(header.getByte(4) & 0xff); + int encodedSector = decodeDataGcr(header.getByte(1)); + int encodedSide = decodeDataGcr(header.getByte(2)); + int formatByte = decodeDataGcr(header.getByte(3)); + int wantedsum = decodeDataGcr(header.getByte(4)); if (encodedSector > 11) return; @@ -301,17 +304,17 @@ protected void decodeSectorRecord() @Override protected void decodeDataRecord() { - if (readRaw24() != Macintosh.MAC_DATA_RECORD) + if (readRaw24() != MAC_DATA_RECORD) return; /* Read data. */ readRawBits(8); /* skip spare byte */ - Bytes inputbuffer = readRawBits(Macintosh.MAC_ENCODED_SECTOR_LENGTH * 8).toBytes() - .slice(0, Macintosh.MAC_ENCODED_SECTOR_LENGTH); + Bytes inputbuffer = readRawBits(MAC_ENCODED_SECTOR_LENGTH * 8).toBytes() + .slice(0, MAC_ENCODED_SECTOR_LENGTH); for (int i = 0; i < inputbuffer.size(); i++) - inputbuffer.setByte(i, (byte) decodeDataGcr(inputbuffer.getByte(i) & 0xff)); + inputbuffer.setByte(i, decodeDataGcr(inputbuffer.getByte(i))); Sector.Status[] status = {Sector.Status.BAD_CHECKSUM}; sector.status = status[0]; diff --git a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel index 7c5c7573..a2acc687 100644 --- a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel +++ b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel @@ -28,3 +28,9 @@ java_library( "@maven//:com_google_guava_guava", ], ) + +java_binary( + name = "encodedecodetest_bin", + main_class = "com.cowlark.fluxengine.buildtools.EncodeDecodeTest", + runtime_deps = [":encodedecodetest"], +) diff --git a/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java b/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java index 2253e3da..4a69db18 100644 --- a/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java +++ b/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java @@ -28,8 +28,8 @@ public static void main(String[] args) throws Exception { String format = args[0]; String ext = args[1]; - ImmutableList flags = ImmutableList.copyOf( - java.util.Arrays.asList(args).subList(2, args.length)); + ImmutableList flags = + ImmutableList.copyOf(java.util.Arrays.asList(args).subList(2, args.length)); Path dir = Files.createTempDirectory("encodedecodetest"); Path srcFile = dir.resolve("src.img"); @@ -38,19 +38,17 @@ public static void main(String[] args) throws Exception writeRandomImage(srcFile); - run(new WriteCommand(), - ImmutableList.builder() - .add("-c", format, "-i", srcFile.toString(), - "-d", fluxFile.toString()) + run( + new WriteCommand(), ImmutableList.builder() + .add("-c", format, "-i", srcFile.toString(), "-d", fluxFile.toString()) .add("--drive.rotational_period_ms=200") .add("--no-verify") .addAll(flags) .build()); - run(new ReadCommand(), - ImmutableList.builder() - .add("-c", format, "-s", fluxFile.toString(), - "-o", destFile.toString()) + run( + new ReadCommand(), ImmutableList.builder() + .add("-c", format, "-s", fluxFile.toString(), "-o", destFile.toString()) .add("--drive.rotational_period_ms=200") .addAll(flags) .build()); @@ -69,20 +67,22 @@ public static void main(String[] args) throws Exception raf.setLength(destSize); } - if (Files.mismatch(srcFile, destFile) != -1) + long firstDifference = Files.mismatch(srcFile, destFile); + if (firstDifference != -1) { - System.err.println("Comparison failed!"); + System.err.printf("Comparison failed at offset %d!\n", firstDifference); System.err.println("Run this to repeat:"); - System.err.println("bazel run //java/com/cowlark/fluxengine/buildtools:encodedecodetest_bin -- " - + String.join(" ", args)); + System.err.println( + "bazel run //java/com/cowlark/fluxengine/buildtools:encodedecodetest_bin -- " + + String.join(" ", args)); System.exit(1); } } - private static void run(Command command, - ImmutableList args) throws Exception + private static void run(Command command, ImmutableList args) throws Exception { - System.out.printf("fluxengine %s %s%n", + System.out.printf( + "fluxengine %s %s%n", command instanceof WriteCommand ? "write" : "read", String.join(" ", args)); command.run(args); diff --git a/java/com/cowlark/fluxengine/core/ByteReader.java b/java/com/cowlark/fluxengine/core/ByteReader.java index 96874c7b..57133725 100644 --- a/java/com/cowlark/fluxengine/core/ByteReader.java +++ b/java/com/cowlark/fluxengine/core/ByteReader.java @@ -69,7 +69,7 @@ public Bytes read(int len) public int read8() { checkReadable(1); - return bytes.getByte(pos++) & 0xff; + return bytes.getByte(pos++); } public int readBe16() From 139e51c67170a9fad98586924d6f156e71297530 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 22:12:52 +0200 Subject: [PATCH 166/192] Build jpackage distributables. --- BUILD.bazel | 10 +++ java/com/cowlark/fluxengine/BUILD.bazel | 17 ++++ jpackage.bzl | 111 ++++++++++++++++++++++++ 3 files changed, 138 insertions(+) create mode 100644 jpackage.bzl diff --git a/BUILD.bazel b/BUILD.bazel index e816d5f3..a5fc3886 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -13,6 +13,16 @@ alias( actual = "//java/com/cowlark/fluxengine:fluxengine_native", ) +alias( + name = "fluxengine_deb", + actual = "//java/com/cowlark/fluxengine:fluxengine_deb", +) + +alias( + name = "fluxengine_rpm", + actual = "//java/com/cowlark/fluxengine:fluxengine_rpm", +) + # Encode/decode round-trip tests, ported from the corpus tests in build.py. CORPUS_TESTS = define_corpus_tests() diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 21ff52a8..71b8ccec 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,4 +1,5 @@ load("@rules_java//java:defs.bzl", "java_binary") +load("//:jpackage.bzl", "jpackage_deb", "jpackage_rpm") load("//:native_image.bzl", "native_image") package(default_visibility = ["//visibility:public"]) @@ -10,6 +11,22 @@ java_binary( runtime_deps = ["//java/com/cowlark/fluxengine/cli"], ) +jpackage_deb( + name = "fluxengine_deb", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.cli.Main", + package_name = "fluxengine", + app_version = "1.0.0", +) + +jpackage_rpm( + name = "fluxengine_rpm", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.cli.Main", + package_name = "fluxengine", + app_version = "1.0.0", +) + native_image( name = "fluxengine_native", extra_args = [ diff --git a/jpackage.bzl b/jpackage.bzl new file mode 100644 index 00000000..cfa90453 --- /dev/null +++ b/jpackage.bzl @@ -0,0 +1,111 @@ +def _jpackage_impl(ctx): + # Locate jpackage via the configured Java toolchain's runtime, so the rule + # works with whatever JDK Bazel is using (e.g. remotejdk_21). + java_runtime = ctx.toolchains["@bazel_tools//tools/jdk:toolchain_type"].java.java_runtime + jpackage_path = java_runtime.java_home + "/bin/jpackage" + + package_type = ctx.attr.package_type + extension = "deb" if package_type == "deb" else "rpm" + + jar = ctx.file.jar + out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + "." + extension) + + # jpackage writes a lot of scratch state (a jlink runtime image and an app + # image) and chmods files in it. Do all the scratch work in a plain + # directory under the execroot (which is writable in the sandbox) and only + # declare the final package as an output. The sandbox input jar is a + # symlink to a read-only file, so dereference it (cp -L) and make the copy + # writable. + # + # rpmbuild (invoked by jpackage for --type rpm) creates its temp scripts in + # /var/tmp by default, which is read-only in the sandbox, so point it at the + # scratch dir via a ~/.rpmmacros file. + ctx.actions.run_shell( + outputs = [out], + inputs = [jar], + tools = [java_runtime.files], + use_default_shell_env = True, + command = """ + rm -rf workdir + mkdir -p workdir/input workdir/tmp workdir/dest workdir/home workdir/rpmbuild + cp -L "{jar}" workdir/input/ + chmod u+w workdir/input/* + if [ "{package_type}" = "rpm" ]; then + WORKTMP="$(pwd)/workdir/tmp" + RPMPREFIX="$(pwd)/workdir/rpmbuild" + cat > workdir/home/.rpmmacros < Date: Tue, 11 Aug 2026 22:19:52 +0200 Subject: [PATCH 167/192] Remove the graalvm integration. Sigh. --- AGENTS.md | 23 +- BUILD.bazel | 5 - MODULE.bazel | 3 - MODULE.bazel.lock | 13 - Makefile | 2 +- graalvm_extension.bzl | 8 - graalvm_repository.bzl | 74 - java/com/cowlark/fluxengine/BUILD.bazel | 16 - java/com/cowlark/fluxengine/jni-config.json | 267 - .../cowlark/fluxengine/reflect-config.json | 6880 ----------------- .../cowlark/fluxengine/resource-config.json | 25 - .../fluxengine/serialization-config.json | 8 - native_image.bzl | 86 - 13 files changed, 11 insertions(+), 7399 deletions(-) delete mode 100644 graalvm_extension.bzl delete mode 100644 graalvm_repository.bzl delete mode 100644 java/com/cowlark/fluxengine/jni-config.json delete mode 100644 java/com/cowlark/fluxengine/reflect-config.json delete mode 100644 java/com/cowlark/fluxengine/resource-config.json delete mode 100644 java/com/cowlark/fluxengine/serialization-config.json delete mode 100644 native_image.bzl diff --git a/AGENTS.md b/AGENTS.md index c242d5f7..6574ecba 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -8,8 +8,7 @@ the coding conventions used. Follow it when making changes. ## Build system Bazel with bzlmod. There is **no WORKSPACE file** — all dependency declarations live in -`MODULE.bazel` (rules_java, rules_jvm_external for Maven deps, rules_proto, plus custom -GraalVM extension/rule). +`MODULE.bazel` (rules_java, rules_jvm_external for Maven deps, rules_proto). - Java sources: `java/` (standard Bazel layout, `com` is a direct child of `java`) - Java tests: `javatests/` @@ -23,8 +22,8 @@ Useful commands: - `bazel build //java/...` - `bazel test //javatests/...` - `bazel run //java/com/cowlark/fluxengine:fluxengine -- ` (JVM binary) -- `bazel run //:fluxengine_native -- ` (GraalVM native binary; root aliases - `//:fluxengine` and `//:fluxengine_native` exist) +- `bazel build //:fluxengine_deb //:fluxengine_rpm` (jpackage .deb/.rpm installers; root + aliases `//:fluxengine`, `//:fluxengine_deb`, and `//:fluxengine_rpm` exist) ## Gotchas @@ -37,13 +36,11 @@ Useful commands: resource (`resources = ["//java:javax.usb.properties"]`) by the usb library. Bazel's resource jarring strips the leading `java/`, so it lands at the jar root. Do not move it into the package directory. -- GraalVM native-image requires reachability config generated by the tracing agent: - `jni-config.json`, `reflect-config.json`, `resource-config.json`, - `serialization-config.json` live in `java/com/cowlark/fluxengine/` and are passed to the - `native_image` rule. They are platform-specific — regenerate with the tracing agent - (`-agentlib:native-image-agent=config-output-dir=...`) when adding JNI/reflection paths - or targeting a new platform. -- The native binary must remain a single standalone executable (no runtime files shipped). +- The `.deb` and `.rpm` installers are built with jpackage via the `jpackage_deb` and + `jpackage_rpm` rules in `jpackage.bzl` (which use the configured Java toolchain's + `jpackage`). Because `rpmbuild` writes to `/var/tmp` and read-only sandbox paths by + default, the rules stage everything under a writable `workdir/` and, for rpm, point + rpmbuild's `_tmppath`/`_builddir` etc. at it via a `~/.rpmmacros` file. ## Lombok builders @@ -103,8 +100,8 @@ Useful commands: - Each command carries its own help text, returned by `getHelp()`; `Main.help` prints the table by instantiating each command and calling `getHelp()`. - `Main.dispatch(commands, args)` consumes arguments until it reaches a real command, - instantiates it via the supplier (`TestDevicesCommand::new`, not reflection, so GraalVM - needs no extra reachability config), and calls `run()` with the tail. Group commands + instantiates it via the supplier (`TestDevicesCommand::new`), and calls `run()` with the + tail. Group commands (`analyse`, `fluxfile`, `test`) are `CommandGroup(subcommands, help)` instances, which dispatch again on their sub-table and print extended help if nothing matches. Add new commands by updating the relevant table. diff --git a/BUILD.bazel b/BUILD.bazel index a5fc3886..ec86fd30 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -8,11 +8,6 @@ alias( actual = "//java/com/cowlark/fluxengine", ) -alias( - name = "fluxengine_native", - actual = "//java/com/cowlark/fluxengine:fluxengine_native", -) - alias( name = "fluxengine_deb", actual = "//java/com/cowlark/fluxengine:fluxengine_deb", diff --git a/MODULE.bazel b/MODULE.bazel index 163fc2d8..ffe9fb67 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -5,9 +5,6 @@ bazel_dep(name = "protobuf", version = "33.4", repo_name = "com_google_protobuf" http_archive = use_repo_rule("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -graalvm_ext = use_extension("//:graalvm_extension.bzl", "graalvm_ext") -use_repo(graalvm_ext, "graalvm") - maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") maven.install( artifacts = [ diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 86e94867..6c14c92b 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -188,19 +188,6 @@ }, "selectedYankedVersions": {}, "moduleExtensions": { - "//:graalvm_extension.bzl%graalvm_ext": { - "general": { - "bzlTransitiveDigest": "j3Qz7w1ruIOY8oFfCFAgWWjr/ev5O+1F8Jebvo5QHGo=", - "usagesDigest": "iUXd/3jCJaegO0Dllj33xgb8pqISEwNiOB/aoc6/sBQ=", - "recordedInputs": [], - "generatedRepoSpecs": { - "graalvm": { - "repoRuleId": "@@//:graalvm_repository.bzl%graalvm_repository", - "attributes": {} - } - } - } - }, "@@pybind11_bazel+//:internal_configure.bzl%internal_configure_extension": { "general": { "bzlTransitiveDigest": "NRXra7941UfmNUyIxnLt82V5hULluVGL2nBsijTl4j4=", diff --git a/Makefile b/Makefile index 7c71111a..f6b4469f 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all corpus all: bazel test //javatests/... - bazel build //:fluxengine //:fluxengine_native + bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm corpus: bazel test //:corpus diff --git a/graalvm_extension.bzl b/graalvm_extension.bzl deleted file mode 100644 index 5199881d..00000000 --- a/graalvm_extension.bzl +++ /dev/null @@ -1,8 +0,0 @@ -load("//:graalvm_repository.bzl", "graalvm_repository") - -def _graalvm_ext_impl(mctx): - graalvm_repository(name = "graalvm") - -graalvm_ext = module_extension( - implementation = _graalvm_ext_impl, -) diff --git a/graalvm_repository.bzl b/graalvm_repository.bzl deleted file mode 100644 index 328d2600..00000000 --- a/graalvm_repository.bzl +++ /dev/null @@ -1,74 +0,0 @@ -_GRAALVM_URLS = { - "linux_x86_64": { - "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_linux-x64_bin.tar.gz", - "strip_prefix": "graalvm-jdk-21.0.2+13.1", - }, - "linux_aarch64": { - "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_linux-aarch64_bin.tar.gz", - "strip_prefix": "graalvm-jdk-21.0.2+13.1", - }, - "macos_x86_64": { - "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_macos-x64_bin.tar.gz", - "strip_prefix": "graalvm-jdk-21.0.2+13.1/Contents/Home", - }, - "macos_aarch64": { - "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_macos-aarch64_bin.tar.gz", - "strip_prefix": "graalvm-jdk-21.0.2+13.1/Contents/Home", - }, - "windows_x86_64": { - "url": "https://download.oracle.com/graalvm/21/archive/graalvm-jdk-21.0.2_windows-x64_bin.zip", - "strip_prefix": "graalvm-jdk-21.0.2+13.1", - }, -} - -def _graalvm_repository_impl(ctx): - os_name = ctx.os.name.lower() - arch = ctx.os.arch.lower() - - if "mac" in os_name or "darwin" in os_name: - os_key = "macos" - elif "win" in os_name: - os_key = "windows" - else: - os_key = "linux" - - if arch in ["aarch64", "arm64"]: - arch_key = "aarch64" - else: - arch_key = "x86_64" - - key = "%s_%s" % (os_key, arch_key) - if key not in _GRAALVM_URLS: - fail("Unsupported platform for GraalVM: %s" % key) - - info = _GRAALVM_URLS[key] - - ctx.download_and_extract( - url = info["url"], - stripPrefix = info["strip_prefix"], - ) - - # Alias target points to .cmd on Windows, standard executable on Linux/macOS - launcher = "bin/native-image.cmd" if os_key == "windows" else "bin/native-image" - - ctx.file( - "BUILD.bazel", - """ -package(default_visibility = ["//visibility:public"]) -exports_files(glob(["**/*"])) - -alias( - name = "native_image_tool", - actual = "%s", -) - -filegroup( - name = "java_home", - srcs = glob(["**/*"]), -) -""" % launcher, - ) - -graalvm_repository = repository_rule( - implementation = _graalvm_repository_impl, -) diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 71b8ccec..b7634d2d 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,6 +1,5 @@ load("@rules_java//java:defs.bzl", "java_binary") load("//:jpackage.bzl", "jpackage_deb", "jpackage_rpm") -load("//:native_image.bzl", "native_image") package(default_visibility = ["//visibility:public"]) @@ -26,18 +25,3 @@ jpackage_rpm( package_name = "fluxengine", app_version = "1.0.0", ) - -native_image( - name = "fluxengine_native", - extra_args = [ - "--no-fallback", - "-O2", - "-H:IncludeResources=(javax.usb.properties|org/usb4java/.*/libusb4java\\..*|.*/libjSerialComm.*|.*/jSerialComm.dll)", - "-Djava.awt.headless=false", - ], - jar = ":fluxengine_deploy.jar", - jni_config = ["jni-config.json"], - reflection_config = ["reflect-config.json"], - resource_config = ["resource-config.json"], - serialization_config = ["serialization-config.json"], -) diff --git a/java/com/cowlark/fluxengine/jni-config.json b/java/com/cowlark/fluxengine/jni-config.json deleted file mode 100644 index d16fa69b..00000000 --- a/java/com/cowlark/fluxengine/jni-config.json +++ /dev/null @@ -1,267 +0,0 @@ -[ - { - "name": "[Lcom.fazecast.jSerialComm.SerialPort;" - }, - { - "name": "[Lorg.usb4java.EndpointDescriptor;" - }, - { - "name": "[Lorg.usb4java.Interface;" - }, - { - "name": "[Lorg.usb4java.InterfaceDescriptor;" - }, - { - "name": "com.fazecast.jSerialComm.SerialPort", - "fields": [ - { - "name": "autoFlushIOBuffers" - }, - { - "name": "baudRate" - }, - { - "name": "comPort" - }, - { - "name": "dataBits" - }, - { - "name": "disableConfig" - }, - { - "name": "disableExclusiveLock" - }, - { - "name": "eventFlags" - }, - { - "name": "eventListenerRunning" - }, - { - "name": "flowControl" - }, - { - "name": "friendlyName" - }, - { - "name": "isDtrEnabled" - }, - { - "name": "isRtsEnabled" - }, - { - "name": "manufacturer" - }, - { - "name": "parity" - }, - { - "name": "portDescription" - }, - { - "name": "portHandle" - }, - { - "name": "portLocation" - }, - { - "name": "productID" - }, - { - "name": "readTimeout" - }, - { - "name": "receiveDeviceQueueSize" - }, - { - "name": "requestElevatedPermissions" - }, - { - "name": "rs485ActiveHigh" - }, - { - "name": "rs485DelayAfter" - }, - { - "name": "rs485DelayBefore" - }, - { - "name": "rs485EnableTermination" - }, - { - "name": "rs485Mode" - }, - { - "name": "rs485ModeControlEnabled" - }, - { - "name": "rs485RxDuringTx" - }, - { - "name": "sendDeviceQueueSize" - }, - { - "name": "serialNumber" - }, - { - "name": "stopBits" - }, - { - "name": "timeoutMode" - }, - { - "name": "vendorID" - }, - { - "name": "writeTimeout" - }, - { - "name": "xoffStopChar" - }, - { - "name": "xonStartChar" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "java.lang.Exception" - }, - { - "name": "org.usb4java.ConfigDescriptor", - "fields": [ - { - "name": "configDescriptorPointer" - } - ] - }, - { - "name": "org.usb4java.Context", - "fields": [ - { - "name": "contextPointer" - } - ] - }, - { - "name": "org.usb4java.Device", - "fields": [ - { - "name": "devicePointer" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "org.usb4java.DeviceDescriptor", - "fields": [ - { - "name": "deviceDescriptorBuffer" - }, - { - "name": "deviceDescriptorPointer" - } - ] - }, - { - "name": "org.usb4java.DeviceHandle", - "fields": [ - { - "name": "deviceHandlePointer" - } - ] - }, - { - "name": "org.usb4java.DeviceList", - "fields": [ - { - "name": "deviceListPointer" - }, - { - "name": "size" - } - ] - }, - { - "name": "org.usb4java.EndpointDescriptor", - "fields": [ - { - "name": "endpointDescriptorPointer" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "org.usb4java.Interface", - "fields": [ - { - "name": "interfacePointer" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "org.usb4java.InterfaceDescriptor", - "fields": [ - { - "name": "interfaceDescriptorPointer" - } - ], - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "org.usb4java.LibUsb", - "methods": [ - { - "name": "hotplugCallback", - "parameterTypes": [ - "org.usb4java.Context", - "org.usb4java.Device", - "int", - "long" - ] - }, - { - "name": "triggerPollfdAdded", - "parameterTypes": [ - "java.io.FileDescriptor", - "int", - "long" - ] - }, - { - "name": "triggerPollfdRemoved", - "parameterTypes": [ - "java.io.FileDescriptor", - "long" - ] - } - ] - } -] diff --git a/java/com/cowlark/fluxengine/reflect-config.json b/java/com/cowlark/fluxengine/reflect-config.json deleted file mode 100644 index 868d0b8d..00000000 --- a/java/com/cowlark/fluxengine/reflect-config.json +++ /dev/null @@ -1,6880 +0,0 @@ -[ - { - "name": "com.cowlark.fluxengine.cli.TestDevicesCommand", - "allDeclaredFields": true, - "queryAllDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.gui.Gui", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "queryAllDeclaredMethods": true - }, - { - "name": "com.formdev.flatlaf.FlatLightLaf", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "queryAllDeclaredMethods": true - }, - { - "name": "com.formdev.flatlaf.UIDefaultsLoader", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "queryAllDeclaredMethods": true - }, - { - "name": "com.sun.javafx.tk.quantum.QuantumToolkit", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "queryAllDeclaredMethods": true - }, - { - "name": "groovy.lang.Closure" - }, - { - "name": "java.lang.Object", - "allDeclaredFields": true, - "queryAllDeclaredMethods": true - }, - { - "name": "java.nio.file.Path" - }, - { - "name": "java.nio.file.Paths", - "methods": [ - { - "name": "get", - "parameterTypes": [ - "java.lang.String", - "java.lang.String[]" - ] - } - ] - }, - { - "name": "java.security.SecureRandomParameters" - }, - { - "name": "java.sql.Connection" - }, - { - "name": "java.sql.Driver" - }, - { - "name": "java.sql.DriverManager", - "methods": [ - { - "name": "getConnection", - "parameterTypes": [ - "java.lang.String" - ] - }, - { - "name": "getDriver", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "name": "java.sql.Time", - "methods": [ - { - "name": "", - "parameterTypes": [ - "long" - ] - } - ] - }, - { - "name": "java.sql.Timestamp", - "methods": [ - { - "name": "valueOf", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "name": "java.time.Duration", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.Instant", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.LocalDate", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.LocalDateTime", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.LocalTime", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.MonthDay", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.OffsetDateTime", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.OffsetTime", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.Period", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.Year", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.YearMonth", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "java.time.ZoneId", - "methods": [ - { - "name": "of", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "name": "java.time.ZoneOffset", - "methods": [ - { - "name": "of", - "parameterTypes": [ - "java.lang.String" - ] - } - ] - }, - { - "name": "java.time.ZonedDateTime", - "methods": [ - { - "name": "parse", - "parameterTypes": [ - "java.lang.CharSequence" - ] - } - ] - }, - { - "name": "javax.usb.UsbHostManager" - }, - { - "name": "org.usb4java.javax.Services", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "sun.security.provider.NativePRNG", - "methods": [ - { - "name": "", - "parameterTypes": [] - }, - { - "name": "", - "parameterTypes": [ - "java.security.SecureRandomParameters" - ] - } - ] - }, - { - "name": "sun.security.provider.SHA", - "methods": [ - { - "name": "", - "parameterTypes": [] - } - ] - }, - { - "name": "com.cowlark.fluxengine.aeslanier.AesLanierDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.aeslanier.AesLanierDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.aeslanier.AesLanierDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.agat.AgatDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.agat.AgatDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.agat.AgatDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.agat.AgatEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.agat.AgatEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.agat.AgatEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.amiga.AmigaDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.amiga.AmigaDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.amiga.AmigaDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.amiga.AmigaEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.amiga.AmigaEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.amiga.AmigaEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.apple2.Apple2DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.apple2.Apple2DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.apple2.Apple2DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.apple2.Apple2EncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.apple2.Apple2EncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.apple2.Apple2EncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.BrotherDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.BrotherDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.BrotherDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.BrotherEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.BrotherEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.BrotherEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.c64.Commodore64DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.c64.Commodore64DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.c64.Commodore64DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.c64.Commodore64EncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.c64.Commodore64EncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.c64.Commodore64EncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ConfigProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ConfigProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ConfigProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.DriveProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.DriveProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.DriveProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.DriveProto$ErrorBehaviour", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.DriveProto$ErrorBehaviour$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto$LayoutdataProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto$LayoutdataProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto$LayoutdataProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto$Order", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.LayoutProto$Order$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionGroupProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionGroupProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionGroupProto$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionGroupProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionPrerequisiteProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionPrerequisiteProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionPrerequisiteProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionProto$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ProtoPath", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ProtoPath$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ProtoPath$PathComponent", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ProtoPathNotFoundException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.SectorListProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.SectorListProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.SectorListProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.DecoderProto$FormatCase", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.encoders.EncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.encoders.EncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.encoders.EncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.encoders.EncoderProto$FormatCase", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxFileProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxFileProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxFileProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.TrackFluxProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.TrackFluxProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.TrackFluxProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.f85.F85DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.f85.F85DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.f85.F85DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fb100.Fb100DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fb100.Fb100DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fb100.Fb100DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.A2RFluxSinkProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.A2RFluxSinkProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.A2RFluxSinkProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.AuFluxSinkProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.AuFluxSinkProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.AuFluxSinkProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.Fl2FluxSinkProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.Fl2FluxSinkProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.Fl2FluxSinkProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.FluxSinkProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.FluxSinkProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.FluxSinkProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.HardwareFluxSinkProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.HardwareFluxSinkProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.HardwareFluxSinkProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.ScpFluxSinkProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.ScpFluxSinkProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.ScpFluxSinkProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.VcdFluxSinkProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.VcdFluxSinkProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsink.VcdFluxSinkProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.A2rFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.A2rFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.A2rFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.CwfFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.CwfFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.CwfFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.DmkFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.DmkFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.DmkFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.EraseFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.EraseFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.EraseFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.Fl2FluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.Fl2FluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.Fl2FluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.FluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.FluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.FluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.FlxFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.FlxFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.FlxFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.HardwareFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.HardwareFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.HardwareFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.KryofluxFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.KryofluxFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.KryofluxFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.ScpFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.ScpFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.ScpFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.TestPatternFluxSourceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.TestPatternFluxSourceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.TestPatternFluxSourceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$TrackdataProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$TrackdataProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmDecoderProto$TrackdataProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$TrackdataProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$TrackdataProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.IbmEncoderProto$TrackdataProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.D64InputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.D64InputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.D64InputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.D88InputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.D88InputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.D88InputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.DimInputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.DimInputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.DimInputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.DiskCopyInputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.DiskCopyInputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.DiskCopyInputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.FdiInputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.FdiInputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.FdiInputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImageReaderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImageReaderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImageReaderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImdInputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImdInputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImdInputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImgInputOutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImgInputOutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.ImgInputOutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.Jv3InputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.Jv3InputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.Jv3InputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.NfdInputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.NfdInputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.NfdInputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.NsiInputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.NsiInputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.NsiInputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.Td0InputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.Td0InputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagereader.Td0InputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.D64OutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.D64OutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.D64OutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.D88OutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.D88OutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.D88OutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.DiskCopyOutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.DiskCopyOutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.DiskCopyOutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImageWriterProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImageWriterProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImageWriterProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$DataRate", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$DataRate$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$RecordingMode", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.ImdOutputProto$RecordingMode$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$DataRate", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$DataRate$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$RecordingMode", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.LDBSOutputProto$RecordingMode$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.NsiOutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.NsiOutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.NsiOutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.RawOutputProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.RawOutputProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.imagewriter.RawOutputProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.macintosh.MacintoshDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.macintosh.MacintoshDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.macintosh.MacintoshDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.macintosh.MacintoshEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.macintosh.MacintoshEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.macintosh.MacintoshEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$ChecksumType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$ChecksumType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$EccType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisDecoderProto$EccType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$EccType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.MicropolisEncoderProto$EccType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.mx.MxDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.mx.MxDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.mx.MxDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.northstar.NorthstarDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.northstar.NorthstarDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.northstar.NorthstarDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.northstar.NorthstarEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.northstar.NorthstarEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.northstar.NorthstarEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.rolandd20.RolandD20DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.rolandd20.RolandD20DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.rolandd20.RolandD20DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.smaky6.Smaky6DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.smaky6.Smaky6DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.smaky6.Smaky6DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tartu.TartuDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tartu.TartuDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tartu.TartuDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tartu.TartuEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tartu.TartuEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tartu.TartuEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tids990.Tids990DecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tids990.Tids990DecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tids990.Tids990DecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tids990.Tids990EncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tids990.Tids990EncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tids990.Tids990EncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.ApplesauceProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.ApplesauceProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.ApplesauceProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.GreaseweazleProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$BusType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.GreaseweazleProto$BusType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.UsbProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.UsbProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.UsbProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AcornDfsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$Flavour", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AcornDfsProto$Flavour$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AmigaFfsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AmigaFfsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AmigaFfsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AppledosProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AppledosProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.AppledosProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.Brother120FsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.Brother120FsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.Brother120FsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CbmfsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CbmfsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CbmfsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Location", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Location$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Location$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Padding", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Padding$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.CpmFsProto$Padding$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FatFsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FatFsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FatFsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FilesystemProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FilesystemProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FilesystemProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FilesystemProto$FilesystemType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.FilesystemProto$FilesystemType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.LifProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.LifProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.LifProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.MacHfsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.MacHfsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.MacHfsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.MicrodosProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.MicrodosProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.MicrodosProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.PhileProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.PhileProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.PhileProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ProdosProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ProdosProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ProdosProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.RolandFsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.RolandFsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.RolandFsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.Smaky6FsProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.Smaky6FsProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.Smaky6FsProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ZDosProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ZDosProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ZDosProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ZDosProto$Location", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ZDosProto$Location$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.ZDosProto$Location$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$TrackdataProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$TrackdataProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9kEncoderProto$TrackdataProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.zilogmcz.ZilogMczDecoderProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.zilogmcz.ZilogMczDecoderProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.zilogmcz.ZilogMczDecoderProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractMessage", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractMessage$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractMessage$BuilderParent", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractMessageLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractMessageLite$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractMessageLite$Builder$LimitedInputStream", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractMessageLite$InternalOneOfEnum", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractParser", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AbstractProtobufList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AllocatedBuffer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AllocatedBuffer$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AllocatedBuffer$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Android", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Any", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Any$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Any$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.AnyProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Api", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Api$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Api$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ApiProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ArrayDecoders", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ArrayDecoders$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ArrayDecoders$Registers", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryReader", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryReader$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryReader$SafeHeapReader", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryWriter$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryWriter$SafeDirectWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryWriter$SafeHeapWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryWriter$UnsafeDirectWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BinaryWriter$UnsafeHeapWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BlockingRpcChannel", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BlockingService", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BoolValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BoolValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BoolValue$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BooleanArrayList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BufferAllocator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BufferAllocator$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteBufferWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteOutput", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$AbstractByteIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$ArraysByteArrayCopier", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$BoundedByteString", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$ByteArrayCopier", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$ByteIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$CodedBuilder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$LeafByteString", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$LiteralByteString", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$NioByteString", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$NioByteString$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$Output", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ByteString$SystemByteArrayCopier", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BytesValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BytesValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.BytesValue$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CanIgnoreReturnValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CheckReturnValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream$ArrayDecoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream$IterableDirectByteBufferDecoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream$StreamDecoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream$StreamDecoder$RefillCallback", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream$StreamDecoder$SkippedDataSink", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStream$UnsafeDirectNioDecoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStreamReader", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedInputStreamReader$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$AbstractBufferedEncoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$ArrayEncoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$ByteOutputEncoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$HeapNioEncoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$OutOfSpaceException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$OutputStreamEncoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$SafeDirectNioEncoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStream$UnsafeDirectNioEncoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStreamWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CodedOutputStreamWriter$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.CompileTimeConstant", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DebugFormat", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DebugFormat$LazyDebugOutput", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory$3", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory$IsInitializedCheckAnalyzer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory$IsInitializedCheckAnalyzer$Node", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory$IsInitializedCheckAnalyzer$StronglyConnectedComponent", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorMessageInfoFactory$OneofState", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ExtensionRange$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$DescriptorProto$ReservedRange$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$Edition", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$Edition$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumDescriptorProto$EnumReservedRange$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumValueDescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$EnumValueOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$Declaration$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$VerificationState", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ExtensionRangeOptions$VerificationState$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnforceNamingStyle", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnforceNamingStyle$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnumType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$EnumType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$FieldPresence", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$FieldPresence$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$JsonFormat", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$JsonFormat$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$MessageEncoding", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$MessageEncoding$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$RepeatedFieldEncoding", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$RepeatedFieldEncoding$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$Utf8Validation", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$Utf8Validation$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$DefaultSymbolVisibility", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSet$VisibilityFeature$DefaultSymbolVisibility$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$FeatureSetEditionDefault", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$FeatureSetEditionDefault$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FeatureSetDefaults$FeatureSetEditionDefault$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Label", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Label$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Type", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldDescriptorProto$Type$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$CType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$CType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$EditionDefault", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$EditionDefault$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$EditionDefault$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$FeatureSupport", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$FeatureSupport$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$FeatureSupport$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$JSType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$JSType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionRetention", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionRetention$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionTargetType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FieldOptions$OptionTargetType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileDescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileDescriptorSet$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileOptions$OptimizeMode", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$FileOptions$OptimizeMode$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Semantic", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Annotation$Semantic$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$GeneratedCodeInfo$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MessageOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MessageOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MessageOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodDescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodOptions$IdempotencyLevel", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$MethodOptions$IdempotencyLevel$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$OneofDescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$OneofOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$OneofOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$OneofOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ServiceDescriptorProto$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ServiceOptions", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ServiceOptions$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$ServiceOptions$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SourceCodeInfo$Location$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SymbolVisibility", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$SymbolVisibility$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DescriptorProtos$UninterpretedOption$NamePart$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$Descriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$DescriptorValidationException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$EnumDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$EnumDescriptor$UnknownEnumValueReference", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$EnumValueDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$EnumValueDescriptor$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FieldDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FieldDescriptor$JavaType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FieldDescriptor$RedactionState", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FieldDescriptor$Type", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FileDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FileDescriptor$InternalDescriptorAssigner", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FileDescriptorTables", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FileDescriptorTables$PackageDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$FileDescriptorTables$SearchFilter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$GenericDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$MethodDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$OneofDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Descriptors$ServiceDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DiscardUnknownFieldsParser", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DiscardUnknownFieldsParser$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DoubleArrayList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DoubleValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DoubleValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DoubleValue$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Duration", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Duration$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Duration$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DurationProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DynamicMessage", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DynamicMessage$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DynamicMessage$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.DynamicMessage$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Empty", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Empty$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Empty$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.EmptyProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Enum", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Enum$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Enum$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.EnumValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.EnumValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.EnumValue$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExperimentalApi", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Extension", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Extension$ExtensionType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Extension$MessageType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistry", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistry$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistry$DescriptorIntPair", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistry$ExtensionInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistryFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistryLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistryLite$ExtensionClassHolder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionRegistryLite$ObjectIntPair", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionSchemaFull", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionSchemaFull$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionSchemaLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionSchemaLite$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ExtensionSchemas", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Field", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Field$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Field$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Field$Cardinality", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Field$Cardinality$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Field$Kind", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Field$Kind$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldInfo$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldInfo$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldMask", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldMask$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldMask$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldMaskProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldSet", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldSet$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldSet$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldSet$FieldDescriptorLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FieldType$Collection", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FloatArrayList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FloatValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FloatValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.FloatValue$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Generated", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedFile", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$Builder$BuilderParentImpl", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$CachedDescriptorRetriever", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtendableBuilder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$ExtensionSerializer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$ExtensionWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$FieldEntry", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$FieldEntryIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtendableMessage$NoOpExtensionSerializer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$ExtensionDescriptorRetriever", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$FieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$MapFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$OneofAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RealOneofAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedEnumFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedFieldAccessor$MethodInvoker", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedFieldAccessor$ReflectionInvoker", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$RepeatedMessageFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularEnumFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularFieldAccessor$MethodInvoker", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularFieldAccessor$ReflectionInvoker", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularMessageFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SingularStringFieldAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$FieldAccessorTable$SyntheticOneofAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$GeneratedExtension", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$GeneratedExtension$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessage$UnusedPrivateParameter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageInfoFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$DefaultInstanceBasedParser", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$ExtendableBuilder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$ExtendableMessage", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$ExtendableMessage$ExtensionWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$ExtensionDescriptor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$GeneratedExtension", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$MethodToInvoke", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageLite$SerializedForm", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$Builder$BuilderParentImpl", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$BuilderParent", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$ExtendableBuilder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$ExtendableMessage", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$ExtendableMessage$ExtensionWriter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$FieldAccessorTable", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratedMessageV3$UnusedPrivateParameter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratorNames", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.GeneratorNames$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.InlineMe", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Int32Value", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Int32Value$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Int32Value$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Int64Value", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Int64Value$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Int64Value$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.IntArrayList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$BooleanList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$DoubleList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$EnumLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$EnumLiteMap", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$EnumVerifier", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$FloatList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$IntList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$IntListAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$IntListAdapter$IntConverter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$ListAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$ListAdapter$Converter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$LongList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$MapAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$MapAdapter$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$MapAdapter$Converter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$MapAdapter$EntryAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$MapAdapter$IteratorAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$MapAdapter$SetAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Internal$ProtobufList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.InvalidProtocolBufferException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.InvalidProtocolBufferException$InvalidWireTypeException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.IterableByteBufferInputStream", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Java8Compatibility", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaEditionDefaults", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$NestInFileClass", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$NestInFileClassFeature$NestInFileClass$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$Utf8Validation", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaFeaturesProto$JavaFeatures$Utf8Validation$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.JavaType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyField", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyField$LazyEntry", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyField$LazyIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyFieldLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyStringArrayList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyStringArrayList$ByteArrayListView", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyStringArrayList$ByteStringListView", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LazyStringList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LegacyUnredactedTextFormat", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ListFieldSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ListFieldSchemaFull", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ListFieldSchemaLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ListFieldSchemas", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ListValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ListValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ListValue$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.LongArrayList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ManifestSchemaFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ManifestSchemaFactory$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ManifestSchemaFactory$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ManifestSchemaFactory$CompositeMessageInfoFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapEntry", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapEntry$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapEntry$Metadata", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapEntry$Metadata$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapEntryLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapEntryLite$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapEntryLite$Metadata", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField$Converter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField$ImmutableMessageConverter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField$MutabilityAwareMap", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField$MutabilityAwareMap$MutabilityAwareCollection", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField$MutabilityAwareMap$MutabilityAwareIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField$MutabilityAwareMap$MutabilityAwareSet", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapField$StorageMode", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldBuilder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldBuilder$Converter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldReflectionAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldSchemaFull", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldSchemaLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MapFieldSchemas", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Message", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Message$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageInfoFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageLite$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageLiteToString", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageReflection", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageReflection$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageReflection$BuilderAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageReflection$ExtensionAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageReflection$ExtensionBuilderAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageReflection$MergeTarget", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageReflection$MergeTarget$ContainerType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageSchema$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MessageSetSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Method", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Method$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Method$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Mixin", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Mixin$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Mixin$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MutabilityOracle", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.MutabilityOracle$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.NewInstanceSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.NewInstanceSchemaFull", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.NewInstanceSchemaLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.NewInstanceSchemas", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.NullValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.NullValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.OneofInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Option", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Option$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Option$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Parser", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.PrimitiveNonBoxingCollection", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ProtoSyntax", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Protobuf", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ProtobufArrayList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ProtobufToStringOutput", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ProtobufToStringOutput$OutputMode", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ProtocolMessageEnum", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ProtocolStringList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RawMessageInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Reader", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilder$BuilderExternalList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilder$MessageExternalList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilder$MessageOrBuilderExternalList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilderV3", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilderV3$BuilderExternalList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilderV3$MessageExternalList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RepeatedFieldBuilderV3$MessageOrBuilderExternalList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RopeByteString", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RopeByteString$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RopeByteString$Balancer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RopeByteString$PieceIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RopeByteString$RopeInputStream", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RpcCallback", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RpcChannel", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RpcController", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RpcUtil", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RpcUtil$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RpcUtil$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RpcUtil$AlreadyCalledException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RuntimeVersion", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RuntimeVersion$ProtobufRuntimeVersionException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.RuntimeVersion$RuntimeDomain", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Schema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SchemaFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SchemaUtil", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Service", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.ServiceException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SingleFieldBuilder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SingleFieldBuilderV3", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SmallSortedMap", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SmallSortedMap$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SmallSortedMap$DescendingEntryIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SmallSortedMap$DescendingEntrySet", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SmallSortedMap$Entry", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SmallSortedMap$EntryIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SmallSortedMap$EntrySet", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SourceContext", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SourceContext$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SourceContext$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.SourceContextProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.StringValue", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.StringValue$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.StringValue$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Struct", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Struct$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Struct$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Struct$Builder$FieldsConverter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Struct$FieldsDefaultEntryHolder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.StructProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.StructuralMessageInfo", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.StructuralMessageInfo$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Syntax", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Syntax$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$InvalidEscapeSequenceException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$ParseException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Parser", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Parser$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Parser$SingularOverwritePolicy", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Parser$UnknownField", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Parser$UnknownField$Type", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Printer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Printer$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Printer$FieldReporterLevel", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Printer$MapEntryAdapter", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$TextGenerator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$Tokenizer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormat$UnknownFieldParseException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormatEscaper", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormatEscaper$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormatEscaper$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormatEscaper$ByteSequence", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormatParseInfoTree", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormatParseInfoTree$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TextFormatParseLocation", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Timestamp", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Timestamp$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Timestamp$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TimestampProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Type", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Type$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Type$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TypeProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TypeRegistry", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TypeRegistry$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.TypeRegistry$EmptyTypeRegistryHolder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UInt32Value", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UInt32Value$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UInt32Value$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UInt64Value", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UInt64Value$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UInt64Value$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UninitializedMessageException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSet", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSet$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSet$Field", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSet$Field$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSet$Parser", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSetLite", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSetLiteSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnknownFieldSetSchema", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnmodifiableLazyStringList", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnmodifiableLazyStringList$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnmodifiableLazyStringList$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnredactedDebugFormatForTest", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnsafeByteOperations", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnsafeUtil", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnsafeUtil$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnsafeUtil$Android32MemoryAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnsafeUtil$Android64MemoryAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnsafeUtil$JvmMemoryAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.UnsafeUtil$MemoryAccessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Utf8", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Utf8$DecodeUtil", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Utf8$Processor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Utf8$SafeProcessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Utf8$UnpairedSurrogateException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Utf8$UnsafeProcessor", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Value", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Value$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Value$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Value$KindCase", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat$FieldType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat$FieldType$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat$FieldType$2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat$FieldType$3", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat$FieldType$4", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat$JavaType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WireFormat$Utf8Validation", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.WrappersProto", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Writer", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.Writer$FieldOrder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorRequest", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorRequest$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorRequest$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$Feature", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$Feature$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$File", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$File$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$CodeGeneratorResponse$File$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$Version", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$Version$1", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.google.protobuf.compiler.PluginProtos$Version$Builder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.aeslanier.Aeslanier", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.agat.Agat", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.amiga.Amiga", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.apple2.Apple2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.Brother", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.brother.BrotherFormat", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.c64.C64", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.Common", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.Config", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ConfigTools", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.Drive", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.FluxSourceSinkType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.ImageReaderWriterType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.IndexMode", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.Layout", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.OptionApplicabilityHint", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.SupportStatus", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.Decoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.Decoder$RecordType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.Decoders", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.decoders.FluxDecoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.encoders.Encoder", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.encoders.Encoders", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.A2R", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.Crc", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.DriveType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.Fl2", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$AnyFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$DebugFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$EraseFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$ErrorFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$FrameHeader", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$MeasureSpeedFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$ReadFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$SeekFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$SetDriveFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$SpeedFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$VersionFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$Voltages", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$VoltagesFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxEngine$WriteFrame", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxFileVersion", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FluxMagic", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FmMfm", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.FormatType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.GreaseweazleUtils", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.external.Scp", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.f85.F85", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fb100.Fb100", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.EmptyFluxSourceIterator", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.fluxsource.Fluxsource", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.ibm.Ibm", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.macintosh.Macintosh", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.micropolis.Micropolis", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.mx.Mx", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.northstar.Northstar", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.rolandd20.Rolandd20", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.smaky6.Smaky6", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tartu.Tartu", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.tids990.Tids990", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.ApplesauceUsbDevice", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.FluxEngineUsbDevice", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.GreaseweazleUsbDevice", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.GreaseweazleUsbDevice$Version", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.RetryableUsbException", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.Serial", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.Usb", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.UsbDevice", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.UsbFactory", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.VoltageMeasurements", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.usb.Voltages", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.vfs.Vfs", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.victor9k.Victor9K", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.zilogmcz.Zilogmcz", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - }, - { - "name": "com.cowlark.fluxengine.config.UsbFinder$DeviceType", - "allDeclaredConstructors": true, - "allDeclaredFields": true, - "allDeclaredMethods": true - } -] diff --git a/java/com/cowlark/fluxengine/resource-config.json b/java/com/cowlark/fluxengine/resource-config.json deleted file mode 100644 index e9eaa197..00000000 --- a/java/com/cowlark/fluxengine/resource-config.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "resources": { - "includes": [ - { - "pattern": "\\QMETA-INF/services/java.time.zone.ZoneRulesProvider\\E" - }, - { - "pattern": "\\Qjavax.usb.properties\\E" - }, - { - "pattern": "\\Qorg/usb4java/linux-x86-64/libusb4java.so\\E" - }, - { - "pattern": "\\Qcom/formdev/flatlaf/\\E.*" - }, - { - "pattern": "\\QMETA-INF/services/\\E.*" - }, - { - "pattern": "\\Qformats/\\E.*" - } - ] - }, - "bundles": [] -} diff --git a/java/com/cowlark/fluxengine/serialization-config.json b/java/com/cowlark/fluxengine/serialization-config.json deleted file mode 100644 index 681d9fd4..00000000 --- a/java/com/cowlark/fluxengine/serialization-config.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "types": [ - ], - "lambdaCapturingTypes": [ - ], - "proxies": [ - ] -} diff --git a/native_image.bzl b/native_image.bzl deleted file mode 100644 index b2539e17..00000000 --- a/native_image.bzl +++ /dev/null @@ -1,86 +0,0 @@ -def _native_image_impl(ctx): - is_windows = ctx.configuration.host_path_separator == ";" - - # 1. Ensure out_name ends with .exe on Windows (without duplicating it) - base_name = ctx.label.name - if is_windows and not base_name.lower().endswith(".exe"): - out_name = base_name + ".exe" - else: - out_name = base_name - - out_binary = ctx.actions.declare_file(out_name) - - # 2. Strip .exe for -H:Name on Windows because native-image auto-appends .exe on Windows - h_name_path = out_binary.path - if is_windows and h_name_path.lower().endswith(".exe"): - h_name_path = h_name_path[:-4] - - jar_file = ctx.file.jar - - inputs = [jar_file] - - args = ctx.actions.args() - args.add("-jar", jar_file.path) - args.add("-H:Name=" + h_name_path) - - sep = ctx.configuration.host_path_separator - - # 3. Pass GraalVM configuration files (JNI / reflection / resources / serialization) - # generated by the native-image tracing agent. - config_specs = [ - ("jni_config", "JNIConfigurationFiles"), - ("reflection_config", "ReflectionConfigurationFiles"), - ("resource_config", "ResourceConfigurationFiles"), - ("serialization_config", "SerializationConfigurationFiles"), - ] - - for attr_name, flag_name in config_specs: - files = getattr(ctx.files, attr_name) - if files: - inputs.extend(files) - args.add("-H:%s=%s" % (flag_name, sep.join([f.path for f in files]))) - - for extra_arg in ctx.attr.extra_args: - args.add(extra_arg) - - ctx.actions.run( - outputs = [out_binary], - inputs = inputs, - executable = ctx.executable._native_image_tool, - arguments = [args], - mnemonic = "GraalVMNativeImage", - progress_message = "Building GraalVM native image %s" % ctx.label.name, - use_default_shell_env = True, - ) - - return [DefaultInfo(executable = out_binary)] - -native_image = rule( - implementation = _native_image_impl, - executable = True, - attrs = { - "jar": attr.label( - mandatory = True, - allow_single_file = [".jar"], - ), - "jni_config": attr.label_list( - allow_files = [".json"], - ), - "reflection_config": attr.label_list( - allow_files = [".json"], - ), - "resource_config": attr.label_list( - allow_files = [".json"], - ), - "serialization_config": attr.label_list( - allow_files = [".json"], - ), - "extra_args": attr.string_list(default = []), - "_native_image_tool": attr.label( - default = Label("@graalvm//:native_image_tool"), - allow_single_file = True, - executable = True, - cfg = "exec", - ), - }, -) From 204547718db45671e54ac7afd0fcc9b8d50693b0 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 22:28:24 +0200 Subject: [PATCH 168/192] Add a simple appimage. --- AGENTS.md | 4 +- BUILD.bazel | 5 +++ Makefile | 2 +- java/com/cowlark/fluxengine/BUILD.bazel | 10 ++++- jpackage.bzl | 58 +++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 3 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 6574ecba..2c40148d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -24,6 +24,7 @@ Useful commands: - `bazel run //java/com/cowlark/fluxengine:fluxengine -- ` (JVM binary) - `bazel build //:fluxengine_deb //:fluxengine_rpm` (jpackage .deb/.rpm installers; root aliases `//:fluxengine`, `//:fluxengine_deb`, and `//:fluxengine_rpm` exist) +- `bazel build //:fluxengine_app_image` (jpackage app-image, produced as a tar file) ## Gotchas @@ -40,7 +41,8 @@ Useful commands: `jpackage_rpm` rules in `jpackage.bzl` (which use the configured Java toolchain's `jpackage`). Because `rpmbuild` writes to `/var/tmp` and read-only sandbox paths by default, the rules stage everything under a writable `workdir/` and, for rpm, point - rpmbuild's `_tmppath`/`_builddir` etc. at it via a `~/.rpmmacros` file. + rpmbuild's `_tmppath`/`_builddir` etc. at it via a `~/.rpmmacros` file. The + `jpackage_app_image` rule produces the raw app-image directory as a tar file. ## Lombok builders diff --git a/BUILD.bazel b/BUILD.bazel index ec86fd30..ce953284 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -18,6 +18,11 @@ alias( actual = "//java/com/cowlark/fluxengine:fluxengine_rpm", ) +alias( + name = "fluxengine_app_image", + actual = "//java/com/cowlark/fluxengine:fluxengine_app_image", +) + # Encode/decode round-trip tests, ported from the corpus tests in build.py. CORPUS_TESTS = define_corpus_tests() diff --git a/Makefile b/Makefile index f6b4469f..8efc1766 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all corpus all: bazel test //javatests/... - bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm + bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm //:fluxengine_app_image corpus: bazel test //:corpus diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index b7634d2d..3cc8f394 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,5 +1,5 @@ load("@rules_java//java:defs.bzl", "java_binary") -load("//:jpackage.bzl", "jpackage_deb", "jpackage_rpm") +load("//:jpackage.bzl", "jpackage_app_image", "jpackage_deb", "jpackage_rpm") package(default_visibility = ["//visibility:public"]) @@ -25,3 +25,11 @@ jpackage_rpm( package_name = "fluxengine", app_version = "1.0.0", ) + +jpackage_app_image( + name = "fluxengine_app_image", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.cli.Main", + package_name = "fluxengine", + app_version = "1.0.0", +) diff --git a/jpackage.bzl b/jpackage.bzl index cfa90453..0ff10635 100644 --- a/jpackage.bzl +++ b/jpackage.bzl @@ -74,6 +74,58 @@ EOF return [DefaultInfo(files = depset([out]))] +def _jpackage_app_image_impl(ctx): + # Locate jpackage via the configured Java toolchain's runtime, so the rule + # works with whatever JDK Bazel is using (e.g. remotejdk_21). + java_runtime = ctx.toolchains["@bazel_tools//tools/jdk:toolchain_type"].java.java_runtime + jpackage_path = java_runtime.java_home + "/bin/jpackage" + + jar = ctx.file.jar + out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + ".tar") + + # jpackage --type app-image writes a directory (with a jlink runtime image + # and the app launcher) and chmods files in it. Do the scratch work in a + # plain directory under the execroot (which is writable in the sandbox), + # then tar the result up. The sandbox input jar is a symlink to a read-only + # file, so dereference it (cp -L) and make the copy writable. + ctx.actions.run_shell( + outputs = [out], + inputs = [jar], + tools = [java_runtime.files], + use_default_shell_env = True, + command = """ + rm -rf workdir + mkdir -p workdir/input workdir/tmp workdir/dest workdir/home + cp -L "{jar}" workdir/input/ + chmod u+w workdir/input/* + TMPDIR="$(pwd)/workdir/tmp" + HOME="$(pwd)/workdir/home" + export TMPDIR HOME + "{jpackage}" -J-Djava.io.tmpdir="$(pwd)/workdir/tmp" --type app-image \ + --name "{name}" \ + --app-version "{app_version}" \ + --input "$(pwd)/workdir/input" \ + --main-jar "{main_jar}" \ + --main-class "{main_class}" \ + --dest "$(pwd)/workdir/dest" + tar cf "{out}" -C "$(pwd)/workdir/dest" "{name}" + rm -rf workdir + """.format( + jpackage = jpackage_path, + name = ctx.attr.name, + package_name = ctx.attr.package_name, + app_version = ctx.attr.app_version, + jar = jar.path, + main_jar = jar.basename, + main_class = ctx.attr.main_class, + out = out.path, + ), + mnemonic = "JpackageAppImage", + progress_message = "Building app image %s" % ctx.label.name, + ) + + return [DefaultInfo(files = depset([out]))] + _jpackage_attrs = { "jar": attr.label( mandatory = True, @@ -109,3 +161,9 @@ jpackage_rpm = rule( ), toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], ) + +jpackage_app_image = rule( + implementation = _jpackage_app_image_impl, + attrs = dict(_jpackage_attrs), + toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], +) From 81bd5e0588cf70e1089565101bfd162398266aea Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 22:41:17 +0200 Subject: [PATCH 169/192] Try to build an installer on OSX and Windows too. --- .github/workflows/ccpp.yml | 164 ++++++++++++------------ AGENTS.md | 19 ++- BUILD.bazel | 10 ++ MODULE.bazel | 1 + Makefile | 2 +- java/com/cowlark/fluxengine/BUILD.bazel | 36 +++++- jpackage.bzl | 51 +++++--- 7 files changed, 171 insertions(+), 112 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 574269ac..7ec8e52a 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -1,8 +1,8 @@ name: C/C++ CI -on: [push] +on: [ push ] -concurrency: +concurrency: group: environment-${{ github.head_ref }} cancel-in-progress: true @@ -12,100 +12,100 @@ jobs: strategy: matrix: variant: - - debian12 - - debian13 - - fedora42 - - fedora43 - - fedora43.nooptionaldeps + - debian12 + - debian13 + - fedora42 + - fedora43 + - fedora43.nooptionaldeps steps: - - uses: actions/checkout@v4 - with: - repository: 'davidgiven/fluxengine' - path: 'fluxengine' - - uses: actions/checkout@v4 - with: - repository: 'davidgiven/fluxengine-testdata' - path: 'fluxengine-testdata' - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.19.0 - - name: Build with Bazel - run: | - cd fluxengine - bazel test //... - bazel build //:all - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts - path: | - fluxengine/bazel-bin/** + - uses: actions/checkout@v4 + with: + repository: 'davidgiven/fluxengine' + path: 'fluxengine' + - uses: actions/checkout@v4 + with: + repository: 'davidgiven/fluxengine-testdata' + path: 'fluxengine-testdata' + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.19.0 + - name: Build with Bazel + run: | + cd fluxengine + bazel test //... + bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm //:fluxengine_appimage + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts + path: | + fluxengine/bazel-bin/** build-macos-current: strategy: matrix: - runs-on: [macos-15, macos-15-intel] + runs-on: [ macos-15, macos-15-intel ] runs-on: ${{ matrix.runs-on }} steps: - - uses: actions/checkout@v4 - with: - repository: 'davidgiven/fluxengine' - path: 'fluxengine' - - uses: actions/checkout@v4 - with: - repository: 'davidgiven/fluxengine-testdata' - path: 'fluxengine-testdata' - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.19.0 - - name: Build with Bazel - run: | - cd fluxengine - bazel test //... - bazel build //:all - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts.${{ runner.arch }} - path: | - fluxengine/bazel-bin/** + - uses: actions/checkout@v4 + with: + repository: 'davidgiven/fluxengine' + path: 'fluxengine' + - uses: actions/checkout@v4 + with: + repository: 'davidgiven/fluxengine-testdata' + path: 'fluxengine-testdata' + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.19.0 + - name: Build with Bazel + run: | + cd fluxengine + bazel test //... + bazel build //:fluxengine //:fluxengine_dmg + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts.${{ runner.arch }} + path: | + fluxengine/bazel-bin/** build-windows: runs-on: windows-latest steps: - - uses: actions/checkout@v4 - with: - repository: 'davidgiven/fluxengine' - path: 'fluxengine' + - uses: actions/checkout@v4 + with: + repository: 'davidgiven/fluxengine' + path: 'fluxengine' - - uses: actions/checkout@v4 - with: - repository: 'davidgiven/fluxengine-testdata' - path: 'fluxengine-testdata' + - uses: actions/checkout@v4 + with: + repository: 'davidgiven/fluxengine-testdata' + path: 'fluxengine-testdata' - - name: Setup Bazel - uses: bazel-contrib/setup-bazel@0.19.0 + - name: Setup Bazel + uses: bazel-contrib/setup-bazel@0.19.0 - - name: Set up MSVC Developer Environment - uses: ilammy/msvc-dev-cmd@v1 - with: - arch: x64 + - name: Set up MSVC Developer Environment + uses: ilammy/msvc-dev-cmd@v1 + with: + arch: x64 - - name: Build with Bazel - run: | - cd fluxengine - bazel test //... ` - --action_env=PATH ` - --action_env=INCLUDE ` - --action_env=LIB ` - --action_env=LIBPATH - bazel build //:all ` - --action_env=PATH ` - --action_env=INCLUDE ` - --action_env=LIB ` - --action_env=LIBPATH + - name: Build with Bazel + run: | + cd fluxengine + bazel test //... ` + --action_env=PATH ` + --action_env=INCLUDE ` + --action_env=LIB ` + --action_env=LIBPATH + bazel build //:fluxengine //:fluxengine_msi ` + --action_env=PATH ` + --action_env=INCLUDE ` + --action_env=LIB ` + --action_env=LIBPATH - - name: Upload build artifacts - uses: actions/upload-artifact@v4 - with: - name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts.windows - path: fluxengine/bazel-bin/** + - name: Upload build artifacts + uses: actions/upload-artifact@v4 + with: + name: ${{ github.event.repository.name }}.${{ github.sha }}.bazel.artifacts.windows + path: fluxengine/bazel-bin/** diff --git a/AGENTS.md b/AGENTS.md index 2c40148d..624341a1 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -25,6 +25,8 @@ Useful commands: - `bazel build //:fluxengine_deb //:fluxengine_rpm` (jpackage .deb/.rpm installers; root aliases `//:fluxengine`, `//:fluxengine_deb`, and `//:fluxengine_rpm` exist) - `bazel build //:fluxengine_app_image` (jpackage app-image, produced as a tar file) +- `bazel build //:fluxengine_msi //:fluxengine_dmg` (Windows MSI / macOS DMG installers, + only buildable on their native platforms) ## Gotchas @@ -37,12 +39,17 @@ Useful commands: resource (`resources = ["//java:javax.usb.properties"]`) by the usb library. Bazel's resource jarring strips the leading `java/`, so it lands at the jar root. Do not move it into the package directory. -- The `.deb` and `.rpm` installers are built with jpackage via the `jpackage_deb` and - `jpackage_rpm` rules in `jpackage.bzl` (which use the configured Java toolchain's - `jpackage`). Because `rpmbuild` writes to `/var/tmp` and read-only sandbox paths by - default, the rules stage everything under a writable `workdir/` and, for rpm, point - rpmbuild's `_tmppath`/`_builddir` etc. at it via a `~/.rpmmacros` file. The - `jpackage_app_image` rule produces the raw app-image directory as a tar file. +- The `.deb` and `.rpm` installers are built with jpackage via the `jpackage` rule in + `jpackage.bzl` (which uses the configured Java toolchain's `jpackage`). Because `rpmbuild` + writes to `/var/tmp` and read-only sandbox paths by default, the rule stages everything + under a writable `workdir/` and, for rpm, points rpmbuild's `_tmppath`/`_builddir` etc. at + it via a `~/.rpmmacros` file. The `jpackage_app_image` rule produces the raw app-image + directory as a tar file. +- The MSI (`//:fluxengine_msi`) and DMG (`//:fluxengine_dmg`) targets use `select()` to set + the jpackage `package_type` per platform (`@platforms//os:windows` → `msi`, + `@platforms//os:osx` → `dmg`); jpackage can't cross-compile, so on any other platform the + type is `unsupported`, which makes the rule produce an empty target (so `bazel build + //java/...` still works everywhere). ## Lombok builders diff --git a/BUILD.bazel b/BUILD.bazel index ce953284..bb4c73df 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -23,6 +23,16 @@ alias( actual = "//java/com/cowlark/fluxengine:fluxengine_app_image", ) +alias( + name = "fluxengine_msi", + actual = "//java/com/cowlark/fluxengine:fluxengine_msi", +) + +alias( + name = "fluxengine_dmg", + actual = "//java/com/cowlark/fluxengine:fluxengine_dmg", +) + # Encode/decode round-trip tests, ported from the corpus tests in build.py. CORPUS_TESTS = define_corpus_tests() diff --git a/MODULE.bazel b/MODULE.bazel index ffe9fb67..1a6867f5 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -1,3 +1,4 @@ +bazel_dep(name = "platforms", version = "0.0.11") bazel_dep(name = "rules_java", version = "9.1.0") bazel_dep(name = "rules_jvm_external", version = "6.7") bazel_dep(name = "rules_proto", version = "7.1.0") diff --git a/Makefile b/Makefile index 8efc1766..1a8c6ed2 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ .PHONY: all corpus all: bazel test //javatests/... - bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm //:fluxengine_app_image + bazel build //:fluxengine corpus: bazel test //:corpus diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 3cc8f394..d40ea571 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -1,5 +1,5 @@ load("@rules_java//java:defs.bzl", "java_binary") -load("//:jpackage.bzl", "jpackage_app_image", "jpackage_deb", "jpackage_rpm") +load("//:jpackage.bzl", "jpackage", "jpackage_app_image") package(default_visibility = ["//visibility:public"]) @@ -10,20 +10,50 @@ java_binary( runtime_deps = ["//java/com/cowlark/fluxengine/cli"], ) -jpackage_deb( +jpackage( name = "fluxengine_deb", jar = ":fluxengine_deploy.jar", main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + package_type = "deb", ) -jpackage_rpm( +jpackage( name = "fluxengine_rpm", jar = ":fluxengine_deploy.jar", main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + package_type = "rpm", +) + +# MSI and DMG installers can only be built on their native platforms +# (jpackage can't cross-compile), so select() picks the package type per +# platform; on other platforms it's "unsupported", which produces an empty +# target so `bazel build //java/...` still works everywhere. +jpackage( + name = "fluxengine_msi", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.cli.Main", + package_name = "fluxengine", + app_version = "1.0.0", + package_type = select({ + "@platforms//os:windows": "msi", + "//conditions:default": "unsupported", + }), +) + +jpackage( + name = "fluxengine_dmg", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.cli.Main", + package_name = "fluxengine", + app_version = "1.0.0", + package_type = select({ + "@platforms//os:osx": "dmg", + "//conditions:default": "unsupported", + }), ) jpackage_app_image( diff --git a/jpackage.bzl b/jpackage.bzl index 0ff10635..c5cd2b51 100644 --- a/jpackage.bzl +++ b/jpackage.bzl @@ -1,3 +1,5 @@ +_JPACKAGE_TYPES = ["deb", "rpm", "msi", "dmg", "unsupported"] + def _jpackage_impl(ctx): # Locate jpackage via the configured Java toolchain's runtime, so the rule # works with whatever JDK Bazel is using (e.g. remotejdk_21). @@ -5,7 +7,19 @@ def _jpackage_impl(ctx): jpackage_path = java_runtime.java_home + "/bin/jpackage" package_type = ctx.attr.package_type - extension = "deb" if package_type == "deb" else "rpm" + if package_type == "unsupported": + # Not the platform this installer targets (jpackage can't cross + # compile); produce an empty target so `bazel build //java/...` still + # works everywhere. Trying to actually use the output on the wrong + # platform will just find nothing. + return [DefaultInfo()] + + extension = { + "deb": "deb", + "rpm": "rpm", + "msi": "msi", + "dmg": "dmg", + }[package_type] jar = ctx.file.jar out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + "." + extension) @@ -48,7 +62,6 @@ EOF export TMPDIR HOME "{jpackage}" -J-Djava.io.tmpdir="$(pwd)/workdir/tmp" --type {package_type} \ --name "{name}" \ - --linux-package-name "{package_name}" \ --app-version "{app_version}" \ --input "$(pwd)/workdir/input" \ --main-jar "{main_jar}" \ @@ -61,7 +74,6 @@ EOF package_type = package_type, extension = extension, name = ctx.attr.name, - package_name = ctx.attr.package_name, app_version = ctx.attr.app_version, jar = jar.path, main_jar = jar.basename, @@ -113,7 +125,6 @@ def _jpackage_app_image_impl(ctx): """.format( jpackage = jpackage_path, name = ctx.attr.name, - package_name = ctx.attr.package_name, app_version = ctx.attr.app_version, jar = jar.path, main_jar = jar.basename, @@ -136,34 +147,34 @@ _jpackage_attrs = { ), "package_name": attr.string( mandatory = True, - doc = "The Linux package name; also used for the output filename.", + doc = "The package name; also used for the output filename.", ), "app_version": attr.string( mandatory = True, doc = "Application version, e.g. '1.0.0'.", ), -} - -jpackage_deb = rule( - implementation = _jpackage_impl, - attrs = dict( - _jpackage_attrs, - package_type = attr.string(default = "deb", values = ["deb", "rpm"]), + "package_type": attr.string( + mandatory = True, + values = _JPACKAGE_TYPES, + doc = "The jpackage package type: deb/rpm (Linux), msi (Windows), dmg (macOS). " + + "Use select() so this is only set to the matching platform.", ), - toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], -) +} -jpackage_rpm = rule( +jpackage = rule( implementation = _jpackage_impl, - attrs = dict( - _jpackage_attrs, - package_type = attr.string(default = "rpm", values = ["deb", "rpm"]), - ), + attrs = dict(_jpackage_attrs), toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], ) jpackage_app_image = rule( implementation = _jpackage_app_image_impl, - attrs = dict(_jpackage_attrs), + attrs = dict( + { + k: v + for k, v in _jpackage_attrs.items() + if k != "package_type" + }, + ), toolchains = ["@bazel_tools//tools/jdk:toolchain_type"], ) From a51e04fa273bc9b31fba7c09a3e33bddb99094ea Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 23:12:35 +0200 Subject: [PATCH 170/192] Try to make build on Windows. --- jpackage.bzl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/jpackage.bzl b/jpackage.bzl index c5cd2b51..3d2d4272 100644 --- a/jpackage.bzl +++ b/jpackage.bzl @@ -120,6 +120,10 @@ def _jpackage_app_image_impl(ctx): --main-jar "{main_jar}" \ --main-class "{main_class}" \ --dest "$(pwd)/workdir/dest" + # jpackage leaves the runtime files read-only (Windows sets the +R + # attribute), which makes tar fail with "Cannot open: Permission + # denied". Make everything writable before taring. + chmod -R u+w "$(pwd)/workdir/dest" tar cf "{out}" -C "$(pwd)/workdir/dest" "{name}" rm -rf workdir """.format( From 5b5d6582639d1743412a5d67c1e0b20eba3dcdee Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 23:17:11 +0200 Subject: [PATCH 171/192] Typo fix. --- .github/workflows/ccpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 7ec8e52a..7acddaf2 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -32,7 +32,7 @@ jobs: run: | cd fluxengine bazel test //... - bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm //:fluxengine_appimage + bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm //:fluxengine_app_image - name: Upload build artifacts uses: actions/upload-artifact@v4 with: From 4d3cfda887931fd8dfd65d58b702c3a59efa98f2 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 23:19:18 +0200 Subject: [PATCH 172/192] Make the packaging rules manual. --- BUILD.bazel | 5 +++++ java/com/cowlark/fluxengine/BUILD.bazel | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/BUILD.bazel b/BUILD.bazel index bb4c73df..88aa29d3 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -11,26 +11,31 @@ alias( alias( name = "fluxengine_deb", actual = "//java/com/cowlark/fluxengine:fluxengine_deb", + tags = ["manual"], ) alias( name = "fluxengine_rpm", actual = "//java/com/cowlark/fluxengine:fluxengine_rpm", + tags = ["manual"], ) alias( name = "fluxengine_app_image", actual = "//java/com/cowlark/fluxengine:fluxengine_app_image", + tags = ["manual"], ) alias( name = "fluxengine_msi", actual = "//java/com/cowlark/fluxengine:fluxengine_msi", + tags = ["manual"], ) alias( name = "fluxengine_dmg", actual = "//java/com/cowlark/fluxengine:fluxengine_dmg", + tags = ["manual"], ) # Encode/decode round-trip tests, ported from the corpus tests in build.py. diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index d40ea571..18cdd46f 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -17,6 +17,7 @@ jpackage( package_name = "fluxengine", app_version = "1.0.0", package_type = "deb", + tags = ["manual"], ) jpackage( @@ -26,6 +27,7 @@ jpackage( package_name = "fluxengine", app_version = "1.0.0", package_type = "rpm", + tags = ["manual"], ) # MSI and DMG installers can only be built on their native platforms @@ -42,6 +44,7 @@ jpackage( "@platforms//os:windows": "msi", "//conditions:default": "unsupported", }), + tags = ["manual"], ) jpackage( @@ -54,6 +57,7 @@ jpackage( "@platforms//os:osx": "dmg", "//conditions:default": "unsupported", }), + tags = ["manual"], ) jpackage_app_image( @@ -62,4 +66,5 @@ jpackage_app_image( main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + tags = ["manual"], ) From 809194fa0ddf376e87eb8029548895bcec42bb25 Mon Sep 17 00:00:00 2001 From: David Given Date: Tue, 11 Aug 2026 23:36:21 +0200 Subject: [PATCH 173/192] More build fixes. --- .github/workflows/ccpp.yml | 2 ++ java/com/cowlark/fluxengine/BUILD.bazel | 20 ++++++++++---------- java/com/cowlark/fluxengine/gui/Gui.java | 12 +++++++++++- jpackage.bzl | 6 ++++-- 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index 7acddaf2..f8213cf8 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -92,6 +92,8 @@ jobs: - name: Build with Bazel run: | + $ErrorActionPreference = 'Stop' + $PSNativeCommandUseErrorActionPreference = $true cd fluxengine bazel test //... ` --action_env=PATH ` diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 18cdd46f..0dd80cd5 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -12,20 +12,20 @@ java_binary( jpackage( name = "fluxengine_deb", - jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.gui.Gui", package_type = "deb", tags = ["manual"], ) jpackage( name = "fluxengine_rpm", - jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.gui.Gui", package_type = "rpm", tags = ["manual"], ) @@ -36,10 +36,10 @@ jpackage( # target so `bazel build //java/...` still works everywhere. jpackage( name = "fluxengine_msi", - jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.gui.Gui", package_type = select({ "@platforms//os:windows": "msi", "//conditions:default": "unsupported", @@ -49,10 +49,10 @@ jpackage( jpackage( name = "fluxengine_dmg", - jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.gui.Gui", package_type = select({ "@platforms//os:osx": "dmg", "//conditions:default": "unsupported", @@ -62,9 +62,9 @@ jpackage( jpackage_app_image( name = "fluxengine_app_image", - jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.cli.Main", package_name = "fluxengine", app_version = "1.0.0", + jar = ":fluxengine_deploy.jar", + main_class = "com.cowlark.fluxengine.gui.Gui", tags = ["manual"], ) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index 1a283479..e2264494 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -3,7 +3,6 @@ import com.formdev.flatlaf.FlatDarkLaf; import com.google.common.collect.ImmutableList; import javax.swing.JFrame; -import javax.swing.JLabel; import javax.swing.SwingUtilities; import javax.swing.UIManager; import javax.swing.WindowConstants; @@ -28,4 +27,15 @@ private static void createAndShowGui() frame.setLocationRelativeTo(null); frame.setVisible(true); } + + public static void main(String[] args) + { + try + { + new Gui().run(ImmutableList.copyOf(args)); + } catch (Exception e) + { + throw new RuntimeException(e); + } + } } diff --git a/jpackage.bzl b/jpackage.bzl index 3d2d4272..c7de38f3 100644 --- a/jpackage.bzl +++ b/jpackage.bzl @@ -66,6 +66,7 @@ EOF --input "$(pwd)/workdir/input" \ --main-jar "{main_jar}" \ --main-class "{main_class}" \ + --jlink-options "--strip-debug --no-header-files --no-man-pages --strip-native-commands" \ --dest "$(pwd)/workdir/dest" cp workdir/dest/*.{extension} "{out}" rm -rf workdir @@ -93,7 +94,7 @@ def _jpackage_app_image_impl(ctx): jpackage_path = java_runtime.java_home + "/bin/jpackage" jar = ctx.file.jar - out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + ".tar") + out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + ".tar.xz") # jpackage --type app-image writes a directory (with a jlink runtime image # and the app launcher) and chmods files in it. Do the scratch work in a @@ -119,12 +120,13 @@ def _jpackage_app_image_impl(ctx): --input "$(pwd)/workdir/input" \ --main-jar "{main_jar}" \ --main-class "{main_class}" \ + --jlink-options "--strip-debug --no-header-files --no-man-pages --strip-native-commands" \ --dest "$(pwd)/workdir/dest" # jpackage leaves the runtime files read-only (Windows sets the +R # attribute), which makes tar fail with "Cannot open: Permission # denied". Make everything writable before taring. chmod -R u+w "$(pwd)/workdir/dest" - tar cf "{out}" -C "$(pwd)/workdir/dest" "{name}" + tar cJf "{out}" -C "$(pwd)/workdir/dest" "{name}" rm -rf workdir """.format( jpackage = jpackage_path, From dbf589a9b7ea873c4ed657d082227160657f0c71 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 00:11:15 +0200 Subject: [PATCH 174/192] Rework the secondary launchers to be less bad. --- java/com/cowlark/fluxengine/BUILD.bazel | 15 +++++--- .../fluxengine/fluxengine-gui.properties | 1 + jpackage.bzl | 35 +++++++++++++++---- 3 files changed, 39 insertions(+), 12 deletions(-) create mode 100644 java/com/cowlark/fluxengine/fluxengine-gui.properties diff --git a/java/com/cowlark/fluxengine/BUILD.bazel b/java/com/cowlark/fluxengine/BUILD.bazel index 0dd80cd5..2834ffab 100644 --- a/java/com/cowlark/fluxengine/BUILD.bazel +++ b/java/com/cowlark/fluxengine/BUILD.bazel @@ -15,7 +15,8 @@ jpackage( package_name = "fluxengine", app_version = "1.0.0", jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.gui.Gui", + main_class = "com.cowlark.fluxengine.cli.Main", + extra_launchers = [":fluxengine-gui.properties"], package_type = "deb", tags = ["manual"], ) @@ -25,7 +26,8 @@ jpackage( package_name = "fluxengine", app_version = "1.0.0", jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.gui.Gui", + main_class = "com.cowlark.fluxengine.cli.Main", + extra_launchers = [":fluxengine-gui.properties"], package_type = "rpm", tags = ["manual"], ) @@ -39,7 +41,8 @@ jpackage( package_name = "fluxengine", app_version = "1.0.0", jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.gui.Gui", + main_class = "com.cowlark.fluxengine.cli.Main", + extra_launchers = [":fluxengine-gui.properties"], package_type = select({ "@platforms//os:windows": "msi", "//conditions:default": "unsupported", @@ -52,7 +55,8 @@ jpackage( package_name = "fluxengine", app_version = "1.0.0", jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.gui.Gui", + main_class = "com.cowlark.fluxengine.cli.Main", + extra_launchers = [":fluxengine-gui.properties"], package_type = select({ "@platforms//os:osx": "dmg", "//conditions:default": "unsupported", @@ -65,6 +69,7 @@ jpackage_app_image( package_name = "fluxengine", app_version = "1.0.0", jar = ":fluxengine_deploy.jar", - main_class = "com.cowlark.fluxengine.gui.Gui", + main_class = "com.cowlark.fluxengine.cli.Main", + extra_launchers = [":fluxengine-gui.properties"], tags = ["manual"], ) diff --git a/java/com/cowlark/fluxengine/fluxengine-gui.properties b/java/com/cowlark/fluxengine/fluxengine-gui.properties new file mode 100644 index 00000000..aef5a7e7 --- /dev/null +++ b/java/com/cowlark/fluxengine/fluxengine-gui.properties @@ -0,0 +1 @@ +main-class=com.cowlark.fluxengine.gui.Gui diff --git a/jpackage.bzl b/jpackage.bzl index c7de38f3..b8411d51 100644 --- a/jpackage.bzl +++ b/jpackage.bzl @@ -1,5 +1,15 @@ _JPACKAGE_TYPES = ["deb", "rpm", "msi", "dmg", "unsupported"] +def _add_launcher_args(files): + args = [] + for f in files: + name = f.basename + if name.endswith(".properties"): + name = name[: -len(".properties")] + args.append("--add-launcher %s=%s" % (name, f.path)) + return " ".join(args) + + def _jpackage_impl(ctx): # Locate jpackage via the configured Java toolchain's runtime, so the rule # works with whatever JDK Bazel is using (e.g. remotejdk_21). @@ -23,6 +33,7 @@ def _jpackage_impl(ctx): jar = ctx.file.jar out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + "." + extension) + extra_launchers = ctx.files.extra_launchers # jpackage writes a lot of scratch state (a jlink runtime image and an app # image) and chmods files in it. Do all the scratch work in a plain @@ -36,7 +47,7 @@ def _jpackage_impl(ctx): # scratch dir via a ~/.rpmmacros file. ctx.actions.run_shell( outputs = [out], - inputs = [jar], + inputs = [jar] + extra_launchers, tools = [java_runtime.files], use_default_shell_env = True, command = """ @@ -61,11 +72,12 @@ EOF HOME="$(pwd)/workdir/home" export TMPDIR HOME "{jpackage}" -J-Djava.io.tmpdir="$(pwd)/workdir/tmp" --type {package_type} \ - --name "{name}" \ + --name "{package_name}" \ --app-version "{app_version}" \ --input "$(pwd)/workdir/input" \ --main-jar "{main_jar}" \ --main-class "{main_class}" \ + {add_launcher_args} \ --jlink-options "--strip-debug --no-header-files --no-man-pages --strip-native-commands" \ --dest "$(pwd)/workdir/dest" cp workdir/dest/*.{extension} "{out}" @@ -74,11 +86,12 @@ EOF jpackage = jpackage_path, package_type = package_type, extension = extension, - name = ctx.attr.name, + package_name = ctx.attr.package_name, app_version = ctx.attr.app_version, jar = jar.path, main_jar = jar.basename, main_class = ctx.attr.main_class, + add_launcher_args = _add_launcher_args(extra_launchers), out = out.path, ), mnemonic = "Jpackage" + package_type.title(), @@ -95,6 +108,7 @@ def _jpackage_app_image_impl(ctx): jar = ctx.file.jar out = ctx.actions.declare_file(ctx.attr.package_name + "_" + ctx.attr.app_version + ".tar.xz") + extra_launchers = ctx.files.extra_launchers # jpackage --type app-image writes a directory (with a jlink runtime image # and the app launcher) and chmods files in it. Do the scratch work in a @@ -103,7 +117,7 @@ def _jpackage_app_image_impl(ctx): # file, so dereference it (cp -L) and make the copy writable. ctx.actions.run_shell( outputs = [out], - inputs = [jar], + inputs = [jar] + extra_launchers, tools = [java_runtime.files], use_default_shell_env = True, command = """ @@ -115,26 +129,28 @@ def _jpackage_app_image_impl(ctx): HOME="$(pwd)/workdir/home" export TMPDIR HOME "{jpackage}" -J-Djava.io.tmpdir="$(pwd)/workdir/tmp" --type app-image \ - --name "{name}" \ + --name "{package_name}" \ --app-version "{app_version}" \ --input "$(pwd)/workdir/input" \ --main-jar "{main_jar}" \ --main-class "{main_class}" \ + {add_launcher_args} \ --jlink-options "--strip-debug --no-header-files --no-man-pages --strip-native-commands" \ --dest "$(pwd)/workdir/dest" # jpackage leaves the runtime files read-only (Windows sets the +R # attribute), which makes tar fail with "Cannot open: Permission # denied". Make everything writable before taring. chmod -R u+w "$(pwd)/workdir/dest" - tar cJf "{out}" -C "$(pwd)/workdir/dest" "{name}" + tar cJf "{out}" -C "$(pwd)/workdir/dest" "{package_name}" rm -rf workdir """.format( jpackage = jpackage_path, - name = ctx.attr.name, + package_name = ctx.attr.package_name, app_version = ctx.attr.app_version, jar = jar.path, main_jar = jar.basename, main_class = ctx.attr.main_class, + add_launcher_args = _add_launcher_args(extra_launchers), out = out.path, ), mnemonic = "JpackageAppImage", @@ -151,6 +167,11 @@ _jpackage_attrs = { "main_class": attr.string( mandatory = True, ), + "extra_launchers": attr.label_list( + allow_files = [".properties"], + doc = "jpackage launcher properties files for additional launchers; " + + "each launcher is named after the file (minus its .properties suffix).", + ), "package_name": attr.string( mandatory = True, doc = "The package name; also used for the output filename.", From 2cfd81d9ffbeb13e6e73d18e8031a59ab2ddfd7c Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 00:25:32 +0200 Subject: [PATCH 175/192] Make tests verbose. --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index f8213cf8..a6638ea8 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -31,7 +31,7 @@ jobs: - name: Build with Bazel run: | cd fluxengine - bazel test //... + bazel test //... --test_output=all bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm //:fluxengine_app_image - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -59,7 +59,7 @@ jobs: - name: Build with Bazel run: | cd fluxengine - bazel test //... + bazel test //... --test_output=all bazel build //:fluxengine //:fluxengine_dmg - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -95,7 +95,7 @@ jobs: $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true cd fluxengine - bazel test //... ` + bazel test //... --test_output=all ` --action_env=PATH ` --action_env=INCLUDE ` --action_env=LIB ` From 77a08537ea23339bf5fb9edf515eb5e325a54b35 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 00:36:08 +0200 Subject: [PATCH 176/192] Don't interleave test output. --- .github/workflows/ccpp.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ccpp.yml b/.github/workflows/ccpp.yml index a6638ea8..57874ca8 100644 --- a/.github/workflows/ccpp.yml +++ b/.github/workflows/ccpp.yml @@ -31,7 +31,7 @@ jobs: - name: Build with Bazel run: | cd fluxengine - bazel test //... --test_output=all + bazel test //... --test_output=errors bazel build //:fluxengine //:fluxengine_deb //:fluxengine_rpm //:fluxengine_app_image - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -59,7 +59,7 @@ jobs: - name: Build with Bazel run: | cd fluxengine - bazel test //... --test_output=all + bazel test //... --test_output=errors bazel build //:fluxengine //:fluxengine_dmg - name: Upload build artifacts uses: actions/upload-artifact@v4 @@ -95,7 +95,7 @@ jobs: $ErrorActionPreference = 'Stop' $PSNativeCommandUseErrorActionPreference = $true cd fluxengine - bazel test //... --test_output=all ` + bazel test //... --test_output=errors ` --action_env=PATH ` --action_env=INCLUDE ` --action_env=LIB ` From 2484d8799e89f9d99ccb9d5e2f5c66c25732e1b7 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 00:39:49 +0200 Subject: [PATCH 177/192] Hopefully fix line ending issues. --- java/com/cowlark/fluxengine/core/LogRenderer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/java/com/cowlark/fluxengine/core/LogRenderer.java b/java/com/cowlark/fluxengine/core/LogRenderer.java index bfd884ba..c7adbdf2 100644 --- a/java/com/cowlark/fluxengine/core/LogRenderer.java +++ b/java/com/cowlark/fluxengine/core/LogRenderer.java @@ -64,7 +64,7 @@ public LogRenderer add(String message) lineLen += message.length(); if (lineLen >= 80) { - stream.println(); + stream.print('\n'); indent(); } stream.print(message); @@ -77,7 +77,7 @@ public LogRenderer add(String message) public LogRenderer header(String message) { if (!newline) - stream.println(); + stream.print('\n'); stream.print(message); lineLen = message.length(); header = true; @@ -104,7 +104,7 @@ public LogRenderer newline() if (!header) { if (!newline) - stream.println(); + stream.print('\n'); lineLen = 0; header = false; From 3885cc5187ba771c46b069cf700e01b94fb07887 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 00:45:55 +0200 Subject: [PATCH 178/192] Add the rxjava integration. --- MODULE.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/MODULE.bazel b/MODULE.bazel index 1a6867f5..b3b5000b 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -20,6 +20,7 @@ maven.install( "org.usb4java:usb4java:1.3.0", "org.usb4java:usb4java-javax:1.3.0", "com.formdev:flatlaf:3.0", + "io.reactivex.rxjava3:rxjava:3.1.10", ], repositories = [ "https://repo1.maven.org/maven2", From c3f1823886a3ca6f8c521d2684df0d59d07db6d2 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 00:46:04 +0200 Subject: [PATCH 179/192] Start refactoring algorithms to use rxjava. --- .../cowlark/fluxengine/algorithms/BUILD.bazel | 1 + .../algorithms/FluxOperationFactory.java | 53 +++++ .../algorithms/TrackReadLogMessage.java | 28 +-- .../cowlark/fluxengine/buildtools/BUILD.bazel | 1 + .../buildtools/EncodeDecodeTest.java | 4 + java/com/cowlark/fluxengine/cli/Main.java | 4 + java/com/cowlark/fluxengine/core/Logger.java | 15 +- .../cowlark/fluxengine/algorithms/BUILD.bazel | 14 ++ .../algorithms/FluxOperationFactoryTest.java | 189 ++++++++++++++++++ .../algorithms/WriteOperationTest.java | 4 + .../fluxengine/arch/ArchEncoderTest.java | 4 + .../com/cowlark/fluxengine/arch/BUILD.bazel | 1 + .../arch/amiga/AmigaEncoderTest.java | 4 + .../cowlark/fluxengine/arch/amiga/BUILD.bazel | 1 + .../com/cowlark/fluxengine/config/BUILD.bazel | 1 + .../fluxengine/config/ConfigBuilderTest.java | 4 + .../cowlark/fluxengine/encoders/BUILD.bazel | 1 + .../fluxengine/encoders/EncoderTest.java | 4 + .../cowlark/fluxengine/fluxsink/BUILD.bazel | 2 + .../fluxengine/fluxsink/Fl2FluxSinkTest.java | 4 + .../fluxengine/fluxsink/FluxSinkTest.java | 4 + .../fluxsource/A2RFluxSourceTest.java | 4 + .../cowlark/fluxengine/fluxsource/BUILD.bazel | 5 + .../fluxsource/Fl2FluxSourceTest.java | 4 + .../fluxsource/HardwareFluxSourceTest.java | 4 + .../fluxsource/KryofluxFluxSourceTest.java | 4 + .../fluxsource/ScpFluxSourceTest.java | 4 + .../fluxengine/imagereader/BUILD.bazel | 1 + .../imagereader/ImageReaderTest.java | 4 + .../fluxengine/imagewriter/BUILD.bazel | 1 + .../imagewriter/ImageWriterTest.java | 4 + .../cowlark/fluxengine/testing/BUILD.bazel | 6 +- .../fluxengine/testing/LoggerRule.java | 41 ++++ .../fluxengine/testing/TestHelpers.java | 15 +- .../com/cowlark/fluxengine/usb/BUILD.bazel | 1 + .../fluxengine/usb/UsbFactoryTest.java | 4 + 36 files changed, 427 insertions(+), 18 deletions(-) create mode 100644 java/com/cowlark/fluxengine/algorithms/FluxOperationFactory.java create mode 100644 javatests/com/cowlark/fluxengine/algorithms/FluxOperationFactoryTest.java create mode 100644 javatests/com/cowlark/fluxengine/testing/LoggerRule.java diff --git a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel index 5dd64690..2b62a0f8 100644 --- a/java/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/java/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -21,6 +21,7 @@ java_library( "//java/com/cowlark/fluxengine/usb", "@com_google_protobuf//java/core", "@maven//:com_google_guava_guava", + "@maven//:io_reactivex_rxjava3_rxjava", "@maven//:org_apache_commons_commons_lang3", ], ) diff --git a/java/com/cowlark/fluxengine/algorithms/FluxOperationFactory.java b/java/com/cowlark/fluxengine/algorithms/FluxOperationFactory.java new file mode 100644 index 00000000..254488b4 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/FluxOperationFactory.java @@ -0,0 +1,53 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.Logger; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.schedulers.Schedulers; +import io.reactivex.rxjava3.subjects.PublishSubject; +import java.util.function.Consumer; + +/** + * Runs an operation once on its own worker thread, multicasting its log + * messages to all subscribers via a {@link PublishSubject}. + */ +public abstract class FluxOperationFactory implements Runnable +{ + /* Serialises all operations across the whole program: only one may run at + * a time, because the hardware doesn't cope with concurrent access. */ + private static final Object lock = new Object(); + + protected FluxOperationFactory() + { + } + + /* Runs the given operation on its own fresh worker thread, forwarding the + * messages it logs to all subscribers of the returned Observable. */ + public Observable create() + { + PublishSubject subject = PublishSubject.create(); + + Schedulers.newThread().scheduleDirect(() -> { + synchronized (lock) + { + Consumer oldLogger = Logger.getLogger(); + Logger.setLogger(subject::onNext); + try + { + run(); + subject.onComplete(); + } catch (Throwable t) + { + subject.onError(t); + } finally + { + Logger.setLogger(oldLogger); + } + } + }); + + return subject; + } + + public abstract void run(); +} diff --git a/java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java b/java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java index a18462da..fd22dbad 100644 --- a/java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java +++ b/java/com/cowlark/fluxengine/algorithms/TrackReadLogMessage.java @@ -6,6 +6,7 @@ import com.cowlark.fluxengine.data.Sector; import com.cowlark.fluxengine.data.Track; import java.util.Collections; +import java.util.HashSet; import java.util.IdentityHashMap; import java.util.List; import java.util.Set; @@ -14,15 +15,13 @@ * We've just read a track (we might reread it if there are errors), ported * from lib/algorithms/readerwriter.cc. */ -public record TrackReadLogMessage(List tracks, List sectors) - implements LogMessage +public record TrackReadLogMessage(List tracks, List sectors) implements LogMessage { @Override public void render(LogRenderer r) { - /* The C++ dedupes these by pointer, so use identity. */ - Set rawSectors = Collections.newSetFromMap(new IdentityHashMap<>()); - Set rawRecords = Collections.newSetFromMap(new IdentityHashMap<>()); + Set rawSectors = new HashSet<>(); + Set rawRecords = new HashSet<>(); for (Track track : tracks) { rawSectors.addAll(track.allSectors); @@ -35,18 +34,23 @@ public void render(LogRenderer r) if (!rawSectors.isEmpty()) clock /= rawSectors.size(); - r.comma().add(String.format("%d raw records, %d raw sectors", - rawRecords.size(), - rawSectors.size())); + r.comma() + .add(String.format( + "%d raw records, %d raw sectors", + rawRecords.size(), + rawSectors.size())); if (clock != 0) - r.comma().add(String.format("%.2fus clock (%.0fkHz)", - clock / 1000.0, - 1000000.0 / clock)); + r.comma() + .add(String.format( + "%.2fus clock (%.0fkHz)", + clock / 1000.0, + 1000000.0 / clock)); r.newline().add("sectors:"); for (Sector sector : rawSectors) - r.add(String.format("%d.%d.%d%s", + r.add(String.format( + "%d.%d.%d%s", sector.location.logicalCylinder(), sector.location.logicalHead(), sector.location.logicalSector(), diff --git a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel index a2acc687..929d6bde 100644 --- a/java/com/cowlark/fluxengine/buildtools/BUILD.bazel +++ b/java/com/cowlark/fluxengine/buildtools/BUILD.bazel @@ -25,6 +25,7 @@ java_library( srcs = ["EncodeDecodeTest.java"], deps = [ "//java/com/cowlark/fluxengine/cli", + "//java/com/cowlark/fluxengine/core", "@maven//:com_google_guava_guava", ], ) diff --git a/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java b/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java index 4a69db18..3830cd03 100644 --- a/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java +++ b/java/com/cowlark/fluxengine/buildtools/EncodeDecodeTest.java @@ -3,6 +3,8 @@ import com.cowlark.fluxengine.cli.Command; import com.cowlark.fluxengine.cli.ReadCommand; import com.cowlark.fluxengine.cli.WriteCommand; +import com.cowlark.fluxengine.core.LogRenderer; +import com.cowlark.fluxengine.core.Logger; import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.RandomAccessFile; @@ -26,6 +28,8 @@ public class EncodeDecodeTest { public static void main(String[] args) throws Exception { + Logger.setLogger(LogRenderer.create(System.out)::add); + String format = args[0]; String ext = args[1]; ImmutableList flags = diff --git a/java/com/cowlark/fluxengine/cli/Main.java b/java/com/cowlark/fluxengine/cli/Main.java index 96f64297..2799659c 100644 --- a/java/com/cowlark/fluxengine/cli/Main.java +++ b/java/com/cowlark/fluxengine/cli/Main.java @@ -1,5 +1,7 @@ package com.cowlark.fluxengine.cli; +import com.cowlark.fluxengine.core.LogRenderer; +import com.cowlark.fluxengine.core.Logger; import com.google.common.collect.ImmutableList; import java.util.Map; import java.util.function.Supplier; @@ -18,6 +20,8 @@ private Main() public static void main(String[] args) { + Logger.setLogger(LogRenderer.create(System.out)::add); + if (args.length == 0 || args[0].equals("--help")) { help(Command.COMMANDS, " [...]"); diff --git a/java/com/cowlark/fluxengine/core/Logger.java b/java/com/cowlark/fluxengine/core/Logger.java index 388f6965..5d732a60 100644 --- a/java/com/cowlark/fluxengine/core/Logger.java +++ b/java/com/cowlark/fluxengine/core/Logger.java @@ -8,8 +8,10 @@ */ public final class Logger { - private static Consumer loggerImpl = - new DefaultLogRenderer(System.out)::add; + private static final ThreadLocal> loggerImpl = + ThreadLocal.withInitial(() -> message -> { + throw new IllegalStateException("logging from a thread with no logger set"); + }); private Logger() { @@ -22,11 +24,16 @@ public static void logf(String message, Object... args) public static void log(LogMessage message) { - loggerImpl.accept(message); + loggerImpl.get().accept(message); } public static void setLogger(Consumer callback) { - loggerImpl = callback; + loggerImpl.set(callback); + } + + public static Consumer getLogger() + { + return loggerImpl.get(); } } diff --git a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel index 1c9e2302..073b5f8e 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -20,6 +20,7 @@ java_test( name = "WriteOperationTest", srcs = ["WriteOperationTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/algorithms", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", @@ -30,6 +31,19 @@ java_test( ], ) +java_test( + name = "FluxOperationFactoryTest", + srcs = ["FluxOperationFactoryTest.java"], + deps = [ + "//javatests/com/cowlark/fluxengine/testing", + "//java/com/cowlark/fluxengine/algorithms", + "//java/com/cowlark/fluxengine/core", + "@maven//:io_reactivex_rxjava3_rxjava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + ], +) + java_test( name = "CommonTest", srcs = ["CommonTest.java"], diff --git a/javatests/com/cowlark/fluxengine/algorithms/FluxOperationFactoryTest.java b/javatests/com/cowlark/fluxengine/algorithms/FluxOperationFactoryTest.java new file mode 100644 index 00000000..aa2b5e16 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/algorithms/FluxOperationFactoryTest.java @@ -0,0 +1,189 @@ +package com.cowlark.fluxengine.algorithms; + +import static com.google.common.truth.Truth.assertThat; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogMessage.StringMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.testing.TestHelpers; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.disposables.Disposable; +import io.reactivex.rxjava3.schedulers.Schedulers; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class FluxOperationFactoryTest +{ + @Rule public final TestRule loggerRule = TestHelpers.loggerRule(); + + /* A harness whose run() blocks on a semaphore until the test releases it, + * then logs a message. */ + private static class Harness extends FluxOperationFactory + { + final Semaphore gate = new Semaphore(0); + final CountDownLatch started = new CountDownLatch(1); + final CountDownLatch finished = new CountDownLatch(1); + volatile Thread runThread; + + @Override + public void run() + { + runThread = Thread.currentThread(); + started.countDown(); + try + { + gate.acquire(); + } catch (InterruptedException e) + { + throw new RuntimeException(e); + } + Logger.log(new StringMessage("hello")); + finished.countDown(); + } + } + + @Test + public void multipleSubscribersSeeSameOperation() throws Exception + { + Harness harness = new Harness(); + Observable observable = harness.create(); + + List first = new ArrayList<>(); + List second = new ArrayList<>(); + CountDownLatch done = new CountDownLatch(2); + Disposable firstSubscription = observable.subscribe(m -> { + synchronized (first) + { + first.add(m); + } + }, t -> { + }, done::countDown); + Disposable secondSubscription = observable.subscribe(m -> { + synchronized (second) + { + second.add(m); + } + }, t -> { + }, done::countDown); + + harness.gate.release(); + + assertThat(done.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(first).containsExactly(new StringMessage("hello")); + assertThat(second).containsExactly(new StringMessage("hello")); + } + + @Test + public void consecutiveOperationsRunOnDifferentThreads() throws Exception + { + Harness first = new Harness(); + first.create().subscribe(); + first.gate.release(); + + Harness second = new Harness(); + second.create().subscribe(); + second.gate.release(); + + Harness third = new Harness(); + third.create().subscribe(); + third.gate.release(); + + assertThat(first.finished.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(second.finished.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(third.finished.await(5, TimeUnit.SECONDS)).isTrue(); + + /* Each operation gets its own fresh worker thread, not the test + * thread, and no two operations share a thread. */ + assertThat(first.runThread).isNotEqualTo(Thread.currentThread()); + assertThat(second.runThread).isNotEqualTo(first.runThread); + assertThat(third.runThread).isNotEqualTo(first.runThread); + assertThat(third.runThread).isNotEqualTo(second.runThread); + } + + @Test + public void operationsStartedAtSameTimeAreSerialised() throws Exception + { + Harness first = new Harness(); + Harness second = new Harness(); + first.create().subscribe(); + second.create().subscribe(); + + try + { + /* The first operation starts immediately. */ + assertThat(first.started.await(5, TimeUnit.SECONDS)).isTrue(); + + /* Only one operation may run at a time: the second must wait + * until the first has finished. */ + assertThat(second.started.await(100, TimeUnit.MILLISECONDS)).isFalse(); + + /* Releasing the first operation lets the second run, on its own + * thread. */ + first.gate.release(); + assertThat(second.started.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(second.runThread).isNotEqualTo(first.runThread); + + second.gate.release(); + assertThat(first.finished.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(second.finished.await(5, TimeUnit.SECONDS)).isTrue(); + } finally + { + /* Always release both gates so a failed assertion doesn't leave + * worker threads blocked. */ + first.gate.release(); + second.gate.release(); + } + } + + @Test + public void failingOperationDeliversErrorAndCleansUpLogger() throws Exception + { + FluxOperationFactory failing = new FluxOperationFactory() + { + @Override + public void run() + { + throw new RuntimeException("boom"); + } + }; + + List errors = new ArrayList<>(); + CountDownLatch done = new CountDownLatch(1); + Disposable subscription = failing.create().subscribe(m -> { + }, t -> { + errors.add(t); + done.countDown(); + }, done::countDown); + + assertThat(done.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(errors).hasSize(1); + + /* A fresh worker thread must not inherit the failed operation's + * logger; the default (unset) logger throws. */ + AtomicBoolean loggerThrows = new AtomicBoolean(); + CountDownLatch probed = new CountDownLatch(1); + Schedulers.newThread().scheduleDirect(() -> { + try + { + Logger.log(new StringMessage("probe")); + } catch (IllegalStateException e) + { + loggerThrows.set(true); + } + probed.countDown(); + }); + + assertThat(probed.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(loggerThrows.get()).isTrue(); + } +} diff --git a/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java b/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java index 421d200b..78b3f523 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java @@ -12,6 +12,10 @@ @RunWith(JUnit4.class) public class WriteOperationTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private static ConfigProto makeConfig() { return new ConfigBuilder().set("usb.serial", "test-serial") diff --git a/javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java b/javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java index 6bd79288..95e8bbe6 100644 --- a/javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java +++ b/javatests/com/cowlark/fluxengine/arch/ArchEncoderTest.java @@ -14,6 +14,10 @@ @RunWith(JUnit4.class) public class ArchEncoderTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + @Test public void noEncoderConfiguredThrows() { diff --git a/javatests/com/cowlark/fluxengine/arch/BUILD.bazel b/javatests/com/cowlark/fluxengine/arch/BUILD.bazel index dc977b59..44c6493a 100644 --- a/javatests/com/cowlark/fluxengine/arch/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/arch/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "ArchEncoderTest", srcs = ["ArchEncoderTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/arch", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java index 42be4a3a..eed0f136 100644 --- a/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java +++ b/javatests/com/cowlark/fluxengine/arch/amiga/AmigaEncoderTest.java @@ -19,6 +19,10 @@ @RunWith(JUnit4.class) public class AmigaEncoderTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private ConfigProto makeConfig() { return new ConfigBuilder() diff --git a/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel b/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel index bf6ff0e1..f15e6282 100644 --- a/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/arch/amiga/BUILD.bazel @@ -17,6 +17,7 @@ java_test( name = "AmigaEncoderTest", srcs = ["AmigaEncoderTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/arch", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/javatests/com/cowlark/fluxengine/config/BUILD.bazel b/javatests/com/cowlark/fluxengine/config/BUILD.bazel index d82aba50..63d1865f 100644 --- a/javatests/com/cowlark/fluxengine/config/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/config/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "ConfigBuilderTest", srcs = ["ConfigBuilderTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index 2cf7a15e..76152f58 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -16,6 +16,10 @@ @RunWith(JUnit4.class) public class ConfigBuilderTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + /* ConfigBuilder defaults to a drive flux source, which makes build() * select a USB device; stub the serial so no hardware is needed. */ private static ConfigBuilder builder() diff --git a/javatests/com/cowlark/fluxengine/encoders/BUILD.bazel b/javatests/com/cowlark/fluxengine/encoders/BUILD.bazel index 468e7963..8936984e 100644 --- a/javatests/com/cowlark/fluxengine/encoders/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/encoders/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "EncoderTest", srcs = ["EncoderTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", diff --git a/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java b/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java index bbfd5d79..d6378800 100644 --- a/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java +++ b/javatests/com/cowlark/fluxengine/encoders/EncoderTest.java @@ -21,6 +21,10 @@ @RunWith(JUnit4.class) public class EncoderTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private static final class TestEncoder extends Encoder { TestEncoder(double diskRotationalPeriodNs) diff --git a/javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel index 5552d3da..d41343f7 100644 --- a/javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsink/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "Fl2FluxSinkTest", srcs = ["Fl2FluxSinkTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", @@ -23,6 +24,7 @@ java_test( name = "FluxSinkTest", srcs = ["FluxSinkTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", diff --git a/javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java b/javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java index a30ed962..aaff1377 100644 --- a/javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsink/Fl2FluxSinkTest.java @@ -18,6 +18,10 @@ @RunWith(JUnit4.class) public class Fl2FluxSinkTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private static ConfigProto makeConfig() { return new ConfigBuilder() diff --git a/javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java b/javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java index 0f4d44c3..651f2b39 100644 --- a/javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsink/FluxSinkTest.java @@ -20,6 +20,10 @@ @RunWith(JUnit4.class) public class FluxSinkTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private static ConfigProto makeConfig() { return new ConfigBuilder() diff --git a/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java index 599a3741..8172fc7a 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/A2RFluxSourceTest.java @@ -17,6 +17,10 @@ @RunWith(JUnit4.class) public class A2RFluxSourceTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + /* Builds an A2R file containing a single track 0/0, encoded as a 3.5" * disk with two short intervals. */ private static Path writeTempFile() throws IOException diff --git a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel index 3c247b53..8debf4fe 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/fluxsource/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "A2RFluxSourceTest", srcs = ["A2RFluxSourceTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", @@ -23,6 +24,7 @@ java_test( name = "Fl2FluxSourceTest", srcs = ["Fl2FluxSourceTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", @@ -40,6 +42,7 @@ java_test( name = "ScpFluxSourceTest", srcs = ["ScpFluxSourceTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", @@ -58,6 +61,7 @@ java_test( name = "HardwareFluxSourceTest", srcs = ["HardwareFluxSourceTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", @@ -75,6 +79,7 @@ java_test( name = "KryofluxFluxSourceTest", srcs = ["KryofluxFluxSourceTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", diff --git a/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java index 6579961c..12616214 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/Fl2FluxSourceTest.java @@ -18,6 +18,10 @@ @RunWith(JUnit4.class) public class Fl2FluxSourceTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private static Path writeTemp(FluxFileProto file) throws IOException { Path path = Files.createTempFile("flux", ".fl2"); diff --git a/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java index c65f24c2..f0b826d5 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/HardwareFluxSourceTest.java @@ -15,6 +15,10 @@ @RunWith(JUnit4.class) public class HardwareFluxSourceTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private static class FakeUsbDevice extends UsbDevice { int seekedTo = -1; diff --git a/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java index ed1a97fe..2f0462b2 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/KryofluxFluxSourceTest.java @@ -16,6 +16,10 @@ @RunWith(JUnit4.class) public class KryofluxFluxSourceTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + @Rule public TemporaryFolder folder = new TemporaryFolder(); @Test diff --git a/javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java b/javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java index a8527d55..a3e5a1e7 100644 --- a/javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java +++ b/javatests/com/cowlark/fluxengine/fluxsource/ScpFluxSourceTest.java @@ -18,6 +18,10 @@ @RunWith(JUnit4.class) public class ScpFluxSourceTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + /* Builds an SCP file containing a single track 0/0 (strack 0), encoded * with two intervals of 100 and 200 at a 25ns resolution. */ private static Path writeTempFile() throws IOException diff --git a/javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel b/javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel index 6beb8c90..dca1091d 100644 --- a/javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/imagereader/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "ImageReaderTest", srcs = ["ImageReaderTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java b/javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java index 54cf2574..3de31c9b 100644 --- a/javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java +++ b/javatests/com/cowlark/fluxengine/imagereader/ImageReaderTest.java @@ -19,6 +19,10 @@ @RunWith(JUnit4.class) public class ImageReaderTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + @Test public void createD64ImageReader() { diff --git a/javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel b/javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel index fbc4b879..49548f64 100644 --- a/javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/imagewriter/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "ImageWriterTest", srcs = ["ImageWriterTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:common_java_proto", "//java/com/cowlark/fluxengine/config:config_java_proto", diff --git a/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java b/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java index 01e200e4..7a30a3d1 100644 --- a/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java +++ b/javatests/com/cowlark/fluxengine/imagewriter/ImageWriterTest.java @@ -20,6 +20,10 @@ @RunWith(JUnit4.class) public class ImageWriterTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + @Test public void createUnportedTypeThrows() { diff --git a/javatests/com/cowlark/fluxengine/testing/BUILD.bazel b/javatests/com/cowlark/fluxengine/testing/BUILD.bazel index 67449081..e9762b50 100644 --- a/javatests/com/cowlark/fluxengine/testing/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/testing/BUILD.bazel @@ -4,8 +4,12 @@ package(default_visibility = ["//visibility:public"]) java_library( name = "testing", - srcs = ["TestHelpers.java"], + srcs = [ + "LoggerRule.java", + "TestHelpers.java", + ], deps = [ "//java/com/cowlark/fluxengine/core", + "@maven//:junit_junit", ], ) diff --git a/javatests/com/cowlark/fluxengine/testing/LoggerRule.java b/javatests/com/cowlark/fluxengine/testing/LoggerRule.java new file mode 100644 index 00000000..e6129312 --- /dev/null +++ b/javatests/com/cowlark/fluxengine/testing/LoggerRule.java @@ -0,0 +1,41 @@ +package com.cowlark.fluxengine.testing; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; +import com.cowlark.fluxengine.core.Logger; +import java.util.function.Consumer; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +/** + * A JUnit rule which installs a stdout-rendering logger for the thread running + * the test, and restores the previous logger afterwards. Attach it with: + * + *

+ * @Rule public final TestRule loggerRule = new LoggerRule();
+ * 
+ */ +public final class LoggerRule implements TestRule +{ + @Override + public Statement apply(Statement base, Description description) + { + return new Statement() + { + @Override + public void evaluate() throws Throwable + { + Consumer oldLogger = Logger.getLogger(); + Logger.setLogger(LogRenderer.create(System.out)::add); + try + { + base.evaluate(); + } finally + { + Logger.setLogger(oldLogger); + } + } + }; + } +} diff --git a/javatests/com/cowlark/fluxengine/testing/TestHelpers.java b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java index d663b85b..309d7db0 100644 --- a/javatests/com/cowlark/fluxengine/testing/TestHelpers.java +++ b/javatests/com/cowlark/fluxengine/testing/TestHelpers.java @@ -1,5 +1,18 @@ package com.cowlark.fluxengine.testing; -public class TestHelpers +import org.junit.rules.TestRule; + +/** + * A convenience wrapper for creating a {@link LoggerRule}. + */ +public final class TestHelpers { + private TestHelpers() + { + } + + public static TestRule loggerRule() + { + return new LoggerRule(); + } } diff --git a/javatests/com/cowlark/fluxengine/usb/BUILD.bazel b/javatests/com/cowlark/fluxengine/usb/BUILD.bazel index 798df9ae..ffccf985 100644 --- a/javatests/com/cowlark/fluxengine/usb/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/usb/BUILD.bazel @@ -6,6 +6,7 @@ java_test( name = "UsbFactoryTest", srcs = ["UsbFactoryTest.java"], deps = [ + "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", diff --git a/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java b/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java index e83f96a9..7e13cd99 100644 --- a/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java +++ b/javatests/com/cowlark/fluxengine/usb/UsbFactoryTest.java @@ -12,6 +12,10 @@ @RunWith(JUnit4.class) public class UsbFactoryTest { + @org.junit.Rule + public final org.junit.rules.TestRule loggerRule = + com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); + private static class FakeUsbDevice extends UsbDevice { int closed = 0; From 86a273bbe2c50fcd3bcc55e48866a8c3a7a9b8da Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 23:15:27 +0200 Subject: [PATCH 180/192] Refactor to use RxJava for passing log messages out of workers. --- ...ationFactory.java => FluxRxOperation.java} | 50 +- .../algorithms/RawWriteOperation.java | 114 --- .../fluxengine/algorithms/ReadOperation.java | 408 --------- .../algorithms/ReadWriteFluxRxOperation.java | 795 ++++++++++++++++++ .../fluxengine/algorithms/WriteOperation.java | 184 ---- .../fluxengine/cli/RawwriteCommand.java | 34 +- .../cowlark/fluxengine/cli/ReadCommand.java | 29 +- .../cowlark/fluxengine/cli/WriteCommand.java | 63 +- .../fluxengine/core/DefaultLogRenderer.java | 90 -- .../cowlark/fluxengine/algorithms/BUILD.bazel | 23 +- ...toryTest.java => FluxRxOperationTest.java} | 137 ++- ...java => ReadWriteFluxRxOperationTest.java} | 90 +- .../algorithms/WriteOperationTest.java | 73 -- 13 files changed, 1097 insertions(+), 993 deletions(-) rename java/com/cowlark/fluxengine/algorithms/{FluxOperationFactory.java => FluxRxOperation.java} (54%) delete mode 100644 java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java delete mode 100644 java/com/cowlark/fluxengine/algorithms/ReadOperation.java create mode 100644 java/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperation.java delete mode 100644 java/com/cowlark/fluxengine/algorithms/WriteOperation.java delete mode 100644 java/com/cowlark/fluxengine/core/DefaultLogRenderer.java rename javatests/com/cowlark/fluxengine/algorithms/{FluxOperationFactoryTest.java => FluxRxOperationTest.java} (64%) rename javatests/com/cowlark/fluxengine/algorithms/{ReadOperationTest.java => ReadWriteFluxRxOperationTest.java} (64%) delete mode 100644 javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java diff --git a/java/com/cowlark/fluxengine/algorithms/FluxOperationFactory.java b/java/com/cowlark/fluxengine/algorithms/FluxRxOperation.java similarity index 54% rename from java/com/cowlark/fluxengine/algorithms/FluxOperationFactory.java rename to java/com/cowlark/fluxengine/algorithms/FluxRxOperation.java index 254488b4..3e9b27af 100644 --- a/java/com/cowlark/fluxengine/algorithms/FluxOperationFactory.java +++ b/java/com/cowlark/fluxengine/algorithms/FluxRxOperation.java @@ -1,5 +1,6 @@ package com.cowlark.fluxengine.algorithms; +import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.LogMessage; import com.cowlark.fluxengine.core.Logger; import io.reactivex.rxjava3.core.Observable; @@ -11,18 +12,34 @@ * Runs an operation once on its own worker thread, multicasting its log * messages to all subscribers via a {@link PublishSubject}. */ -public abstract class FluxOperationFactory implements Runnable +public abstract class FluxRxOperation> implements Runnable { /* Serialises all operations across the whole program: only one may run at * a time, because the hardware doesn't cope with concurrent access. */ private static final Object lock = new Object(); - protected FluxOperationFactory() + protected ConfigProto configProto = null; + private boolean disposed = false; + + protected FluxRxOperation() + { + } + + public FluxRxOperation setConfig(ConfigProto config) { + this.configProto = config; + return this; + } + + public ConfigProto getConfig() + { + return configProto; } /* Runs the given operation on its own fresh worker thread, forwarding the - * messages it logs to all subscribers of the returned Observable. */ + * messages it logs to all subscribers of the returned Observable. The + * factory is disposed when the returned Observable terminates, so that any + * AutoCloseable resources it holds are released. */ public Observable create() { PublishSubject subject = PublishSubject.create(); @@ -34,6 +51,7 @@ public Observable create() Logger.setLogger(subject::onNext); try { + init(); run(); subject.onComplete(); } catch (Throwable t) @@ -46,7 +64,31 @@ public Observable create() } }); - return subject; + return Observable.using(() -> this, op -> subject, op -> op.dispose()); + } + + /* Disposes the factory, releasing any AutoCloseable resources it holds. + * Safe to call multiple times; only the first call has any effect. */ + public void dispose() + { + boolean wasDisposed; + synchronized (this) + { + wasDisposed = disposed; + disposed = true; + } + if (!wasDisposed) + onDispose(); + } + + /* Hook for subclasses to close their AutoCloseable resources. Called at + * most once, by dispose(). */ + protected void onDispose() + { + } + + public void init() + { } public abstract void run(); diff --git a/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java b/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java deleted file mode 100644 index 583ea193..00000000 --- a/java/com/cowlark/fluxengine/algorithms/RawWriteOperation.java +++ /dev/null @@ -1,114 +0,0 @@ -package com.cowlark.fluxengine.algorithms; - -import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.FluxEngineException; -import com.cowlark.fluxengine.core.LogMessage; -import com.cowlark.fluxengine.core.Logger; -import com.cowlark.fluxengine.data.CylinderHead; -import com.cowlark.fluxengine.data.Fluxmap; -import com.cowlark.fluxengine.data.LogicalTrackLayout; -import com.cowlark.fluxengine.fluxsink.FluxSink; -import com.cowlark.fluxengine.fluxsource.FluxReadParameters; -import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; - -public class RawWriteOperation extends ReadOperation -{ - public RawWriteOperation(ConfigProto configProto) - { - super(configProto); - } - - private void writeTracks(Function producer, - Predicate verifier, - List logicalLocations) - { - Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); - - getDiskRotationalPeriodNs(); - try (FluxSink fluxSink = getFluxSinkFactory().create()) - { - int index = 0; - for (CylinderHead ch : logicalLocations) - { - Logger.log(new LogMessage.OperationProgressLogMessage( - index * 100 / logicalLocations.size())); - index++; - - Common.testForEmergencyStop(); - - LogicalTrackLayout ltl = getDiskLayout().layoutByLogicalLocation.get(ch); - int retriesRemaining = getConfig().getDecoder().getRetries(); - for (; ; ) - { - for (int offset = 0; offset < ltl.groupSize; - offset += getDiskLayout().headWidth) - { - int physicalCylinder = ltl.physicalCylinder + offset; - int physicalHead = ltl.physicalHead; - - Logger.log(new LogMessage.BeginWriteOperationLogMessage( - physicalCylinder, - ltl.physicalHead)); - - boolean erase = false; - if (offset == getConfig().getDrive().getGroupOffset()) - { - Fluxmap fluxmap = producer.apply(ltl); - if (fluxmap == null) - erase = true; - else - { - fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); - Logger.logf( - "writing %d ms in %d bytes", - (int) (fluxmap.durationNs() / 1e6), - fluxmap.bytes()); - } - } else - erase = true; - - if (erase) - { - /* Erase this track rather than writing. */ - - Fluxmap blank = new Fluxmap(); - fluxSink.addFlux(physicalCylinder, physicalHead, blank); - Logger.logf("erased"); - } - - Logger.log(new LogMessage.EndWriteOperationLogMessage()); - } - - if (verifier.test(ltl)) - break; - - if (retriesRemaining == 0) - throw new FluxEngineException("fatal error on write"); - - Logger.logf("retrying; %d retries remaining", retriesRemaining); - retriesRemaining--; - } - } - } - - Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); - } - - public void rawWrite() - { - writeTracks( - ltl -> { - FluxSourceIterator iterator = - getFluxSource().readFlux(FluxReadParameters.builder() - .setCylinder(ltl.physicalCylinder) - .setHead(ltl.physicalHead) - .build()); - if (!iterator.hasNext()) - return null; - return iterator.next(); - }, ltl -> true, getDiskLayout().logicalLocations); - } -} diff --git a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadOperation.java deleted file mode 100644 index d2492d36..00000000 --- a/java/com/cowlark/fluxengine/algorithms/ReadOperation.java +++ /dev/null @@ -1,408 +0,0 @@ -package com.cowlark.fluxengine.algorithms; - -import com.cowlark.fluxengine.algorithms.Common.FluxSourceIteratorHolder; -import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.LogMessage.BeginOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.EndOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.EndReadOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.OperationProgressLogMessage; -import com.cowlark.fluxengine.core.Logger; -import com.cowlark.fluxengine.core.Utils; -import com.cowlark.fluxengine.data.CylinderHead; -import com.cowlark.fluxengine.data.Disk; -import com.cowlark.fluxengine.data.Fluxmap; -import com.cowlark.fluxengine.data.Image; -import com.cowlark.fluxengine.data.LogicalLocation; -import com.cowlark.fluxengine.data.LogicalTrackLayout; -import com.cowlark.fluxengine.data.PhysicalTrackLayout; -import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.data.Track; -import com.cowlark.fluxengine.fluxsink.FluxSink; -import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; -import com.cowlark.fluxengine.fluxsource.FluxReadParameters; -import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; -import com.cowlark.fluxengine.imagewriter.ImageWriter; -import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Disk read/write algorithms, ported from lib/algorithms/readerwriter.cc. - */ -public class ReadOperation extends Operation -{ - public ReadOperation(ConfigProto configProto) - { - super(configProto); - } - - - enum ReadResult - { - GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY - } - - enum BadSectorsState - { - HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS - } - - static class CombinationResult - { - BadSectorsState result; - List sectors; - } - - static class ReadGroupResult - { - ReadResult result; - List combinedSectors; - } - - static CombinationResult combineRecordAndSectors(List tracks, LogicalTrackLayout ltl) - { - CombinationResult cr = new CombinationResult(); - cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; - List trackSectors = new ArrayList<>(); - - /* Add the sectors which were there. */ - - for (Track track : tracks) - trackSectors.addAll(track.allSectors); - - /* Add the sectors which should be there. */ - - for (int sectorId : ltl.diskSectorOrder) - { - Sector sector = - new Sector(new LogicalLocation(ltl.logicalCylinder, ltl.logicalHead, sectorId)); - - sector.status = Sector.Status.MISSING; - sector.physicalLocation = new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); - trackSectors.add(sector); - } - - /* Deduplicate. */ - - cr.sectors = collectSectors(trackSectors); - if (cr.sectors.isEmpty()) - cr.result = BadSectorsState.HAS_BAD_SECTORS; - for (Sector sector : cr.sectors) - if (sector.status != Sector.Status.OK) - cr.result = BadSectorsState.HAS_BAD_SECTORS; - - return cr; - } - - protected ReadGroupResult readGroup(FluxSourceIteratorHolder fluxSourceIteratorHolder, - LogicalTrackLayout ltl, - List tracks) - { - ReadGroupResult rgr = new ReadGroupResult(); - rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; - - /* Before doing the read, look to see if we already have the necessary - * sectors. */ - - { - CombinationResult cr = combineRecordAndSectors(tracks, ltl); - rgr.combinedSectors = cr.sectors; - if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) - { - /* We have all necessary sectors, so can stop here. */ - rgr.result = ReadResult.GOOD_READ; - if (getConfig().getDecoder().getSkipUnnecessaryTracks()) - return rgr; - } - } - - for (int offset = 0; offset < ltl.groupSize; offset += getDiskLayout().headWidth) - { - int physicalCylinder = ltl.physicalCylinder + offset; - int physicalHead = ltl.physicalHead; - PhysicalTrackLayout ptl = getDiskLayout().layoutByPhysicalLocation.get(new CylinderHead( - physicalCylinder, - physicalHead)); - - /* Do the physical read. */ - - Logger.log(new BeginReadOperationLogMessage(physicalCylinder, physicalHead)); - - FluxSourceIterator fluxSourceIterator = - fluxSourceIteratorHolder.getIterator(FluxReadParameters.builder() - .setCylinder(physicalCylinder) - .setHead(physicalHead) - .setSyncWithIndex(getConfig().getDrive().getSyncWithIndex()) - .setReadTimeNs(getConfig().getDrive().getRevolutions() * - getDiskRotationalPeriodNs()) - .setHardSectorThresholdNs(getConfig().getDrive() - .getHardSectorThresholdNs()) - .build()); - if (!fluxSourceIterator.hasNext()) - continue; - - Fluxmap fluxmap = fluxSourceIterator.next(); - Logger.log(new EndReadOperationLogMessage()); - Logger.logf("%d ms in %d bytes", (int) (fluxmap.durationNs() / 1e6), fluxmap.bytes()); - - Track flux = getDecoder().decodeToSectors(fluxmap, ptl); - flux.normalisedSectors = collectSectors(flux.allSectors); - tracks.add(flux); - - /* Decode what we've got so far. */ - - CombinationResult cr = combineRecordAndSectors(tracks, ltl); - rgr.combinedSectors = cr.sectors; - if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) - { - /* We have all necessary sectors, so can stop here. */ - rgr.result = ReadResult.GOOD_READ; - if (getConfig().getDecoder().getSkipUnnecessaryTracks()) - break; - } else if (fluxSourceIterator.hasNext()) - { - /* The flux source claims it can do more reads, so mark this - * group as being retryable. */ - rgr.result = ReadResult.BAD_AND_CAN_RETRY; - } - } - - return rgr; - } - - private void readAndDecodeTrack(LogicalTrackLayout ltl, - List tracks, - List combinedSectors) - { - FluxSourceIteratorHolder fluxSourceIteratorHolder = - new FluxSourceIteratorHolder(getFluxSource()); - int retriesRemaining = getConfig().getDecoder().getRetries(); - for (; ; ) - { - ReadGroupResult rgr = readGroup(fluxSourceIteratorHolder, ltl, tracks); - combinedSectors.clear(); - combinedSectors.addAll(rgr.combinedSectors); - if (rgr.result == ReadResult.GOOD_READ) - break; - if (rgr.result == ReadResult.BAD_AND_CAN_NOT_RETRY) - { - Logger.logf("no more data; giving up"); - break; - } - - if (retriesRemaining == 0) - { - Logger.logf("giving up"); - break; - } - - if (getFluxSource().isHardware()) - { - adjustTrackOnError(ltl.physicalCylinder); - Logger.logf("retrying; %d retries remaining", retriesRemaining); - retriesRemaining--; - } - } - } - - /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a - * good and bad version of the same sector, the bad version is dropped). */ - static List collectSectors(List trackSectors, boolean collapseConflicts) - { - Map> sectors = new LinkedHashMap<>(); - for (Sector sector : trackSectors) - sectors.computeIfAbsent(sector.location, k -> new ArrayList<>()).add(sector); - - List sectorSet = new ArrayList<>(); - for (Map.Entry> entry : sectors.entrySet()) - { - List bucket = entry.getValue(); - Sector newSector = bucket.get(0); - for (int i = 1; i < bucket.size(); i++) - { - Sector right = bucket.get(i); - if ((newSector.status == Sector.Status.OK) && (right.status == Sector.Status.OK) && - (!newSector.data.equals(right.data))) - { - if (!collapseConflicts) - { - Sector s = new Sector(right); - s.status = Sector.Status.CONFLICT; - sectorSet.add(s); - } - Sector s = new Sector(newSector); - s.status = Sector.Status.CONFLICT; - newSector = s; - continue; - } - if (newSector.status == Sector.Status.CONFLICT) - continue; - if (right.status == Sector.Status.CONFLICT) - { - newSector = right; - continue; - } - if (newSector.status == Sector.Status.OK) - continue; - if (right.status == Sector.Status.OK) - newSector = right; - } - sectorSet.add(newSector); - } - - return sectorSet; - } - - static List collectSectors(List trackSectors) - { - return collectSectors(trackSectors, true); - } - - public void read(Disk disk) - { - FluxSinkFactory outputFluxSinkFactory = null; - if (getConfig().getDecoder().hasCopyFluxTo()) - outputFluxSinkFactory = - FluxSinkFactory.create(getConfig(), getConfig().getDecoder().getCopyFluxTo()); - - Map> tracksByLogicalLocation = new HashMap<>(); - for (Map.Entry entry : disk.tracksByPhysicalLocation.entries()) - { - Track track = entry.getValue(); - tracksByLogicalLocation.computeIfAbsent( - new CylinderHead(track.ltl.logicalCylinder, track.ltl.logicalHead), - k -> new ArrayList<>()).add(track); - } - - Logger.log(new BeginOperationLogMessage("Reading and decoding disk")); - - disk.rotationalPeriodNs = getDiskRotationalPeriodNs(); - - try (FluxSink outputFluxSink = outputFluxSinkFactory != null ? - outputFluxSinkFactory.create() : - null) - { - int index = 0; - for (Map.Entry entry : - getDiskLayout().layoutByLogicalLocation.entrySet()) - { - CylinderHead logicalLocation = entry.getKey(); - LogicalTrackLayout ltl = entry.getValue(); - Logger.log(new OperationProgressLogMessage( - index * 100 / getDiskLayout().layoutByLogicalLocation.size())); - index++; - - Common.testForEmergencyStop(); - - List trackFluxes = tracksByLogicalLocation.computeIfAbsent( - logicalLocation, - k -> new ArrayList<>()); - List trackSectors = new ArrayList<>(); - readAndDecodeTrack(ltl, trackFluxes, trackSectors); - - /* Replace all tracks on the disk by the new combined set. */ - - for (Track flux : trackFluxes) - disk.tracksByPhysicalLocation.removeAll(new CylinderHead( - flux.ptl.physicalCylinder, - flux.ptl.physicalHead)); - for (Track flux : trackFluxes) - disk.tracksByPhysicalLocation.put( - new CylinderHead( - flux.ptl.physicalCylinder, - flux.ptl.physicalHead), - flux); - - /* Likewise for sectors. */ - - for (Sector sector : trackSectors) - disk.sectorsByPhysicalLocation.removeAll(sector.physicalLocation); - for (Sector sector : trackSectors) - disk.sectorsByPhysicalLocation.put(sector.physicalLocation, sector); - - if (outputFluxSink != null) - { - for (Track data : trackFluxes) - outputFluxSink.addFlux( - data.ptl.physicalCylinder, - data.ptl.physicalHead, - data.fluxmap); - } - - if (getConfig().getDecoder().getDumpRecords()) - { - List sortedRecords = new ArrayList<>(); - for (Track data : trackFluxes) - sortedRecords.addAll(data.records); - sortedRecords.sort(Comparator.comparingDouble(r -> r.startTimeNs)); - - System.out.println("\nRaw (undecoded) records follow:\n"); - for (com.cowlark.fluxengine.data.Record record : sortedRecords) - { - System.out.printf( - "I+%.2fus with %.2fus clock%n", - record.startTimeNs / 1000.0, - record.clockNs / 1000.0); - Utils.hexdump(System.out, record.rawData); - System.out.println(); - } - } - - if (getConfig().getDecoder().getDumpSectors()) - { - List sectors = collectSectors(trackSectors, false); - sectors.sort(Comparator.comparing((Sector s) -> s.location.logicalCylinder()) - .thenComparing((Sector s) -> s.location.logicalHead()) - .thenComparing((Sector s) -> s.location.logicalSector())); - - System.out.println("\nDecoded sectors follow:\n"); - for (Sector sector : sectors) - { - System.out.printf( - "%d.%02d.%02d: I+%.2fus with %.2fus clock: " + "status %s%n", - sector.location.logicalCylinder(), - sector.location.logicalHead(), - sector.location.logicalSector(), - sector.headerStartTimeNs / 1000.0, - sector.clockNs / 1000.0, - Sector.statusToString(sector.status)); - Utils.hexdump(System.out, sector.data); - System.out.println(); - } - } - - /* track can't be modified below this point. */ - Logger.log(new TrackReadLogMessage(trackFluxes, trackSectors)); - - List allSectors = new ArrayList<>(); - for (Sector sector : disk.sectorsByPhysicalLocation.values()) - allSectors.add(sector); - allSectors = collectSectors(allSectors); - disk.image = new Image(allSectors); - } - } - - if (disk.image == null) - disk.image = new Image(); - - Logger.log(new EndOperationLogMessage("Read complete")); - } - - public Disk read() - { - Disk disk = new Disk(); - read(disk); - - ImageWriter writer = getImageWriter(); - writer.printMap(disk.image); - if (getConfig().getDecoder().hasWriteCsvTo()) - writer.writeCsv(disk.image, getConfig().getDecoder().getWriteCsvTo()); - writer.writeImage(disk.image); - - return disk; - } -} diff --git a/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperation.java new file mode 100644 index 00000000..0c6fcd9b --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperation.java @@ -0,0 +1,795 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.arch.Arch; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.Logger; +import com.cowlark.fluxengine.core.SupplierOfAutocloseable; +import com.cowlark.fluxengine.core.Utils; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Disk; +import com.cowlark.fluxengine.data.DiskLayout; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.data.Image; +import com.cowlark.fluxengine.data.LogicalLocation; +import com.cowlark.fluxengine.data.LogicalTrackLayout; +import com.cowlark.fluxengine.data.PhysicalTrackLayout; +import com.cowlark.fluxengine.data.Sector; +import com.cowlark.fluxengine.data.Track; +import com.cowlark.fluxengine.decoders.Decoder; +import com.cowlark.fluxengine.encoders.Encoder; +import com.cowlark.fluxengine.fluxsink.FluxSink; +import com.cowlark.fluxengine.fluxsink.FluxSinkFactory; +import com.cowlark.fluxengine.fluxsource.FluxReadParameters; +import com.cowlark.fluxengine.fluxsource.FluxSource; +import com.cowlark.fluxengine.fluxsource.FluxSourceIterator; +import com.cowlark.fluxengine.imagereader.ImageReader; +import com.cowlark.fluxengine.imagewriter.ImageWriter; +import com.cowlark.fluxengine.usb.UsbDevice; +import com.cowlark.fluxengine.usb.UsbFactory; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; + +public abstract class ReadWriteFluxRxOperation extends FluxRxOperation +{ + private double diskRotationalPeriodNs; + private Supplier diskLayoutSupplier; + private SupplierOfAutocloseable fluxSourceSupplier; + private SupplierOfAutocloseable fluxSinkFactorySupplier; + private SupplierOfAutocloseable usbDeviceSupplier; + private Supplier decoderSupplier; + private Supplier encoderSupplier; + private SupplierOfAutocloseable imageReaderSupplier; + private SupplierOfAutocloseable imageWriterSupplier; + + @Override + public void init() + { + ConfigProto configProto = getConfig(); + + diskLayoutSupplier = Suppliers.memoize(() -> new DiskLayout(configProto)); + fluxSourceSupplier = new SupplierOfAutocloseable(() -> FluxSource.create(configProto)); + fluxSinkFactorySupplier = + new SupplierOfAutocloseable(() -> FluxSinkFactory.create(configProto)); + usbDeviceSupplier = new SupplierOfAutocloseable(() -> UsbFactory.connect(configProto)); + decoderSupplier = Suppliers.memoize(() -> Arch.createDecoder(configProto)); + encoderSupplier = Suppliers.memoize(() -> Arch.createEncoder( + configProto, + getDiskRotationalPeriodNs())); + imageWriterSupplier = new SupplierOfAutocloseable(() -> ImageWriter.create(configProto)); + imageReaderSupplier = new SupplierOfAutocloseable(() -> ImageReader.create(configProto)); + } + + public DiskLayout getDiskLayout() + { + return diskLayoutSupplier.get(); + } + + public FluxSource getFluxSource() + { + return fluxSourceSupplier.get(); + } + + public FluxSinkFactory getFluxSinkFactory() + { + return fluxSinkFactorySupplier.get(); + } + + public Decoder getDecoder() + { + return decoderSupplier.get(); + } + + public Encoder getEncoder() + { + return encoderSupplier.get(); + } + + public ImageReader getImageReader() + { + return imageReaderSupplier.get(); + } + + public ImageWriter getImageWriter() + { + return imageWriterSupplier.get(); + } + + public double getDiskRotationalPeriodNs() + { + if (diskRotationalPeriodNs != 0) + return diskRotationalPeriodNs; + diskRotationalPeriodNs = configProto.getDrive().getRotationalPeriodMs() * 1e6; + if (diskRotationalPeriodNs == 0) + { + UsbDevice device = UsbFactory.reconnect(configProto); + + Logger.log(new LogMessage.BeginOperationLogMessage("Measuring drive rotational speed")); + Logger.log(new LogMessage.BeginSpeedOperationLogMessage()); + + int retries = 5; + do + { + diskRotationalPeriodNs = + device.getRotationalPeriod(configProto.getDrive().getHardSectorCount()); + retries--; + } while ((diskRotationalPeriodNs == 0) && (retries > 0)); + Logger.log(new LogMessage.EndOperationLogMessage("")); + } + + if (diskRotationalPeriodNs == 0) + throw new FluxEngineException("Failed\nIs a disk in the drive?"); + + Logger.log(new LogMessage.EndSpeedOperationLogMessage(diskRotationalPeriodNs)); + return diskRotationalPeriodNs; + } + + @Override + protected void onDispose() + { + closeResource(fluxSourceSupplier); + closeResource(fluxSinkFactorySupplier); + closeResource(usbDeviceSupplier); + closeResource(imageWriterSupplier); + closeResource(imageReaderSupplier); + } + + private void closeResource(SupplierOfAutocloseable resource) + { + if (resource != null) + { + try + { + resource.close(); + } catch (Exception e) + { + throw new RuntimeException(e); + } + } + } + + void adjustTrackOnError(int baseTrack) + { + switch (getConfig().getDrive().getErrorBehaviour()) + { + case NOTHING: + break; + + case RECALIBRATE: + getFluxSource().recalibrate(); + break; + + case JIGGLE: + if (baseTrack > 0) + getFluxSource().seek(baseTrack - 1); + else + getFluxSource().seek(baseTrack + 1); + break; + } + } + + enum ReadResult + { + GOOD_READ, BAD_AND_CAN_RETRY, BAD_AND_CAN_NOT_RETRY + } + + enum BadSectorsState + { + HAS_NO_BAD_SECTORS, HAS_BAD_SECTORS + } + + static class CombinationResult + { + BadSectorsState result; + List sectors; + } + + static class ReadGroupResult + { + ReadResult result; + List combinedSectors; + } + + static CombinationResult combineRecordAndSectors(List tracks, LogicalTrackLayout ltl) + { + CombinationResult cr = new CombinationResult(); + cr.result = BadSectorsState.HAS_NO_BAD_SECTORS; + List trackSectors = new ArrayList<>(); + + /* Add the sectors which were there. */ + + for (Track track : tracks) + trackSectors.addAll(track.allSectors); + + /* Add the sectors which should be there. */ + + for (int sectorId : ltl.diskSectorOrder) + { + Sector sector = + new Sector(new LogicalLocation(ltl.logicalCylinder, ltl.logicalHead, sectorId)); + + sector.status = Sector.Status.MISSING; + sector.physicalLocation = new CylinderHead(ltl.physicalCylinder, ltl.physicalHead); + trackSectors.add(sector); + } + + /* Deduplicate. */ + + cr.sectors = collectSectors(trackSectors); + if (cr.sectors.isEmpty()) + cr.result = BadSectorsState.HAS_BAD_SECTORS; + for (Sector sector : cr.sectors) + if (sector.status != Sector.Status.OK) + cr.result = BadSectorsState.HAS_BAD_SECTORS; + + return cr; + } + + protected ReadGroupResult readGroup(Common.FluxSourceIteratorHolder fluxSourceIteratorHolder, + LogicalTrackLayout ltl, + List tracks) + { + ReadGroupResult rgr = new ReadGroupResult(); + rgr.result = ReadResult.BAD_AND_CAN_NOT_RETRY; + + /* Before doing the read, look to see if we already have the necessary + * sectors. */ + + { + CombinationResult cr = combineRecordAndSectors(tracks, ltl); + rgr.combinedSectors = cr.sectors; + if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) + { + /* We have all necessary sectors, so can stop here. */ + rgr.result = ReadResult.GOOD_READ; + if (getConfig().getDecoder().getSkipUnnecessaryTracks()) + return rgr; + } + } + + for (int offset = 0; offset < ltl.groupSize; offset += getDiskLayout().headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + PhysicalTrackLayout ptl = getDiskLayout().layoutByPhysicalLocation.get(new CylinderHead( + physicalCylinder, + physicalHead)); + + /* Do the physical read. */ + + Logger.log(new LogMessage.BeginReadOperationLogMessage(physicalCylinder, physicalHead)); + + FluxSourceIterator fluxSourceIterator = + fluxSourceIteratorHolder.getIterator(FluxReadParameters.builder() + .setCylinder(physicalCylinder) + .setHead(physicalHead) + .setSyncWithIndex(getConfig().getDrive().getSyncWithIndex()) + .setReadTimeNs(getConfig().getDrive().getRevolutions() * + getDiskRotationalPeriodNs()) + .setHardSectorThresholdNs(getConfig().getDrive() + .getHardSectorThresholdNs()) + .build()); + if (!fluxSourceIterator.hasNext()) + continue; + + Fluxmap fluxmap = fluxSourceIterator.next(); + Logger.log(new LogMessage.EndReadOperationLogMessage()); + Logger.logf("%d ms in %d bytes", (int) (fluxmap.durationNs() / 1e6), fluxmap.bytes()); + + Track flux = getDecoder().decodeToSectors(fluxmap, ptl); + flux.normalisedSectors = collectSectors(flux.allSectors); + tracks.add(flux); + + /* Decode what we've got so far. */ + + CombinationResult cr = combineRecordAndSectors(tracks, ltl); + rgr.combinedSectors = cr.sectors; + if (cr.result == BadSectorsState.HAS_NO_BAD_SECTORS) + { + /* We have all necessary sectors, so can stop here. */ + rgr.result = ReadResult.GOOD_READ; + if (getConfig().getDecoder().getSkipUnnecessaryTracks()) + break; + } else if (fluxSourceIterator.hasNext()) + { + /* The flux source claims it can do more reads, so mark this + * group as being retryable. */ + rgr.result = ReadResult.BAD_AND_CAN_RETRY; + } + } + + return rgr; + } + + private void readAndDecodeTrack(LogicalTrackLayout ltl, + List tracks, + List combinedSectors) + { + Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = + new Common.FluxSourceIteratorHolder(getFluxSource()); + int retriesRemaining = getConfig().getDecoder().getRetries(); + for (; ; ) + { + ReadGroupResult rgr = readGroup(fluxSourceIteratorHolder, ltl, tracks); + combinedSectors.clear(); + combinedSectors.addAll(rgr.combinedSectors); + if (rgr.result == ReadResult.GOOD_READ) + break; + if (rgr.result == ReadResult.BAD_AND_CAN_NOT_RETRY) + { + Logger.logf("no more data; giving up"); + break; + } + + if (retriesRemaining == 0) + { + Logger.logf("giving up"); + break; + } + + if (getFluxSource().isHardware()) + { + adjustTrackOnError(ltl.physicalCylinder); + Logger.logf("retrying; %d retries remaining", retriesRemaining); + retriesRemaining--; + } + } + } + + /* Given a set of sectors, deduplicates them sensibly (e.g. if there is a + * good and bad version of the same sector, the bad version is dropped). */ + static List collectSectors(List trackSectors, boolean collapseConflicts) + { + Map> sectors = new LinkedHashMap<>(); + for (Sector sector : trackSectors) + sectors.computeIfAbsent(sector.location, k -> new ArrayList<>()).add(sector); + + List sectorSet = new ArrayList<>(); + for (Map.Entry> entry : sectors.entrySet()) + { + List bucket = entry.getValue(); + Sector newSector = bucket.get(0); + for (int i = 1; i < bucket.size(); i++) + { + Sector right = bucket.get(i); + if ((newSector.status == Sector.Status.OK) && (right.status == Sector.Status.OK) && + (!newSector.data.equals(right.data))) + { + if (!collapseConflicts) + { + Sector s = new Sector(right); + s.status = Sector.Status.CONFLICT; + sectorSet.add(s); + } + Sector s = new Sector(newSector); + s.status = Sector.Status.CONFLICT; + newSector = s; + continue; + } + if (newSector.status == Sector.Status.CONFLICT) + continue; + if (right.status == Sector.Status.CONFLICT) + { + newSector = right; + continue; + } + if (newSector.status == Sector.Status.OK) + continue; + if (right.status == Sector.Status.OK) + newSector = right; + } + sectorSet.add(newSector); + } + + return sectorSet; + } + + static List collectSectors(List trackSectors) + { + return collectSectors(trackSectors, true); + } + + public void readDisk(Disk disk) + { + FluxSinkFactory outputFluxSinkFactory = null; + if (getConfig().getDecoder().hasCopyFluxTo()) + outputFluxSinkFactory = + FluxSinkFactory.create(getConfig(), getConfig().getDecoder().getCopyFluxTo()); + + Map> tracksByLogicalLocation = new HashMap<>(); + for (Map.Entry entry : disk.tracksByPhysicalLocation.entries()) + { + Track track = entry.getValue(); + tracksByLogicalLocation.computeIfAbsent( + new CylinderHead( + track.ltl.logicalCylinder, + track.ltl.logicalHead), + k -> new ArrayList<>()).add(track); + } + + Logger.log(new LogMessage.BeginOperationLogMessage("Reading and decoding disk")); + + disk.rotationalPeriodNs = getDiskRotationalPeriodNs(); + + try (FluxSink outputFluxSink = outputFluxSinkFactory != null ? + outputFluxSinkFactory.create() : + null) + { + int index = 0; + for (Map.Entry entry : + getDiskLayout().layoutByLogicalLocation.entrySet()) + { + CylinderHead logicalLocation = entry.getKey(); + LogicalTrackLayout ltl = entry.getValue(); + Logger.log(new LogMessage.OperationProgressLogMessage( + index * 100 / getDiskLayout().layoutByLogicalLocation.size())); + index++; + + Common.testForEmergencyStop(); + + List trackFluxes = tracksByLogicalLocation.computeIfAbsent( + logicalLocation, + k -> new ArrayList<>()); + List trackSectors = new ArrayList<>(); + readAndDecodeTrack(ltl, trackFluxes, trackSectors); + + /* Replace all tracks on the disk by the new combined set. */ + + for (Track flux : trackFluxes) + disk.tracksByPhysicalLocation.removeAll(new CylinderHead( + flux.ptl.physicalCylinder, + flux.ptl.physicalHead)); + for (Track flux : trackFluxes) + disk.tracksByPhysicalLocation.put( + new CylinderHead( + flux.ptl.physicalCylinder, + flux.ptl.physicalHead), + flux); + + /* Likewise for sectors. */ + + for (Sector sector : trackSectors) + disk.sectorsByPhysicalLocation.removeAll(sector.physicalLocation); + for (Sector sector : trackSectors) + disk.sectorsByPhysicalLocation.put(sector.physicalLocation, sector); + + if (outputFluxSink != null) + { + for (Track data : trackFluxes) + outputFluxSink.addFlux( + data.ptl.physicalCylinder, + data.ptl.physicalHead, + data.fluxmap); + } + + if (getConfig().getDecoder().getDumpRecords()) + { + List sortedRecords = new ArrayList<>(); + for (Track data : trackFluxes) + sortedRecords.addAll(data.records); + sortedRecords.sort(Comparator.comparingDouble(r -> r.startTimeNs)); + + System.out.println("\nRaw (undecoded) records follow:\n"); + for (com.cowlark.fluxengine.data.Record record : sortedRecords) + { + System.out.printf( + "I+%.2fus with %.2fus clock%n", + record.startTimeNs / 1000.0, + record.clockNs / 1000.0); + Utils.hexdump(System.out, record.rawData); + System.out.println(); + } + } + + if (getConfig().getDecoder().getDumpSectors()) + { + List sectors = collectSectors(trackSectors, false); + sectors.sort(Comparator.comparing((Sector s) -> s.location.logicalCylinder()) + .thenComparing((Sector s) -> s.location.logicalHead()) + .thenComparing((Sector s) -> s.location.logicalSector())); + + System.out.println("\nDecoded sectors follow:\n"); + for (Sector sector : sectors) + { + System.out.printf( + "%d.%02d.%02d: I+%.2fus with %.2fus clock: " + "status %s%n", + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector(), + sector.headerStartTimeNs / 1000.0, + sector.clockNs / 1000.0, + Sector.statusToString(sector.status)); + Utils.hexdump(System.out, sector.data); + System.out.println(); + } + } + + /* track can't be modified below this point. */ + Logger.log(new TrackReadLogMessage(trackFluxes, trackSectors)); + + List allSectors = new ArrayList<>(); + for (Sector sector : disk.sectorsByPhysicalLocation.values()) + allSectors.add(sector); + allSectors = collectSectors(allSectors); + disk.image = new Image(allSectors); + } + } + + if (disk.image == null) + disk.image = new Image(); + + Logger.log(new LogMessage.EndOperationLogMessage("Read complete")); + } + + public Disk readDisk() + { + Disk disk = new Disk(); + readDisk(disk); + + ImageWriter writer = getImageWriter(); + writer.printMap(disk.image); + if (getConfig().getDecoder().hasWriteCsvTo()) + writer.writeCsv(disk.image, getConfig().getDecoder().getWriteCsvTo()); + writer.writeImage(disk.image); + + return disk; + } + + private void writeTracks(Function producer, + Predicate verifier, + List logicalLocations) + { + Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + + getDiskRotationalPeriodNs(); + try (FluxSink fluxSink = getFluxSinkFactory().create()) + { + int index = 0; + for (CylinderHead ch : logicalLocations) + { + Logger.log(new LogMessage.OperationProgressLogMessage( + index * 100 / logicalLocations.size())); + index++; + + Common.testForEmergencyStop(); + + LogicalTrackLayout ltl = getDiskLayout().layoutByLogicalLocation.get(ch); + int retriesRemaining = getConfig().getDecoder().getRetries(); + for (; ; ) + { + for (int offset = 0; offset < ltl.groupSize; + offset += getDiskLayout().headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + + Logger.log(new LogMessage.BeginWriteOperationLogMessage( + physicalCylinder, + ltl.physicalHead)); + + boolean erase = false; + if (offset == getConfig().getDrive().getGroupOffset()) + { + Fluxmap fluxmap = producer.apply(ltl); + if (fluxmap == null) + erase = true; + else + { + fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); + Logger.logf( + "writing %d ms in %d bytes", + (int) (fluxmap.durationNs() / 1e6), + fluxmap.bytes()); + } + } else + erase = true; + + if (erase) + { + /* Erase this track rather than writing. */ + + Fluxmap blank = new Fluxmap(); + fluxSink.addFlux(physicalCylinder, physicalHead, blank); + Logger.logf("erased"); + } + + Logger.log(new LogMessage.EndWriteOperationLogMessage()); + } + + if (verifier.test(ltl)) + break; + + if (retriesRemaining == 0) + throw new FluxEngineException("fatal error on write"); + + Logger.logf("retrying; %d retries remaining", retriesRemaining); + retriesRemaining--; + } + } + } + + Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); + } + + public void rawWrite() + { + writeTracks( + ltl -> { + FluxSourceIterator iterator = + getFluxSource().readFlux(FluxReadParameters.builder() + .setCylinder(ltl.physicalCylinder) + .setHead(ltl.physicalHead) + .build()); + if (!iterator.hasNext()) + return null; + return iterator.next(); + }, ltl -> true, getDiskLayout().logicalLocations); + } + + private void writeTracks(Function producer, + Predicate verifier, + ImmutableSet logicalLocations) + { + Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + + getDiskRotationalPeriodNs(); + try (FluxSink fluxSink = getFluxSinkFactory().create()) + { + int index = 0; + for (CylinderHead ch : logicalLocations) + { + Logger.log(new LogMessage.OperationProgressLogMessage( + index * 100 / logicalLocations.size())); + index++; + + Common.testForEmergencyStop(); + + LogicalTrackLayout ltl = getDiskLayout().layoutByLogicalLocation.get(ch); + int retriesRemaining = getConfig().getDecoder().getRetries(); + for (; ; ) + { + for (int offset = 0; offset < ltl.groupSize; + offset += getDiskLayout().headWidth) + { + int physicalCylinder = ltl.physicalCylinder + offset; + int physicalHead = ltl.physicalHead; + + Logger.log(new LogMessage.BeginWriteOperationLogMessage( + physicalCylinder, + ltl.physicalHead)); + + boolean erase = false; + if (offset == getConfig().getDrive().getGroupOffset()) + { + Fluxmap fluxmap = producer.apply(ltl); + if (fluxmap == null) + erase = true; + else + { + fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); + Logger.logf( + "writing %d ms in %d bytes", + (int) (fluxmap.durationNs() / 1e6), + fluxmap.bytes()); + } + } else + erase = true; + + if (erase) + { + /* Erase this track rather than writing. */ + + Fluxmap blank = new Fluxmap(); + fluxSink.addFlux(physicalCylinder, physicalHead, blank); + Logger.logf("erased"); + } + + Logger.log(new LogMessage.EndWriteOperationLogMessage()); + } + + if (verifier.test(ltl)) + break; + + if (retriesRemaining == 0) + throw new FluxEngineException("fatal error on write"); + + Logger.logf("retrying; %d retries remaining", retriesRemaining); + retriesRemaining--; + } + } + } + + Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); + } + + private void writeTracks(Image image, ImmutableSet chs) + { + writeTracks( + ltl -> { + ImmutableList sectors = getEncoder().collectSectors(ltl, image); + return getEncoder().encode(ltl, sectors, image); + }, ltl -> true, chs); + } + + private void writeTracksAndVerify(Image image, ImmutableSet chs) + { + writeTracks( + ltl -> { + List sectors = getEncoder().collectSectors(ltl, image); + return getEncoder().encode(ltl, sectors, image); + }, ltl -> { + Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = + new Common.FluxSourceIteratorHolder(getFluxSource()); + List tracks = new ArrayList<>(); + ReadGroupResult rgr = readGroup(fluxSourceIteratorHolder, ltl, tracks); + + if (rgr.result != ReadResult.GOOD_READ) + { + adjustTrackOnError(ltl.physicalCylinder); + Logger.logf("bad read"); + return false; + } + + Image wanted = new Image(); + for (Sector sector : getEncoder().collectSectors(ltl, image)) + wanted.put( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()).data = sector.data; + + for (Sector sector : rgr.combinedSectors) + { + Sector s = wanted.get( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()); + if (s == null) + { + Logger.logf("spurious sector on verify"); + return false; + } + if (!s.data.equals(sector.data.slice(0, s.data.size()))) + { + Logger.logf("data mismatch on verify"); + return false; + } + wanted.erase( + sector.location.logicalCylinder(), + sector.location.logicalHead(), + sector.location.logicalSector()); + } + if (!wanted.empty()) + { + Logger.logf("missing sector on verify"); + return false; + } + return true; + }, chs); + } + + public void writeDisk(Image image, Collection physicalLocations) + { + ImmutableSet chs = getDiskLayout().layoutByLogicalLocation.keySet(); + if (getConfig().getVerifyWrites()) + writeTracksAndVerify(image, chs); + else + writeTracks(image, chs); + } + + public void writeDisk(Image image) + { + writeDisk(image, getDiskLayout().layoutByLogicalLocation.keySet()); + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/WriteOperation.java b/java/com/cowlark/fluxengine/algorithms/WriteOperation.java deleted file mode 100644 index 721f8391..00000000 --- a/java/com/cowlark/fluxengine/algorithms/WriteOperation.java +++ /dev/null @@ -1,184 +0,0 @@ -package com.cowlark.fluxengine.algorithms; - -import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.core.FluxEngineException; -import com.cowlark.fluxengine.core.LogMessage; -import com.cowlark.fluxengine.core.LogMessage.BeginOperationLogMessage; -import com.cowlark.fluxengine.core.Logger; -import com.cowlark.fluxengine.data.CylinderHead; -import com.cowlark.fluxengine.data.Fluxmap; -import com.cowlark.fluxengine.data.Image; -import com.cowlark.fluxengine.data.LogicalTrackLayout; -import com.cowlark.fluxengine.data.Sector; -import com.cowlark.fluxengine.data.Track; -import com.cowlark.fluxengine.fluxsink.FluxSink; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableSet; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.function.Function; -import java.util.function.Predicate; - -public class WriteOperation extends RawWriteOperation -{ - public WriteOperation(ConfigProto configProto) - { - super(configProto); - } - - private void writeTracks(Function producer, - Predicate verifier, - ImmutableSet logicalLocations) - { - Logger.log(new BeginOperationLogMessage("Encoding and writing to disk")); - - getDiskRotationalPeriodNs(); - try (FluxSink fluxSink = getFluxSinkFactory().create()) - { - int index = 0; - for (CylinderHead ch : logicalLocations) - { - Logger.log(new LogMessage.OperationProgressLogMessage( - index * 100 / logicalLocations.size())); - index++; - - Common.testForEmergencyStop(); - - LogicalTrackLayout ltl = getDiskLayout().layoutByLogicalLocation.get(ch); - int retriesRemaining = getConfig().getDecoder().getRetries(); - for (; ; ) - { - for (int offset = 0; offset < ltl.groupSize; - offset += getDiskLayout().headWidth) - { - int physicalCylinder = ltl.physicalCylinder + offset; - int physicalHead = ltl.physicalHead; - - Logger.log(new LogMessage.BeginWriteOperationLogMessage( - physicalCylinder, - ltl.physicalHead)); - - boolean erase = false; - if (offset == getConfig().getDrive().getGroupOffset()) - { - Fluxmap fluxmap = producer.apply(ltl); - if (fluxmap == null) - erase = true; - else - { - fluxSink.addFlux(physicalCylinder, physicalHead, fluxmap); - Logger.logf( - "writing %d ms in %d bytes", - (int) (fluxmap.durationNs() / 1e6), - fluxmap.bytes()); - } - } else - erase = true; - - if (erase) - { - /* Erase this track rather than writing. */ - - Fluxmap blank = new Fluxmap(); - fluxSink.addFlux(physicalCylinder, physicalHead, blank); - Logger.logf("erased"); - } - - Logger.log(new LogMessage.EndWriteOperationLogMessage()); - } - - if (verifier.test(ltl)) - break; - - if (retriesRemaining == 0) - throw new FluxEngineException("fatal error on write"); - - Logger.logf("retrying; %d retries remaining", retriesRemaining); - retriesRemaining--; - } - } - } - - Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); - } - - private void writeTracks(Image image, ImmutableSet chs) - { - writeTracks( - ltl -> { - ImmutableList sectors = getEncoder().collectSectors(ltl, image); - return getEncoder().encode(ltl, sectors, image); - }, ltl -> true, chs); - } - - private void writeTracksAndVerify(Image image, ImmutableSet chs) - { - writeTracks( - ltl -> { - List sectors = getEncoder().collectSectors(ltl, image); - return getEncoder().encode(ltl, sectors, image); - }, ltl -> { - Common.FluxSourceIteratorHolder fluxSourceIteratorHolder = - new Common.FluxSourceIteratorHolder(getFluxSource()); - List tracks = new ArrayList<>(); - ReadGroupResult rgr = readGroup(fluxSourceIteratorHolder, ltl, tracks); - - if (rgr.result != ReadOperation.ReadResult.GOOD_READ) - { - adjustTrackOnError(ltl.physicalCylinder); - Logger.logf("bad read"); - return false; - } - - Image wanted = new Image(); - for (Sector sector : getEncoder().collectSectors(ltl, image)) - wanted.put( - sector.location.logicalCylinder(), - sector.location.logicalHead(), - sector.location.logicalSector()).data = sector.data; - - for (Sector sector : rgr.combinedSectors) - { - Sector s = wanted.get( - sector.location.logicalCylinder(), - sector.location.logicalHead(), - sector.location.logicalSector()); - if (s == null) - { - Logger.logf("spurious sector on verify"); - return false; - } - if (!s.data.equals(sector.data.slice(0, s.data.size()))) - { - Logger.logf("data mismatch on verify"); - return false; - } - wanted.erase( - sector.location.logicalCylinder(), - sector.location.logicalHead(), - sector.location.logicalSector()); - } - if (!wanted.empty()) - { - Logger.logf("missing sector on verify"); - return false; - } - return true; - }, chs); - } - - public void writeDiskCommand(Image image, Collection physicalLocations) - { - ImmutableSet chs = getDiskLayout().layoutByLogicalLocation.keySet(); - if (getConfig().getVerifyWrites()) - writeTracksAndVerify(image, chs); - else - writeTracks(image, chs); - } - - public void writeDiskCommand(Image image) - { - writeDiskCommand(image, getDiskLayout().layoutByLogicalLocation.keySet()); - } -} diff --git a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java index 9fea734f..805fcde0 100644 --- a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java @@ -2,13 +2,11 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import com.cowlark.fluxengine.algorithms.RawWriteOperation; -import com.cowlark.fluxengine.algorithms.WriteOperation; +import com.cowlark.fluxengine.algorithms.ReadWriteFluxRxOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.config.ConfigProtoOrBuilder; import com.cowlark.fluxengine.core.FluxEngineException; -import com.cowlark.fluxengine.core.flags.ActionFlag; +import com.cowlark.fluxengine.core.LogRenderer; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; @@ -39,20 +37,32 @@ public String getHelp() return "Writes a flux file to a disk. Warning: you can't use this to copy disks."; } + private class RawwriteRxOperation extends ReadWriteFluxRxOperation + { + @Override + public void run() + { + rawWrite(); + } + } + @Override - public void run(ImmutableList args) throws Exception + public void run(ImmutableList args) { - ConfigProto configProto = new ConfigBuilder().fromFlags(args, flags) + ConfigProto config = new ConfigBuilder().fromFlags(args, flags) .withFluxSource(sourceFluxFlag.get()) .withFluxSink(destFluxFlag.get()) .build(); - if (configProto.getFluxSource().getType() == FLUXTYPE_DRIVE) + if (config.getFluxSource().getType() == FLUXTYPE_DRIVE) throw new FluxEngineException("you can't use rawwrite to read from hardware"); - try (RawWriteOperation operation = new RawWriteOperation(configProto)) - { - operation.rawWrite(); - } + LogRenderer renderer = LogRenderer.create(System.out); + new RawwriteRxOperation().setConfig(config).create().blockingSubscribe( + renderer::add, e -> { + System.err.println("Failed!"); + e.printStackTrace(); + }); + System.out.println("done."); } -} \ No newline at end of file +} diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index 9d100572..4d33e78a 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -2,10 +2,11 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import com.cowlark.fluxengine.algorithms.ReadOperation; +import com.cowlark.fluxengine.algorithms.ReadWriteFluxRxOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.LogRenderer; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; import com.cowlark.fluxengine.core.flags.ValueFlag; @@ -41,6 +42,15 @@ public String getHelp() return "Reads a disk, producing a sector image."; } + private class ReadRxOperation extends ReadWriteFluxRxOperation + { + @Override + public void run() + { + readDisk(); + } + } + @Override public void run(ImmutableList args) { @@ -56,15 +66,12 @@ public void run(ImmutableList args) if (config.getDecoder().getCopyFluxTo().getType() == FLUXTYPE_DRIVE) throw new FluxEngineException("you cannot copy flux to a hardware device"); - try - { - try (ReadOperation operation = new ReadOperation(config)) - { - operation.read(); - } - } catch (Exception e) - { - throw new RuntimeException(e); - } + LogRenderer renderer = LogRenderer.create(System.out); + new ReadRxOperation().setConfig(config).create().blockingSubscribe( + renderer::add, e -> { + System.err.println("Failed!"); + e.printStackTrace(); + }); + System.out.println("done."); } } diff --git a/java/com/cowlark/fluxengine/cli/WriteCommand.java b/java/com/cowlark/fluxengine/cli/WriteCommand.java index 71e9ff22..d62503d1 100644 --- a/java/com/cowlark/fluxengine/cli/WriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/WriteCommand.java @@ -1,8 +1,9 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.algorithms.WriteOperation; +import com.cowlark.fluxengine.algorithms.ReadWriteFluxRxOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.core.LogRenderer; import com.cowlark.fluxengine.core.flags.ActionFlag; import com.cowlark.fluxengine.core.flags.FlagGroup; import com.cowlark.fluxengine.core.flags.StringFlag; @@ -43,10 +44,20 @@ public String getHelp() return "Writes a sector image to a disk."; } + private class WriteRxOperation extends ReadWriteFluxRxOperation + { + @Override + public void run() + { + Image image = getImageReader().readImage(); + writeDisk(image); + } + } + @Override - public void run(ImmutableList args) throws Exception + public void run(ImmutableList args) { - ConfigProto configProto = new ConfigBuilder().fromFlags(args, flags) + ConfigProto config = new ConfigBuilder().fromFlags(args, flags) .withImageReader(sourceImageFlag.get()) .withFluxSink(destFluxFlag.get()) .withFluxSource(destFluxFlag.get()) /* for verification */.set( @@ -54,44 +65,12 @@ public void run(ImmutableList args) throws Exception Boolean.toString(verify)) .build(); - try (WriteOperation operation = new WriteOperation(configProto)) - { - Image image = operation.getImageReader().readImage(); - operation.writeDiskCommand(image); - // FluxSource verificationFluxSource = null; - // if (configProto.hasDecoder() && operation.getFluxSinkFactory() - // .isHardware() && verify) - // { - // verificationFluxSource = FluxSource.create(operation - // .getVerificationFluxSource()); - // } - // ImageReader reader = operation.getImageReader(); - // Image image = reader.readImage(); - // - // config = config.toBuilder().mergeFrom(reader.getExtraConfig()).build(); - // - // DiskLayout diskLayout = new DiskLayout(config); - // Encoder encoder = Arch.createEncoder(config); - // FluxSinkFactory fluxSinkFactory = FluxSinkFactory.create(config); - // - // Decoder decoder = null; - // FluxSource verificationFluxSource = null; - // if (config.hasDecoder() && fluxSinkFactory.isHardware() && verify) - // { - // decoder = Arch.createDecoder(config); - // ConfigBuilder verifyBuilder = new ConfigBuilder().fromFlags(args, flags); - // verifyBuilder.withFluxSource(dest); - // verificationFluxSource = FluxSource.create(verifyBuilder.build()); - // } - // - // Writer.writeDiskCommand( - // config, - // diskLayout, - // image, - // encoder, - // fluxSinkFactory, - // decoder, - // verificationFluxSource); - } + LogRenderer renderer = LogRenderer.create(System.out); + new WriteRxOperation().setConfig(config).create().blockingSubscribe( + renderer::add, e -> { + System.err.println("Failed!"); + e.printStackTrace(); + }); + System.out.println("done."); } } diff --git a/java/com/cowlark/fluxengine/core/DefaultLogRenderer.java b/java/com/cowlark/fluxengine/core/DefaultLogRenderer.java deleted file mode 100644 index 76b1409e..00000000 --- a/java/com/cowlark/fluxengine/core/DefaultLogRenderer.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.cowlark.fluxengine.core; - -import java.io.PrintStream; - -class DefaultLogRenderer extends LogRenderer -{ - private final PrintStream stream; - private boolean header = false; - private boolean newline = false; - private boolean space = false; - private int lineLen = 0; - - DefaultLogRenderer(PrintStream stream) - { - this.stream = stream; - } - - private void indent() - { - stream.print(" "); - lineLen = 7; - space = true; - } - - @Override - public LogRenderer add(String message) - { - if (newline && !header) - indent(); - - if (!space) - { - stream.print(' '); - lineLen++; - } - - newline = false; - header = false; - - lineLen += message.length(); - if (lineLen >= 80) - { - stream.println(); - indent(); - } - stream.print(message); - space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); - return this; - } - - @Override - public LogRenderer header(String message) - { - if (!newline) - stream.println(); - stream.print(message); - lineLen = message.length(); - header = true; - newline = true; - space = !message.isEmpty() && Character.isWhitespace(message.charAt(message.length() - 1)); - return this; - } - - @Override - public LogRenderer comma() - { - if (!newline || header) - { - stream.print(';'); - space = false; - } - return this; - } - - @Override - public LogRenderer newline() - { - if (!header) - { - if (!newline) - stream.println(); - - lineLen = 0; - header = false; - newline = true; - space = true; - } - return this; - } -} diff --git a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel index 073b5f8e..f1aef694 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -3,22 +3,8 @@ load("@rules_java//java:defs.bzl", "java_test") package(default_visibility = ["//visibility:public"]) java_test( - name = "ReadOperationTest", - srcs = ["ReadOperationTest.java"], - deps = [ - "//java/com/cowlark/fluxengine/algorithms", - "//java/com/cowlark/fluxengine/config:config_java_proto", - "//java/com/cowlark/fluxengine/core", - "//java/com/cowlark/fluxengine/data", - "@maven//:com_google_guava_guava", - "@maven//:com_google_truth_truth", - "@maven//:junit_junit", - ], -) - -java_test( - name = "WriteOperationTest", - srcs = ["WriteOperationTest.java"], + name = "ReadWriteFluxRxOperationTest", + srcs = ["ReadWriteFluxRxOperationTest.java"], deps = [ "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/algorithms", @@ -26,14 +12,15 @@ java_test( "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", "//java/com/cowlark/fluxengine/data", + "@maven//:com_google_guava_guava", "@maven//:com_google_truth_truth", "@maven//:junit_junit", ], ) java_test( - name = "FluxOperationFactoryTest", - srcs = ["FluxOperationFactoryTest.java"], + name = "FluxRxOperationTest", + srcs = ["FluxRxOperationTest.java"], deps = [ "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/algorithms", diff --git a/javatests/com/cowlark/fluxengine/algorithms/FluxOperationFactoryTest.java b/javatests/com/cowlark/fluxengine/algorithms/FluxRxOperationTest.java similarity index 64% rename from javatests/com/cowlark/fluxengine/algorithms/FluxOperationFactoryTest.java rename to javatests/com/cowlark/fluxengine/algorithms/FluxRxOperationTest.java index aa2b5e16..8ece7924 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/FluxOperationFactoryTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/FluxRxOperationTest.java @@ -9,30 +9,33 @@ import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestRule; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; +import java.util.concurrent.atomic.AtomicInteger; @RunWith(JUnit4.class) -public class FluxOperationFactoryTest +public class FluxRxOperationTest { @Rule public final TestRule loggerRule = TestHelpers.loggerRule(); /* A harness whose run() blocks on a semaphore until the test releases it, * then logs a message. */ - private static class Harness extends FluxOperationFactory + private static class Harness extends FluxRxOperation { final Semaphore gate = new Semaphore(0); final CountDownLatch started = new CountDownLatch(1); final CountDownLatch finished = new CountDownLatch(1); + final AtomicInteger disposeCount = new AtomicInteger(); + final CountDownLatch disposed = new CountDownLatch(1); volatile Thread runThread; @Override @@ -50,6 +53,13 @@ public void run() Logger.log(new StringMessage("hello")); finished.countDown(); } + + @Override + protected void onDispose() + { + disposeCount.incrementAndGet(); + disposed.countDown(); + } } @Test @@ -61,20 +71,22 @@ public void multipleSubscribersSeeSameOperation() throws Exception List first = new ArrayList<>(); List second = new ArrayList<>(); CountDownLatch done = new CountDownLatch(2); - Disposable firstSubscription = observable.subscribe(m -> { - synchronized (first) - { - first.add(m); - } - }, t -> { - }, done::countDown); - Disposable secondSubscription = observable.subscribe(m -> { - synchronized (second) - { - second.add(m); - } - }, t -> { - }, done::countDown); + Disposable firstSubscription = observable.subscribe( + m -> { + synchronized (first) + { + first.add(m); + } + }, t -> { + }, done::countDown); + Disposable secondSubscription = observable.subscribe( + m -> { + synchronized (second) + { + second.add(m); + } + }, t -> { + }, done::countDown); harness.gate.release(); @@ -148,22 +160,24 @@ public void operationsStartedAtSameTimeAreSerialised() throws Exception @Test public void failingOperationDeliversErrorAndCleansUpLogger() throws Exception { - FluxOperationFactory failing = new FluxOperationFactory() + class TestFluxRxOperation extends FluxRxOperation { @Override public void run() { throw new RuntimeException("boom"); } - }; + } + TestFluxRxOperation failing = new TestFluxRxOperation(); List errors = new ArrayList<>(); CountDownLatch done = new CountDownLatch(1); - Disposable subscription = failing.create().subscribe(m -> { - }, t -> { - errors.add(t); - done.countDown(); - }, done::countDown); + Disposable subscription = failing.create().subscribe( + m -> { + }, t -> { + errors.add(t); + done.countDown(); + }, done::countDown); assertThat(done.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(errors).hasSize(1); @@ -186,4 +200,71 @@ public void run() assertThat(probed.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(loggerThrows.get()).isTrue(); } + + @Test + public void operationIsDisposedWhenItCompletes() throws Exception + { + Harness harness = new Harness(); + harness.create().subscribe(); + + harness.gate.release(); + + assertThat(harness.finished.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(harness.disposed.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(harness.disposeCount.get()).isEqualTo(1); + } + + @Test + public void operationIsDisposedWhenItFails() throws Exception + { + Harness harness = new Harness() + { + @Override + public void run() + { + throw new RuntimeException("boom"); + } + }; + + Disposable subscription = harness.create().subscribe(m -> { + }, t -> { + }, () -> { + }); + + assertThat(harness.disposed.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(harness.disposeCount.get()).isEqualTo(1); + } + + @Test + public void disposingSubscriptionDisposesOperation() throws Exception + { + Harness harness = new Harness(); + Disposable subscription = harness.create().subscribe(); + + try + { + assertThat(harness.started.await(5, TimeUnit.SECONDS)).isTrue(); + + subscription.dispose(); + + assertThat(harness.disposed.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(harness.disposeCount.get()).isEqualTo(1); + } finally + { + /* Always release the gate so a failed assertion doesn't leave a + * worker thread blocked. */ + harness.gate.release(); + } + } + + @Test + public void disposeIsIdempotent() + { + Harness harness = new Harness(); + + harness.dispose(); + harness.dispose(); + + assertThat(harness.disposeCount.get()).isEqualTo(1); + } } diff --git a/javatests/com/cowlark/fluxengine/algorithms/ReadOperationTest.java b/javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperationTest.java similarity index 64% rename from javatests/com/cowlark/fluxengine/algorithms/ReadOperationTest.java rename to javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperationTest.java index 4aa2c9f8..d8c0644f 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/ReadOperationTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperationTest.java @@ -2,22 +2,38 @@ import static com.google.common.truth.Truth.assertThat; +import com.cowlark.fluxengine.config.ConfigBuilder; +import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.data.DiskLayout; import com.cowlark.fluxengine.data.LogicalLocation; import com.cowlark.fluxengine.data.LogicalTrackLayout; import com.cowlark.fluxengine.data.Sector; import com.cowlark.fluxengine.data.Track; +import com.cowlark.fluxengine.testing.TestHelpers; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; import java.util.List; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.TestRule; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @RunWith(JUnit4.class) -public class ReadOperationTest +public class ReadWriteFluxRxOperationTest { + @Rule public final TestRule loggerRule = TestHelpers.loggerRule(); + + private static class TestOperation extends ReadWriteFluxRxOperation + { + @Override + public void run() + { + } + } + private static LogicalTrackLayout makeLtl() { ImmutableList order = ImmutableList.of(0, 1, 2); @@ -43,6 +59,18 @@ private static Sector makeSector(int sectorId, Sector.Status status) return sector; } + private static ConfigProto makeConfig() + { + return new ConfigBuilder().set("usb.serial", "test-serial") + .set("drive.rotational_period_ms", "200") + .set("layout.tracks", "1") + .set("layout.sides", "1") + .set("layout.layoutdata[0].sector_size", "256") + .set("layout.layoutdata[0].physical.start_sector", "0") + .set("layout.layoutdata[0].physical.count", "8") + .build(); + } + @Test public void collectSectorsDeduplicatesOkAndBad() { @@ -53,7 +81,7 @@ public void collectSectorsDeduplicatesOkAndBad() sectors.add(makeSector(1, Sector.Status.OK)); sectors.add(makeSector(2, Sector.Status.BAD_CHECKSUM)); - List result = ReadOperation.collectSectors(sectors, true); + List result = ReadWriteFluxRxOperation.collectSectors(sectors, true); assertThat(result).hasSize(3); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -68,7 +96,7 @@ public void collectSectorsPrefersOkOverMissing() sectors.add(makeSector(0, Sector.Status.MISSING)); sectors.add(makeSector(0, Sector.Status.OK)); - List result = ReadOperation.collectSectors(sectors); + List result = ReadWriteFluxRxOperation.collectSectors(sectors); assertThat(result).hasSize(1); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -83,13 +111,13 @@ public void collectSectorsConflictWhenBothOkDifferentData() b.data = Bytes.of(2); /* collapseConflicts=false keeps both as CONFLICT. */ - List result = ReadOperation.collectSectors(List.of(a, b), false); + List result = ReadWriteFluxRxOperation.collectSectors(List.of(a, b), false); assertThat(result).hasSize(2); assertThat(result.get(0).status).isEqualTo(Sector.Status.CONFLICT); assertThat(result.get(1).status).isEqualTo(Sector.Status.CONFLICT); /* collapseConflicts=true collapses to a single CONFLICT. */ - List collapsed = ReadOperation.collectSectors(List.of(a, b), true); + List collapsed = ReadWriteFluxRxOperation.collectSectors(List.of(a, b), true); assertThat(collapsed).hasSize(1); assertThat(collapsed.get(0).status).isEqualTo(Sector.Status.CONFLICT); } @@ -102,7 +130,7 @@ public void collectSectorsOkDataSameCollapses() Sector b = makeSector(0, Sector.Status.OK); b.data = Bytes.of(1); - List result = ReadOperation.collectSectors(List.of(a, b), false); + List result = ReadWriteFluxRxOperation.collectSectors(List.of(a, b), false); assertThat(result).hasSize(1); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -117,7 +145,7 @@ public void combineRecordAndSectorsFillsMissing() track.allSectors.add(makeSector(0, Sector.Status.OK)); ReadOperation.CombinationResult cr = - ReadOperation.combineRecordAndSectors(List.of(track), makeLtl()); + ReadWriteFluxRxOperation.combineRecordAndSectors(List.of(track), makeLtl()); assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); @@ -143,7 +171,7 @@ public void combineRecordAndSectorsNoBadWhenAllPresent() track.allSectors.add(makeSector(2, Sector.Status.OK)); ReadOperation.CombinationResult cr = - ReadOperation.combineRecordAndSectors(List.of(track), makeLtl()); + ReadWriteFluxRxOperation.combineRecordAndSectors(List.of(track), makeLtl()); assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_NO_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); @@ -155,11 +183,55 @@ public void combineRecordAndSectorsNoBadWhenAllPresent() public void combineRecordAndSectorsEmptyTrackIsBad() { ReadOperation.CombinationResult cr = - ReadOperation.combineRecordAndSectors(List.of(), makeLtl()); + ReadWriteFluxRxOperation.combineRecordAndSectors(List.of(), makeLtl()); assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); for (Sector sector : cr.sectors) assertThat(sector.status).isEqualTo(Sector.Status.MISSING); } + + @Test + public void getConfigReturnsConfiguredConfig() + { + ConfigProto config = makeConfig(); + TestOperation operation = new TestOperation(); + operation.setConfig(config); + + assertThat(operation.getConfig()).isSameInstanceAs(config); + } + + @Test + public void getDiskLayoutBuildsFromConfig() + { + TestOperation operation = new TestOperation(); + operation.setConfig(makeConfig()); + operation.init(); + + DiskLayout diskLayout = operation.getDiskLayout(); + + assertThat(diskLayout).isNotNull(); + assertThat(diskLayout.logicalLocations).isNotEmpty(); + assertThat(diskLayout.layoutByLogicalLocation.size()).isEqualTo(1); + } + + @Test + public void getDiskLayoutIsMemoized() + { + TestOperation operation = new TestOperation(); + operation.setConfig(makeConfig()); + operation.init(); + + assertThat(operation.getDiskLayout()).isSameInstanceAs(operation.getDiskLayout()); + } + + @Test + public void disposeDoesNotThrowWhenNothingCreated() + { + TestOperation operation = new TestOperation(); + operation.setConfig(makeConfig()); + operation.init(); + + operation.dispose(); + } } diff --git a/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java b/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java deleted file mode 100644 index 78b3f523..00000000 --- a/javatests/com/cowlark/fluxengine/algorithms/WriteOperationTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.cowlark.fluxengine.algorithms; - -import static com.google.common.truth.Truth.assertThat; - -import com.cowlark.fluxengine.config.ConfigBuilder; -import com.cowlark.fluxengine.config.ConfigProto; -import com.cowlark.fluxengine.data.DiskLayout; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class WriteOperationTest -{ - @org.junit.Rule - public final org.junit.rules.TestRule loggerRule = - com.cowlark.fluxengine.testing.TestHelpers.loggerRule(); - - private static ConfigProto makeConfig() - { - return new ConfigBuilder().set("usb.serial", "test-serial") - .set("drive.rotational_period_ms", "200") - .set("layout.tracks", "1") - .set("layout.sides", "1") - .set("layout.layoutdata[0].sector_size", "256") - .set("layout.layoutdata[0].physical.start_sector", "0") - .set("layout.layoutdata[0].physical.count", "8") - .build(); - } - - @Test - public void getConfigReturnsConfiguredConfig() - { - ConfigProto config = makeConfig(); - WriteOperation operation = new WriteOperation(config); - - assertThat(operation.getConfig()).isSameInstanceAs(config); - } - - @Test - public void getDiskLayoutBuildsFromConfig() - { - WriteOperation operation = new WriteOperation(makeConfig()); - - DiskLayout diskLayout = operation.getDiskLayout(); - - assertThat(diskLayout).isNotNull(); - assertThat(diskLayout.logicalLocations).isNotEmpty(); - assertThat(diskLayout.layoutByLogicalLocation.size()).isEqualTo(1); - } - - @Test - public void getDiskLayoutIsMemoized() - { - WriteOperation operation = new WriteOperation(makeConfig()); - - assertThat(operation.getDiskLayout()).isSameInstanceAs(operation.getDiskLayout()); - } - - @Test - public void closeDoesNotThrowWhenNothingCreated() - { - WriteOperation operation = new WriteOperation(makeConfig()); - - try - { - operation.close(); - } catch (Exception e) - { - throw new AssertionError("close should not throw", e); - } - } -} From bd210a62a9b623f60165b1ff0ee8a5f3e97f5893 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 23:24:38 +0200 Subject: [PATCH 181/192] Renaming. --- ...{FluxRxOperation.java => FluxOperation.java} | 6 +++--- ...eration.java => ReadWriteFluxOperation.java} | 6 ++---- .../cowlark/fluxengine/cli/RawwriteCommand.java | 6 +++--- .../com/cowlark/fluxengine/cli/ReadCommand.java | 6 +++--- .../cowlark/fluxengine/cli/WriteCommand.java | 6 +++--- ...perationTest.java => FluxOperationTest.java} | 17 +++++++++-------- 6 files changed, 23 insertions(+), 24 deletions(-) rename java/com/cowlark/fluxengine/algorithms/{FluxRxOperation.java => FluxOperation.java} (93%) rename java/com/cowlark/fluxengine/algorithms/{ReadWriteFluxRxOperation.java => ReadWriteFluxOperation.java} (99%) rename javatests/com/cowlark/fluxengine/algorithms/{FluxRxOperationTest.java => FluxOperationTest.java} (95%) diff --git a/java/com/cowlark/fluxengine/algorithms/FluxRxOperation.java b/java/com/cowlark/fluxengine/algorithms/FluxOperation.java similarity index 93% rename from java/com/cowlark/fluxengine/algorithms/FluxRxOperation.java rename to java/com/cowlark/fluxengine/algorithms/FluxOperation.java index 3e9b27af..46335aa0 100644 --- a/java/com/cowlark/fluxengine/algorithms/FluxRxOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/FluxOperation.java @@ -12,7 +12,7 @@ * Runs an operation once on its own worker thread, multicasting its log * messages to all subscribers via a {@link PublishSubject}. */ -public abstract class FluxRxOperation> implements Runnable +public abstract class FluxOperation> implements Runnable { /* Serialises all operations across the whole program: only one may run at * a time, because the hardware doesn't cope with concurrent access. */ @@ -21,11 +21,11 @@ public abstract class FluxRxOperation> implements R protected ConfigProto configProto = null; private boolean disposed = false; - protected FluxRxOperation() + protected FluxOperation() { } - public FluxRxOperation setConfig(ConfigProto config) + public FluxOperation setConfig(ConfigProto config) { this.configProto = config; return this; diff --git a/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperation.java similarity index 99% rename from java/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperation.java rename to java/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperation.java index 0c6fcd9b..0fdd6c05 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperation.java @@ -42,7 +42,7 @@ import java.util.function.Function; import java.util.function.Predicate; -public abstract class ReadWriteFluxRxOperation extends FluxRxOperation +public abstract class ReadWriteFluxOperation extends FluxOperation { private double diskRotationalPeriodNs; private Supplier diskLayoutSupplier; @@ -413,9 +413,7 @@ public void readDisk(Disk disk) { Track track = entry.getValue(); tracksByLogicalLocation.computeIfAbsent( - new CylinderHead( - track.ltl.logicalCylinder, - track.ltl.logicalHead), + new CylinderHead(track.ltl.logicalCylinder, track.ltl.logicalHead), k -> new ArrayList<>()).add(track); } diff --git a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java index 805fcde0..13a2f703 100644 --- a/java/com/cowlark/fluxengine/cli/RawwriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/RawwriteCommand.java @@ -2,7 +2,7 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import com.cowlark.fluxengine.algorithms.ReadWriteFluxRxOperation; +import com.cowlark.fluxengine.algorithms.ReadWriteFluxOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; @@ -37,7 +37,7 @@ public String getHelp() return "Writes a flux file to a disk. Warning: you can't use this to copy disks."; } - private class RawwriteRxOperation extends ReadWriteFluxRxOperation + private class RawwriteOperation extends ReadWriteFluxOperation { @Override public void run() @@ -58,7 +58,7 @@ public void run(ImmutableList args) throw new FluxEngineException("you can't use rawwrite to read from hardware"); LogRenderer renderer = LogRenderer.create(System.out); - new RawwriteRxOperation().setConfig(config).create().blockingSubscribe( + new RawwriteOperation().setConfig(config).create().blockingSubscribe( renderer::add, e -> { System.err.println("Failed!"); e.printStackTrace(); diff --git a/java/com/cowlark/fluxengine/cli/ReadCommand.java b/java/com/cowlark/fluxengine/cli/ReadCommand.java index 4d33e78a..42f3d890 100644 --- a/java/com/cowlark/fluxengine/cli/ReadCommand.java +++ b/java/com/cowlark/fluxengine/cli/ReadCommand.java @@ -2,7 +2,7 @@ import static com.cowlark.fluxengine.config.FluxSourceSinkType.FLUXTYPE_DRIVE; -import com.cowlark.fluxengine.algorithms.ReadWriteFluxRxOperation; +import com.cowlark.fluxengine.algorithms.ReadWriteFluxOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.FluxEngineException; @@ -42,7 +42,7 @@ public String getHelp() return "Reads a disk, producing a sector image."; } - private class ReadRxOperation extends ReadWriteFluxRxOperation + private class ReadOperation extends ReadWriteFluxOperation { @Override public void run() @@ -67,7 +67,7 @@ public void run(ImmutableList args) throw new FluxEngineException("you cannot copy flux to a hardware device"); LogRenderer renderer = LogRenderer.create(System.out); - new ReadRxOperation().setConfig(config).create().blockingSubscribe( + new ReadOperation().setConfig(config).create().blockingSubscribe( renderer::add, e -> { System.err.println("Failed!"); e.printStackTrace(); diff --git a/java/com/cowlark/fluxengine/cli/WriteCommand.java b/java/com/cowlark/fluxengine/cli/WriteCommand.java index d62503d1..3f8b601b 100644 --- a/java/com/cowlark/fluxengine/cli/WriteCommand.java +++ b/java/com/cowlark/fluxengine/cli/WriteCommand.java @@ -1,6 +1,6 @@ package com.cowlark.fluxengine.cli; -import com.cowlark.fluxengine.algorithms.ReadWriteFluxRxOperation; +import com.cowlark.fluxengine.algorithms.ReadWriteFluxOperation; import com.cowlark.fluxengine.config.ConfigBuilder; import com.cowlark.fluxengine.config.ConfigProto; import com.cowlark.fluxengine.core.LogRenderer; @@ -44,7 +44,7 @@ public String getHelp() return "Writes a sector image to a disk."; } - private class WriteRxOperation extends ReadWriteFluxRxOperation + private class WriteOperation extends ReadWriteFluxOperation { @Override public void run() @@ -66,7 +66,7 @@ public void run(ImmutableList args) .build(); LogRenderer renderer = LogRenderer.create(System.out); - new WriteRxOperation().setConfig(config).create().blockingSubscribe( + new WriteOperation().setConfig(config).create().blockingSubscribe( renderer::add, e -> { System.err.println("Failed!"); e.printStackTrace(); diff --git a/javatests/com/cowlark/fluxengine/algorithms/FluxRxOperationTest.java b/javatests/com/cowlark/fluxengine/algorithms/FluxOperationTest.java similarity index 95% rename from javatests/com/cowlark/fluxengine/algorithms/FluxRxOperationTest.java rename to javatests/com/cowlark/fluxengine/algorithms/FluxOperationTest.java index 8ece7924..56e8ac60 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/FluxRxOperationTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/FluxOperationTest.java @@ -23,13 +23,13 @@ import java.util.concurrent.atomic.AtomicInteger; @RunWith(JUnit4.class) -public class FluxRxOperationTest +public class FluxOperationTest { @Rule public final TestRule loggerRule = TestHelpers.loggerRule(); /* A harness whose run() blocks on a semaphore until the test releases it, * then logs a message. */ - private static class Harness extends FluxRxOperation + private static class Harness extends FluxOperation { final Semaphore gate = new Semaphore(0); final CountDownLatch started = new CountDownLatch(1); @@ -160,7 +160,7 @@ public void operationsStartedAtSameTimeAreSerialised() throws Exception @Test public void failingOperationDeliversErrorAndCleansUpLogger() throws Exception { - class TestFluxRxOperation extends FluxRxOperation + class TestFluxOperation extends FluxOperation { @Override public void run() @@ -169,7 +169,7 @@ public void run() } } - TestFluxRxOperation failing = new TestFluxRxOperation(); + TestFluxOperation failing = new TestFluxOperation(); List errors = new ArrayList<>(); CountDownLatch done = new CountDownLatch(1); Disposable subscription = failing.create().subscribe( @@ -226,10 +226,11 @@ public void run() } }; - Disposable subscription = harness.create().subscribe(m -> { - }, t -> { - }, () -> { - }); + Disposable subscription = harness.create().subscribe( + m -> { + }, t -> { + }, () -> { + }); assertThat(harness.disposed.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(harness.disposeCount.get()).isEqualTo(1); From cafa2cc00d694b08ab659859fac991ac878b9c65 Mon Sep 17 00:00:00 2001 From: David Given Date: Wed, 12 Aug 2026 23:38:33 +0200 Subject: [PATCH 182/192] Refactor all the logmessage stuff. --- .../algorithms/BeginOperationLogMessage.java | 16 ++++ .../BeginReadOperationLogMessage.java | 17 ++++ .../BeginSpeedOperationLogMessage.java | 17 ++++ .../BeginWriteOperationLogMessage.java | 17 ++++ .../algorithms/DiskReadLogMessage.java | 16 ++++ .../algorithms/EndOperationLogMessage.java | 16 ++++ .../EndReadOperationLogMessage.java | 16 ++++ .../EndSpeedOperationLogMessage.java | 20 +++++ .../EndWriteOperationLogMessage.java | 16 ++++ .../fluxengine/algorithms/Operation.java | 8 +- .../OperationProgressLogMessage.java | 16 ++++ .../algorithms/ReadWriteFluxOperation.java | 42 +++++----- .../cowlark/fluxengine/core/LogMessage.java | 80 ------------------- java/com/cowlark/fluxengine/data/Disk.java | 10 +++ .../cowlark/fluxengine/algorithms/BUILD.bazel | 8 +- ...t.java => ReadWriteFluxOperationTest.java} | 32 ++++---- .../com/cowlark/fluxengine/core/BUILD.bazel | 2 + .../fluxengine/core/LogRendererTest.java | 6 +- .../cowlark/fluxengine/core/LoggerTest.java | 2 +- 19 files changed, 230 insertions(+), 127 deletions(-) create mode 100644 java/com/cowlark/fluxengine/algorithms/BeginOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/BeginReadOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/BeginSpeedOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/BeginWriteOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/DiskReadLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/EndOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/EndReadOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/EndSpeedOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/EndWriteOperationLogMessage.java create mode 100644 java/com/cowlark/fluxengine/algorithms/OperationProgressLogMessage.java rename javatests/com/cowlark/fluxengine/algorithms/{ReadWriteFluxRxOperationTest.java => ReadWriteFluxOperationTest.java} (84%) diff --git a/java/com/cowlark/fluxengine/algorithms/BeginOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/BeginOperationLogMessage.java new file mode 100644 index 00000000..fa065e65 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/BeginOperationLogMessage.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We're starting a large-scale operation, ported from + * lib/algorithms/readerwriter.cc. + */ +public record BeginOperationLogMessage(String message) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/BeginReadOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/BeginReadOperationLogMessage.java new file mode 100644 index 00000000..23865ae9 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/BeginReadOperationLogMessage.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We're starting a read operation on a track, ported from + * lib/algorithms/readerwriter.cc. + */ +public record BeginReadOperationLogMessage(int track, int head) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + r.header(String.format("R%2d.%d: ", track, head)); + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/BeginSpeedOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/BeginSpeedOperationLogMessage.java new file mode 100644 index 00000000..5f1cfa5e --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/BeginSpeedOperationLogMessage.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We're starting to measure the drive's rotational speed, ported from + * lib/algorithms/readerwriter.cc. + */ +public record BeginSpeedOperationLogMessage() implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + r.newline().add("Measuring rotational speed...").newline(); + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/BeginWriteOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/BeginWriteOperationLogMessage.java new file mode 100644 index 00000000..d3707c12 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/BeginWriteOperationLogMessage.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We're starting a write operation on a track, ported from + * lib/algorithms/readerwriter.cc. + */ +public record BeginWriteOperationLogMessage(int track, int head) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + r.header(String.format("W%2d.%d: ", track, head)); + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/DiskReadLogMessage.java b/java/com/cowlark/fluxengine/algorithms/DiskReadLogMessage.java new file mode 100644 index 00000000..950d2bc8 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/DiskReadLogMessage.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; +import com.cowlark.fluxengine.data.Disk; + +/** + * We've just read a disk, ported from lib/algorithms/readerwriter.cc. + */ +public record DiskReadLogMessage(Disk disk) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/EndOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/EndOperationLogMessage.java new file mode 100644 index 00000000..123dff65 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/EndOperationLogMessage.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We've finished a large-scale operation, ported from + * lib/algorithms/readerwriter.cc. + */ +public record EndOperationLogMessage(String message) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/EndReadOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/EndReadOperationLogMessage.java new file mode 100644 index 00000000..310e378d --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/EndReadOperationLogMessage.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We've finished a read operation on a track, ported from + * lib/algorithms/readerwriter.cc. + */ +public record EndReadOperationLogMessage() implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/EndSpeedOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/EndSpeedOperationLogMessage.java new file mode 100644 index 00000000..0890c3ef --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/EndSpeedOperationLogMessage.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We've just finished measuring the drive's rotational speed, ported from + * lib/algorithms/readerwriter.cc. + */ +public record EndSpeedOperationLogMessage(double rotationalPeriodNs) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + r.newline().add(String.format( + "Rotational period is %.1fms (%.1frpm)", + rotationalPeriodNs / 1e6, + 60e9 / rotationalPeriodNs)).newline(); + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/EndWriteOperationLogMessage.java b/java/com/cowlark/fluxengine/algorithms/EndWriteOperationLogMessage.java new file mode 100644 index 00000000..394d08ee --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/EndWriteOperationLogMessage.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * We've finished a write operation on a track, ported from + * lib/algorithms/readerwriter.cc. + */ +public record EndWriteOperationLogMessage() implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/Operation.java b/java/com/cowlark/fluxengine/algorithms/Operation.java index db920ae4..e28b467b 100644 --- a/java/com/cowlark/fluxengine/algorithms/Operation.java +++ b/java/com/cowlark/fluxengine/algorithms/Operation.java @@ -105,8 +105,8 @@ public double getDiskRotationalPeriodNs() { UsbDevice device = UsbFactory.reconnect(configProto); - Logger.log(new LogMessage.BeginOperationLogMessage("Measuring drive rotational speed")); - Logger.log(new LogMessage.BeginSpeedOperationLogMessage()); + Logger.log(new BeginOperationLogMessage("Measuring drive rotational speed")); + Logger.log(new BeginSpeedOperationLogMessage()); int retries = 5; do @@ -115,13 +115,13 @@ public double getDiskRotationalPeriodNs() device.getRotationalPeriod(configProto.getDrive().getHardSectorCount()); retries--; } while ((diskRotationalPeriodNs == 0) && (retries > 0)); - Logger.log(new LogMessage.EndOperationLogMessage("")); + Logger.log(new EndOperationLogMessage("")); } if (diskRotationalPeriodNs == 0) throw new FluxEngineException("Failed\nIs a disk in the drive?"); - Logger.log(new LogMessage.EndSpeedOperationLogMessage(diskRotationalPeriodNs)); + Logger.log(new EndSpeedOperationLogMessage(diskRotationalPeriodNs)); return diskRotationalPeriodNs; } diff --git a/java/com/cowlark/fluxengine/algorithms/OperationProgressLogMessage.java b/java/com/cowlark/fluxengine/algorithms/OperationProgressLogMessage.java new file mode 100644 index 00000000..7f81b348 --- /dev/null +++ b/java/com/cowlark/fluxengine/algorithms/OperationProgressLogMessage.java @@ -0,0 +1,16 @@ +package com.cowlark.fluxengine.algorithms; + +import com.cowlark.fluxengine.core.LogMessage; +import com.cowlark.fluxengine.core.LogRenderer; + +/** + * A large-scale operation has made progress, ported from + * lib/algorithms/readerwriter.cc. + */ +public record OperationProgressLogMessage(int progress) implements LogMessage +{ + @Override + public void render(LogRenderer r) + { + } +} diff --git a/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperation.java b/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperation.java index 0fdd6c05..f12f7f3c 100644 --- a/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperation.java +++ b/java/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperation.java @@ -116,8 +116,8 @@ public double getDiskRotationalPeriodNs() { UsbDevice device = UsbFactory.reconnect(configProto); - Logger.log(new LogMessage.BeginOperationLogMessage("Measuring drive rotational speed")); - Logger.log(new LogMessage.BeginSpeedOperationLogMessage()); + Logger.log(new BeginOperationLogMessage("Measuring drive rotational speed")); + Logger.log(new BeginSpeedOperationLogMessage()); int retries = 5; do @@ -126,13 +126,13 @@ public double getDiskRotationalPeriodNs() device.getRotationalPeriod(configProto.getDrive().getHardSectorCount()); retries--; } while ((diskRotationalPeriodNs == 0) && (retries > 0)); - Logger.log(new LogMessage.EndOperationLogMessage("")); + Logger.log(new EndOperationLogMessage("")); } if (diskRotationalPeriodNs == 0) throw new FluxEngineException("Failed\nIs a disk in the drive?"); - Logger.log(new LogMessage.EndSpeedOperationLogMessage(diskRotationalPeriodNs)); + Logger.log(new EndSpeedOperationLogMessage(diskRotationalPeriodNs)); return diskRotationalPeriodNs; } @@ -269,7 +269,7 @@ protected ReadGroupResult readGroup(Common.FluxSourceIteratorHolder fluxSourceIt /* Do the physical read. */ - Logger.log(new LogMessage.BeginReadOperationLogMessage(physicalCylinder, physicalHead)); + Logger.log(new BeginReadOperationLogMessage(physicalCylinder, physicalHead)); FluxSourceIterator fluxSourceIterator = fluxSourceIteratorHolder.getIterator(FluxReadParameters.builder() @@ -285,7 +285,7 @@ protected ReadGroupResult readGroup(Common.FluxSourceIteratorHolder fluxSourceIt continue; Fluxmap fluxmap = fluxSourceIterator.next(); - Logger.log(new LogMessage.EndReadOperationLogMessage()); + Logger.log(new EndReadOperationLogMessage()); Logger.logf("%d ms in %d bytes", (int) (fluxmap.durationNs() / 1e6), fluxmap.bytes()); Track flux = getDecoder().decodeToSectors(fluxmap, ptl); @@ -417,7 +417,7 @@ public void readDisk(Disk disk) k -> new ArrayList<>()).add(track); } - Logger.log(new LogMessage.BeginOperationLogMessage("Reading and decoding disk")); + Logger.log(new BeginOperationLogMessage("Reading and decoding disk")); disk.rotationalPeriodNs = getDiskRotationalPeriodNs(); @@ -431,7 +431,7 @@ public void readDisk(Disk disk) { CylinderHead logicalLocation = entry.getKey(); LogicalTrackLayout ltl = entry.getValue(); - Logger.log(new LogMessage.OperationProgressLogMessage( + Logger.log(new OperationProgressLogMessage( index * 100 / getDiskLayout().layoutByLogicalLocation.size())); index++; @@ -522,13 +522,17 @@ public void readDisk(Disk disk) allSectors.add(sector); allSectors = collectSectors(allSectors); disk.image = new Image(allSectors); + + /* Log a _copy_ of the disk structure so that the logger + * doesn't see the disk get mutated in subsequent reads. */ + Logger.log(new DiskReadLogMessage(new Disk(disk))); } } if (disk.image == null) disk.image = new Image(); - Logger.log(new LogMessage.EndOperationLogMessage("Read complete")); + Logger.log(new EndOperationLogMessage("Read complete")); } public Disk readDisk() @@ -549,7 +553,7 @@ private void writeTracks(Function producer, Predicate verifier, List logicalLocations) { - Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + Logger.log(new BeginOperationLogMessage("Encoding and writing to disk")); getDiskRotationalPeriodNs(); try (FluxSink fluxSink = getFluxSinkFactory().create()) @@ -557,7 +561,7 @@ private void writeTracks(Function producer, int index = 0; for (CylinderHead ch : logicalLocations) { - Logger.log(new LogMessage.OperationProgressLogMessage( + Logger.log(new OperationProgressLogMessage( index * 100 / logicalLocations.size())); index++; @@ -573,7 +577,7 @@ private void writeTracks(Function producer, int physicalCylinder = ltl.physicalCylinder + offset; int physicalHead = ltl.physicalHead; - Logger.log(new LogMessage.BeginWriteOperationLogMessage( + Logger.log(new BeginWriteOperationLogMessage( physicalCylinder, ltl.physicalHead)); @@ -603,7 +607,7 @@ private void writeTracks(Function producer, Logger.logf("erased"); } - Logger.log(new LogMessage.EndWriteOperationLogMessage()); + Logger.log(new EndWriteOperationLogMessage()); } if (verifier.test(ltl)) @@ -618,7 +622,7 @@ private void writeTracks(Function producer, } } - Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); + Logger.log(new EndOperationLogMessage("Write complete")); } public void rawWrite() @@ -640,7 +644,7 @@ private void writeTracks(Function producer, Predicate verifier, ImmutableSet logicalLocations) { - Logger.log(new LogMessage.BeginOperationLogMessage("Encoding and writing to disk")); + Logger.log(new BeginOperationLogMessage("Encoding and writing to disk")); getDiskRotationalPeriodNs(); try (FluxSink fluxSink = getFluxSinkFactory().create()) @@ -648,7 +652,7 @@ private void writeTracks(Function producer, int index = 0; for (CylinderHead ch : logicalLocations) { - Logger.log(new LogMessage.OperationProgressLogMessage( + Logger.log(new OperationProgressLogMessage( index * 100 / logicalLocations.size())); index++; @@ -664,7 +668,7 @@ private void writeTracks(Function producer, int physicalCylinder = ltl.physicalCylinder + offset; int physicalHead = ltl.physicalHead; - Logger.log(new LogMessage.BeginWriteOperationLogMessage( + Logger.log(new BeginWriteOperationLogMessage( physicalCylinder, ltl.physicalHead)); @@ -694,7 +698,7 @@ private void writeTracks(Function producer, Logger.logf("erased"); } - Logger.log(new LogMessage.EndWriteOperationLogMessage()); + Logger.log(new EndWriteOperationLogMessage()); } if (verifier.test(ltl)) @@ -709,7 +713,7 @@ private void writeTracks(Function producer, } } - Logger.log(new LogMessage.EndOperationLogMessage("Write complete")); + Logger.log(new EndOperationLogMessage("Write complete")); } private void writeTracks(Image image, ImmutableSet chs) diff --git a/java/com/cowlark/fluxengine/core/LogMessage.java b/java/com/cowlark/fluxengine/core/LogMessage.java index a86c5fd4..24775d0e 100644 --- a/java/com/cowlark/fluxengine/core/LogMessage.java +++ b/java/com/cowlark/fluxengine/core/LogMessage.java @@ -35,84 +35,4 @@ public void render(LogRenderer r) r.newline().add("Stop!").newline(); } } - - record BeginSpeedOperationLogMessage() implements LogMessage - { - @Override - public void render(LogRenderer r) - { - r.newline().add("Measuring rotational speed...").newline(); - } - } - - record EndSpeedOperationLogMessage(double rotationalPeriodNs) implements LogMessage - { - @Override - public void render(LogRenderer r) - { - r.newline().add(String.format( - "Rotational period is %.1fms (%.1frpm)", - rotationalPeriodNs / 1e6, - 60e9 / rotationalPeriodNs)).newline(); - } - } - - record BeginReadOperationLogMessage(int track, int head) implements LogMessage - { - @Override - public void render(LogRenderer r) - { - r.header(String.format("R%2d.%d: ", track, head)); - } - } - - record EndReadOperationLogMessage() implements LogMessage - { - @Override - public void render(LogRenderer r) - { - } - } - - record BeginWriteOperationLogMessage(int track, int head) implements LogMessage - { - @Override - public void render(LogRenderer r) - { - r.header(String.format("W%2d.%d: ", track, head)); - } - } - - record EndWriteOperationLogMessage() implements LogMessage - { - @Override - public void render(LogRenderer r) - { - } - } - - record BeginOperationLogMessage(String message) implements LogMessage - { - @Override - public void render(LogRenderer r) - { - } - } - - record EndOperationLogMessage(String message) implements LogMessage - { - @Override - public void render(LogRenderer r) - { - } - } - - record OperationProgressLogMessage(int progress) implements LogMessage - { - @Override - public void render(LogRenderer r) - { - } - } - } diff --git a/java/com/cowlark/fluxengine/data/Disk.java b/java/com/cowlark/fluxengine/data/Disk.java index 400fcdec..e1d4e1db 100644 --- a/java/com/cowlark/fluxengine/data/Disk.java +++ b/java/com/cowlark/fluxengine/data/Disk.java @@ -55,4 +55,14 @@ public Disk(Image image, DiskLayout diskLayout) } } } + + /* Creates a copy of the given disk, so that the copy doesn't see the + * original get mutated later. */ + public Disk(Disk disk) + { + tracksByPhysicalLocation.putAll(disk.tracksByPhysicalLocation); + sectorsByPhysicalLocation.putAll(disk.sectorsByPhysicalLocation); + image = disk.image; + rotationalPeriodNs = disk.rotationalPeriodNs; + } } diff --git a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel index f1aef694..54cbc5bd 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/algorithms/BUILD.bazel @@ -3,8 +3,8 @@ load("@rules_java//java:defs.bzl", "java_test") package(default_visibility = ["//visibility:public"]) java_test( - name = "ReadWriteFluxRxOperationTest", - srcs = ["ReadWriteFluxRxOperationTest.java"], + name = "ReadWriteFluxOperationTest", + srcs = ["ReadWriteFluxOperationTest.java"], deps = [ "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/algorithms", @@ -19,8 +19,8 @@ java_test( ) java_test( - name = "FluxRxOperationTest", - srcs = ["FluxRxOperationTest.java"], + name = "FluxOperationTest", + srcs = ["FluxOperationTest.java"], deps = [ "//javatests/com/cowlark/fluxengine/testing", "//java/com/cowlark/fluxengine/algorithms", diff --git a/javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperationTest.java b/javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperationTest.java similarity index 84% rename from javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperationTest.java rename to javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperationTest.java index d8c0644f..c3ee9ac7 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxRxOperationTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/ReadWriteFluxOperationTest.java @@ -22,11 +22,11 @@ import org.junit.runners.JUnit4; @RunWith(JUnit4.class) -public class ReadWriteFluxRxOperationTest +public class ReadWriteFluxOperationTest { @Rule public final TestRule loggerRule = TestHelpers.loggerRule(); - private static class TestOperation extends ReadWriteFluxRxOperation + private static class TestOperation extends ReadWriteFluxOperation { @Override public void run() @@ -81,7 +81,7 @@ public void collectSectorsDeduplicatesOkAndBad() sectors.add(makeSector(1, Sector.Status.OK)); sectors.add(makeSector(2, Sector.Status.BAD_CHECKSUM)); - List result = ReadWriteFluxRxOperation.collectSectors(sectors, true); + List result = ReadWriteFluxOperation.collectSectors(sectors, true); assertThat(result).hasSize(3); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -96,7 +96,7 @@ public void collectSectorsPrefersOkOverMissing() sectors.add(makeSector(0, Sector.Status.MISSING)); sectors.add(makeSector(0, Sector.Status.OK)); - List result = ReadWriteFluxRxOperation.collectSectors(sectors); + List result = ReadWriteFluxOperation.collectSectors(sectors); assertThat(result).hasSize(1); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -111,13 +111,13 @@ public void collectSectorsConflictWhenBothOkDifferentData() b.data = Bytes.of(2); /* collapseConflicts=false keeps both as CONFLICT. */ - List result = ReadWriteFluxRxOperation.collectSectors(List.of(a, b), false); + List result = ReadWriteFluxOperation.collectSectors(List.of(a, b), false); assertThat(result).hasSize(2); assertThat(result.get(0).status).isEqualTo(Sector.Status.CONFLICT); assertThat(result.get(1).status).isEqualTo(Sector.Status.CONFLICT); /* collapseConflicts=true collapses to a single CONFLICT. */ - List collapsed = ReadWriteFluxRxOperation.collectSectors(List.of(a, b), true); + List collapsed = ReadWriteFluxOperation.collectSectors(List.of(a, b), true); assertThat(collapsed).hasSize(1); assertThat(collapsed.get(0).status).isEqualTo(Sector.Status.CONFLICT); } @@ -130,7 +130,7 @@ public void collectSectorsOkDataSameCollapses() Sector b = makeSector(0, Sector.Status.OK); b.data = Bytes.of(1); - List result = ReadWriteFluxRxOperation.collectSectors(List.of(a, b), false); + List result = ReadWriteFluxOperation.collectSectors(List.of(a, b), false); assertThat(result).hasSize(1); assertThat(result.get(0).status).isEqualTo(Sector.Status.OK); @@ -144,10 +144,10 @@ public void combineRecordAndSectorsFillsMissing() track.allSectors = new ArrayList<>(); track.allSectors.add(makeSector(0, Sector.Status.OK)); - ReadOperation.CombinationResult cr = - ReadWriteFluxRxOperation.combineRecordAndSectors(List.of(track), makeLtl()); + ReadWriteFluxOperation.CombinationResult cr = + ReadWriteFluxOperation.combineRecordAndSectors(List.of(track), makeLtl()); - assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_BAD_SECTORS); + assertThat(cr.result).isEqualTo(ReadWriteFluxOperation.BadSectorsState.HAS_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); Sector s0 = @@ -170,10 +170,10 @@ public void combineRecordAndSectorsNoBadWhenAllPresent() track.allSectors.add(makeSector(1, Sector.Status.OK)); track.allSectors.add(makeSector(2, Sector.Status.OK)); - ReadOperation.CombinationResult cr = - ReadWriteFluxRxOperation.combineRecordAndSectors(List.of(track), makeLtl()); + ReadWriteFluxOperation.CombinationResult cr = + ReadWriteFluxOperation.combineRecordAndSectors(List.of(track), makeLtl()); - assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_NO_BAD_SECTORS); + assertThat(cr.result).isEqualTo(ReadWriteFluxOperation.BadSectorsState.HAS_NO_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); for (Sector sector : cr.sectors) assertThat(sector.status).isEqualTo(Sector.Status.OK); @@ -182,10 +182,10 @@ public void combineRecordAndSectorsNoBadWhenAllPresent() @Test public void combineRecordAndSectorsEmptyTrackIsBad() { - ReadOperation.CombinationResult cr = - ReadWriteFluxRxOperation.combineRecordAndSectors(List.of(), makeLtl()); + ReadWriteFluxOperation.CombinationResult cr = + ReadWriteFluxOperation.combineRecordAndSectors(List.of(), makeLtl()); - assertThat(cr.result).isEqualTo(ReadOperation.BadSectorsState.HAS_BAD_SECTORS); + assertThat(cr.result).isEqualTo(ReadWriteFluxOperation.BadSectorsState.HAS_BAD_SECTORS); assertThat(cr.sectors).hasSize(3); for (Sector sector : cr.sectors) assertThat(sector.status).isEqualTo(Sector.Status.MISSING); diff --git a/javatests/com/cowlark/fluxengine/core/BUILD.bazel b/javatests/com/cowlark/fluxengine/core/BUILD.bazel index 4087f905..fe3050e4 100644 --- a/javatests/com/cowlark/fluxengine/core/BUILD.bazel +++ b/javatests/com/cowlark/fluxengine/core/BUILD.bazel @@ -57,6 +57,7 @@ java_test( name = "LoggerTest", srcs = ["LoggerTest.java"], deps = [ + "//java/com/cowlark/fluxengine/algorithms", "//java/com/cowlark/fluxengine/core", "@maven//:com_google_truth_truth", "@maven//:junit_junit", @@ -67,6 +68,7 @@ java_test( name = "LogRendererTest", srcs = ["LogRendererTest.java"], deps = [ + "//java/com/cowlark/fluxengine/algorithms", "//java/com/cowlark/fluxengine/config", "//java/com/cowlark/fluxengine/config:config_java_proto", "//java/com/cowlark/fluxengine/core", diff --git a/javatests/com/cowlark/fluxengine/core/LogRendererTest.java b/javatests/com/cowlark/fluxengine/core/LogRendererTest.java index 87e6f479..231436de 100644 --- a/javatests/com/cowlark/fluxengine/core/LogRendererTest.java +++ b/javatests/com/cowlark/fluxengine/core/LogRendererTest.java @@ -2,12 +2,12 @@ import static com.google.common.truth.Truth.assertThat; +import com.cowlark.fluxengine.algorithms.BeginReadOperationLogMessage; +import com.cowlark.fluxengine.algorithms.BeginWriteOperationLogMessage; +import com.cowlark.fluxengine.algorithms.EndSpeedOperationLogMessage; import com.cowlark.fluxengine.config.OptionLogMessage; import com.cowlark.fluxengine.config.OptionProto; -import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; -import com.cowlark.fluxengine.core.LogMessage.BeginWriteOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.EmergencyStopMessage; -import com.cowlark.fluxengine.core.LogMessage.EndSpeedOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.ErrorLogMessage; import java.io.ByteArrayOutputStream; import java.io.PrintStream; diff --git a/javatests/com/cowlark/fluxengine/core/LoggerTest.java b/javatests/com/cowlark/fluxengine/core/LoggerTest.java index 1571c160..74e562a3 100644 --- a/javatests/com/cowlark/fluxengine/core/LoggerTest.java +++ b/javatests/com/cowlark/fluxengine/core/LoggerTest.java @@ -2,7 +2,7 @@ import static com.google.common.truth.Truth.assertThat; -import com.cowlark.fluxengine.core.LogMessage.BeginReadOperationLogMessage; +import com.cowlark.fluxengine.algorithms.BeginReadOperationLogMessage; import com.cowlark.fluxengine.core.LogMessage.ErrorLogMessage; import com.cowlark.fluxengine.core.LogMessage.StringMessage; import java.io.ByteArrayOutputStream; From e33e83c0d726a214b469d124833922a384bf83e5 Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 13 Aug 2026 00:20:05 +0200 Subject: [PATCH 183/192] Fix option setting so that keys with no dots can be set. --- .../fluxengine/config/ConfigBuilder.java | 8 ++++ .../fluxengine/config/ConfigFlagGroup.java | 47 ++++++++++++------- .../cowlark/fluxengine/config/ProtoPath.java | 23 ++++----- .../fluxengine/config/ConfigBuilderTest.java | 28 +++++++++++ 4 files changed, 78 insertions(+), 28 deletions(-) diff --git a/java/com/cowlark/fluxengine/config/ConfigBuilder.java b/java/com/cowlark/fluxengine/config/ConfigBuilder.java index 45235237..58e8e800 100644 --- a/java/com/cowlark/fluxengine/config/ConfigBuilder.java +++ b/java/com/cowlark/fluxengine/config/ConfigBuilder.java @@ -280,6 +280,14 @@ public ConfigBuilder set(String key, String value) return this; } + /* Returns the value of the config key at the given path, or throws a + * ProtoPathNotFoundException if it isn't a real config field, ported from + * Config::get. */ + public String get(String key) + { + return ProtoPath.get(proto, key); + } + /* Looks up an option by name, ported from Config::findOption. The group * value parameter of the C++ version is not needed here, so it takes a * key only. */ diff --git a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java index 0283a62f..683ef43e 100644 --- a/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java +++ b/java/com/cowlark/fluxengine/config/ConfigFlagGroup.java @@ -33,30 +33,43 @@ public Flag findFlag(String key) if (key.startsWith("--")) { String path = key.substring(2); - if (key.contains(".")) + try { - /* Dots: setting a config key. */ + /* This is a config key. */ + builder.get(path); return ActionFlag.builder() .setGroup(this) .setValueCallback(value -> builder.set(path, value)) .build(); - } else + } catch (ProtoPathNotFoundException e) { - /* No dots: this is an option name; look it up (throws if - * unknown). */ - ConfigBuilder.OptionInfo option = builder.findOption(path); - if (option.usesValue()) - return ActionFlag.builder() - .setGroup(this) - .setValueCallback(arg -> builder.applyOption(option, arg)) - .build(); - else - return ActionFlag.builder() - .setGroup(this) - .setVoidCallback(() -> builder.applyOption(option, null)) - .build(); + /* Not a config key. */ } } - return super.findFlag(key); + + /* Look for a registered flag (e.g. --config, --show-config). */ + Flag flag = super.findFlag(key); + if (flag != null) + return flag; + + if (key.startsWith("--")) + { + /* Not a config key or registered flag: this is an option name; + * look it up (throws if unknown). */ + String path = key.substring(2); + ConfigBuilder.OptionInfo option = builder.findOption(path); + if (option.usesValue()) + return ActionFlag.builder() + .setGroup(this) + .setValueCallback(arg -> builder.applyOption(option, arg)) + .build(); + else + return ActionFlag.builder() + .setGroup(this) + .setVoidCallback(() -> builder.applyOption(option, null)) + .build(); + } + + return null; } } diff --git a/java/com/cowlark/fluxengine/config/ProtoPath.java b/java/com/cowlark/fluxengine/config/ProtoPath.java index f0389210..a15baac3 100644 --- a/java/com/cowlark/fluxengine/config/ProtoPath.java +++ b/java/com/cowlark/fluxengine/config/ProtoPath.java @@ -28,7 +28,9 @@ public static void set(Message.Builder builder, String path, String value) } /* Resolves a dotted path against a message and returns the leaf value as - * a string, ported from lib/config/proto.cc's findProtoPath/get. */ + * a string, ported from lib/config/proto.cc's findProtoPath/get. Throws a + * ProtoPathNotFoundException if the path doesn't correspond to a real + * config field. */ public static String get(Message.Builder builder, String path) { List components = parsePath(path); @@ -57,20 +59,19 @@ private static String getRecursive(Message.Builder builder, if (field.isRepeated()) { int index = requireIndex(component, field); - if (builder.getRepeatedFieldCount(field) <= index) - throw new ProtoPathNotFoundException( - "could not find config field '" + field.getName() + "'"); - Message element = (Message) builder.getRepeatedField(field, index); - elementBuilder = element.toBuilder(); + if (builder.getRepeatedFieldCount(field) > index) + elementBuilder = ((Message) builder.getRepeatedField(field, index)).toBuilder(); + else + elementBuilder = builder.newBuilderForField(field); } else { if (component.index() >= 0) throw new ProtoPathNotFoundException("config field '" + component.name() + "' is not repeated but an index is provided"); - if (!builder.hasField(field)) - throw new ProtoPathNotFoundException( - "could not find config field '" + field.getName() + "'"); - elementBuilder = ((Message) builder.getField(field)).toBuilder(); + if (builder.hasField(field)) + elementBuilder = ((Message) builder.getField(field)).toBuilder(); + else + elementBuilder = builder.newBuilderForField(field); } return getRecursive(elementBuilder, path, pos + 1, originalPath); } @@ -124,7 +125,7 @@ private static List parsePath(String path) { Matcher matcher = PATH_COMPONENT.matcher(token); if (!matcher.matches()) - throw new ConfigException("invalid config path '" + path + "'"); + throw new ProtoPathNotFoundException("invalid config path '" + path + "'"); String index = matcher.group(2); components.add(new PathComponent( matcher.group(1), diff --git a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java index 76152f58..28528ab8 100644 --- a/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java +++ b/javatests/com/cowlark/fluxengine/config/ConfigBuilderTest.java @@ -183,6 +183,34 @@ public void fromFlagsConfigKeySetsValue() assertThat(builder.build().getDrive().getDrive()).isEqualTo(1); } + @Test + public void fromFlagsConfigKeyWithoutDotSetsValue() + { + /* A config key which doesn't have a dot (e.g. --tracks) is also a + * config path, not an option. */ + ConfigBuilder builder = builder(); + + builder.fromFlags(ImmutableList.of("--tracks=c0-80h0-1"), new FlagGroup()); + + assertThat(builder.build().getTracks()).isEqualTo("c0-80h0-1"); + } + + @Test + public void getReturnsConfigValue() + { + ConfigBuilder builder = builder().set("tracks", "c0-80h0-1"); + + assertThat(builder.get("tracks")).isEqualTo("c0-80h0-1"); + } + + @Test + public void getOnUnknownKeyThrows() + { + ConfigBuilder builder = builder(); + + assertThrows(ProtoPathNotFoundException.class, () -> builder.get("nosuchfield")); + } + @Test public void applyOptionIsCallable() { From 101c9fb22c94610a71a51788fe2b8a8cb441481f Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 13 Aug 2026 00:33:30 +0200 Subject: [PATCH 184/192] Port the fluxfile utilities. --- java/com/cowlark/fluxengine/cli/BUILD.bazel | 1 + java/com/cowlark/fluxengine/cli/Command.java | 6 +- .../fluxengine/cli/FluxfileCpCommand.java | 112 ++++++++++++++++++ .../fluxengine/cli/FluxfileLsCommand.java | 87 ++++++++++++++ .../fluxengine/cli/FluxfileRmCommand.java | 82 +++++++++++++ .../fluxengine/fluxsink/Fl2FluxSink.java | 2 +- .../fluxengine/fluxsource/Fl2FluxSource.java | 2 +- 7 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 java/com/cowlark/fluxengine/cli/FluxfileCpCommand.java create mode 100644 java/com/cowlark/fluxengine/cli/FluxfileLsCommand.java create mode 100644 java/com/cowlark/fluxengine/cli/FluxfileRmCommand.java diff --git a/java/com/cowlark/fluxengine/cli/BUILD.bazel b/java/com/cowlark/fluxengine/cli/BUILD.bazel index 41d1ce88..c76fb27b 100644 --- a/java/com/cowlark/fluxengine/cli/BUILD.bazel +++ b/java/com/cowlark/fluxengine/cli/BUILD.bazel @@ -18,6 +18,7 @@ java_library( "//java/com/cowlark/fluxengine/decoders:decoders_java_proto", "//java/com/cowlark/fluxengine/encoders", "//java/com/cowlark/fluxengine/external", + "//java/com/cowlark/fluxengine/external:fl2_java_proto", "//java/com/cowlark/fluxengine/fluxsink", "//java/com/cowlark/fluxengine/fluxsource", "//java/com/cowlark/fluxengine/gui", diff --git a/java/com/cowlark/fluxengine/cli/Command.java b/java/com/cowlark/fluxengine/cli/Command.java index 46b26904..42983b91 100644 --- a/java/com/cowlark/fluxengine/cli/Command.java +++ b/java/com/cowlark/fluxengine/cli/Command.java @@ -17,9 +17,9 @@ public interface Command ImmutableMap> FLUXFILEABLES = ImmutableMap.>builder() - .put("ls", stub("ls", "Lists the contents of a flux file.")) - .put("rm", stub("rm", "Removes flux from a flux file.")) - .put("cp", stub("cp", "Copies flux from one flux file to another.")) + .put("ls", FluxfileLsCommand::new) + .put("rm", FluxfileRmCommand::new) + .put("cp", FluxfileCpCommand::new) .build(); ImmutableMap> TESTABLES = diff --git a/java/com/cowlark/fluxengine/cli/FluxfileCpCommand.java b/java/com/cowlark/fluxengine/cli/FluxfileCpCommand.java new file mode 100644 index 00000000..ce96775d --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/FluxfileCpCommand.java @@ -0,0 +1,112 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.core.flags.Flags.parse; + +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Locations; +import com.cowlark.fluxengine.external.FluxFileProto; +import com.cowlark.fluxengine.external.TrackFluxProto; +import com.cowlark.fluxengine.fluxsink.Fl2FluxSink; +import com.cowlark.fluxengine.fluxsource.Fl2FluxSource; +import com.google.common.collect.ImmutableList; + +/** + * Copies flux from one flux file to another, modelled after + * src/fe-fluxfilecp.cc. + */ +public class FluxfileCpCommand implements Command +{ + private FlagGroup flags = new FlagGroup(); + private ValueFlag inputFilenameFlag = StringFlag.builder() + .setGroup(flags) + .setName("--input") + .setName("-i") + .setHelpText("input flux file") + .build(); + private ValueFlag outputFilenameFlag = StringFlag.builder() + .setGroup(flags) + .setName("--output") + .setName("-o") + .setHelpText("output flux file (must exist)") + .build(); + private ValueFlag tracksFlag = StringFlag.builder() + .setGroup(flags) + .setName("--tracks") + .setName("-t") + .setHelpText("tracks to copy") + .build(); + + private static TrackFluxProto findTrack(FluxFileProto f, int cylinder, int head) + { + for (TrackFluxProto trackFlux : f.getTrackList()) + if ((trackFlux.getTrack() == cylinder) && (trackFlux.getHead() == head)) + return trackFlux; + + return null; + } + + private static TrackFluxProto.Builder findOrMakeTrack(FluxFileProto.Builder f, + int cylinder, + int head) + { + for (TrackFluxProto.Builder trackFlux : f.getTrackBuilderList()) + if ((trackFlux.getTrack() == cylinder) && (trackFlux.getHead() == head)) + return trackFlux; + + TrackFluxProto.Builder tf = f.addTrackBuilder(); + tf.setTrack(cylinder); + tf.setHead(head); + return tf; + } + + @Override + public String getHelp() + { + return "Copies flux from one flux file to another."; + } + + @Override + public void run(ImmutableList args) + { + parse(args, flags); + if (!inputFilenameFlag.isSet()) + throw new FluxEngineException("you must specify an input filename with -i"); + if (!outputFilenameFlag.isSet()) + throw new FluxEngineException("you must specify an output filename with -o"); + + System.out.println(inputFilenameFlag.get() + " -> " + outputFilenameFlag.get() + ":"); + FluxFileProto inf = Fl2FluxSource.loadFl2File(inputFilenameFlag.get()); + FluxFileProto outf = Fl2FluxSource.loadFl2File(outputFilenameFlag.get()); + + boolean changed = false; + FluxFileProto.Builder outBuilder = outf.toBuilder(); + for (CylinderHead location : Locations.parseCylinderHeadsString(tracksFlag.get())) + { + TrackFluxProto intrack = findTrack(inf, location.cylinder(), location.head()); + if (intrack == null) + { + System.out.println(" location c" + location.cylinder() + "h" + location.head() + + " not found"); + continue; + } + + TrackFluxProto.Builder outtrack = + findOrMakeTrack(outBuilder, location.cylinder(), location.head()); + System.out.println(" copying c" + location.cylinder() + "h" + location.head()); + for (int i = 0; i < intrack.getFluxCount(); i++) + outtrack.addFlux(intrack.getFlux(i)); + changed = true; + } + + if (changed) + { + System.out.println("writing back output file"); + Fl2FluxSink.saveFl2File(outputFilenameFlag.get(), outBuilder); + } else + System.out.println("output file not modified"); + } +} diff --git a/java/com/cowlark/fluxengine/cli/FluxfileLsCommand.java b/java/com/cowlark/fluxengine/cli/FluxfileLsCommand.java new file mode 100644 index 00000000..e4e19b2a --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/FluxfileLsCommand.java @@ -0,0 +1,87 @@ +package com.cowlark.fluxengine.cli; + +import static com.cowlark.fluxengine.core.flags.Flags.parse; + +import com.cowlark.fluxengine.core.Bytes; +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.Fluxmap; +import com.cowlark.fluxengine.external.FluxFileProto; +import com.cowlark.fluxengine.external.TrackFluxProto; +import com.cowlark.fluxengine.fluxsource.Fl2FluxSource; +import com.google.common.collect.ImmutableList; + +/** + * Lists the contents of a flux file, modelled after src/fe-fluxfilels.cc. + */ +public class FluxfileLsCommand implements Command +{ + private FlagGroup flags = new FlagGroup(); + private ValueFlag fluxFilename = StringFlag.builder() + .setGroup(flags) + .setName("--fluxfile") + .setName("-f") + .setHelpText("flux file to show") + .build(); + + @Override + public String getHelp() + { + return "Lists the contents of a flux file."; + } + + @Override + public void run(ImmutableList args) + { + parse(args, flags); + if (!fluxFilename.isSet()) + throw new FluxEngineException("you must specify a filename with -f"); + + System.out.println(fluxFilename.get() + ":"); + FluxFileProto f = Fl2FluxSource.loadFl2File(fluxFilename.get()); + + String[] fields = {"version", "rotational_period_ms", "drive_type", "format_type"}; + for (String field : fields) + { + String value; + switch (field) + { + case "version": + value = f.getVersion().name(); + break; + case "rotational_period_ms": + value = Double.toString(f.getRotationalPeriodMs()); + break; + case "drive_type": + value = f.getDriveType().name(); + break; + case "format_type": + value = f.getFormatType().name(); + break; + default: + throw new IllegalStateException(); + } + System.out.println(" " + field + ": " + value); + } + + for (TrackFluxProto trackFlux : f.getTrackList()) + { + System.out.print( + " flux for c" + trackFlux.getTrack() + "h" + trackFlux.getHead() + ":"); + + boolean first = true; + for (int i = 0; i < trackFlux.getFluxCount(); i++) + { + Fluxmap fluxmap = new Fluxmap(new Bytes(trackFlux.getFlux(i).toByteArray())); + if (!first) + System.out.print(","); + System.out.printf(" %.3fms", fluxmap.durationNs() / 1000000.0); + first = false; + } + + System.out.println(); + } + } +} diff --git a/java/com/cowlark/fluxengine/cli/FluxfileRmCommand.java b/java/com/cowlark/fluxengine/cli/FluxfileRmCommand.java new file mode 100644 index 00000000..9ea709dc --- /dev/null +++ b/java/com/cowlark/fluxengine/cli/FluxfileRmCommand.java @@ -0,0 +1,82 @@ +package com.cowlark.fluxengine.cli; + +import com.cowlark.fluxengine.core.FluxEngineException; +import com.cowlark.fluxengine.core.flags.FlagGroup; +import com.cowlark.fluxengine.core.flags.Flags; +import com.cowlark.fluxengine.core.flags.StringFlag; +import com.cowlark.fluxengine.core.flags.ValueFlag; +import com.cowlark.fluxengine.data.CylinderHead; +import com.cowlark.fluxengine.data.Locations; +import com.cowlark.fluxengine.external.FluxFileProto; +import com.cowlark.fluxengine.external.TrackFluxProto; +import com.cowlark.fluxengine.fluxsink.Fl2FluxSink; +import com.cowlark.fluxengine.fluxsource.Fl2FluxSource; +import com.google.common.collect.ImmutableList; + +/** + * Removes flux from a flux file, modelled after src/fe-fluxfilerm.cc. + */ +public class FluxfileRmCommand implements Command +{ + private FlagGroup flags = new FlagGroup(); + private ValueFlag fluxFilename = StringFlag.builder() + .setGroup(flags) + .setName("--fluxfile") + .setName("-f") + .setHelpText("flux file to remove from") + .build(); + private ValueFlag tracksFlag = StringFlag.builder() + .setGroup(flags) + .setName("--tracks") + .setName("-t") + .setHelpText("tracks to remove") + .build(); + + @Override + public String getHelp() + { + return "Removes flux from a flux file."; + } + + @Override + public void run(ImmutableList args) + { + Flags.parse(args, flags); + if (!fluxFilename.isSet()) + throw new FluxEngineException("you must specify a filename with -f"); + + System.out.println(fluxFilename.get() + ":"); + FluxFileProto f = Fl2FluxSource.loadFl2File(fluxFilename.get()); + + boolean changed = false; + FluxFileProto.Builder builder = f.toBuilder(); + for (CylinderHead location : Locations.parseCylinderHeadsString(tracksFlag.get())) + { + boolean found = false; + for (int i = 0; i < builder.getTrackCount(); i++) + { + TrackFluxProto trackFlux = builder.getTrack(i); + if ((trackFlux.getTrack() == location.cylinder()) && + (trackFlux.getHead() == location.head())) + { + System.out.println( + " removing c" + location.cylinder() + "h" + location.head()); + builder.removeTrack(i); + found = changed = true; + i--; + } + } + + if (!found) + System.out.println(" location c" + location.cylinder() + "h" + location.head() + + " not found"); + } + + if (changed) + { + System.out.println("writing back file"); + Fl2FluxSink.saveFl2File(fluxFilename.get(), builder); + } else + System.out.println("file not modified"); + } +} diff --git a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java index 66e44b4b..99c47021 100644 --- a/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java +++ b/java/com/cowlark/fluxengine/fluxsink/Fl2FluxSink.java @@ -45,7 +45,7 @@ public Fl2FluxSink(String filename, ConfigProto config) } } - private static void saveFl2File(String filename, FluxFileProto.Builder proto) + public static void saveFl2File(String filename, FluxFileProto.Builder proto) { proto.setMagic(FluxMagic.MAGIC.getNumber()); proto.setVersion(FluxFileVersion.VERSION_2); diff --git a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java index 73667438..b6c2bf52 100644 --- a/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java +++ b/java/com/cowlark/fluxengine/fluxsource/Fl2FluxSource.java @@ -45,7 +45,7 @@ public Fl2FluxSource(Fl2FluxSourceProto config) extraConfig = builder.build(); } - private static FluxFileProto loadFl2File(String filename) + public static FluxFileProto loadFl2File(String filename) { Bytes data; try From d23ad7e4bed56cfed7b48287daebb2623d0322ce Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 13 Aug 2026 01:07:31 +0200 Subject: [PATCH 185/192] Play with menus a bit. They're horrible in Swing. --- java/com/cowlark/fluxengine/gui/Gui.java | 1 + .../com/cowlark/fluxengine/gui/NewJFrame.form | 35 +++++++++++++++++++ .../com/cowlark/fluxengine/gui/NewJFrame.java | 33 +++++++++++++++++ 3 files changed, 69 insertions(+) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index e2264494..c72f6c78 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -15,6 +15,7 @@ public class Gui public void run(ImmutableList args) throws Exception { UIManager.setLookAndFeel(new FlatDarkLaf()); + System.setProperty("apple.laf.useScreenMenuBar", "true"); SwingUtilities.invokeLater(() -> { createAndShowGui(); }); diff --git a/java/com/cowlark/fluxengine/gui/NewJFrame.form b/java/com/cowlark/fluxengine/gui/NewJFrame.form index 3e6ca105..70784fd2 100644 --- a/java/com/cowlark/fluxengine/gui/NewJFrame.form +++ b/java/com/cowlark/fluxengine/gui/NewJFrame.form @@ -8,11 +8,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com/cowlark/fluxengine/gui/NewJFrame.java b/java/com/cowlark/fluxengine/gui/NewJFrame.java index 8631fc91..46092448 100644 --- a/java/com/cowlark/fluxengine/gui/NewJFrame.java +++ b/java/com/cowlark/fluxengine/gui/NewJFrame.java @@ -35,7 +35,12 @@ private void initComponents() { jRadioButton1 = new javax.swing.JRadioButton(); jMenuBar1 = new javax.swing.JMenuBar(); jMenu1 = new javax.swing.JMenu(); + jMenuItem1 = new javax.swing.JMenuItem(); jMenu2 = new javax.swing.JMenu(); + cutMenuItem = new javax.swing.JMenuItem(); + copyMenuItem = new javax.swing.JMenuItem(); + pasteMenuItem = new javax.swing.JMenuItem(); + deleteMenuItem = new javax.swing.JMenuItem(); setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); getContentPane().setLayout(new java.awt.GridBagLayout()); @@ -56,9 +61,28 @@ private void initComponents() { getContentPane().add(jRadioButton1, gridBagConstraints); jMenu1.setText("File"); + + jMenuItem1.setText("Exit"); + jMenuItem1.addActionListener(this::jMenuItem1ActionPerformed); + jMenu1.add(jMenuItem1); + jMenuBar1.add(jMenu1); jMenu2.setText("Edit"); + + cutMenuItem.setIcon(javax.swing.UIManager.getIcon("Actions.cut")); + cutMenuItem.setText("Cut"); + jMenu2.add(cutMenuItem); + + copyMenuItem.setText("Copy"); + jMenu2.add(copyMenuItem); + + pasteMenuItem.setText("Paste"); + jMenu2.add(pasteMenuItem); + + deleteMenuItem.setText("Delete"); + jMenu2.add(deleteMenuItem); + jMenuBar1.add(jMenu2); setJMenuBar(jMenuBar1); @@ -66,6 +90,10 @@ private void initComponents() { pack(); }// //GEN-END:initComponents + private void jMenuItem1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem1ActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_jMenuItem1ActionPerformed + /** * @param args the command line arguments */ @@ -92,12 +120,17 @@ public static void main(String args[]) { } // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JMenuItem copyMenuItem; + private javax.swing.JMenuItem cutMenuItem; + private javax.swing.JMenuItem deleteMenuItem; private javax.swing.JButton jButton1; private javax.swing.JLabel jLabel1; private javax.swing.JMenu jMenu1; private javax.swing.JMenu jMenu2; private javax.swing.JMenuBar jMenuBar1; + private javax.swing.JMenuItem jMenuItem1; private javax.swing.JRadioButton jRadioButton1; private javax.swing.JTextField jTextField1; + private javax.swing.JMenuItem pasteMenuItem; // End of variables declaration//GEN-END:variables } From 4e7568e5a0485503f11c567b40202db2b82bf33c Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 13 Aug 2026 01:45:39 +0200 Subject: [PATCH 186/192] Integrate SwingTree. --- .opencode/skill/swingtree/SKILL.md | 1633 +++++++++++++++++ MODULE.bazel | 2 + java/com/cowlark/fluxengine/gui/BUILD.bazel | 2 + java/com/cowlark/fluxengine/gui/Gui.java | 43 +- .../com/cowlark/fluxengine/gui/NewJFrame.form | 119 -- .../com/cowlark/fluxengine/gui/NewJFrame.java | 136 -- 6 files changed, 1668 insertions(+), 267 deletions(-) create mode 100644 .opencode/skill/swingtree/SKILL.md delete mode 100644 java/com/cowlark/fluxengine/gui/NewJFrame.form delete mode 100644 java/com/cowlark/fluxengine/gui/NewJFrame.java diff --git a/.opencode/skill/swingtree/SKILL.md b/.opencode/skill/swingtree/SKILL.md new file mode 100644 index 00000000..256a7c87 --- /dev/null +++ b/.opencode/skill/swingtree/SKILL.md @@ -0,0 +1,1633 @@ +--- +name: swingtree +description: > + Write Swing desktop UIs with the SwingTree library and its companion property + library Sprouts. Use this skill whenever you are building, editing, reviewing, + or debugging Java Swing GUI code that imports `swingtree.UI` or `sprouts.*` — + declarative component trees, convergent/responsive/reactive layouts, the + functional style API, SVG icons, animations, and MVI/MVL or MVVM view models. +--- + +# Writing SwingTree Applications + +SwingTree is a Java library for building **Swing** desktop GUIs **declaratively**, +the way Flutter / SwiftUI / Jetpack Compose / JetBrains' Kotlin UI DSL build +theirs. You describe the component tree with **method chaining + nesting**, bind +it to state with the **Sprouts** property library (`Var`/`Val`/`Vars`/`Tuple`), +and paint it with a **functional, immutable style API**. There is no XML, no FXML, +no separate template language — it is all plain Java, fully type-safe and +debuggable. + +This document gives you the intuition to write *any* SwingTree app: the builder, +layout, properties & lenses, the two architecture patterns (MVI/MVL and classic +MVVM), events, styling, animation, tables, icons & SVG, dialogs, and the +non-obvious gotchas that bite people. Read it top to bottom once; thereafter use the cheat sheet at +the end. + +> **One library-wide preference up front: SwingTree views are expected to be +> *convergent*** — usable whether the window is maximised on an ultrawide or +> tiled into a tall, 500-pixel strip. Users run tiling window managers, snap +> windows to halves, and rotate monitors into portrait; a view that only works +> at the size you developed it in is considered broken. §2c is the short +> version and the checklist; apply it to every view you write or review. + +--- + +## 0. The one import and the mental model + +```java +import swingtree.UI; +import static swingtree.UI.*; // brings panel(), button(), FILL, WRAP, GROW, ... +``` + +A UI is a **tree of components**. Every node is built by a `UI.xxx(..)` **factory** +that returns a **builder** (`UIForPanel`, `UIForButton`, `UIForLabel`, … all +subtypes of `UIForAnySwing`). On a builder you: + +- **configure** it with chained `withXyz(..)` / `isXyzIf(..)` calls, +- **nest children** with `.add(..)`, +- **bind** it to `Var`/`Val` properties for reactivity, +- **style** it with `.withStyle(it -> ...)`, +- **wire events** with `.onXyz(..)`, +- and finally **unwrap** it with `.get(JPanel.class)` or hand it to `UI.show(..)`. + +Crucial idea: **a builder is a recipe, not the component.** It produces a real +`JComponent` underneath. You can escape to the raw component with `.peek(c -> ...)` +or unwrap with `.get(Type.class)` — but treat `peek` as a last resort (§12): reach +for a SwingTree `with*`/`is*If`/`on*` method first. + +The smallest complete program: + +```java +import static swingtree.UI.*; + +public static void main(String[] args) { + UI.show( + panel("wrap 1") + .add(label("Welcome to SwingTree!")) + .add(button("Click me").onClick(it -> System.out.println("clicked"))) + ); +} +``` + +--- + +## 1. Growing the tree — factories, nesting, `add` + +`UI.show(component | builder | title, builder | Function)` +opens a window. Inside it you compose nodes: + +```java +UI.show( + panel("wrap 2") // a JPanel, MigLayout "wrap 2" + .add(label("Name:")) + .add("grow", textField("John")) // first String arg = per-child layout constraint + .add(label("Age:")) + .add("grow", textField("42")) + .add("span", separator()) // span all columns, then wrap + .add(button("Save")) +); +``` + +Rules of `.add(..)`: + +- `.add(childBuilder)` — add with no constraint. +- `.add("growx, span 2", childBuilder)` — first arg is a **MigLayout add-constraint string**. +- `.add(GROW.and(SPAN), childBuilder)` — or a **type-safe constraint** (see §2). +- `.add(a, b, c)` — add several children at once (same constraint applies to each). + +### Common factories (each returns a builder) + +| Factory | Component | +|---|---| +| `panel(...)`, `box(...)` | `JPanel` / `JBox` (a transparent, insets-free panel — perfect for grouping) | +| `label(text)`, `html("

..

")` | `JLabel` (html(..) renders HTML) | +| `button(text)`, `toggleButton(text)`, `checkBox(text)`, `radioButton(text)` | buttons | +| `textField(text)`, `textArea(text)`, `passwordField()`, `numericTextField(var)` | text inputs | +| `comboBox(...)`, `slider(Align, min, max)`, `spinner(...)`, `progressBar(...)` | value pickers | +| `separator()`, `scrollPane()`, `scrollPanels()`, `splitPane(Align)`, `tabbedPane()` | structure | +| `table(Var)`, `table()`, `list(...)`, `menu(...)`, `menuItem(...)`, `splitButton(text)` | data / menus (bind a `TableData` value — see §10) | +| `icon(path)`, `icon(w,h,path)` | `JIcon` (supports SVG, see §10) | + +`box(...)` vs `panel(...)`: a `JBox` is non-opaque with zero default insets — use it +for invisible structural grouping; use `panel` when you want a real surface to +style. **Never call `setOpaque(..)` yourself on a styled component — the style +engine owns opacity and will fight you.** + +### Wrapping a custom / third-party component + +```java +.add( UI.of(new MyCustomJComponent()).onMouseClick(it -> ...) ) +``` + +`UI.of(jcomponent)` wraps any `JComponent` so the declaration keeps flowing. +`UI.of(this)` is the standard way to start a `View extends JPanel` (see §5). + +--- + +## 2. Layout — MigLayout, type-safe constants, and convergence + +SwingTree's default layout manager is **MigLayout**; §2a and §2b are the two ways +to drive it. §2c onwards is about making the result work at **any** window size, +which SwingTree treats as the default expectation rather than a nice-to-have. + +### 2a. String constraints (most common, terse) + +The **container** constraint goes in the factory; **per-child** constraints go as +the first `add(..)` arg: + +```java +panel("fill, wrap 3, insets 12, gap 8") // container: fill space, 3 cols, 12px insets +.add("growx", a) +.add("span 2, growx", b) // this child spans 2 columns +.add("wrap", c) // force a new row after c +``` + +Memorize these MigLayout keywords: +- Container: `fill`, `fillx`, `filly`, `wrap N` (N columns), `insets T L B R` / `ins N`, `gap`, `debug` (draws guide borders — great for diagnosing layout). +- Per-child: `grow`, `growx`, `growy`, `push`, `pushx`, `pushy`, `span` / `span N`, `wrap`, `align center/left/right`, `top/bottom`, `width 60px::`, `w 180!`, `h 90!`. +- `60px::` means "min 60, no max"; `180!` means "exactly 180". + +`withLayout("fill, wrap 2")` sets the container constraint after the fact, and +`withLayout(layout, colConstraints, rowConstraints)` gives full control, e.g. +`.withLayout("fill, wrap 2", "[grow 60][grow 40]")`. + +Full keyword reference: http://www.miglayout.com/ + +### 2b. Type-safe constants (refactor-safe, composable) + +`import static swingtree.UI.*` exposes constants that compose with `.and(..)`: + +```java +of(this).withLayout(FILL.and(WRAP(1)).and(INS(16))) +.add(GROW.and(PUSH), child) +.add(CENTER.and(SPAN), html("

Title

")) +.add(RIGHT, button("OK")); +``` + +Container constants: `FILL`, `FILL_X`, `FILL_Y`, `WRAP(n)`, `INS(n)` / `INS(t,l,b,r)`, `GAP_REL(n)`, `FLOW_X`, `DEBUG`. +Per-child constants: `GROW`, `GROW_X`, `GROW_Y`, `PUSH`, `PUSH_X`, `PUSH_Y`, `SPAN`, `SPAN(n)`, `WRAP`, `SHRINK`, `CENTER`, `LEFT`, `RIGHT`, `TOP`, `BOTTOM`, `ALIGN_CENTER`, `ALIGN_LEFT`, `ALIGN_X_CENTER`, `ALIGN_Y_TOP`, `GAP_LEFT(n)`, … + +String constraints and constants are interchangeable — pick whichever reads +clearer locally. (Examples in this codebase mix both freely.) + +### 2c. Convergence — a SwingTree view is expected to survive any window shape + +**This is a strong preference of the library, not an optional polish step. Write +convergent views by default; treat "it only works maximised on a landscape +monitor" as a bug.** + +Desktop windows are not a fixed size any more. Users run tiling window managers +(i3, sway, Hyprland, yabai, AeroSpace, FancyZones), snap windows to halves and +thirds, put four windows across an ultrawide, rotate a monitor into portrait, +and drag your app onto a smaller second screen. A view that assumes ~1400×900 +is broken for a large fraction of real users. + +**Convergent** ≠ merely responsive. Responsive means nothing *overlaps*; +convergent means nothing is *lost* — the layout rearranges, the content +re-prioritises, and the primary action stays reachable at every size. + +#### The default recipe (start every page like this) + +```java +UI.scrollPane( conf -> conf.fitWidth(true) ) // the page may outgrow the window +.withHorizontalScrollBarPolicy(UI.Active.NEVER) // never scroll sideways +.withVerticalScrollIncrement(24) +.add( + UI.panel().withFlowLayout(UI.HorizontalAlignment.LEFT, 18, 18) + .withMinSize(0, 0) // may shrink to whatever it is given + .withPrefSize(PAGE_REFERENCE_WIDTH, 0) // the reference width (see 2d) + .add(SIDEBAR_SPAN, sidebar(vm)) + .add(CONTENT_SPAN, content(vm)) +); +``` + +#### Four mechanisms, in the order you should reach for them + +| Gear | Mechanism | Use when | Costs | +|---|---|---|---| +| **0** | `"wmin 0"` + `withMinSize(0,0)` | **always** — a prerequisite for all of the others | nothing | +| **1** | `withFlowLayout()` + `AUTO_SPAN` (§2d) | the same regions want a different number of columns | **no state at all** | +| **2** | `Var` reflow (§2e) | the same widgets want a genuinely different arrangement (a toolbar folding into rows) | one property; **nothing is rebuilt** | +| **3** | form-factor state + view swap (§2f) | the two shapes want different component trees (split pane ⇄ scrolling column) | rebuilds — loses focus/caret/scroll | +| **4** | `isVisibleIf` + shorter bound labels (below) | content, not layout, must re-prioritise | nothing | + +Most views need **gear 0 + gear 1**. Escalate only when the shape of the problem +demands it — gear 3 is the only one that destroys component state. + +#### Gear 0 — minimum sizes are a hard floor (the #1 cause of "it won't narrow") + +A `JLabel`'s minimum width is its full text; containers propagate child minimums +upward; a flow grid reports the **sum** of its children's minimums. One +forgotten label deep in the tree gives the whole *window* a minimum width, and +the responsive bands are then unreachable — the layout never even gets to try. + +```java +.add("growx, wmin 0", label("A long descriptive caption")) // ellipsizes instead of strutting +.withMinSize(0, 0) // on every flow-grid panel +``` + +Prefer ranges over hard sizes: `width 90::200`, not `width 200!`. +**Rule:** drag the window as narrow as it goes. If it stops at an arbitrary +width, that is a minimum-size bug — fix it before touching anything else. + +#### Gear 4 — the content converges too + +```java +label("Live timetable · click any train to see its route").isVisibleIf(isWide) // + "hidemode 3" +Val btn = Viewable.of(String.class, theme, formfactor, + (t, f) -> f.isTall() ? "☾" : "☾ Dark mode"); +``` + +`hidemode 3` on the container constraint makes a hidden child stop reserving its +cell. Drop what is **redundant** (already shown elsewhere) before what is unique. + +#### The convergence checklist (apply this in every review) + +- [ ] Window narrows freely — no arbitrary floor (`wmin 0`, `withMinSize(0,0)`). +- [ ] Multi-column regions collapse to one column rather than becoming slivers. +- [ ] A stacked page sits in `scrollPane(conf -> conf.fitWidth(true))`. +- [ ] Everything with no natural preferred size (`scrollPane`, `scrollPanels`, + empty `textField`) has been given one. +- [ ] A nested grid lives inside another **grid**, never in a MigLayout cell (§2d). +- [ ] The primary action is reachable at every size. +- [ ] What vanishes when narrow is redundant, not unique. +- [ ] A view swap (gear 3) has hysteresis. +- [ ] Every `Var` variant spells out a constraint for **every** child. + +Full prose: [Convergent-Design.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/Convergent-Design.md). + +### 2d. Gear 1 — the responsive flow grid (`ResponsiveGridFlowLayout`, Bootstrap-style 12 columns) + +The workhorse, and **stateless**: no breakpoint field, no resize listener, no +view-model change. Each child declares how many of 12 virtual columns it occupies +per size category; the lambda re-runs on every resize. + +```java +private static final FlowCell ROSTER_SPAN = AUTO_SPAN( it -> it.fill(true) + .verySmall(12).small(12).medium(12).large(5).veryLarge(4).oversize(4) ); +private static final FlowCell EDITOR_SPAN = AUTO_SPAN( it -> it.fill(true) + .verySmall(12).small(12).medium(12).large(7).veryLarge(8).oversize(8) ); + +panel().withFlowLayout(UI.HorizontalAlignment.LEFT, 18, 18) +.withMinSize(0, 0).withPrefSize(900, 0) +.add(ROSTER_SPAN, roster(vm)) +.add(EDITOR_SPAN, editor(vm)); +``` + +That span table **is** the responsive design — read it out loud: *side by side +from LARGE up, one stacked column below.* + +**Size categories are exact fifths of the grid's reference width** (not pixels): + +| `VERY_SMALL` | `SMALL` | `MEDIUM` | `LARGE` | `VERY_LARGE` | `OVERSIZE` | +|---|---|---|---|---|---| +| 0…⅕ | ⅕…⅖ | ⅖…⅗ | ⅗…⅘ | ⅘…1 | ≥1 | + +An undeclared category falls back to the **nearest declared** one, so +`AUTO_SPAN(it -> it.large(12))` means "always full width". Spell all six out in +shared code anyway — the table then documents the design. + +**Reference width** = the explicitly set preferred width if there is one, else +the ideal single-row sum of all children. Declaring it (`withPrefSize(w, 0)`) is +how you *move* the breakpoints, and it is mandatory for a nested grid — otherwise +the nested grid reports "all children in one row" upward and silently rewrites +the parent's bands. + +**Other cell options:** `.fill(true)` stretches the cell to the row height (how a +short sidebar card ends up flush with a tall content card; a MigLayout child with +a `fill`/`filly` container constraint gets this automatically), and +`.align(UI.VerticalAlignment.TOP|CENTER|BOTTOM)` positions a non-filling cell. + +**Heights: a row is as tall as its tallest child's *preferred* height** — a flow +grid never stretches a row to fill a tall window. Hence two rules: +1. Give a preferred height to anything that has none: + `scrollPanels().withPrefSize(340, 470)`, or it collapses to one line. +2. Put a page-level grid in a `scrollPane(conf -> conf.fitWidth(true))`, or the + stacked layout is clipped at the bottom. + +> ⚠️ **THE NESTING TRAP — a grid nests in a grid, NOT in a MigLayout cell.** +> A wrapping grid is a *width-for-height* layout, so `ResponsiveGridFlowLayout` +> asks each child "how tall at the width you are about to get?" — and only +> another `ResponsiveGridFlowLayout` can answer. Meanwhile +> `JComponent.getPreferredSize()` short-circuits the layout manager once a +> preferred size is set, so a **MigLayout** parent reads the literal `0` from +> `withPrefSize(w, 0)` and **the nested grid collapses to zero height**, silently +> clipping everything in it. +> ```java +> // ❌ form laid out at height 0 +> panel("fill, wrap 1").add("grow, push", panel().withFlowLayout(..).withPrefSize(620,0)...) +> // ✅ make the card a grid too +> panel().withFlowLayout(UI.HorizontalAlignment.LEFT, 0, 0).withMinSize(0,0).withPrefSize(620,0) +> .add(FULL_ROW, titleStrip()).add(FULL_ROW, form()) +> ``` +> A grid declaring a reference width must live inside another grid or directly +> inside a `scrollPane(fitWidth(true))`. Anywhere else, drop the explicit +> preferred size. + +### 2e. Gear 2 — reactive layout: bind the layout itself to a `Var` + +To swap the *entire layout manager* at runtime (one toolbar row ↔ three rows, +compact ↔ wide, edit ↔ read mode) **without destroying or rebuilding any +child** — so focus, caret, selection and scroll offsets all survive: + +```java +import swingtree.api.Layout; +import swingtree.layout.MigAddConstraint; + +Var layout = Var.of(Layout.class, Layout.mig("fill, wrap 1")); + +panel(layout) // == panel().withLayout(layout) +.add("growx", a).add("growx", b); + +// later, anywhere — atomic reflow, no rebuild: +layout.set(Layout.mig("fill, wrap 2").withChildConstraints( + MigAddConstraint.of("growx"), + MigAddConstraint.of("growx, span 2") // positional: index 0, 1, ... +)); +``` + +`Layout` factories: `Layout.mig(constraints)`, `Layout.flow(FlowCell...)`, +`Layout.border()`, `Layout.grid(rows,cols)`, `Layout.box(UI.Axis.X)`, +`Layout.none()` (absolute positioning — `setLayout(null)`), `Layout.unspecific()` +(no-op, leaves current manager alone). `withChildConstraints(...)` maps +positionally to children. This is how `SalesDashboard`, `AlmanackView` and +`CelestialScribe` work — see §5.4 for deriving a layout from data. + +Two rules that cost debug time: +- **Every variant must supply a constraint for *every* child.** They apply + positionally and are only overwritten where a new layout supplies one, so a gap + leaves the previous variant's constraint (a stray `"wrap"`) in place after + switching back. +- **Add `nogrid`** to a wrapped MigLayout variant, or every row's columns line up + with every other row's and the second row inherits the first column's width. + +### 2f. Gear 3 — form-factor state and view swapping + +When the two shapes want genuinely different component trees (a split pane is a +good landscape design and a bad portrait one), classify the shape into a small +enum, keep it in the **view model** like any other state, and swap the body with +the property-bound `add(Val, ViewSupplier)`: + +```java +public enum Formfactor { + WIDE, TALL; + public boolean isTall(){ return this == TALL; } + /** 10% dead band — without hysteresis, dragging along the diagonal strobes. */ + public static Formfactor of( int width, int height, Formfactor current ) { + double slack = 1.1; + return current == TALL ? (width > height * slack ? WIDE : TALL) + : (height > width * slack ? TALL : WIDE); + } +} +``` +```java +of(this).withLayout(FILL.and(WRAP(1))) +.onResize( it -> formfactor.update(From.VIEW, f -> Formfactor.of(it.getWidth(), it.getHeight(), f)) ) +.add(GROW.and(PUSH), formfactor, this::body); // rebuilds only on an actual shape change +``` + +- **Always add hysteresis** (gears 1 and 2 don't need it — reflowing never + changes the width it was measured against; a view *swap* can). +- `onResize` fires per pixel of a drag, but `Var.update(..)` is a no-op when the + value is unchanged, so the rebuild happens once per shape change. +- The form factor is ordinary, Swing-free state ⇒ unit-testable without a GUI. +- If the swapped sub-view is built under a `StyleSheet`, re-enter the scope: + `UI.of(UI.use(sheet, () -> tallBody().get(JScrollPane.class)))` — `UI.use` + consumes the builder and returns the component (§7). + +--- + +## 3. State — Sprouts properties (`Var`, `Val`) and binding + +Reactivity comes from the **Sprouts** library. The whole point: **the view never +holds Swing state; it binds to properties, and the property system keeps the two +in sync bidirectionally.** Your business logic never imports a Swing class. + +- `Var` — a **mutable** property. `get()`, `set(value)`, `update(fn)`, `onChange(..)`. +- `Val` — a **read-only** view of a property. `Var extends Val`, so you can + expose `Val` from a view model to prevent the view from writing. +- `Vars` / `Vals` — observable **lists** of properties (classic MVVM). +- `Tuple` — an **immutable** ordered collection (functional MVI/MVL). + +```java +Var name = Var.of("Joseph"); +Var ok = Var.of(true); +Var count = Var.of(0); +Var lay = Var.of(Layout.class, Layout.mig("fill")); // explicit type when value could be null/ambiguous +``` + +### Binding properties to components + +Pass the property to the factory and the binding is automatic and bidirectional: + +```java +textField(name) // user typing -> name.set(..); name.set(..) -> field text +checkBox("Agree", ok) // toggling <-> ok +slider(Align.HORIZONTAL, 0.0, 1.0, ratio) // generic over Number: int OR double +comboBox(selectedEnum, e -> prettyLabel(e)) // selection <-> Var +label(name) // one-way: label text follows name +progressBar(Align.HORIZONTAL, ratioVal) // one-way Val 0..1 +``` + +Flags bind through `isXyzIf(Val)`: + +```java +textField(name).isEnabledIf(ok).isVisibleIf(showAdvanced) +button("Go").isEnabledIf(canSubmit) +checkBox("edit").isSelectedIf(...) // and isEditableIf on text components +``` + +### Derived (computed) read-only views + +`view*` methods produce a `Val` that recomputes when the source changes — perfect +for labels and computed flags: + +```java +Val caption = count.viewAsString(n -> "Items: " + n); +Val isEmpty = name.viewAs(Boolean.class, s -> s.isBlank()); +Val asD = count.viewAsDouble(n -> n / 100.0); +label(caption); +``` + +`viewAsString/Int/Double()` with **no mapper** just stringify/convert the value; +the `nullObject`-first overloads (`viewAsString("", fn)`) define what to show when +the source is null — null-safe by construction. To derive from **two** sources at +once, combine them — the result recomputes when *either* input changes: + +```java +Viewable total = Viewable.of(price, taxRate, (p, tr) -> p * (1 + tr)); // Val, updates live +``` + +To merge **any number** of sources (not just two) into one value without nesting, +use the **composite view builder** (Sprouts ≥ 2.7.0): a seed plus one +`join(property, wither)` per input, each folding that property's item into the seed. +It recomputes as a whole on any input change — ideal for feeding a *single* +`withStyle` from a whole cluster of view-model properties (§8): + +```java +Viewable weather = Viewable.of(Weather.blank(), it -> it + .join(city, Weather::withCity) + .join(temperature, Weather::withTemperature) + .join(humidity, Weather::withHumidity)); // Val, recomputed on any change +``` + +> All `view*`/`viewAs*` results are `Viewable` (a `Val` you may listen on). They +> are held **weakly** by their source — see the GC gotcha in §9c: if you only +> register an `onChange` on one, keep it in a field or it is collected. + +### The two change channels (`From.VIEW` vs `From.VIEW_MODEL`) + +Every `Var` distinguishes who caused a change: + +- `set(From.VIEW, v)` — the **user/view** changed it (SwingTree calls this for you when the user types/clicks). +- `set(From.VIEW_MODEL, v)` / plain `set(v)` — your **application logic** changed it. + +Register listeners per channel via `Viewable.cast(prop).onChange(From.VIEW_MODEL, it -> ...)` +(or `From.VIEW`, or `From.ALL`). This split prevents infinite feedback loops and +lets you react only to user input or only to logic. Inside a listener, +`it.currentValue()` is the new value. + +> **`prop.view()` vs `Viewable.cast(prop)`.** You cannot listen on a raw +> `Var`/`Val` directly — you need a `Viewable`. Two ways to get one, and the +> difference is lifecycle: `prop.view()` returns a **new, weakly-held** view +> (the sprouts-preferred default) — store it in a field so it isn't GC'd. +> `Viewable.cast(prop)` reinterprets the property *itself* as `Viewable`, so the +> listener lives exactly as long as that property object. Both are safe **only +> when the thing you listen on is reachable**: for a lens (which its parent holds +> *weakly*) you must keep the lens — or its `view()` — in a field either way (§9c). + +```java +Viewable.cast(firstName).onChange(From.ALL, it -> + fullName.set(it.currentValue().orElseThrowUnchecked() + " " + lastName.get()) +); +``` +**Warning:** The approach above can lead to memory leaks due to change listeners +never being garbage collected and still holding strong references to captured variables. + +→ So the prefer custom change listener registration on views instead of directly! + +--- + +## 4. Lenses — `zoomTo` and immutable view models + +This is the heart of the **recommended** SwingTree architecture (MVI/MVL). A +**lens** focuses a root `Var` down onto one field, giving you +a `Var` that reads via a getter and writes via a **wither** (a method that +returns a *new* record with that field changed). + +```java +record Person(String forename, String surname, Address address) { + Person withForename(String f){ return new Person(f, surname, address); } + Person withSurname(String s){ return new Person(forename, s, address); } + Person withAddress(Address a){ return new Person(forename, surname, a); } +} + +Var person = Var.of(new Person("Tom","Schultz", addr)); +Var forename = person.zoomTo(Person::forename, Person::withForename); +Var
address = person.zoomTo(Person::address, Person::withAddress); +Var street = address.zoomTo(Address::street, Address::withStreet); // lenses nest! +``` + +Now `textField(forename)` edits the forename, and a keystroke produces a brand-new +`Person` (and `Team`, etc., all the way up) inside `person`. Lenses are **smart**: +they fire change events only when *their own slice* actually changes, even if the +whole root record was replaced. + +Other lens flavors: +- `viewAs(Type.class, getter)` / `viewAsString/Double/Int(getter)` — **read-only** derived `Val`. +- `zoomToNullable(Type.class, getter, wither)` — when the focused value may be null. +- `zoomTo(defaultValue, getter, wither)` — supply a fallback for null parents. +- `zoomTo(Lens)` — a hand-written lens (implement `Lens.getter`/`wither`, or + `Lens.of(getter, wither)`) when the focus needs **logic** — clamping, derived + fields, or zooming into a collection entry (see below). + +**Tip:** Generate withers with Lombok `@With` on records to avoid boilerplate +(this is also how you stay on **Java 8** — records need 16+, but `@With @Getter` +on a `final class` gives the same value semantics): +```java +@With record Person(String forename, String surname, Address address) {} +// person.zoomTo(Person::forename, Person::withForename) // withForename generated by @With +``` + +### Sprouts immutable collections — `Tuple`, `Association`, `ValueSet`, `Pair` + +Records model *fixed* shape; for *variable-size* state inside a view model, use +Sprouts' **persistent** (structural-sharing) collections instead of +`java.util` — they are immutable value objects, so they fit record fields and +withers, and SwingTree binds to several of them directly. Every "mutation" +returns a **new** instance. + +| Type | `java.util` analogue | Make it | Key ops (all return a new instance) | +|---|---|---|---| +| `Tuple` | `List` | `Tuple.of(a,b,c)`, `Tuple.of(T.class)` (empty), `Tuple.of(T.class, iterable)` | `add`, `remove`, `removeAt`, `setAt(i,x)`, `map`, `retainIf`/`removeIf`, `slice`, `sort`, `first`/`last` | +| `Association` | `Map` | `Association.between(K.class, V.class)` (empty!), `.ofLinked(..)` (insertion-ordered) | `put`, `putAll(Pair...)`, `get(k) → Optional`, `remove`, `removeIf(pair->..)` | +| `ValueSet` | `Set` | `ValueSet.of(E.class)`, `ValueSet.of(a,b,..)`, `.ofLinked(..)` | `add`, `addAll`, `remove`, `retainAll`, `retainIf`, `any(pred)` | +| `Pair` | `Map.Entry` | `Pair.of(a, b)` | `.first()`, `.second()` | + +> ⚠️ The empty-map factory is **`Association.between(K.class, V.class)`**, *not* +> `Association.of(..)` — `of(key, value)` builds a one-entry map (and +> `of(String.class, Integer.class)` would silently make an `Association`). + +A field of one of these *is* part of the immutable value, so it composes with +lenses and withers like any other field: + +```java +@With record PartyPlan( + Tuple guests, // ordered, may repeat + Association drinkStock, // name -> quantity + ValueSet decorations // unique, unordered +) {} + +Var plan = Var.of(initialPlan); +Var> guests = plan.zoomTo(PartyPlan::guests, PartyPlan::withGuests); +Var> stock = plan.zoomTo(PartyPlan::drinkStock, PartyPlan::withDrinkStock); + +guests.update(g -> g.add(new Guest("Gimli"))); // immutable add, fires change +stock.update(s -> s.put("Ale", 12)); // immutable put +``` + +You can even **lens into a single entry** of a collection with logic lenses — +the write rebuilds the whole collection immutably, but the property behaves like +a plain `Var` (great for binding one map value to one field): + +```java +Var aleStock = stock.zoomTo( + s -> s.get("Ale").orElse(0), // getter: read the entry + (s, qty) -> s.put("Ale", qty) // wither: return a new map +); +aleStock.set(20); // updates the entire association inside `plan` +``` + +`Tuple` is the one most wired into SwingTree: `addAll(..)` renders one sub-view +per element (§5.2), and `Var>` is the canonical MVI list. + +--- + +## 5. Architecture — how to structure a real app + +A SwingTree **view** is conventionally a `class extends JPanel` whose constructor +takes the view model (or a `Var` of it) and builds itself with `UI.of(this)`: + +```java +public final class MyView extends JPanel { + public MyView(Var vm) { + UI.of(this).withLayout("fill, wrap 1") + .add(...) + .add(...); + } + public static void main(String[] args) { + Var vm = Var.of(new MyViewModel()); + UI.show(f -> new MyView(vm)); + EventProcessor.DECOUPLED.join(); // keep the app thread alive (see §11), processes events forever (blocks) + } +} +``` + +Pull repeated fragments into `private static UIForAnySwing someSection(...)` +methods that return builders — this is the standard way large views (TeamView, +BreathingView, CelestialScribe) stay readable. + +### 5.1 MVI / MVL — the recommended pattern (immutable records + lenses) + +The whole UI state lives in **one immutable record** (the view model). The view is +a pure function of it; every change produces a new record via withers; the view +reaches fields through `zoomTo`. There are no Swing references and no mutable +fields in the view model — it is unit-testable in isolation. + +**View model** (note: `static empty()` / no-arg constructor for the initial state, +withers for every field, and *business methods* that return new instances): + +```java +public record CalculatorViewModel(CalculatorInputs inputs, CalculatorOutput output) { + public static CalculatorViewModel empty(){ return new CalculatorViewModel(CalculatorInputs.empty(), CalculatorOutput.empty()); } + public CalculatorViewModel withInputs(CalculatorInputs i){ return new CalculatorViewModel(i, output); } + public CalculatorViewModel withOutput(CalculatorOutput o){ return new CalculatorViewModel(inputs, o); } + public CalculatorViewModel runCalculation(){ // business logic = pure function returning new VM + try { + double l = Double.parseDouble(inputs.left()), r = Double.parseDouble(inputs.right()); + double res = switch (inputs.operator()) { + case ADD -> l+r; case SUBTRACT -> l-r; case MULTIPLY -> l*r; case DIVIDE -> l/r; + }; + return withOutput(output.withResult(res).withValid(true)); + } catch (NumberFormatException e) { return withOutput(output.withError("Invalid number").withValid(false)); } + } +} +``` + +For business logic that can **fail** (parsing, validation, IO), Sprouts' +`Result` is a cleaner alternative to ad-hoc error fields: it is a `Maybe` +(present-or-empty, like `Optional`) that *also* carries a `Tuple` +describing what went wrong. `Result.ofTry(T.class, () -> risky())` runs a +throwing supplier and captures any exception as a `Problem` instead of +propagating it — ideal inside a pure view-model method. The view then renders +`result.problems()` (e.g. an error label) and `result.orElse(fallback)` for the +value. (SwingTree itself returns `Result` from table-cell conversions.) + +**View** zooms in and triggers business methods with `vm.set(vm.get().runCalculation())` +or, more idiomatically, `vm.update(CalculatorViewModel::runCalculation)`: + +```java +public final class CalculatorView extends JPanel { + public CalculatorView(Var vm) { + Var inputs = vm.zoomTo(CalculatorViewModel::inputs, CalculatorViewModel::withInputs); + Var output = vm.zoomTo(CalculatorViewModel::output, CalculatorViewModel::withOutput); + UI.of(this).withLayout("fill") + .add("growx", textField(inputs.zoomTo(CalculatorInputs::left, CalculatorInputs::withLeft))) + .add(comboBox(inputs.zoomTo(CalculatorInputs::operator, CalculatorInputs::withOperator), Operator::symbol)) + .add("growx", textField(inputs.zoomTo(CalculatorInputs::right, CalculatorInputs::withRight))) + .add("wrap", button("Run!").onClick(e -> vm.update(CalculatorViewModel::runCalculation))) + .add("span", label(output.viewAsString(o -> o.valid() ? "= " + o.result() : o.error()))); + } +} +``` + +`vm.update(fn)` is shorthand for `vm.set(fn.apply(vm.get()))` — prefer it for +applying a business method. + +### 5.2 Lists in MVI/MVL — `Tuple` + `addAll` + `HasId` + +Model a collection as a `Tuple` field; zoom to it; render with `addAll`: + +```java +record ChatVM(Tuple allMessages, String draft) { + record Message(UUID id, String text, LocalDateTime sentAt, boolean editing) implements HasId { + Message(){ this(UUID.randomUUID(), "", LocalDateTime.now(), false); } + } +} + +Var> messages = vm.zoomTo(ChatVM::allMessages, ChatVM::withAllMessages); + +scrollPanels() +.addAll(messages, (Var entry) -> { // one sub-view per item; entry is a per-item lens + Var text = entry.zoomTo(Message::text, Message::withText); + return panel(FILL) + .add(GROW_X.and(WRAP), textArea(text)) + .add(RIGHT, button("✕").onClick(it -> messages.update(t -> t.remove(entry)))); +}); + +// add an item: +messages.update(t -> t.add(new Message().withText(draft.get()))); +``` + +> **CRITICAL: when you bind a *mutable* `Var>` and want a per-item lens, +> the item type MUST implement `sprouts.HasId`** (carry a `UUID`/stable +> id). That overload — `addAll(Var>, entry -> ...)`, where `entry` is a +> `Var` lens — is the one above, and it is `>`. Value +> records define identity by *content*, so two equal records would confuse the +> component binding; `HasId.id()` gives each item a stable identity so SwingTree +> knows which sub-view maps to which item, which item-lens to hand it, and which +> rows to reuse vs. rebuild on change. Add a `UUID id` field and `implements +> HasId`. +> +> The **read-only** overloads do *not* require `HasId`: `addAll(Val>, +> m -> view)` and `addAll(Tuple, m -> view)` (and the `Vals` MVVM overload) +> hand the supplier the **value** `M`, not a lens — use these when items aren't +> individually editable. `HasId` is the price of admission for per-item editing. + +> **A bound `addAll` OWNS its container — give it a panel of its own.** The +> binding manages every child, so a component that already had children added by +> hand is **cleared** when `addAll` binds to it (SwingTree logs "Trying to bind +> multiple sub-views to component … Clearing component now"). A heading plus a +> bound list is therefore two components, not one: +> ```java +> // ❌ the heading is silently deleted when the binding attaches +> panel().add(FULL_ROW, label("ROOMS")).addAll(CHIP_SPAN, rooms, this::roomChip) +> // ✅ the list gets a container to itself +> panel().add(FULL_ROW, label("ROOMS")).add(FULL_ROW, roomRail()) +> // where roomRail() == panel().withFlowLayout(..).addAll(CHIP_SPAN, rooms, this::roomChip) +> ``` + +> **A row supplier runs *later*, so under a `StyleSheet` it must re-enter the +> scope.** `UI.use(sheet, ..)` only binds what is built inside its lambda, and +> `addAll` rebuilds rows whenever the tuple changes — long after the constructor +> returned. Initial rows then look right and every row built after the first +> model change comes out unstyled (§7): +> ```java +> private UIForAnySwing row( Var entry ) { // the supplier passed to addAll +> return UI.of(UI.use(sheet, () -> rowBody(entry).get(JPanel.class))); +> } +> ``` + +`Tuple` is functional: `.add(x)`, `.remove(x)`, `.map(fn)`, `.setAt(i, x)`, +`.get(i)`, `.size()`, `.isEmpty()` — all return new tuples (or values). +`Tuple.of(Message.class)` makes an empty typed tuple; `Tuple.of(a, b, c)` a +populated one. + +### 5.3 Classic MVVM — mutable view models (the alternative) + +If you prefer mutable view models: the view model holds `Var` *fields* directly +(no root record, no lenses), exposes them through getters, and uses `Vars` for +observable lists. The view binds straight to those fields. + +```java +public class PersonVM { + private final Var firstName = Var.of("Joseph"); + private final Var lastName = Var.of("Armstrong"); + private final Var fullName = Var.of(""); + public PersonVM() { + Viewable.cast(firstName).onChange(From.ALL, it -> recompute()); + Viewable.cast(lastName ).onChange(From.ALL, it -> recompute()); + recompute(); + } + private void recompute(){ fullName.set(firstName.get() + " " + lastName.get()); } + public Var firstName(){ return firstName; } // mutable out + public Var lastName(){ return lastName; } + public Val fullName(){ return fullName; } // read-only out +} +``` + +**Polymorphic / dynamic sub-views** work in both patterns via the property-bound +`add` overload — when the property changes, SwingTree swaps the sub-view: + +```java +// MVVM: Var subVM, view supplier dispatches on type +.add(vm.subViewModel(), subVM -> + subVM instanceof SubVM1 s ? new SubView1(s) : new SubView2((SubVM2) subVM)) + +// MVI: Val + supplier picks which fragment to (re)build +.add("grow, push", hasSelection, has -> has ? editorBody(vm) : emptyState()) +``` + +A `Vars` (MVVM) and a `Var>` (MVI) are both rendered with +`addAll(list, viewSupplier)`. **TeamView exists in the SwingTree repo in both flavors** +(`examples.team.mvi` and `examples.team.mvvm`) — the clearest side-by-side +contrast. Choose **MVI/MVL for new code**; reach for MVVM only when integrating +with existing mutable models. + +### 5.4 Deriving a layout from data (advanced reactive) + +`CelestialScribe` derives the entire child layout from a tuple of model objects — +positions are a pure function of state, so dragging a star just updates the model: + +```java +Val layout = stars.viewAs(Layout.class, tuple -> { + Layout.None none = Layout.none(); + for (int i = 0; i < tuple.size(); i++) + none = none.withChildBound(i, tuple.get(i).bounds()); + return none; +}); +box().withLayout(layout).withRepaintOn(stars).addAll(stars, this::starPanel); +``` + +--- + +## 6. Events + +Every component supports the same base events; the handler receives a delegate +(conventionally `it`) that wraps **both the component and the event state** and +offers query/animation helpers. + +```java +button("Go") +.onClick(it -> doThing()) // also: onClick(Runnable) for no-arg +.onMouseClick(it -> ...).onMousePress(it -> ...).onMouseRelease(it -> ...) +.onMouseEnter(it -> ...).onMouseExit(it -> ...).onMouseMove(it -> ...).onMouseDrag(it -> ...) +.onFocusGain(it -> ...).onFocusLoss(it -> ...) +.onKeyPress(it -> ...).onKeyRelease(it -> ...).onKeyTyped(it -> ...) +.onResize(it -> ...).onShown(it -> ...).onHidden(it -> ...); +``` + +Useful delegate methods: `it.get()` / `it.getComponent()` (the component), +`it.getParent()`, `it.mouseX()` / `it.mouseY()`, `it.animateFor(..)` (§9), +`it.paint(status, g -> ...)` (custom rendering), drag deltas +(`it.deltaXSinceStart()`, `it.initialComponentPosition()`). **All geometry these +return is in DPI-agnostic "developer pixels"** (except `mouse*OnScreen()`, which +is raw screen pixels) — see §13. + +### Custom / model-driven events: `on(..)` vs `onView(..)` + +Both attach an `Action` to any `sprouts.Observable` (e.g. an `Event` from +`Event.create()`, or a property). The difference is **which thread runs the +handler**: + +| Method | Handler runs on | Use for | +|---|---|---| +| `onView(observable, it -> ...)` | **EDT** (Swing thread) | reacting to model changes that **touch the view** — resize a label, animate a colour | +| `on(observable, it -> ...)` | **application thread** | reacting to external/business events that **update your model** — network, custom input | + +Rule: if your handler sets Swing properties → `onView`; if it mutates the view +model or does non-UI work → `on`. + +--- + +## 7. Styling — the functional `withStyle` API + +`.withStyle(it -> it. ... )` receives a `ComponentStyleDelegate` (`it`) and returns +a configured one. It is **immutable and re-run on every paint**, so styles can +depend on live state (selection, animation progress, model fields). This is how +SwingTree paints shadows, gradients, rounded borders, etc. *on top of* the current +Look-and-Feel — things plain Swing cannot do. + +```java +panel("fill") +.withStyle(it -> it + .margin(8).padding(24) + .backgroundColor(new Color(57,221,255)) + .foregroundColor(Color.WHITE) + .borderRadius(32) + .border(2, Color.DARK_GRAY) // width + color + .borderAt(Edge.LEFT, 5, accent) // one edge only (great for accent bars) + .shadowColor(new Color(0,0,0,128)).shadowBlurRadius(5).shadowSpreadRadius(1).shadowOffset(0,2) + .shadowIsInset(false) +); +``` + +Frequently used delegate methods (all chainable, all DPI/HiDPI aware): + +- Box: `margin`, `padding`, `borderRadius`, `borderRadiusAt(Corner, w, h)`, `border`, `borderAt(Edge, w, color)`, `prefSize`, `size`. +- Fill: `backgroundColor` / `foundationColor`, `foregroundColor`, `gradient(...)`, `noise(...)`, `image(img -> ...)`. +- Shadow: `shadowColor`, `shadowBlurRadius`, `shadowSpreadRadius`, `shadowOffset`, `shadowIsInset`. Named shadows: `.shadow("name", s -> s.color(..).offset(..))`. +- Layered painting: `.painter(Layer.CONTENT, g -> ...)` for raw `Graphics2D`. +- `component()` returns the live component, so you can branch on its state (e.g. `it.component().isSelected()`). **Deprecated for reading geometry** — its sizes are in *component pixels* and double-scale if fed back in; use `componentWidth/Height()` / `componentPrefWidth/Height()` instead (§13). + +Gradients and named layers: + +```java +.gradient(Layer.BACKGROUND, "glow", g -> g + .type(GradientType.RADIAL) // or LINEAR + .boundary(ComponentBoundary.BORDER_TO_INTERIOR) + .span(Span.TOP_LEFT_TO_BOTTOM_RIGHT) + .offset(cx, cy).size(radius) + .colors(color(0.75,1,0.5,0.5), color(0.5,1,1,0)) // UI.color(r,g,b[,a]) -> UI.Color + .clipTo(ComponentArea.BODY) +) +``` + +`UI.Color` (via `color(...)`, `Color.ofRgb(...)`, `Color.ofHsb(...)`) adds +`.blend(other, t)`, `.shade(amount)`, `.brighter()`, alpha helpers — handy for +deriving palettes. + +### Font styling (`componentFont`) + +```java +.withStyle(it -> it.componentFont(f -> f + .size(32).family("Arial").weight(2f).color(Color.WHITE).posture(0.1f).spacing(0.12f) + .gradient(grad -> grad.colors(Color.GREEN, Color.BLUE).span(UI.Span.LEFT_TO_RIGHT)) + .noise(n -> n.colors(Color.DARK_GRAY, Color.CYAN).function(UI.NoiseType.CELLS).scale(1.25)) +)) +``` + +There are also `.withFontSize(n)`, `.withForeground(color)`, `.withBackground(color)` +shortcuts directly on the builder for simple cases. + +### Background filtering (frosted glass) + +A non-opaque child can blur/scale the parent's pixels behind it: + +```java +.withStyle(it -> it + .backgroundColor(Color.TRANSPARENT) // must be non-opaque for the filter to show + .parentFilter(f -> f.area(ComponentArea.BODY).blur(16).scale(1.25, 1.25)) +) +``` + +### Central style sheets + semantic groups (CSS-like, hot-swappable themes) + +For app-wide styling, pull rules into a `StyleSheet` and tag components with +`.group(EnumTag)` / `.id("name")`. This is how the **Theme Garden** swaps five +complete themes at runtime with zero changes to the view skeleton. + +```java +enum Skin { PRIMARY, SECONDARY } + +final class MySheet extends StyleSheet { + @Override protected void configure() { + add(type(JButton.class), it -> it.borderRadius(8).padding(6,14,6,14)); + add(type(JButton.class).group(Skin.PRIMARY), it -> it.backgroundColor(BLUE).foregroundColor(WHITE)); + add(id("ok-button"), it -> it.shadowBlurRadius(8)); + } +} +``` + +Traits: `id("x")` (most specific), `group(tag)` (prefer **enum** tags over +strings — type-safe), `type(Class)`. They compose: +`type(JButton.class).group(Skin.PRIMARY)`. Specificity: `id` > `type+group` > +`group` > `type`; later `add(..)` wins ties. + +Install a sheet either globally — +`SwingTree.initializeUsing(cfg -> cfg.styleSheet(new MySheet()))` — or for a scope: + +```java +UI.use(new MySheet(), () -> UI.show(f -> new MyView())); // only components built INSIDE the lambda bind +``` + +> `UI.use(sheet, supplier)` **consumes** the builder it is handed and returns the +> finished component. And it only binds what is built *inside* the lambda — so a +> sub-view built later (a property-bound `add(Val, ViewSupplier)`, a lazy tab) +> must re-enter the scope itself, or it comes out unstyled: +> `UI.of(UI.use(sheet, () -> tallBody().get(JScrollPane.class)))`. + +**Hot-swap themes**: keep mutable state in the sheet and call `reconfigure()` to +re-run `configure()` and instantly repaint every component in the `UI.use` scope: + +```java +final class ThemedSheet extends StyleSheet { + private Theme theme = Theme.LIGHT; + public void setTheme(Theme t){ if (t != theme){ theme = t; reconfigure(); } } + @Override protected void configure(){ switch (theme){ case LIGHT -> light(); case DARK -> dark(); } } +} +// in the view: bind a Var to the sheet +Viewable.cast(theme).onChange(From.ALL, it -> sheet.setTheme(theme.get())); +UI.use(sheet, () -> of(this).group(Skin.FRAME). ... .add(comboBox(theme))); +``` + +--- + +## 8. Property-driven styles — `withStyle(prop, styler)` (and `withRepaintOn`) + +Style lambdas are evaluated by the **UI thread**, as part of the paint cycle. So when +a style depends on property state, don't read the property inside a plain `withStyle` +lambda — hand the property to the style and receive its item as an argument: + +```java +box() +.withStyle(orbScale, (scale, it) -> it.shadowBlurRadius((int)(16 + 78 * scale)). ...) +``` + +The item is captured on the property's owning thread and passed to the lambda +explicitly, and the component re-styles and repaints **automatically** on every +change. This is the thread-safe and preferred way to use property state in styles: +a plain `withStyle(it -> ... someVal.get() ...)` reads application-thread state from +the UI thread (unsafe under `EventProcessor.DECOUPLED`) and doesn't refresh by +itself either. Styles driven by several properties compose by chaining: +`.withStyle(a, ..).withStyle(b, ..)`. + +### Merging *many* properties into **one** `withStyle` (Sprouts ≥ 2.7.0) + +When one style rule genuinely depends on **several** properties at once, you don't +have to chain a `withStyle` per property. Declare a small **record in the view** that +holds everything the style needs, and merge all the source properties into a single +`Viewable` with the Sprouts **composite view builder** +`Viewable.of(seed, it -> it.join(p, combiner)...)` — a seed record plus one +`join(property, wither)` per input, each folding that property's item into the record. +A *single* `withStyle` then drives the whole style from the merged item, for **any** +number of inputs: + +```java +record Avatar(Color accent, int diameter, boolean online) { + Avatar withAccent(Color c) { return new Avatar(c, diameter, online); } + Avatar withDiameter(int d) { return new Avatar(accent, d, online); } + Avatar withOnline(boolean o) { return new Avatar(accent, diameter, o); } +} + +label(initials) +.withStyle( + Viewable.of(new Avatar(Color.GRAY, 38, false), it -> it + .join(accentColor, Avatar::withAccent) // Val + .join(diameter, Avatar::withDiameter) // Val + .join(isOnline, Avatar::withOnline)), // Val + (a, it) -> it + .prefSize(a.diameter(), a.diameter()) + .backgroundColor(a.accent()) + .borderRadius(1000) + .border(a.online() ? 2 : 0, Color.GREEN) +); +``` + +The composite item is recomputed **as a whole** whenever *any* joined property +changes (fold starts at the seed, applies each combiner in join order, reads the +*current* item of every input), so one `withStyle` stays in sync with all of its +inputs. It scales to any number of properties without nesting, and a property may be +joined more than once. **This is the idiomatic way to capture multiple reactive +view-model properties in a single thread-safe styler.** Requires **Sprouts 2.7.0+** +(`Viewable.of(seed, configurator)` — the composite builder — was added there). Use the +`Viewable.of(Type.class, seed, ..)` overload when the record type is polymorphic. + +> **No field needed — build it inline.** A composite is a *view* +> (`isView() == true`), and SwingTree's property bindings hold **views (and lenses) +> strongly** internally (§9c), so the inline `Viewable.of(..)` above is safe from GC +> even though views are otherwise only weakly held by their sources. (Chaining +> separate `withStyle(a,..).withStyle(b,..)` calls is still fine and reads clearer when +> the rules are independent; reach for the composite when one rule needs several +> inputs together, or when you want a single styler for a whole cluster of state.) + +An animated flavor transitions towards each new item over a `LifeTime` +(`anim.progress()` runs 0→1 on every item change): + +```java +label("status") +.withStyle(status, LifeTime.of(0.5, TimeUnit.SECONDS), (s, anim, it) -> it + .backgroundColor(mix(s.color(), anim.progress()))) +``` + +The same **composite merge** works here: hand a merged `Viewable` (built with +`Viewable.of(seed, it -> it.join(...)...)`, Sprouts ≥ 2.7) as the property, and every +change of *any* joined input restarts the transition towards the newly merged item. + +The full family of property/animation styling entry points (all cross-linked in their +Javadocs): + +| Method | Driven by | Use for | +|---|---|---| +| `withStyle(it -> ..)` | nothing (plain) | static style, or live state you read *safely* (no app-thread props) | +| `withStyle(prop, (item, it) -> ..)` | a property **item** | thread-safe property-driven style, auto-repaint | +| `withStyle(prop, LifeTime, (item, anim, it) -> ..)` | a property **item** + transition | *animate towards* each new item | +| `withTransitionalStyle(boolVar, LifeTime, (state, it) -> ..)` | a **boolean** property | bidirectional 0↔1 transition as the flag flips (§9b) | +| `withTransitoryStyle(observable, LifeTime, (state, it) -> ..)` | an `Observable`/`Event` | a one-shot temporary style animation on each fire | + +The two item-driven rows (`ItemStyler`/`AnimatedItemStyler`) are the ones that benefit +from the composite merge — collapse *N* properties into one record and feed a single call. + +`withRepaintOn(observableOrEvent, ...)` remains the right tool for repaint triggers +that are *not* property-item-driven styles — e.g. repainting a custom painter when +an `Event` fires, or a bound custom layout (§5) whose inputs changed. + +--- + +## 9. Animation + +Animations are timer-driven lambdas invoked ~60×/s on the EDT. Two levels: + +### 9a. View-side, fire-and-forget (`it.animateFor` / `UI.animateFor`) + +```java +button("hover me") +.onMouseEnter(it -> it.animateFor(0.5, TimeUnit.SECONDS, status -> { + double h = 1 - status.progress() * 0.5; + it.setBackgroundColor(h, 1, h); +})); +``` + +The `AnimationStatus status` gives you `progress()` (0→1), `fadeIn()`, `fadeOut()`, +`pulse()`, `cycle()`. Drive *anything* from it: colors, bounds (`setBounds`), +text, or custom rendering via `it.paint(status, g -> ...)`: + +```java +.onMouseClick(it -> it.animateFor(1.2, TimeUnit.SECONDS, s -> it.paint(s, g -> { + g.setColor(new Color(120,176,238,(int)(200*s.fadeOut()))); + for (int i=0;i<5;i++){ double r=280*s.fadeIn()*(1-i*0.18); + g.drawOval((int)(it.mouseX()-r/2),(int)(it.mouseY()-r/2),(int)r,(int)r); } +}))); +``` + +`UI.animateFor(dur, unit).go(s -> someVar.set(s.progress()))` runs an animation not +tied to an event; `.asLongAs(s -> true).go(...)` loops forever (ambient effects). +A common idiom: animate a `Var` and let a property-bound +`withStyle(progress, (p, it) -> ..)` (§8) render the frames. + +### 9b. View-side transition between two states (`withTransitionalStyle`) + +Given a `Var` and a duration, SwingTree interpolates `progress` 0↔1 every +time the flag flips. Multiply style props by `state.progress()`: + +```java +label("toggle me") +.withTransitionalStyle(isOn, LifeTime.of(2, TimeUnit.SECONDS), (state, it) -> it + .borderRadius(38 * state.progress()) + .backgroundColor(200/255d, 210/255d, 220/255d, state.progress()) + .shadowBlurRadius(10 * state.progress()) +); +// elsewhere: toggleButton("toggle").onClick(it -> isOn.set(it.get().isSelected())); +``` + +### 9c. Modelled animation (MVI-friendly — state lives in the view model) + +For testable, multi-phase animation, the view model exposes an `Animatable` (a pure +function of `AnimationStatus` → new model). The view hands it to `UI.animate(vm, vm::xxx)` +and **re-arms** the next phase by listening for the model's phase change. + +```java +// view model +public Animatable breathAnimation() { + BreathPhase ph = this.phase; double secs = settings.secondsFor(ph); + return Animatable.of(LifeTime.of(secs, TimeUnit.SECONDS), this, + new AnimationTransformation<>() { + public BreathingViewModel run(AnimationStatus s, BreathingViewModel m){ // pure, every frame + return m.withPhase(ph).withPhaseProgress(s.progress()).withOrbScale(ph.scaleAt(s)); + } + public BreathingViewModel finish(AnimationStatus s, BreathingViewModel m){ // once, at end + return m.advancePhase(); + } + }); +} + +// view: chain phases by re-arming on phase change +Viewable.cast(phase).onChange(From.VIEW_MODEL, it -> { + if (vm.get().running()) UI.animate(vm, BreathingViewModel::breathAnimation); +}); +// start it: +button.onClick(it -> { vm.update(BreathingViewModel::begin); UI.animate(vm, BreathingViewModel::breathAnimation); }); +``` + +> **GC GOTCHA (this WILL bite you):** Sprouts lenses/views observe their parent +> **weakly**. SwingTree's own bindings (`label`, `slider`, `withRepaintOn`, …) +> hold a strong ref internally, so lenses you pass *to them* are safe as locals. +> But a lens consumed **only** by a raw `Viewable.cast(lens).onChange(..)` +> subscription (like the `phase` re-arming lens above) is **not** retained — it +> gets garbage-collected and the animation silently freezes after one phase. +> **Fix: keep that lens as a `private final` field of the view.** (See the +> `BreathingView.phase` field and its Javadoc.) + +--- + +## 10. Tables, lists, icons, dialogs + +### Tables — model them as **data** (`TableData`), never as a `TableModel` + +`TableData` (`swingtree.api.model`) is an **immutable value describing a whole +table**: cells + column names + column classes + a `UI.ListData` layout. Put it in a +`Var`, bind it, done — no model subclass, no `updateTableOn(..)`, no event to fire, +and thread-safe by construction (§11). **This is the preferred way to build tables.** + +```java +Var data = Var.of( + TableData.of(UI.ListData.ROW_MAJOR, "Name", "Age") // columns first, no rows yet + .addRow("Alice", 30) + .addRow("Bob", 42) +); + +UI.table(data); // that's the whole binding +data.update(it -> it.addRow("Carol", 55)); // ...and the table follows +``` + +Every method returns a **new** `TableData` (verbs mirror `Tuple`, §4): + +| | | +|---|---| +| read | `getValueAt(r,c)`, `getRow(r)`, `getColumn(c)`, `getRowCount()`, `getColumnCount()`, `isEmpty()`, `indexOfColumn(name)`, `getColumnName(i)`, `getColumnClass(i)`, `isEditable()`, `layout()`, `cells()`, `columnNames()`, `columnClasses()` | +| cell | `setCellAt(r, c, value)` — **not** `setValueAt` (that is `TableModel`'s *mutator*) | +| rows | `addRow(vals…)`, `addRowAt(i, vals…)`, `addRows(t)`, `addRowsAt(i, t)`, `setRowAt(i, vals…)`, `setRowsAt(i, t)`, `removeRowAt(i)`, `removeRowsAt(i, n)`, `removeAllRows()` | +| columns | `addColumn(name, cls, vals)`, `addColumnAt(i, ..)`, `setColumnAt(i, vals)`, `removeColumnAt(i)`, `removeColumnsAt(i, n)`, `setColumnNameAt(i, name)`, `setColumnClassAt(i, cls)`, `setColumnNames(..)`, `setColumnClasses(..)` | +| whole | `setCells(t)`, `withLayout(listData)`, `TableData.empty()`, `TableData.row(vals…)` | + +Rows, columns, names, classes and both counts may **all change at any time** — +reshaping a table is just another value, not a special case. Indices rot when columns +move, so address columns by meaning: `it.setCellAt(0, it.indexOfColumn("Age"), 31)`. + +**Performance — do not hand-roll around it.** `Tuple`s are persistent (structural +sharing: adding a row to a 1000-row table copies no rows), and a `ROW_MAJOR` table +forwards the tuple's change-diff to the `JTable` as **targeted** row events — a row +add repaints that row, not the table. **Prefer range ops**: `addRows(..)` / +`removeRowsAt(..)` / `setRowsAt(..)` emit **one** event instead of N. +(`COLUMN_MAJOR` stores columns, so a change never maps onto a row range and it must +rebuild — use `ROW_MAJOR` for big/lively tables. All methods still speak +`(row, column)` in either layout.) + +**Editable needs BOTH** a `*_EDITABLE` layout **and** a mutable `Var` — a `Val`, or a +`Var` with a non-editable layout, yields a read-only table. Edits flow back into the +property as a new value. Flip it live with `it.withLayout(ROW_MAJOR_EDITABLE)`. + +`getColumnClass` drives the `JTable`'s renderer/editor, so +`setColumnClassAt(i, Boolean.class)` buys you check boxes for free. + +Custom cell rendering: `.withCell(cell -> cell.view(c -> c.orGetUi(() -> textField()).updateIf(JTextField.class, tf -> { tf.setText(cell.entryAsString()); return tf; })))`. + +#### Legacy table sources — still supported, but prefer `TableData` + +All of these are **pull-based**: they need `updateTableOn(..)`/`updateOn(..)`, they +cannot say *what* changed (so every refresh rebuilds the whole table), and they are +read live — which forces SwingTree to copy the whole table on every refresh under +`DECOUPLED`. + +```java +UI.table().withModel(m -> m.colName(i -> headers[i]).colCount(() -> headers.length) + .rowCount(() -> data.length).getsEntryAt((r,c) -> data[r][c]) + .setsEntryAt((r,c,val) -> data[r][c] = (int) val) + .isEditableIf(() -> true).updateOn(dataChangedEvent)); // must .fire() by hand +UI.table(UI.ListData.ROW_MAJOR_EDITABLE, () -> listOfRows).updateTableOn(evt); +UI.table(UI.MapData.EDITABLE, () -> mapOfColumns).updateTableOn(evt); +UI.table(UI.ListData.ROW_MAJOR, tupleVar); // Var>>: TableData minus the + // column metadata; keeps the diff fast-path +``` +`BasicTableModel` is only a *description of where the data lives* — SwingTree wraps it +in a thread-safe model of its own, so `JTable.getModel()` does **not** return the +object you passed to `withModel(..)`. + +Full prose: [Writing-Tables.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/Writing-Tables.md). +Executable catalogue of the whole `TableData` API: +[Table_Data_Spec.groovy](https://github.com/globaltcad/swing-tree/blob/main/src/test/groovy/swingtree/Table_Data_Spec.groovy). + +### Icons & SVG (first-class, HiDPI-crisp) + +SVG works **everywhere an icon can appear** (icons, buttons, labels, tabs, +menus, dialogs, style-API images), rendered via JSVG + Java2D, re-rendered at +the current UI scale — never blurry. All icon sizes are developer px (§13). + +**`IconDeclaration` — the right type for view models.** A lightweight immutable +value (path or SVG text + preferred size); loading is lazy + cached, and a +missing resource logs instead of throwing. It is a functional interface over +`source()`: + +```java +IconDeclaration funnel = () -> "img/funnel.svg"; // simplest: lambda +enum Icons implements IconDeclaration { // idiomatic: constants + FUNNEL("img/funnel.svg"), SEED("img/seed.png"); + private final String path; + Icons(String p){ this.path = p; } + @Override public String source(){ return path; } +} +Icons.FUNNEL.withSize(24, 24) / .withWidth(24) // sizing withers +IconDeclaration.ofSvg(svgText) // SVG string; reports the size declared in the SVG +IconDeclaration.ofAutoScaledSvg(svgText) // SVG string; size -1 -> stretches to its component +``` + +**Using them:** `icon(decl)`, `icon(48, 48, decl)`, `button(decl)`, +`label("x").withIcon(decl)`, `tab("t").withIcon(decl)`. **Dynamic:** bind a +`Var` — `icon(iconProp)`, `labelWithIcon(iconProp)`, +`buttonWithIcon(iconProp)`, `menuItem("Connect", iconProp)`; set the property +and the icon swaps. View models hold `IconDeclaration`s, never `ImageIcon`s. + +**Loading by hand:** `UI.findIcon("path")` → `Optional` (classpath → +file system → cache; returns an `SvgIcon` for `.svg`); `UI.findSvgIcon(..)` → +`Optional`. Cache lives in `SwingTree.get().getIconCache()`, keyed by +declaration — prefer declarations over hand-built `SvgIcon`s so equal +declarations share one instance. + +**`SvgIcon`** (`swingtree.style`) — immutable `ImageIcon` subclass; construct +directly only when the SVG text is dynamic (editors, server-sent graphics): +`SvgIcon.of(svgString | stream | document)` / `SvgIcon.at(path | url)`, then +`.withIconSize(w,h)`, `.withIconSizeFromWidth(w)` (height from aspect ratio), +`.withOpacity(f)`, `.withFitComponent(..)`, `.withPreferredPlacement(..)`. +Reported size (`getIconWidth()/getIconHeight()`, DPI-scaled): an explicit size +wins; else a **directly constructed** `SvgIcon.at/of(..)` adopts the px +`width`/`height` declared in the SVG text; **-1** (= unknown → icon adapts to +its component) when those are missing, `%`-based, or non-px units — **and for +every declaration-pipeline load** (`findIcon`, `icon(path)`, +`IconDeclaration.of(path)`): the declaration's default `Size.unknown()` +deliberately resets the icon to flexible. While a dimension is unknown, two +policies control rendering: `UI.FitComponent` — `NO`, `WIDTH`, `HEIGHT`, +`WIDTH_AND_HEIGHT` (these three may distort), `MIN_DIM`/`MAX_DIM` (fit +smaller/larger dimension, keep aspect ratio — usually what you want) — and +`UI.Placement` (`CENTER`, `TOP_LEFT`, … 9 positions). `.getImage()` rasterizes +to a `BufferedImage` (loses scalability — visibly blurry when stretched). + +**Style API images:** `.image(img -> img.svg(svgText).fitMode(..).placement(..))` +or `img.image(iconDeclOrImageIcon)`; plus `opacity`, `size`, `offset`, `repeat`, +`primer(color)`, `clipTo(ComponentArea.BODY|BORDER|INTERIOR|..)`. Layer via the +outer overload `image(Layer.BACKGROUND, img -> ..)`. If the SVG text/config comes +from a property, use the property-bound `withStyle(prop, (svg, it) -> ..)` (§8). +Playground example covering all of this: +[SvgViewer.java](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/stylish/SvgViewer.java). + +### Dialogs (`JOptionPane` wrappers) + +```java +ConfirmAnswer a = UI.confirmation("Continue?").titled("Confirm").show(); // YES/NO/CANCEL/CLOSE +UI.confirmation("Heads up!").showAsWarning(); // .showAsError() .showAsInfo() +UI.message("Saved.").showAsInfo(); // no return value +// customize buttons: .yesOption("OK").noOption("").cancelOption("") (empty hides a button) +``` + +--- + +## 11. Threading & lifecycle + +- SwingTree binding/animation callbacks run on the **EDT**. Business logic that you + trigger via `on(..)` runs on the **application thread**. +- `UI.run(r)` runs on EDT now; `UI.runLater(r)` / `runLater(delay, r)` defer to EDT. +- In a `main`, after `UI.show(...)`, call `EventProcessor.DECOUPLED.join()` to keep + the (decoupled) application thread alive so the program doesn't exit. +- **Under `DECOUPLED`, never let the EDT read mutable application state.** Bind + *values* (immutable records, `Tuple`s, `TableData` — §10) rather than live data + sources: an immutable value cannot be seen half-updated, so no locking, no torn + reads. Pull-based sources (lambda/collection table models, §10) force SwingTree to + copy the whole thing on every refresh to get the same guarantee. +- Set a Look-and-Feel before showing if desired (examples use FlatLaf: + `FlatDarkLaf.setup();` / `FlatLightLaf.setup();`). + +--- + +## 12. Escape hatches & error containment + +SwingTree wraps **every lambda it invokes for you** in try/catch + SLF4J logging, +so a thrown exception in one fragment doesn't tear down the whole UI ("the show +must go on"). Caught: `peek`, `apply`, `applyIf`, `applyIfPresent`, `withStyle`, +all `onXyz` handlers, and `zoomTo` map/wither lambdas. **NOT** caught: code at the +top level of your declaration (your own `for`/`if`/arithmetic *outside* a captured +lambda) — push risky top-level code into `apply(ui -> ...)` or `peek(c -> ...)`. + +| Hatch | Use | +|---|---| +| `.peek(c -> ...)` | **last resort** — reach into the raw Swing component only when SwingTree wraps no equivalent (see the caution below) | +| `.apply(ui -> ...)` | imperative loop that `add(..)`s many children (the lambda gets the builder) | +| `.applyIf(boolean, ui -> ...)` | inline conditional sub-tree (static shape decisions) | +| `.applyIfPresent(Optional>)` | inline `Optional`-driven sub-tree | +| `.get(JPanel.class)` | unwrap the builder to the real component | +| `UI.of(jcomponent)` | wrap a hand-rolled/3rd-party component into the tree | + +> **Prefer reactivity over hatches.** If a condition depends on app state, bind it +> (`isVisibleIf`, `isEnabledIf`, property-bound `add`) instead of `applyIf`, so the +> UI updates automatically. The hatches are for *construction-time* decisions. + +> **`peek(..)` is a code smell — always look for a SwingTree method first.** It hands +> you the raw component and steps *outside* SwingTree's control, forfeiting what the +> library gives you for free: HiDPI "developer-pixel" scaling (§13), the style +> engine's ownership of colours/opacity/borders (§7), decoupled-thread safety (§11), +> and any usability fixes SwingTree layers over raw Swing. So before writing `peek`, +> look for the SwingTree variant — a `with*` setter (e.g. `withPrefSize`, +> `withBackground`, `withTooltip`), an `is*If(Val)` binding, an `on*(..)` +> event handler, `withStyle(..)`, or `withProperty(key, value)` for a client +> property. `peek` is legitimate **only** when no such method exists — a niche Swing +> setter SwingTree genuinely does not wrap (say `JTable#setRowHeight`), or capturing +> a third-party component — and then keep it to that one imperative line. + +--- + +## 13. HiDPI scaling — "developer pixels" vs "component pixels" + +SwingTree maintains one **UI scale factor** (`UI.scale()`, a `float`, derived +from the system font) and applies it everywhere, because vanilla Swing + the +JDK's bundled Look-and-Feels do **not** scale for HiDPI. This creates two +coordinate spaces: + +- **Developer pixels** — the DPI-agnostic numbers *you* write (`withPrefSize(100,50)`). +- **Component pixels** — the real scaled numbers Swing lays out/paints (at scale `2.0` → `200×100`). + +**The symmetry you can rely on:** everything you pass *into* the SwingTree API is +in developer pixels and gets scaled **up** for you; everything SwingTree reads +*back* for you is scaled **down** into developer pixels. So values round-trip +cleanly — you almost never call `UI.scale(..)` yourself. + +- **Inputs scaled up:** all builder dims (`withPrefSize/withMinSize/withWidth/withSizeExactly/...`) + and all style dims (`prefSize`, `minHeight`, `margin`, `padding`, `borderWidth`, + `borderRadius`, gradient/shadow offsets & sizes, …). +- **Outputs scaled down (already in developer px):** + - Style delegate: `it.componentWidth()`, `it.componentHeight()`, + `it.componentPrefWidth()`, `it.componentPrefHeight()`. + - Event delegates (`onClick`, `onResize`, `onMouseMove`, `onDrag`, …): + `it.getX/getY/getPosition`, `it.getWidth/getHeight/getSize`, `it.getPrefSize`, + `it.getBounds`; setters like `it.setBounds/setPrefSize/setMinSize` take + developer px. Mouse: `it.mouseX()/mouseY()/mousePosition()`. Drag: + `it.initialComponentPosition()`, `it.dragPositions()`, `it.deltaXSinceStart()`. + +> **THE DOUBLE-SCALING TRAP (this is why `component()` is deprecated):** the raw +> Swing component returns **component pixels**. If you read +> `it.component().getPreferredSize().height` (already scaled) and pass it back +> into a scaling method like `minHeight(..)`, it is scaled **twice** — min height +> becomes `200` when you meant `100`, and the error grows with the scale factor. +> **Fix:** use the developer-pixel accessor instead: +> ```java +> .withStyle( it -> it.minHeight(it.componentPrefHeight()) ) // ✅ round-trips; NOT it.component().getPreferredSize().height ❌ +> ``` + +> **THE ONE EXCEPTION:** absolute on-screen coords are **raw**, not unscaled — +> `it.mouseXOnScreen()`, `it.mouseYOnScreen()`, `it.mousePositionOnScreen()` are +> in real screen pixels (they're desktop-absolute, possibly multi-monitor). + +Only call the raw helpers when working **against raw Swing** (custom `Graphics2D` +painting, a peeked component, a third-party widget): `UI.scale(int|float|double)` +(developer→component), `UI.unscale(int|float|Dimension)` +(component→developer), `UI.scale(Graphics2D)` (scales a context in place), +`UI.scale()` (the raw factor). Override the factor with +`SwingTree.get().setUiScaleFactor(2.0f)` or +`SwingTree.initializeUsing(cfg -> cfg.uiScaleFactor(2.0f))`. Full prose: +[HiDPI-Scaling.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/HiDPI-Scaling.md). + +## 14. Hard-won gotchas (check these in any review) + +1. **A view that only works at one window size is a bug.** Build convergent by + default (§2c): `wmin 0` / `withMinSize(0,0)` everywhere, a 12-column + `AUTO_SPAN` grid for the page, a `scrollPane(conf -> conf.fitWidth(true))` + around it so the stacked arrangement can outgrow the window. +2. **Minimum sizes are a hard floor and propagate upward.** A label's minimum + width is its full text and a flow grid's minimum is the **sum** of its + children's — one forgotten row gives the whole *window* a minimum width and + the responsive bands become unreachable. `"wmin 0"` on rows, `withMinSize(0,0)` + on grids, `width 90::200` instead of `width 200!`. +3. **A responsive grid nests inside another grid — never inside a MigLayout + cell.** `withPrefSize(w, 0)` declares the reference width, but + `getPreferredSize()` short-circuits the layout manager, so a MigLayout parent + reads that literal `0` and the nested grid **collapses to zero height**, + silently clipping its content. Make the containing card a grid too (§2d). Also + give a preferred height to anything that has none (`scrollPane`, + `scrollPanels`, empty `textField`) — a grid row is only as tall as its + tallest child *prefers* to be. +4. **Never `setOpaque(..)`** on a styled component — the style engine controls + opacity; manual calls fight it. Use `backgroundColor(Color.TRANSPARENT)` / a real + color in `withStyle` instead. +5. **`Tuple` items bound for *per-item editing* (`addAll(Var>, entry -> ..)`, + where `entry` is a `Var` lens) must `implement HasId`** with a stable id — + otherwise equal value-records collide and bindings target the wrong sub-view. The + read-only `addAll(Val>/Tuple, m -> ..)` overloads pass the value and + need no `HasId` (§5.2). +5b. **A bound `addAll` owns its container and clears hand-added children, and its + row supplier runs *later*** — so give the list a panel of its own, and under a + `StyleSheet` wrap the supplier in `UI.use(sheet, ..)` or every row built after + the first model change comes out unstyled (§5.2, §7). +6. **Hold a strong reference (a view field) to any lens used only by a raw + `onChange` subscription** — weak observation will GC it and silently break (§9c). +7. **Tables: bind a `Var`; don't reach for a `TableModel` or a pull-based + data source** (§10). An editable table needs **both** a `*_EDITABLE` layout **and** + a mutable `Var` — either alone is silently read-only. Use **`ROW_MAJOR`** (the + diff-driven, incremental path) and **range ops** (`addRows`/`removeRowsAt`/ + `setRowsAt`) for bulk changes; per-row loops emit one event each. +8. **Never read property values inside a plain `withStyle` lambda** — use the + property-bound `withStyle(prop, (item, it) -> ..)` (§8), which captures the item + thread-safely and repaints automatically. (`withRepaintOn(props) + prop.get()` + is the legacy version of this pattern.) When one style depends on **several** + properties, merge them into one record with the Sprouts ≥2.7 composite view builder + `Viewable.of(seed, it -> it.join(p, wither)…)` and drive it from a single + `withStyle` — no need to chain one per property (§8). +9. **Pick the right thread:** `onView` for view-touching handlers, `on` for + model/business handlers; respect `From.VIEW` vs `From.VIEW_MODEL` to avoid + feedback loops. +10. **View models import zero Swing classes.** If you find a `JComponent` in a view + model, the architecture is wrong. +11. Expose **`Val`** (not `Var`) from a view model for fields the view must not write. +12. Use **enum** group tags and the type-safe layout constants for refactor safety. +13. Withers must be **pure** and return **new** instances (Lombok `@With` on records + is the cleanest path); never mutate `this`. +14. **Never feed a raw Swing size/position back into the SwingTree API** — values + from `it.component().getPreferredSize()`/`getBounds()`/`getWidth()` are in + *component pixels* (already scaled); passing them to `minHeight(..)`/`size(..)`/etc. + double-scales them. Read geometry through the delegate accessors + (`componentPrefHeight()`, `getWidth()`, `mouseX()`, …) which give developer pixels. (§13) +15. **`peek(..)` is a code smell — prefer a SwingTree method.** Raw-component tweaks + step outside HiDPI scaling, the style engine and decoupled-thread safety; reach + for a `with*`/`is*If`/`on*`/`withStyle`/`withProperty` method first. `peek` is + legitimate only when SwingTree wraps no equivalent (§12). + +--- + +## 15. Cheat sheet + +```java +import static swingtree.UI.*; +import sprouts.*; // Var, Val, Vars, Vals, Tuple, From, Viewable, HasId, Event + +// build + show +UI.show(panel("fill, wrap 2").add("growx", textField(name)).add(button("Go").onClick(it -> ...))); +UI.show(f -> new MyView(vm)); EventProcessor.DECOUPLED.join(); + +// view skeleton +UI.of(this).withLayout(FILL.and(WRAP(1)).and(INS(16))).add(GROW, child); + +// state +Var v = Var.of(value); v.get(); v.set(x); v.update(fn); Val d = v.viewAsString(fn); +v.isEnabledIf / isVisibleIf / isSelectedIf / isEditableIf (Val) +Viewable c = Viewable.of(a, b, (x,y) -> combine); // derived from 2 sources; result type = a's type +Viewable r = Viewable.of(R.class, a, b, (x,y) -> ..); // ...or with an explicitly different result type +Viewable m = Viewable.of(seed, it -> it.join(a,C::withA).join(b,C::withB).join(c,C::withC)); // N sources → 1 record (Sprouts ≥2.7) +Viewable w = v.view(); // weakly-held listenable view (store in a field!) + +// sprouts immutable collections (persistent; every op returns a new instance) +Tuple t = Tuple.of(a,b,c) / Tuple.of(T.class); // List-like: add/remove/setAt/map/retainIf/sort +Association m = Association.between(K.class,V.class);// Map-like: put / get(k)->Optional / remove (NOT .of!) +ValueSet s = ValueSet.of(E.class); // Set-like: add/addAll/retainAll/any +Result res = Result.ofTry(T.class, () -> risky()); // Maybe + Tuple; res.problems()/orElse(x) + +// lenses (MVI/MVL) +Var f = root.zoomTo(Root::f, Root::withF); // mutable lens +Val r = root.viewAs(F.class, Root::f); // read-only view +Var e = root.zoomTo(c -> c.get(k).orElse(d), (c,x) -> c.put(k,x)); // lens into a collection entry +Var> items = root.zoomTo(Root::items, Root::withItems); +panel.addAll(items, (Var it) -> itemView(it)); // per-item lens ⇒ Item implements HasId! +panel.addAll(roTuple, (Item it) -> itemView(it)); // read-only value ⇒ no HasId needed + +// tables (§10) — an immutable value describing the WHOLE table; bind it and it follows +Var d = Var.of(TableData.of(UI.ListData.ROW_MAJOR, "Name","Age").addRow("Alice",30)); +UI.table(d); d.update(it -> it.addRow("Bob", 42)); // no updateTableOn/Event needed +it.setCellAt(r,c,v) / .addRowAt(i,vals…) / .removeRowAt(i) / .setColumnClassAt(i,Boolean.class) +it.addRows(t) / .removeRowsAt(i,n) / .setRowsAt(i,t) // range ops ⇒ ONE table event, not N +// editable ⇔ *_EDITABLE layout AND a mutable Var; ROW_MAJOR ⇒ incremental (diff) updates + +// events +.onClick / .onMouseEnter / .onMouseClick / .onKeyPress / .onResize (it -> ...) +.on(observable, it -> appWork) .onView(observable, it -> viewWork) + +// style +.withStyle(it -> it.padding(8).borderRadius(12).backgroundColor(c).shadowBlurRadius(6) + .gradient(Layer.BACKGROUND,"g",g->g.type(GradientType.RADIAL).colors(a,b)) + .componentFont(fc -> fc.size(14).family("Serif"))) +.withStyle(prop, (item, it) -> it.backgroundColor(item.color())) // property-driven, auto-repaint (§8) +.withStyle(Viewable.of(seed, it -> it.join(a,Seed::withA).join(b,Seed::withB)), (m,it)->..) // N props → 1 styler (Sprouts ≥2.7; §8) +.withRepaintOn(eventA, eventB) +.withTransitionalStyle(boolVar, LifeTime.of(0.4, SECONDS), (state, it) -> it. ...progress()...) + +// animation +it.animateFor(0.5, TimeUnit.SECONDS, s -> ... s.progress() / s.fadeIn() / it.paint(s, g->...)); +UI.animateFor(2, SECONDS).go(s -> p.set(s.progress())); +UI.animate(vm, ViewModel::someAnimatable); + +// convergence — the default page skeleton (§2c). Categories are FIFTHS of the reference width. +scrollPane(conf -> conf.fitWidth(true)).withHorizontalScrollBarPolicy(UI.Active.NEVER).add( + panel().withFlowLayout(UI.HorizontalAlignment.LEFT, 18, 18) + .withMinSize(0,0) // a grid's minimum is the SUM of its children's — kill it + .withPrefSize(REFERENCE_WIDTH, 0) // declares where the bands sit; MANDATORY for a nested grid + .add(AUTO_SPAN(it->it.fill(true).verySmall(12).small(12).medium(12).large(5).veryLarge(4).oversize(4)), sidebar) + .add(AUTO_SPAN(it->it.fill(true).verySmall(12).small(12).medium(12).large(7).veryLarge(8).oversize(8)), content)); +.add("growx, wmin 0", label(..)) // or its text becomes the window's minimum width +scrollPanels().withPrefSize(340, 470) // a grid row is only as tall as its tallest child PREFERS +// ⚠ a grid with withPrefSize(w,0) must sit in a GRID or a fitWidth scrollPane — a MigLayout +// cell reads the literal 0 and the grid renders at zero height (§2d) +label(..).isVisibleIf(isWide) // + "hidemode 3" on the container ⇒ content converges too + +// reactive layout (gear 2 — reflow, nothing rebuilt: focus/caret/scroll survive) +Var L = Var.of(Layout.class, Layout.mig("fill, wrap 1")); +panel(L)...; L.set(Layout.mig("fill, wrap 2, nogrid").withChildConstraints(MigAddConstraint.of("growx, span 2"))); +// every variant must give EVERY child a constraint (positional, only overwritten where supplied) + +// form factor (gear 3 — swaps the tree; needs hysteresis, loses component state) +.onResize(it -> ff.update(From.VIEW, f -> Formfactor.of(it.getWidth(), it.getHeight(), f))) +.add(GROW.and(PUSH), ff, this::body); + +// icons & SVG (crisp at any DPI; sizes in developer px) +IconDeclaration ic = () -> "img/x.svg"; // value object -> belongs in view models +IconDeclaration.ofSvg(svgText) / .ofAutoScaledSvg(svgText) / ic.withSize(24,24) +icon(ic) / button(ic) / label("x").withIcon(ic) / tab("t").withIcon(ic) +icon(iconProp) / labelWithIcon(iconProp) / buttonWithIcon(iconProp) // Val -> swaps live +UI.findIcon("img/x.svg") / UI.findSvgIcon(..) // Optional<..>, classpath + cache +SvgIcon.of(svgText).withIconSizeFromWidth(64).withFitComponent(FitComponent.MIN_DIM) +.withStyle(it -> it.image(img -> img.svg(svgText).fitMode(..).placement(..))) + +// style sheet + theme +UI.use(sheet, () -> UI.show(f -> new View())); // sheet.reconfigure() hot-swaps + +// escape hatches (peek = last resort; prefer a with*/is*If/on*/withStyle method — §12) +.peek(c -> c.setX(..)).apply(ui -> {for(..) ui.add(..);}).applyIf(cond, ui -> ui.add(..)).get(JPanel.class) + +// HiDPI scaling — you write developer px (scaled up), delegates return developer px (scaled down) +.withStyle(it -> it.minHeight(it.componentPrefHeight())) // ✅ round-trips; NOT it.component().getPreferredSize().height ❌ +it.getWidth()/getHeight()/getBounds()/mouseX()/mouseY() // all developer px; mouse*OnScreen() = raw screen px +UI.scale(int|float|double) / UI.unscale(..) / UI.scale(g2d) // only when working against RAW Swing +``` + +### Runnable examples in the SwingTree repo (read these for full context) + +All example sources live under [`src/test/java/examples/`](https://github.com/globaltcad/swing-tree/tree/main/src/test/java/examples) +in the repo; the links below open each on GitHub. + +- [`calculator/mvi/CalculatorView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/calculator/mvi/CalculatorView.java) — canonical MVI/MVL. +- [`team/mvi/TeamView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/team/mvi/TeamView.java) **vs** [`team/mvvm/TeamView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/team/mvvm/TeamView.java) — same UI, both architectures. +- [`chat/mvi/ChatView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/chat/mvi/ChatView.java) (+ `ChatViewModel`, `Room`, `Message`, `ChatStyle`, `ChatArt`) — **the reference for `Tuple` + `addAll` + `HasId`**, inside a whole messenger: a room rail, a roster, message bubbles editable in place, and emoji reactions, all bound off one immutable root. Three less obvious ideas live here too: a **lens onto a *computed* projection** (`vm.zoomTo(ChatViewModel::visibleMessages, ChatViewModel::withVisibleMessages)` — the getter filters the selected room by the search box, the wither merges edits and deletions back by `id`, so one lens reacts to three inputs with zero listeners); **generated SVG as a value** (`ChatArt` builds the room sigils and a "conversation ribbon" as SVG *text*, fed to `withStyle(svgVal, (svg, it) -> it.image(img -> img.svg(svg)))`); and a hot-swapped `StyleSheet` whose row suppliers **re-enter the `UI.use(..)` scope** — the gotcha that otherwise leaves every dynamically added row unstyled (§5.2). +- [`trains/mvi/TrainsView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/trains/mvi/TrainsView.java) (+ `TrainsViewModel`, `TransitClient`) — real-world MVI: `Tuple`-valued state, a Swing-free data layer doing blocking IO off the EDT, and Lombok `@With`/`@Getter` value objects (records-free, **Java 8**-clean). +- [`budget/mvi/BudgetView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/budget/mvi/BudgetView.java) (+ `BudgetViewModel`, `Budget`, `BudgetHealth`) — **the reference for convergence (§2c/2d): four arrangements of three cards from one span table, with zero state.** It also showcases three other ideas at once: a **value-model table** bound with `UI.table(Var)` (editable, edits flow back as a new value; a `withCellForColumn` renderer/editor euro-formats the Amount column yet commits back a `Double`), a **value-capturing SVG style** `withStyle(svgText, (svg, it) -> it.image(img -> img.svg(svg)))` driving a donut chart generated from the data, and a **composite view** `Viewable.of(seed, it -> it.join(a, ..).join(b, ..)…)` (Sprouts ≥2.7) merging three properties into one item for a single `withStyle`. +- [`breathing/mvi/BreathingView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/breathing/mvi/BreathingView.java) (+ `BreathingViewModel`) — modelled animation, re-arming, the GC gotcha. +- [`animated/AnimatedView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/animated/AnimatedView.java) / [`TransitionalAnimation.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/animated/TransitionalAnimation.java) — the full animation primitive tour. +- [`zen/ThemeGardenView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/zen/ThemeGardenView.java) (+ `ThemedStyleSheet`) — style sheets, groups, runtime theme swap. +- [`scribe/CelestialScribe.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/scribe/CelestialScribe.java) — `Layout.none()` derived from data, styled text flowing around children. +- [`dashboard/SalesDashboard.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/dashboard/SalesDashboard.java) — reactive `Var` reflow. +- [`almanack/mvi/AlmanackView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/almanack/mvi/AlmanackView.java) (+ `AlmanackViewModel`) — every tab binding mechanism in one field-notebook app: a two-way `Var` selection index that may point at tabs which don't exist yet (deferred selection), `addAll(Val>, TabSupplier)` dynamic tabs, enum⇄index lenses, bound tab titles/tooltips/enabled flags. +- [`stylish/SoftUIView.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/stylish/SoftUIView.java) — soft-UI style sheet, custom paint. +- [`stylish/SvgViewer.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/stylish/SvgViewer.java) — SVG playground: one SVG rendered through four pipelines (`SvgIcon` in style API, `img.svg(..)` string, rasterized `getImage()`, component icon) with live `Placement`/`FitComponent` switching. +- [`simple/ResponsiveLayout.java`](https://github.com/globaltcad/swing-tree/blob/main/src/test/java/examples/simple/ResponsiveLayout.java) (+ `ResponsiveLayoutAlign`, `ResponsiveLayoutFill`) — the smallest `AUTO_SPAN` responsive flow demo. + +**Convergent examples, by which gears they use (§2c):** `budget/mvi/BudgetView` +and `zen/ThemeGardenView` (gears 0+1, pure span tables); `animated/AnimatedView` +(0+1 with a **nested** grid — the recipe list is a column as a sidebar, a chip +grid when stacked); `team/mvi/TeamView` + its `mvvm` twin (0+1, master–detail +with a nested responsive *form*, and the grid-in-a-grid card that makes it +measure correctly); `breathing/mvi/BreathingView` (0+1 plus size-relative +*painting* — the orb is sized from its box, not in pixels); +`almanack/mvi/AlmanackView` (0+2+4, four breakpoints feeding four `Val` +properties, nothing ever rebuilt); `trains/mvi/TrainsView` (0+2+3+4 — a +`Formfactor` in the view model swapping a split pane for a scrolling column, +plus a reactive toolbar and bound labels that shorten); +`chat/mvi/ChatView` (0+1+2+4 and **deliberately no gear 3** — a chat is full of +state you must not destroy, so every shape is reached by reflowing: nested grids +turn the room rail and the roster from sidebars into banners, a `Val` +composer measures *its own* width rather than the window's, and the conversation's +preferred height is derived from the window inside the view model, because a flow +grid gives a row the height its tallest child *prefers* and never stretches it). + +The wiki ([`docs/markdown/`](https://github.com/globaltcad/swing-tree/tree/main/docs/markdown)) is the prose +companion; start at [README.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/README.md) → +[Climbing-Swing-Tree.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/Climbing-Swing-Tree.md) → +[Functional-MVVM.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/Functional-MVVM.md). +For layout specifically: +[Convergent-Design.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/Convergent-Design.md) +(strategy + checklist) → +[Responsive-Layouts.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/Responsive-Layouts.md) +(grid mechanics, nesting rules, a debugging table) → +[Reactive-Layouts.md](https://github.com/globaltcad/swing-tree/blob/main/docs/markdown/Reactive-Layouts.md). \ No newline at end of file diff --git a/MODULE.bazel b/MODULE.bazel index b3b5000b..aaeb94fa 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -20,7 +20,9 @@ maven.install( "org.usb4java:usb4java:1.3.0", "org.usb4java:usb4java-javax:1.3.0", "com.formdev:flatlaf:3.0", + "io.github.globaltcad:swing-tree:0.24.1", "io.reactivex.rxjava3:rxjava:3.1.10", + "io.github.globaltcad:sprouts:2.7.0", ], repositories = [ "https://repo1.maven.org/maven2", diff --git a/java/com/cowlark/fluxengine/gui/BUILD.bazel b/java/com/cowlark/fluxengine/gui/BUILD.bazel index 7fe9fd4a..e2d8bef7 100644 --- a/java/com/cowlark/fluxengine/gui/BUILD.bazel +++ b/java/com/cowlark/fluxengine/gui/BUILD.bazel @@ -8,5 +8,7 @@ java_library( deps = [ "@maven//:com_formdev_flatlaf", "@maven//:com_google_guava_guava", + "@maven//:io_github_globaltcad_sprouts", + "@maven//:io_github_globaltcad_swing_tree", ], ) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index c72f6c78..3e9094ca 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -1,11 +1,18 @@ package com.cowlark.fluxengine.gui; +import static swingtree.UI.label; +import static swingtree.UI.menu; +import static swingtree.UI.menuItem; +import static swingtree.UI.of; +import static swingtree.UI.panel; + import com.formdev.flatlaf.FlatDarkLaf; import com.google.common.collect.ImmutableList; -import javax.swing.JFrame; -import javax.swing.SwingUtilities; +import javax.swing.JOptionPane; +import javax.swing.JPanel; import javax.swing.UIManager; -import javax.swing.WindowConstants; +import swingtree.UI; +import swingtree.threading.EventProcessor; /** * The FluxEngine GUI, ported from src/gui/main.cc. @@ -16,17 +23,29 @@ public void run(ImmutableList args) throws Exception { UIManager.setLookAndFeel(new FlatDarkLaf()); System.setProperty("apple.laf.useScreenMenuBar", "true"); - SwingUtilities.invokeLater(() -> { - createAndShowGui(); + + UI.MenuBar menuBar = of(new UI.MenuBar()) + .add(menu("File") + .add(menuItem("About FluxEngine...").onClick(it -> + JOptionPane.showMessageDialog( + null, + "FluxEngine\nA disk-flux reader/writer", + "About FluxEngine", + JOptionPane.INFORMATION_MESSAGE))) + .add(menuItem("Exit").onClick(it -> System.exit(0)))) + .get(UI.MenuBar.class); + + UI.show("FluxEngine", frame -> { + frame.setJMenuBar(menuBar); + frame.setSize(800, 600); + frame.setLocationRelativeTo(null); + + return panel("fill") + .add(label("FluxEngine")) + .get(JPanel.class); }); - } - private static void createAndShowGui() - { - JFrame frame = new NewJFrame(); - frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE); - frame.setLocationRelativeTo(null); - frame.setVisible(true); + EventProcessor.DECOUPLED.join(); } public static void main(String[] args) diff --git a/java/com/cowlark/fluxengine/gui/NewJFrame.form b/java/com/cowlark/fluxengine/gui/NewJFrame.form deleted file mode 100644 index 70784fd2..00000000 --- a/java/com/cowlark/fluxengine/gui/NewJFrame.form +++ /dev/null @@ -1,119 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com/cowlark/fluxengine/gui/NewJFrame.java b/java/com/cowlark/fluxengine/gui/NewJFrame.java deleted file mode 100644 index 46092448..00000000 --- a/java/com/cowlark/fluxengine/gui/NewJFrame.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license - * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JFrame.java to edit this template - */ -package com.cowlark.fluxengine.gui; - -/** - * - * @author dg - */ -public class NewJFrame extends javax.swing.JFrame { - - private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(NewJFrame.class.getName()); - - /** - * Creates new form NewJFrame - */ - public NewJFrame() { - initComponents(); - } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - java.awt.GridBagConstraints gridBagConstraints; - - jLabel1 = new javax.swing.JLabel(); - jButton1 = new javax.swing.JButton(); - jTextField1 = new javax.swing.JTextField(); - jRadioButton1 = new javax.swing.JRadioButton(); - jMenuBar1 = new javax.swing.JMenuBar(); - jMenu1 = new javax.swing.JMenu(); - jMenuItem1 = new javax.swing.JMenuItem(); - jMenu2 = new javax.swing.JMenu(); - cutMenuItem = new javax.swing.JMenuItem(); - copyMenuItem = new javax.swing.JMenuItem(); - pasteMenuItem = new javax.swing.JMenuItem(); - deleteMenuItem = new javax.swing.JMenuItem(); - - setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); - getContentPane().setLayout(new java.awt.GridBagLayout()); - - jLabel1.setText("jLabel1"); - getContentPane().add(jLabel1, new java.awt.GridBagConstraints()); - - jButton1.setText("jButton1"); - getContentPane().add(jButton1, new java.awt.GridBagConstraints()); - - jTextField1.setText("jTextField1"); - getContentPane().add(jTextField1, new java.awt.GridBagConstraints()); - - jRadioButton1.setText("jRadioButton1"); - gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 1; - gridBagConstraints.gridy = 1; - getContentPane().add(jRadioButton1, gridBagConstraints); - - jMenu1.setText("File"); - - jMenuItem1.setText("Exit"); - jMenuItem1.addActionListener(this::jMenuItem1ActionPerformed); - jMenu1.add(jMenuItem1); - - jMenuBar1.add(jMenu1); - - jMenu2.setText("Edit"); - - cutMenuItem.setIcon(javax.swing.UIManager.getIcon("Actions.cut")); - cutMenuItem.setText("Cut"); - jMenu2.add(cutMenuItem); - - copyMenuItem.setText("Copy"); - jMenu2.add(copyMenuItem); - - pasteMenuItem.setText("Paste"); - jMenu2.add(pasteMenuItem); - - deleteMenuItem.setText("Delete"); - jMenu2.add(deleteMenuItem); - - jMenuBar1.add(jMenu2); - - setJMenuBar(jMenuBar1); - - pack(); - }// //GEN-END:initComponents - - private void jMenuItem1ActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_jMenuItem1ActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_jMenuItem1ActionPerformed - - /** - * @param args the command line arguments - */ - public static void main(String args[]) { - /* Set the Nimbus look and feel */ - // - /* If Nimbus (introduced in Java SE 6) is not available, stay with the default look and feel. - * For details see http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html - */ - try { - for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { - if ("Nimbus".equals(info.getName())) { - javax.swing.UIManager.setLookAndFeel(info.getClassName()); - break; - } - } - } catch (ReflectiveOperationException | javax.swing.UnsupportedLookAndFeelException ex) { - logger.log(java.util.logging.Level.SEVERE, null, ex); - } - // - - /* Create and display the form */ - java.awt.EventQueue.invokeLater(() -> new NewJFrame().setVisible(true)); - } - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JMenuItem copyMenuItem; - private javax.swing.JMenuItem cutMenuItem; - private javax.swing.JMenuItem deleteMenuItem; - private javax.swing.JButton jButton1; - private javax.swing.JLabel jLabel1; - private javax.swing.JMenu jMenu1; - private javax.swing.JMenu jMenu2; - private javax.swing.JMenuBar jMenuBar1; - private javax.swing.JMenuItem jMenuItem1; - private javax.swing.JRadioButton jRadioButton1; - private javax.swing.JTextField jTextField1; - private javax.swing.JMenuItem pasteMenuItem; - // End of variables declaration//GEN-END:variables -} From 12535aba463ed37d06ae1717d8a21640614307f5 Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 13 Aug 2026 02:31:29 +0200 Subject: [PATCH 187/192] Try and make the menu bar actually work. --- .../cowlark/fluxengine/gui/AboutAction.java | 18 +++ java/com/cowlark/fluxengine/gui/AppMenu.java | 146 ++++++++++++++++++ java/com/cowlark/fluxengine/gui/Gui.java | 37 ++--- java/com/cowlark/fluxengine/gui/UiUtils.java | 18 +++ 4 files changed, 193 insertions(+), 26 deletions(-) create mode 100644 java/com/cowlark/fluxengine/gui/AboutAction.java create mode 100644 java/com/cowlark/fluxengine/gui/AppMenu.java create mode 100644 java/com/cowlark/fluxengine/gui/UiUtils.java diff --git a/java/com/cowlark/fluxengine/gui/AboutAction.java b/java/com/cowlark/fluxengine/gui/AboutAction.java new file mode 100644 index 00000000..3d819f31 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/AboutAction.java @@ -0,0 +1,18 @@ +package com.cowlark.fluxengine.gui; + +import javax.swing.AbstractAction; +import javax.swing.JOptionPane; +import java.awt.event.ActionEvent; + +class AboutAction extends AbstractAction +{ + @Override + public void actionPerformed(ActionEvent e) + { + JOptionPane.showMessageDialog( + null, + "FluxEngine\nA disk-flux reader/writer", + "About FluxEngine", + JOptionPane.INFORMATION_MESSAGE); + } +} diff --git a/java/com/cowlark/fluxengine/gui/AppMenu.java b/java/com/cowlark/fluxengine/gui/AppMenu.java new file mode 100644 index 00000000..ae9bb98e --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/AppMenu.java @@ -0,0 +1,146 @@ +package com.cowlark.fluxengine.gui; + +import static swingtree.UIFactoryMethods.menu; +import static swingtree.UIFactoryMethods.menuItem; +import static swingtree.UIFactoryMethods.of; +import static swingtree.UIFactoryMethods.separator; + +import swingtree.UI; +import swingtree.UIForMenuItem; +import javax.swing.AbstractAction; +import javax.swing.Action; +import javax.swing.JMenuItem; +import javax.swing.KeyStroke; +import javax.swing.UIManager; +import javax.swing.text.DefaultEditorKit; +import javax.swing.text.JTextComponent; +import java.awt.KeyboardFocusManager; +import java.awt.Toolkit; +import java.awt.event.ActionEvent; +import java.awt.event.KeyEvent; + +public class AppMenu +{ + public static UI.MenuBar createMenu() + { + installMacAboutHandler(); + + return of(new UI.MenuBar()).add(menu("File").add(menuItem("About FluxEngine...").onClick(it -> UiUtils.fireAction(new AboutAction(), + it.getComponent()))) + .add(separator()) + .add(menuItem("Exit").onClick(it -> System.exit(0)))) + .add(menu("Edit").add(actionMenuItem( + "Cut", + "cut", + new DefaultEditorKit.CutAction(), + shortcut(KeyEvent.VK_X))) + .add(actionMenuItem( + "Copy", + "copy", + new DefaultEditorKit.CopyAction(), + shortcut(KeyEvent.VK_C))) + .add(actionMenuItem( + "Paste", + "paste", + new DefaultEditorKit.PasteAction(), + shortcut(KeyEvent.VK_V))) + .add(actionMenuItem( + "Delete", + "delete", + new DeleteAction(), + shortcut(KeyEvent.VK_DELETE)))) + .get(UI.MenuBar.class); + + } + + /* On macOS, wires the application menu's About item to AboutAction. The + * com.apple.eawt API is macOS-only, so this is done reflectively to keep + * the code compiling on other platforms. */ + private static void installMacAboutHandler() + { + if (!System.getProperty("os.name").toLowerCase().contains("mac")) + return; + + try + { + Class applicationClass = Class.forName("com.apple.eawt.Application"); + Class aboutHandlerClass = Class.forName("com.apple.eawt.AboutHandler"); + + Object application = applicationClass.getMethod("getApplication").invoke(null); + Object handler = java.lang.reflect.Proxy.newProxyInstance( + AppMenu.class.getClassLoader(), + new Class[]{aboutHandlerClass}, + (proxy, method, args) -> { + if (method.getName().equals("handleAbout")) + new AboutAction().actionPerformed(null); + return null; + }); + + applicationClass.getMethod("setAboutHandler", aboutHandlerClass) + .invoke(application, handler); + } catch (ReflectiveOperationException e) + { + /* The Mac integration isn't available; ignore. */ + } + } + + /* Returns a platform-standard menu accelerator KeyStroke for the given key + * code (Cmd on macOS, Ctrl elsewhere). */ + static KeyStroke shortcut(int keyCode) + { + return KeyStroke.getKeyStroke( + keyCode, + Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx()); + } + + /* Returns the standard platform icon for the given action, or null if the + * look-and-feel doesn't provide one. */ + static javax.swing.Icon actionIcon(String name) + { + return UIManager.getIcon("Actions." + name); + } + + /* Builds a menu item bound to the given action, setting the label, icon, + * and accelerator from the action's properties. */ + static UIForMenuItem actionMenuItem(String name, + String iconName, + Action action, + KeyStroke keyStroke) + { + action.putValue(Action.NAME, name); + javax.swing.Icon icon = actionIcon(iconName); + if (icon != null) + action.putValue(Action.SMALL_ICON, icon); + action.putValue(Action.ACCELERATOR_KEY, keyStroke); + return menuItem(name).peek(item -> item.setAction(action)); + } + + /* Returns the text component which currently has keyboard focus, if any. */ + static JTextComponent focusedTextComponent() + { + java.awt.Component focusOwner = + KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner(); + return focusOwner instanceof JTextComponent component ? component : null; + } + + /* An action which deletes the selected content of the focused text + * component. */ + static class DeleteAction extends AbstractAction + { + @Override + public void actionPerformed(ActionEvent e) + { + JTextComponent component = focusedTextComponent(); + if (component == null) + return; + + Action delete = component.getActionMap().get(DefaultEditorKit.deleteNextCharAction); + if (delete != null) + delete.actionPerformed(new ActionEvent( + component, + ActionEvent.ACTION_PERFORMED, + null)); + } + } + +} \ No newline at end of file diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index 3e9094ca..6d797fa6 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -1,49 +1,34 @@ package com.cowlark.fluxengine.gui; import static swingtree.UI.label; -import static swingtree.UI.menu; -import static swingtree.UI.menuItem; -import static swingtree.UI.of; import static swingtree.UI.panel; import com.formdev.flatlaf.FlatDarkLaf; import com.google.common.collect.ImmutableList; -import javax.swing.JOptionPane; -import javax.swing.JPanel; -import javax.swing.UIManager; import swingtree.UI; import swingtree.threading.EventProcessor; +import javax.swing.JPanel; +import javax.swing.UIManager; /** * The FluxEngine GUI, ported from src/gui/main.cc. */ public class Gui { + public void run(ImmutableList args) throws Exception { UIManager.setLookAndFeel(new FlatDarkLaf()); System.setProperty("apple.laf.useScreenMenuBar", "true"); - UI.MenuBar menuBar = of(new UI.MenuBar()) - .add(menu("File") - .add(menuItem("About FluxEngine...").onClick(it -> - JOptionPane.showMessageDialog( - null, - "FluxEngine\nA disk-flux reader/writer", - "About FluxEngine", - JOptionPane.INFORMATION_MESSAGE))) - .add(menuItem("Exit").onClick(it -> System.exit(0)))) - .get(UI.MenuBar.class); - - UI.show("FluxEngine", frame -> { - frame.setJMenuBar(menuBar); - frame.setSize(800, 600); - frame.setLocationRelativeTo(null); - - return panel("fill") - .add(label("FluxEngine")) - .get(JPanel.class); - }); + UI.show( + "FluxEngine", frame -> { + frame.setJMenuBar(AppMenu.createMenu()); + frame.setSize(800, 600); + frame.setLocationRelativeTo(null); + + return panel("fill").add(label("FluxEngine")).get(JPanel.class); + }); EventProcessor.DECOUPLED.join(); } diff --git a/java/com/cowlark/fluxengine/gui/UiUtils.java b/java/com/cowlark/fluxengine/gui/UiUtils.java new file mode 100644 index 00000000..d1e009fa --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/UiUtils.java @@ -0,0 +1,18 @@ +package com.cowlark.fluxengine.gui; + +import javax.swing.Action; +import java.awt.event.ActionEvent; + +public class UiUtils +{ + /* Fires the given action with the clicked component as its source, so that + * actions which resolve their target from the event source work correctly. + */ + static void fireAction(Action action, java.awt.Component source) + { + action.actionPerformed(new ActionEvent( + source, + ActionEvent.ACTION_PERFORMED, + (String) action.getValue(Action.ACTION_COMMAND_KEY))); + } +} From a5b229030d51521b0d6716842d24ed34aa7995c8 Mon Sep 17 00:00:00 2001 From: David Given Date: Thu, 13 Aug 2026 23:41:41 +0200 Subject: [PATCH 188/192] Make the window closeable. --- java/com/cowlark/fluxengine/gui/Gui.java | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index 6d797fa6..f665ddd2 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -7,7 +7,6 @@ import com.google.common.collect.ImmutableList; import swingtree.UI; import swingtree.threading.EventProcessor; -import javax.swing.JPanel; import javax.swing.UIManager; /** @@ -21,14 +20,16 @@ public void run(ImmutableList args) throws Exception UIManager.setLookAndFeel(new FlatDarkLaf()); System.setProperty("apple.laf.useScreenMenuBar", "true"); - UI.show( - "FluxEngine", frame -> { + UI.frame("FluxEngine") + .withOnCloseOperation(UI.OnWindowClose.DISPOSE) + .onClose(it -> System.exit(0)) + .peek(frame -> { frame.setJMenuBar(AppMenu.createMenu()); frame.setSize(800, 600); frame.setLocationRelativeTo(null); - - return panel("fill").add(label("FluxEngine")).get(JPanel.class); - }); + }) + .add(panel("fill").add(label("FluxEngine"))) + .show(); EventProcessor.DECOUPLED.join(); } From e8c733b527a8c9478a63225f7e79de28700686f1 Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 14 Aug 2026 01:33:41 +0200 Subject: [PATCH 189/192] Add a skeleton Matisse UI. --- .../fluxengine/gui/ApplicationFrame.form | 348 ++++++++++++++++++ .../fluxengine/gui/ApplicationFrame.java | 245 ++++++++++++ java/com/cowlark/fluxengine/gui/Gui.java | 12 +- 3 files changed, 595 insertions(+), 10 deletions(-) create mode 100644 java/com/cowlark/fluxengine/gui/ApplicationFrame.form create mode 100644 java/com/cowlark/fluxengine/gui/ApplicationFrame.java diff --git a/java/com/cowlark/fluxengine/gui/ApplicationFrame.form b/java/com/cowlark/fluxengine/gui/ApplicationFrame.form new file mode 100644 index 00000000..6241af9f --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/ApplicationFrame.form @@ -0,0 +1,348 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/com/cowlark/fluxengine/gui/ApplicationFrame.java b/java/com/cowlark/fluxengine/gui/ApplicationFrame.java new file mode 100644 index 00000000..3f28f192 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/ApplicationFrame.java @@ -0,0 +1,245 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this + * license + * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JFrame.java to edit this template + */ +package com.cowlark.fluxengine.gui; + +import javax.swing.KeyStroke; +import java.awt.Toolkit; +import java.awt.event.KeyEvent; + +/** + * + * @author dg + */ +public class ApplicationFrame extends javax.swing.JFrame +{ + + private static final java.util.logging.Logger logger = + java.util.logging.Logger.getLogger(ApplicationFrame.class.getName()); + + /** + * Creates new form ApplicationFrame + */ + public ApplicationFrame() + { + initComponents(); + statusBarPanel.putClientProperty("FlatLaf.style", "margin: 2,8,2,8"); + } + + /** + * This method is called from within the constructor to initialize the form. + * WARNING: Do NOT modify this code. The content of this method is always + * regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + jMenuItem2 = new javax.swing.JMenuItem(); + jLabel1 = new javax.swing.JLabel(); + statusBarPanel = new javax.swing.JPanel(); + jLabel8 = new javax.swing.JLabel(); + jSplitPane1 = new javax.swing.JSplitPane(); + jSplitPane3 = new javax.swing.JSplitPane(); + jSplitPane4 = new javax.swing.JSplitPane(); + jTabbedPane3 = new javax.swing.JTabbedPane(); + jScrollPane7 = new javax.swing.JScrollPane(); + jPanel3 = new javax.swing.JPanel(); + jLabel6 = new javax.swing.JLabel(); + jTabbedPane4 = new javax.swing.JTabbedPane(); + jScrollPane4 = new javax.swing.JScrollPane(); + jPanel4 = new javax.swing.JPanel(); + jLabel3 = new javax.swing.JLabel(); + jScrollPane5 = new javax.swing.JScrollPane(); + jPanel1 = new javax.swing.JPanel(); + jLabel4 = new javax.swing.JLabel(); + jTabbedPane1 = new javax.swing.JTabbedPane(); + jPanel2 = new javax.swing.JPanel(); + jToolBar1 = new javax.swing.JToolBar(); + jButton2 = new javax.swing.JButton(); + jScrollPane1 = new javax.swing.JScrollPane(); + jPanel5 = new javax.swing.JPanel(); + jLabel7 = new javax.swing.JLabel(); + jMenuBar1 = new javax.swing.JMenuBar(); + jMenu1 = new javax.swing.JMenu(); + aboutMenuItem = new javax.swing.JMenuItem(); + jSeparator1 = new javax.swing.JPopupMenu.Separator(); + exitMenuItem = new javax.swing.JMenuItem(); + jMenu2 = new javax.swing.JMenu(); + cutMenuItem = new javax.swing.JMenuItem(); + copyMenuItem = new javax.swing.JMenuItem(); + pasteMenuItem = new javax.swing.JMenuItem(); + deleteMenuItem = new javax.swing.JMenuItem(); + + jMenuItem2.setText("jMenuItem2"); + + jLabel1.setText("jLabel1"); + + setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); + setPreferredSize(new java.awt.Dimension(800, 600)); + + jLabel8.setText("Status bar"); + statusBarPanel.add(jLabel8); + + getContentPane().add(statusBarPanel, java.awt.BorderLayout.PAGE_END); + + jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); + jSplitPane1.setResizeWeight(1.0); + + jSplitPane4.setResizeWeight(0.5); + + jLabel6.setText("Left pane"); + jPanel3.add(jLabel6); + + jScrollPane7.setViewportView(jPanel3); + + jTabbedPane3.addTab("tab1", jScrollPane7); + + jSplitPane4.setLeftComponent(jTabbedPane3); + + jLabel3.setText("jLabel3"); + jPanel4.add(jLabel3); + + jScrollPane4.setViewportView(jPanel4); + + jTabbedPane4.addTab("tab1", jScrollPane4); + + jSplitPane4.setRightComponent(jTabbedPane4); + + jSplitPane3.setRightComponent(jSplitPane4); + + jLabel4.setText("Configuration pane"); + jPanel1.add(jLabel4); + + jScrollPane5.setViewportView(jPanel1); + + jSplitPane3.setLeftComponent(jScrollPane5); + + jSplitPane1.setLeftComponent(jSplitPane3); + + jTabbedPane1.setMinimumSize(new java.awt.Dimension(100, 100)); + + jPanel2.setLayout(new java.awt.BorderLayout()); + + jToolBar1.setRollover(true); + + jButton2.setText("jButton2"); + jButton2.setFocusable(false); + jButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); + jButton2.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); + jToolBar1.add(jButton2); + + jPanel2.add(jToolBar1, java.awt.BorderLayout.SOUTH); + + jTabbedPane1.addTab("Actions", jPanel2); + jPanel2.getAccessibleContext().setAccessibleName(""); + + jScrollPane1.setMaximumSize(new java.awt.Dimension(32767, 200)); + jScrollPane1.setPreferredSize(new java.awt.Dimension(100, 200)); + + jLabel7.setText("Logging window"); + jPanel5.add(jLabel7); + + jScrollPane1.setViewportView(jPanel5); + + jTabbedPane1.addTab("Logging", jScrollPane1); + + jSplitPane1.setRightComponent(jTabbedPane1); + jTabbedPane1.getAccessibleContext().setAccessibleName("Actions"); + + getContentPane().add(jSplitPane1, java.awt.BorderLayout.CENTER); + + jMenu1.setText("File"); + + aboutMenuItem.setText("About..."); + aboutMenuItem.addActionListener(this::aboutMenuItemActionPerformed); + jMenu1.add(aboutMenuItem); + jMenu1.add(jSeparator1); + + exitMenuItem.setText("Exit"); + jMenu1.add(exitMenuItem); + + jMenuBar1.add(jMenu1); + + jMenu2.setText("Edit"); + + cutMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_X)); + cutMenuItem.setText("Cut"); + jMenu2.add(cutMenuItem); + + copyMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_C)); + copyMenuItem.setText("Copy"); + copyMenuItem.addActionListener(this::copyMenuItemActionPerformed); + jMenu2.add(copyMenuItem); + + pasteMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_V)); + pasteMenuItem.setText("Paste"); + jMenu2.add(pasteMenuItem); + + deleteMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_DELETE)); + deleteMenuItem.setText("Delete"); + jMenu2.add(deleteMenuItem); + + jMenuBar1.add(jMenu2); + + setJMenuBar(jMenuBar1); + + pack(); + }// //GEN-END:initComponents + + private static KeyStroke createMenuAccelerator(int keyCode) + { + int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); + return KeyStroke.getKeyStroke(keyCode, mask); + } + + private void aboutMenuItemActionPerformed(java.awt.event.ActionEvent evt) + {//GEN-FIRST:event_aboutMenuItemActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_aboutMenuItemActionPerformed + + private void copyMenuItemActionPerformed(java.awt.event.ActionEvent evt) + {//GEN-FIRST:event_copyMenuItemActionPerformed + // TODO add your handling code here: + }//GEN-LAST:event_copyMenuItemActionPerformed + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JMenuItem aboutMenuItem; + private javax.swing.JMenuItem copyMenuItem; + private javax.swing.JMenuItem cutMenuItem; + private javax.swing.JMenuItem deleteMenuItem; + private javax.swing.JMenuItem exitMenuItem; + private javax.swing.JButton jButton2; + private javax.swing.JLabel jLabel1; + private javax.swing.JLabel jLabel3; + private javax.swing.JLabel jLabel4; + private javax.swing.JLabel jLabel6; + private javax.swing.JLabel jLabel7; + private javax.swing.JLabel jLabel8; + private javax.swing.JMenu jMenu1; + private javax.swing.JMenu jMenu2; + private javax.swing.JMenuBar jMenuBar1; + private javax.swing.JMenuItem jMenuItem2; + private javax.swing.JPanel jPanel1; + private javax.swing.JPanel jPanel2; + private javax.swing.JPanel jPanel3; + private javax.swing.JPanel jPanel4; + private javax.swing.JPanel jPanel5; + private javax.swing.JScrollPane jScrollPane1; + private javax.swing.JScrollPane jScrollPane4; + private javax.swing.JScrollPane jScrollPane5; + private javax.swing.JScrollPane jScrollPane7; + private javax.swing.JPopupMenu.Separator jSeparator1; + private javax.swing.JSplitPane jSplitPane1; + private javax.swing.JSplitPane jSplitPane3; + private javax.swing.JSplitPane jSplitPane4; + private javax.swing.JTabbedPane jTabbedPane1; + private javax.swing.JTabbedPane jTabbedPane3; + private javax.swing.JTabbedPane jTabbedPane4; + private javax.swing.JToolBar jToolBar1; + private javax.swing.JMenuItem pasteMenuItem; + private javax.swing.JPanel statusBarPanel; + // End of variables declaration//GEN-END:variables +} diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index f665ddd2..001f84b6 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -1,8 +1,5 @@ package com.cowlark.fluxengine.gui; -import static swingtree.UI.label; -import static swingtree.UI.panel; - import com.formdev.flatlaf.FlatDarkLaf; import com.google.common.collect.ImmutableList; import swingtree.UI; @@ -20,15 +17,10 @@ public void run(ImmutableList args) throws Exception UIManager.setLookAndFeel(new FlatDarkLaf()); System.setProperty("apple.laf.useScreenMenuBar", "true"); - UI.frame("FluxEngine") + UI.of(new ApplicationFrame()) .withOnCloseOperation(UI.OnWindowClose.DISPOSE) .onClose(it -> System.exit(0)) - .peek(frame -> { - frame.setJMenuBar(AppMenu.createMenu()); - frame.setSize(800, 600); - frame.setLocationRelativeTo(null); - }) - .add(panel("fill").add(label("FluxEngine"))) + .peek(frame -> frame.setLocationRelativeTo(null)) .show(); EventProcessor.DECOUPLED.join(); From a127fa5543bf0ee1f5048807cd55e1c9e992121a Mon Sep 17 00:00:00 2001 From: David Given Date: Fri, 14 Aug 2026 01:43:27 +0200 Subject: [PATCH 190/192] Switch from Matisse to raw SwingTree. --- .../fluxengine/gui/ApplicationFrame.form | 348 ------------------ .../fluxengine/gui/ApplicationFrame.java | 296 +++------------ .../{AppMenu.java => ApplicationMenu.java} | 4 +- .../fluxengine/gui/ConfigurationPanel.java | 20 + java/com/cowlark/fluxengine/gui/Gui.java | 9 +- .../cowlark/fluxengine/gui/ImagePanel.java | 10 + java/com/cowlark/fluxengine/gui/LogPanel.java | 10 + .../fluxengine/gui/StatusbarPanel.java | 17 + .../cowlark/fluxengine/gui/SummaryPanel.java | 10 + .../fluxengine/gui/VisualiserPanel.java | 10 + 10 files changed, 138 insertions(+), 596 deletions(-) delete mode 100644 java/com/cowlark/fluxengine/gui/ApplicationFrame.form rename java/com/cowlark/fluxengine/gui/{AppMenu.java => ApplicationMenu.java} (98%) create mode 100644 java/com/cowlark/fluxengine/gui/ConfigurationPanel.java create mode 100644 java/com/cowlark/fluxengine/gui/ImagePanel.java create mode 100644 java/com/cowlark/fluxengine/gui/LogPanel.java create mode 100644 java/com/cowlark/fluxengine/gui/StatusbarPanel.java create mode 100644 java/com/cowlark/fluxengine/gui/SummaryPanel.java create mode 100644 java/com/cowlark/fluxengine/gui/VisualiserPanel.java diff --git a/java/com/cowlark/fluxengine/gui/ApplicationFrame.form b/java/com/cowlark/fluxengine/gui/ApplicationFrame.form deleted file mode 100644 index 6241af9f..00000000 --- a/java/com/cowlark/fluxengine/gui/ApplicationFrame.form +++ /dev/null @@ -1,348 +0,0 @@ - - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com/cowlark/fluxengine/gui/ApplicationFrame.java b/java/com/cowlark/fluxengine/gui/ApplicationFrame.java index 3f28f192..a77be983 100644 --- a/java/com/cowlark/fluxengine/gui/ApplicationFrame.java +++ b/java/com/cowlark/fluxengine/gui/ApplicationFrame.java @@ -1,245 +1,63 @@ -/* - * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this - * license - * Click nbfs://nbhost/SystemFileSystem/Templates/GUIForms/JFrame.java to edit this template - */ package com.cowlark.fluxengine.gui; -import javax.swing.KeyStroke; -import java.awt.Toolkit; -import java.awt.event.KeyEvent; - -/** - * - * @author dg - */ -public class ApplicationFrame extends javax.swing.JFrame +import static swingtree.UIFactoryMethods.button; +import static swingtree.UIFactoryMethods.label; +import static swingtree.UIFactoryMethods.of; +import static swingtree.UIFactoryMethods.panel; +import static swingtree.UIFactoryMethods.scrollPane; +import static swingtree.UIFactoryMethods.splitPane; +import static swingtree.UIFactoryMethods.tab; +import static swingtree.UIFactoryMethods.tabbedPane; +import static swingtree.UILayoutConstants.BOTTOM; +import static swingtree.UILayoutConstants.LEFT; +import static swingtree.UILayoutConstants.RIGHT; +import static swingtree.UILayoutConstants.TOP; + +import swingtree.UI; +import javax.swing.JFrame; + +public class ApplicationFrame extends JFrame { - - private static final java.util.logging.Logger logger = - java.util.logging.Logger.getLogger(ApplicationFrame.class.getName()); - - /** - * Creates new form ApplicationFrame - */ - public ApplicationFrame() + private final ConfigurationPanel configurationPanel = new ConfigurationPanel(); + private final VisualiserPanel visualiserPanel = new VisualiserPanel(); + private final ImagePanel imagePanel = new ImagePanel(); + private final LogPanel logPanel = new LogPanel(); + private final SummaryPanel summaryPanel = new SummaryPanel(); + private final StatusbarPanel statusbarPanel = new StatusbarPanel(); + + ApplicationFrame() { - initComponents(); - statusBarPanel.putClientProperty("FlatLaf.style", "margin: 2,8,2,8"); + UI.of(this) + .withOnCloseOperation(UI.OnWindowClose.DISPOSE) + .onClose(it -> System.exit(0)) + .peek(frame -> { + frame.setJMenuBar(ApplicationMenu.createMenu()); + frame.setSize(1280, 720); + frame.setLocationRelativeTo(null); + }) + .add(panel("fill, wrap 1").add( + "grow, push", splitPane(UI.Align.HORIZONTAL).add( + LEFT, + tabbedPane().add(tab("Configuration").add(scrollPane().add(of( + configurationPanel))))).add( + RIGHT, + splitPane(UI.Align.VERTICAL).peek(pane -> pane.setResizeWeight(1.0)) + .add( + TOP, + tabbedPane().add(tab("Visualiser").add(of( + visualiserPanel))) + .add(tab("Image").add(of(imagePanel))) + .add(tab("Log").add(of(logPanel)))) + .add( + BOTTOM, + tabbedPane().add(tab("Summary").add(panel( + "fillx, wrap 1, aligny center").add("growx, h 100!", + of(summaryPanel)).add( + "growx", + panel("wrap 3, alignx center").add(button( + "Read disk")) + .add(button("Reread disk")) + .add(button("Write disk")))))))) + .add("growx", statusbarPanel)); } - - /** - * This method is called from within the constructor to initialize the form. - * WARNING: Do NOT modify this code. The content of this method is always - * regenerated by the Form Editor. - */ - @SuppressWarnings("unchecked") - // //GEN-BEGIN:initComponents - private void initComponents() { - - jMenuItem2 = new javax.swing.JMenuItem(); - jLabel1 = new javax.swing.JLabel(); - statusBarPanel = new javax.swing.JPanel(); - jLabel8 = new javax.swing.JLabel(); - jSplitPane1 = new javax.swing.JSplitPane(); - jSplitPane3 = new javax.swing.JSplitPane(); - jSplitPane4 = new javax.swing.JSplitPane(); - jTabbedPane3 = new javax.swing.JTabbedPane(); - jScrollPane7 = new javax.swing.JScrollPane(); - jPanel3 = new javax.swing.JPanel(); - jLabel6 = new javax.swing.JLabel(); - jTabbedPane4 = new javax.swing.JTabbedPane(); - jScrollPane4 = new javax.swing.JScrollPane(); - jPanel4 = new javax.swing.JPanel(); - jLabel3 = new javax.swing.JLabel(); - jScrollPane5 = new javax.swing.JScrollPane(); - jPanel1 = new javax.swing.JPanel(); - jLabel4 = new javax.swing.JLabel(); - jTabbedPane1 = new javax.swing.JTabbedPane(); - jPanel2 = new javax.swing.JPanel(); - jToolBar1 = new javax.swing.JToolBar(); - jButton2 = new javax.swing.JButton(); - jScrollPane1 = new javax.swing.JScrollPane(); - jPanel5 = new javax.swing.JPanel(); - jLabel7 = new javax.swing.JLabel(); - jMenuBar1 = new javax.swing.JMenuBar(); - jMenu1 = new javax.swing.JMenu(); - aboutMenuItem = new javax.swing.JMenuItem(); - jSeparator1 = new javax.swing.JPopupMenu.Separator(); - exitMenuItem = new javax.swing.JMenuItem(); - jMenu2 = new javax.swing.JMenu(); - cutMenuItem = new javax.swing.JMenuItem(); - copyMenuItem = new javax.swing.JMenuItem(); - pasteMenuItem = new javax.swing.JMenuItem(); - deleteMenuItem = new javax.swing.JMenuItem(); - - jMenuItem2.setText("jMenuItem2"); - - jLabel1.setText("jLabel1"); - - setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE); - setPreferredSize(new java.awt.Dimension(800, 600)); - - jLabel8.setText("Status bar"); - statusBarPanel.add(jLabel8); - - getContentPane().add(statusBarPanel, java.awt.BorderLayout.PAGE_END); - - jSplitPane1.setOrientation(javax.swing.JSplitPane.VERTICAL_SPLIT); - jSplitPane1.setResizeWeight(1.0); - - jSplitPane4.setResizeWeight(0.5); - - jLabel6.setText("Left pane"); - jPanel3.add(jLabel6); - - jScrollPane7.setViewportView(jPanel3); - - jTabbedPane3.addTab("tab1", jScrollPane7); - - jSplitPane4.setLeftComponent(jTabbedPane3); - - jLabel3.setText("jLabel3"); - jPanel4.add(jLabel3); - - jScrollPane4.setViewportView(jPanel4); - - jTabbedPane4.addTab("tab1", jScrollPane4); - - jSplitPane4.setRightComponent(jTabbedPane4); - - jSplitPane3.setRightComponent(jSplitPane4); - - jLabel4.setText("Configuration pane"); - jPanel1.add(jLabel4); - - jScrollPane5.setViewportView(jPanel1); - - jSplitPane3.setLeftComponent(jScrollPane5); - - jSplitPane1.setLeftComponent(jSplitPane3); - - jTabbedPane1.setMinimumSize(new java.awt.Dimension(100, 100)); - - jPanel2.setLayout(new java.awt.BorderLayout()); - - jToolBar1.setRollover(true); - - jButton2.setText("jButton2"); - jButton2.setFocusable(false); - jButton2.setHorizontalTextPosition(javax.swing.SwingConstants.CENTER); - jButton2.setVerticalTextPosition(javax.swing.SwingConstants.BOTTOM); - jToolBar1.add(jButton2); - - jPanel2.add(jToolBar1, java.awt.BorderLayout.SOUTH); - - jTabbedPane1.addTab("Actions", jPanel2); - jPanel2.getAccessibleContext().setAccessibleName(""); - - jScrollPane1.setMaximumSize(new java.awt.Dimension(32767, 200)); - jScrollPane1.setPreferredSize(new java.awt.Dimension(100, 200)); - - jLabel7.setText("Logging window"); - jPanel5.add(jLabel7); - - jScrollPane1.setViewportView(jPanel5); - - jTabbedPane1.addTab("Logging", jScrollPane1); - - jSplitPane1.setRightComponent(jTabbedPane1); - jTabbedPane1.getAccessibleContext().setAccessibleName("Actions"); - - getContentPane().add(jSplitPane1, java.awt.BorderLayout.CENTER); - - jMenu1.setText("File"); - - aboutMenuItem.setText("About..."); - aboutMenuItem.addActionListener(this::aboutMenuItemActionPerformed); - jMenu1.add(aboutMenuItem); - jMenu1.add(jSeparator1); - - exitMenuItem.setText("Exit"); - jMenu1.add(exitMenuItem); - - jMenuBar1.add(jMenu1); - - jMenu2.setText("Edit"); - - cutMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_X)); - cutMenuItem.setText("Cut"); - jMenu2.add(cutMenuItem); - - copyMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_C)); - copyMenuItem.setText("Copy"); - copyMenuItem.addActionListener(this::copyMenuItemActionPerformed); - jMenu2.add(copyMenuItem); - - pasteMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_V)); - pasteMenuItem.setText("Paste"); - jMenu2.add(pasteMenuItem); - - deleteMenuItem.setAccelerator(createMenuAccelerator(KeyEvent.VK_DELETE)); - deleteMenuItem.setText("Delete"); - jMenu2.add(deleteMenuItem); - - jMenuBar1.add(jMenu2); - - setJMenuBar(jMenuBar1); - - pack(); - }// //GEN-END:initComponents - - private static KeyStroke createMenuAccelerator(int keyCode) - { - int mask = Toolkit.getDefaultToolkit().getMenuShortcutKeyMaskEx(); - return KeyStroke.getKeyStroke(keyCode, mask); - } - - private void aboutMenuItemActionPerformed(java.awt.event.ActionEvent evt) - {//GEN-FIRST:event_aboutMenuItemActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_aboutMenuItemActionPerformed - - private void copyMenuItemActionPerformed(java.awt.event.ActionEvent evt) - {//GEN-FIRST:event_copyMenuItemActionPerformed - // TODO add your handling code here: - }//GEN-LAST:event_copyMenuItemActionPerformed - - // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JMenuItem aboutMenuItem; - private javax.swing.JMenuItem copyMenuItem; - private javax.swing.JMenuItem cutMenuItem; - private javax.swing.JMenuItem deleteMenuItem; - private javax.swing.JMenuItem exitMenuItem; - private javax.swing.JButton jButton2; - private javax.swing.JLabel jLabel1; - private javax.swing.JLabel jLabel3; - private javax.swing.JLabel jLabel4; - private javax.swing.JLabel jLabel6; - private javax.swing.JLabel jLabel7; - private javax.swing.JLabel jLabel8; - private javax.swing.JMenu jMenu1; - private javax.swing.JMenu jMenu2; - private javax.swing.JMenuBar jMenuBar1; - private javax.swing.JMenuItem jMenuItem2; - private javax.swing.JPanel jPanel1; - private javax.swing.JPanel jPanel2; - private javax.swing.JPanel jPanel3; - private javax.swing.JPanel jPanel4; - private javax.swing.JPanel jPanel5; - private javax.swing.JScrollPane jScrollPane1; - private javax.swing.JScrollPane jScrollPane4; - private javax.swing.JScrollPane jScrollPane5; - private javax.swing.JScrollPane jScrollPane7; - private javax.swing.JPopupMenu.Separator jSeparator1; - private javax.swing.JSplitPane jSplitPane1; - private javax.swing.JSplitPane jSplitPane3; - private javax.swing.JSplitPane jSplitPane4; - private javax.swing.JTabbedPane jTabbedPane1; - private javax.swing.JTabbedPane jTabbedPane3; - private javax.swing.JTabbedPane jTabbedPane4; - private javax.swing.JToolBar jToolBar1; - private javax.swing.JMenuItem pasteMenuItem; - private javax.swing.JPanel statusBarPanel; - // End of variables declaration//GEN-END:variables } diff --git a/java/com/cowlark/fluxengine/gui/AppMenu.java b/java/com/cowlark/fluxengine/gui/ApplicationMenu.java similarity index 98% rename from java/com/cowlark/fluxengine/gui/AppMenu.java rename to java/com/cowlark/fluxengine/gui/ApplicationMenu.java index ae9bb98e..8a3a46b0 100644 --- a/java/com/cowlark/fluxengine/gui/AppMenu.java +++ b/java/com/cowlark/fluxengine/gui/ApplicationMenu.java @@ -19,7 +19,7 @@ import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; -public class AppMenu +public class ApplicationMenu { public static UI.MenuBar createMenu() { @@ -68,7 +68,7 @@ private static void installMacAboutHandler() Object application = applicationClass.getMethod("getApplication").invoke(null); Object handler = java.lang.reflect.Proxy.newProxyInstance( - AppMenu.class.getClassLoader(), + ApplicationMenu.class.getClassLoader(), new Class[]{aboutHandlerClass}, (proxy, method, args) -> { if (method.getName().equals("handleAbout")) diff --git a/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java b/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java new file mode 100644 index 00000000..fe353d28 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java @@ -0,0 +1,20 @@ +package com.cowlark.fluxengine.gui; + +import static swingtree.UI.comboBox; +import static swingtree.UI.label; +import static swingtree.UI.of; + +import swingtree.UI; +import javax.swing.JPanel; + +public class ConfigurationPanel extends JPanel +{ + public ConfigurationPanel() + { + swingtree.UIForPanel panel = + of(this).withLayout("wrap 2, insets 5"); + for (int i = 0; i < 10; i++) + panel = panel.add(label(String.format("label %d", i))) + .add("growx, pushx", comboBox(1, 2, 3, 4, 5)); + } +} diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index 001f84b6..3e56dac5 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -2,7 +2,6 @@ import com.formdev.flatlaf.FlatDarkLaf; import com.google.common.collect.ImmutableList; -import swingtree.UI; import swingtree.threading.EventProcessor; import javax.swing.UIManager; @@ -11,17 +10,13 @@ */ public class Gui { - public void run(ImmutableList args) throws Exception { UIManager.setLookAndFeel(new FlatDarkLaf()); System.setProperty("apple.laf.useScreenMenuBar", "true"); - UI.of(new ApplicationFrame()) - .withOnCloseOperation(UI.OnWindowClose.DISPOSE) - .onClose(it -> System.exit(0)) - .peek(frame -> frame.setLocationRelativeTo(null)) - .show(); + ApplicationFrame frame = new ApplicationFrame(); + frame.show(); EventProcessor.DECOUPLED.join(); } diff --git a/java/com/cowlark/fluxengine/gui/ImagePanel.java b/java/com/cowlark/fluxengine/gui/ImagePanel.java new file mode 100644 index 00000000..1e3158e6 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/ImagePanel.java @@ -0,0 +1,10 @@ +package com.cowlark.fluxengine.gui; + +import javax.swing.JPanel; + +public class ImagePanel extends JPanel +{ + public ImagePanel() + { + } +} diff --git a/java/com/cowlark/fluxengine/gui/LogPanel.java b/java/com/cowlark/fluxengine/gui/LogPanel.java new file mode 100644 index 00000000..277ce114 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/LogPanel.java @@ -0,0 +1,10 @@ +package com.cowlark.fluxengine.gui; + +import javax.swing.JPanel; + +public class LogPanel extends JPanel +{ + public LogPanel() + { + } +} diff --git a/java/com/cowlark/fluxengine/gui/StatusbarPanel.java b/java/com/cowlark/fluxengine/gui/StatusbarPanel.java new file mode 100644 index 00000000..21afedae --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/StatusbarPanel.java @@ -0,0 +1,17 @@ +package com.cowlark.fluxengine.gui; + +import static swingtree.UIFactoryMethods.button; +import static swingtree.UIFactoryMethods.label; +import static swingtree.UIFactoryMethods.of; + +import javax.swing.JPanel; + +public class StatusbarPanel extends JPanel +{ + StatusbarPanel() + { + of(this).withLayout("fillx, insets 2") + .add(label("Hello, world!")) + .add("right", button("Button")); + } +} diff --git a/java/com/cowlark/fluxengine/gui/SummaryPanel.java b/java/com/cowlark/fluxengine/gui/SummaryPanel.java new file mode 100644 index 00000000..d18f37a3 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/SummaryPanel.java @@ -0,0 +1,10 @@ +package com.cowlark.fluxengine.gui; + +import javax.swing.JPanel; + +public class SummaryPanel extends JPanel +{ + public SummaryPanel() + { + } +} diff --git a/java/com/cowlark/fluxengine/gui/VisualiserPanel.java b/java/com/cowlark/fluxengine/gui/VisualiserPanel.java new file mode 100644 index 00000000..ea21ded0 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/VisualiserPanel.java @@ -0,0 +1,10 @@ +package com.cowlark.fluxengine.gui; + +import javax.swing.JPanel; + +public class VisualiserPanel extends JPanel +{ + public VisualiserPanel() + { + } +} From 30ddf15229362ee3e3391ab40fcab4dc44269f55 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 15 Aug 2026 00:33:27 +0200 Subject: [PATCH 191/192] Start figuring out the config pane. --- MODULE.bazel | 4 + .../fluxengine/gui/ApplicationFrame.java | 33 ++++-- java/com/cowlark/fluxengine/gui/BUILD.bazel | 13 ++- .../fluxengine/gui/ConfigurationPanel.java | 66 +++++++++-- java/com/cowlark/fluxengine/gui/Gui.java | 8 +- .../fluxengine/gui/ImagerViewModel.java | 57 ++++++++++ .../gui/PreferencesReaderWriter.java | 64 +++++++++++ .../fluxengine/gui/StatusbarPanel.java | 11 +- .../com/cowlark/fluxengine/gui/BUILD.bazel | 18 +++ .../gui/PreferencesReaderWriterTest.java | 105 ++++++++++++++++++ 10 files changed, 354 insertions(+), 25 deletions(-) create mode 100644 java/com/cowlark/fluxengine/gui/ImagerViewModel.java create mode 100644 java/com/cowlark/fluxengine/gui/PreferencesReaderWriter.java create mode 100644 javatests/com/cowlark/fluxengine/gui/BUILD.bazel create mode 100644 javatests/com/cowlark/fluxengine/gui/PreferencesReaderWriterTest.java diff --git a/MODULE.bazel b/MODULE.bazel index aaeb94fa..56e2c839 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -23,6 +23,10 @@ maven.install( "io.github.globaltcad:swing-tree:0.24.1", "io.reactivex.rxjava3:rxjava:3.1.10", "io.github.globaltcad:sprouts:2.7.0", + "org.mockito:mockito-core:5.23.0", + "net.bytebuddy:byte-buddy:1.17.7", + "net.bytebuddy:byte-buddy-agent:1.17.7", + "org.objenesis:objenesis:3.3", ], repositories = [ "https://repo1.maven.org/maven2", diff --git a/java/com/cowlark/fluxengine/gui/ApplicationFrame.java b/java/com/cowlark/fluxengine/gui/ApplicationFrame.java index a77be983..b73f7e7e 100644 --- a/java/com/cowlark/fluxengine/gui/ApplicationFrame.java +++ b/java/com/cowlark/fluxengine/gui/ApplicationFrame.java @@ -1,7 +1,6 @@ package com.cowlark.fluxengine.gui; import static swingtree.UIFactoryMethods.button; -import static swingtree.UIFactoryMethods.label; import static swingtree.UIFactoryMethods.of; import static swingtree.UIFactoryMethods.panel; import static swingtree.UIFactoryMethods.scrollPane; @@ -18,15 +17,25 @@ public class ApplicationFrame extends JFrame { - private final ConfigurationPanel configurationPanel = new ConfigurationPanel(); - private final VisualiserPanel visualiserPanel = new VisualiserPanel(); - private final ImagePanel imagePanel = new ImagePanel(); - private final LogPanel logPanel = new LogPanel(); - private final SummaryPanel summaryPanel = new SummaryPanel(); - private final StatusbarPanel statusbarPanel = new StatusbarPanel(); + private final ConfigurationPanel configurationPanel; + private final VisualiserPanel visualiserPanel; + private final ImagePanel imagePanel; + private final LogPanel logPanel; + private final SummaryPanel summaryPanel; + private final StatusbarPanel statusbarPanel; - ApplicationFrame() + private final ImagerViewModel model; + + ApplicationFrame(ImagerViewModel model) { + this.model = model; + statusbarPanel = new StatusbarPanel(model); + summaryPanel = new SummaryPanel(); + logPanel = new LogPanel(); + imagePanel = new ImagePanel(); + visualiserPanel = new VisualiserPanel(); + configurationPanel = new ConfigurationPanel(model); + UI.of(this) .withOnCloseOperation(UI.OnWindowClose.DISPOSE) .onClose(it -> System.exit(0)) @@ -55,9 +64,11 @@ public class ApplicationFrame extends JFrame of(summaryPanel)).add( "growx", panel("wrap 3, alignx center").add(button( - "Read disk")) - .add(button("Reread disk")) - .add(button("Write disk")))))))) + "Read disk").onClick(model::onReadDisk)) + .add(button("Reread disk").onClick( + model::onRereadDisk)) + .add(button("Write disk").onClick( + model::onWriteDisk)))))))) .add("growx", statusbarPanel)); } } diff --git a/java/com/cowlark/fluxengine/gui/BUILD.bazel b/java/com/cowlark/fluxengine/gui/BUILD.bazel index e2d8bef7..a5ef6a37 100644 --- a/java/com/cowlark/fluxengine/gui/BUILD.bazel +++ b/java/com/cowlark/fluxengine/gui/BUILD.bazel @@ -1,14 +1,25 @@ -load("@rules_java//java:defs.bzl", "java_binary", "java_library") +load("@rules_java//java:defs.bzl", "java_binary", "java_library", "java_plugin") package(default_visibility = ["//visibility:public"]) +java_plugin( + name = "lombok_plugin", + generates_api = True, + processor_class = "lombok.launch.AnnotationProcessorHider$AnnotationProcessor", + deps = ["@maven//:org_projectlombok_lombok"], +) + java_library( name = "gui", srcs = glob(["*.java"]), + plugins = [":lombok_plugin"], deps = [ + "//java/com/cowlark/fluxengine/config:config_java_proto", + "//java/com/cowlark/fluxengine/data", "@maven//:com_formdev_flatlaf", "@maven//:com_google_guava_guava", "@maven//:io_github_globaltcad_sprouts", "@maven//:io_github_globaltcad_swing_tree", + "@maven//:org_projectlombok_lombok", ], ) diff --git a/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java b/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java index fe353d28..17db35e7 100644 --- a/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java +++ b/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java @@ -1,20 +1,70 @@ package com.cowlark.fluxengine.gui; -import static swingtree.UI.comboBox; +import static com.google.common.collect.ImmutableMap.toImmutableMap; import static swingtree.UI.label; import static swingtree.UI.of; +import static swingtree.UI.panel; +import static swingtree.UIFactoryMethods.comboBox; +import static swingtree.UIFactoryMethods.separator; -import swingtree.UI; +import com.cowlark.fluxengine.config.ConfigProto; +import com.cowlark.fluxengine.data.Formats; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import sprouts.From; +import sprouts.Pair; +import sprouts.Viewable; +import swingtree.UIForPanel; import javax.swing.JPanel; public class ConfigurationPanel extends JPanel { - public ConfigurationPanel() + private static ImmutableMap formatData = Formats.all() + .stream() + .map(it -> Pair.of(it, Formats.get(it))) + .filter(p -> !p.second().getIsExtension()) + .collect(toImmutableMap(Pair::first, Pair::second)); + + private final ImagerViewModel model; + + public ConfigurationPanel(ImagerViewModel model) + { + this.model = model; + + /* Rebuild whenever the format changes (and once at startup). */ + Viewable.cast(model.getFormat()).onChange(From.ALL, it -> rebuildUi()); + rebuildUi(); + } + + /* Removes the existing UI and recreates it. */ + private void rebuildUi() + { + removeAll(); + + UIForPanel panel = of(this).withLayout("wrap 2, insets 5"); + + panel = panel.add("span 2, growx, wrap", namedSeparator("Format properties")) + .add(label("Format:")) + .add( + "growx, pushx", comboBox( + model.getFormat(), + ImmutableList.copyOf(formatData.keySet()), + ConfigurationPanel::formatRenderer).onSelection(it -> model.getFormat() + .set(From.VIEW, (String) it.get().getSelectedItem()))) + .add("span 2, growx, wrap", namedSeparator("Device properties")) + .add(label("Device:")) + .add("growx, pushx", comboBox(new String[]{"1", "2"})); + } + + private static UIForPanel namedSeparator(String label) + { + return panel("fillx, insets 5 0").add("w 10!", separator()) + .add(label(label)) + .add("growx, pushx", separator()); + } + + private static String formatRenderer(String format) { - swingtree.UIForPanel panel = - of(this).withLayout("wrap 2, insets 5"); - for (int i = 0; i < 10; i++) - panel = panel.add(label(String.format("label %d", i))) - .add("growx, pushx", comboBox(1, 2, 3, 4, 5)); + return formatData.get(format).getShortname(); } } diff --git a/java/com/cowlark/fluxengine/gui/Gui.java b/java/com/cowlark/fluxengine/gui/Gui.java index 3e56dac5..0ee54deb 100644 --- a/java/com/cowlark/fluxengine/gui/Gui.java +++ b/java/com/cowlark/fluxengine/gui/Gui.java @@ -4,18 +4,24 @@ import com.google.common.collect.ImmutableList; import swingtree.threading.EventProcessor; import javax.swing.UIManager; +import java.util.prefs.Preferences; /** * The FluxEngine GUI, ported from src/gui/main.cc. */ public class Gui { + private final Preferences preferences = Preferences.userNodeForPackage(Gui.class); + private PreferencesReaderWriter preferencesReaderWriter = + new PreferencesReaderWriter(preferences); + private ImagerViewModel model = new ImagerViewModel(preferencesReaderWriter); + public void run(ImmutableList args) throws Exception { UIManager.setLookAndFeel(new FlatDarkLaf()); System.setProperty("apple.laf.useScreenMenuBar", "true"); - ApplicationFrame frame = new ApplicationFrame(); + ApplicationFrame frame = new ApplicationFrame(model); frame.show(); EventProcessor.DECOUPLED.join(); diff --git a/java/com/cowlark/fluxengine/gui/ImagerViewModel.java b/java/com/cowlark/fluxengine/gui/ImagerViewModel.java new file mode 100644 index 00000000..4191fddd --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/ImagerViewModel.java @@ -0,0 +1,57 @@ +package com.cowlark.fluxengine.gui; + +import static com.cowlark.fluxengine.gui.PreferencesReaderWriter.FORMAT; + +import com.cowlark.fluxengine.data.Image; +import com.google.common.collect.ImmutableMap; +import lombok.Getter; +import sprouts.From; +import sprouts.Var; +import sprouts.Viewable; +import swingtree.ComponentDelegate; +import javax.swing.JButton; +import java.awt.event.ActionEvent; + +public class ImagerViewModel +{ + private final PreferencesReaderWriter preferencesReaderWriter; + + @Getter private Var statusMessage = Var.of("Ready"); + @Getter private Var format; + @Getter private Var> options = Var.of(ImmutableMap.of()); + @Getter private Var diskImage = Var.of(new Image()); + @Getter private Var busy = Var.of(false); + + ImagerViewModel(PreferencesReaderWriter preferencesReaderWriter) + { + this.preferencesReaderWriter = preferencesReaderWriter; + + format = Var.of(preferencesReaderWriter.getPreference(FORMAT, "ibm")); + options.set(preferencesReaderWriter.getOptionsForFormat(format.get())); + + /* Viewable.cast reinterprets the property itself as a Viewable, so the + * listener lives exactly as long as the property (unlike view(), which + * returns a weakly-held view that must be kept in a field). */ + Viewable.cast(format).onChange( + From.VIEW, + it -> preferencesReaderWriter.setPreference( + FORMAT, + it.currentValue().orElseThrowUnchecked())); + } + + void onReadDisk(ComponentDelegate delegate) + { + } + + void onRereadDisk(ComponentDelegate delegate) + { + } + + void onWriteDisk(ComponentDelegate delegate) + { + } + + void onEmergencyStop(ComponentDelegate delegate) + { + } +} diff --git a/java/com/cowlark/fluxengine/gui/PreferencesReaderWriter.java b/java/com/cowlark/fluxengine/gui/PreferencesReaderWriter.java new file mode 100644 index 00000000..8c236d48 --- /dev/null +++ b/java/com/cowlark/fluxengine/gui/PreferencesReaderWriter.java @@ -0,0 +1,64 @@ +package com.cowlark.fluxengine.gui; + +import static com.google.common.collect.ImmutableMap.toImmutableMap; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableMap; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.prefs.Preferences; +import java.util.stream.Collectors; + +public class PreferencesReaderWriter +{ + static final String FORMAT = "format"; + + private final Preferences preferences; + + PreferencesReaderWriter(Preferences preferences) + { + this.preferences = preferences; + } + + ImmutableMap getOptionsForFormat(String format) + { + String optionsString = preferences.get("format_" + format, ""); + Map rawMap = Splitter.on('&') + .omitEmptyStrings() + .trimResults() + .withKeyValueSeparator(Splitter.on('=').limit(2)) + .split(optionsString); + + // Decode URL-encoded keys and values + return rawMap.entrySet().stream().collect(toImmutableMap( + e -> URLDecoder.decode(e.getKey(), StandardCharsets.UTF_8), + e -> URLDecoder.decode(e.getValue(), StandardCharsets.UTF_8), + (existing, replacement) -> existing)); + } + + void setOptionsForFormat(String format, ImmutableMap options) + { + String optionsString = options.entrySet() + .stream() + .map(entry -> encode(entry.getKey()) + "=" + encode(entry.getValue())) + .collect(Collectors.joining("&")); + preferences.put("format_" + format, optionsString); + } + + String getPreference(String name, String defaultValue) + { + return preferences.get(name, defaultValue); + } + + void setPreference(String name, String value) + { + preferences.put(name, value); + } + + private static String encode(String value) + { + return URLEncoder.encode(value, StandardCharsets.UTF_8); + } +} diff --git a/java/com/cowlark/fluxengine/gui/StatusbarPanel.java b/java/com/cowlark/fluxengine/gui/StatusbarPanel.java index 21afedae..4b71c49d 100644 --- a/java/com/cowlark/fluxengine/gui/StatusbarPanel.java +++ b/java/com/cowlark/fluxengine/gui/StatusbarPanel.java @@ -4,14 +4,17 @@ import static swingtree.UIFactoryMethods.label; import static swingtree.UIFactoryMethods.of; +import java.awt.Color; import javax.swing.JPanel; public class StatusbarPanel extends JPanel { - StatusbarPanel() + StatusbarPanel(ImagerViewModel model) { - of(this).withLayout("fillx, insets 2") - .add(label("Hello, world!")) - .add("right", button("Button")); + of(this).withLayout("fillx, insets 2").add(label(model.getStatusMessage())).add( + "right", + button("Stop").isEnabledIf(model.getBusy()) + .withForeground(Color.RED) + .onClick(model::onEmergencyStop)); } } diff --git a/javatests/com/cowlark/fluxengine/gui/BUILD.bazel b/javatests/com/cowlark/fluxengine/gui/BUILD.bazel new file mode 100644 index 00000000..a323de3a --- /dev/null +++ b/javatests/com/cowlark/fluxengine/gui/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_java//java:defs.bzl", "java_test") + +package(default_visibility = ["//visibility:public"]) + +java_test( + name = "PreferencesReaderWriterTest", + srcs = ["PreferencesReaderWriterTest.java"], + deps = [ + "//java/com/cowlark/fluxengine/gui", + "@maven//:com_google_guava_guava", + "@maven//:com_google_truth_truth", + "@maven//:junit_junit", + "@maven//:net_bytebuddy_byte_buddy", + "@maven//:net_bytebuddy_byte_buddy_agent", + "@maven//:org_mockito_mockito_core", + "@maven//:org_objenesis_objenesis", + ], +) diff --git a/javatests/com/cowlark/fluxengine/gui/PreferencesReaderWriterTest.java b/javatests/com/cowlark/fluxengine/gui/PreferencesReaderWriterTest.java new file mode 100644 index 00000000..ab35b0be --- /dev/null +++ b/javatests/com/cowlark/fluxengine/gui/PreferencesReaderWriterTest.java @@ -0,0 +1,105 @@ +package com.cowlark.fluxengine.gui; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.common.collect.ImmutableMap; +import java.util.prefs.Preferences; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class PreferencesReaderWriterTest +{ + @Mock private Preferences preferences; + private PreferencesReaderWriter writer = null; + + @Test + public void setOptionsForFormatEncodesIntoPreferences() + { + writer = new PreferencesReaderWriter(preferences); + + writer.setOptionsForFormat("ibm", ImmutableMap.of("tracks", "c0-80", "side", "0")); + + verify(preferences).put("format_ibm", "tracks=c0-80&side=0"); + } + + @Test + public void getOptionsForFormatDecodesFromPreferences() + { + when(preferences.get("format_ibm", "")).thenReturn("tracks=c0-80&side=0"); + writer = new PreferencesReaderWriter(preferences); + + assertThat(writer.getOptionsForFormat("ibm")) + .isEqualTo(ImmutableMap.of("tracks", "c0-80", "side", "0")); + } + + @Test + public void roundTripPreservesOptions() + { + writer = new PreferencesReaderWriter(preferences); + ImmutableMap options = ImmutableMap.of( + "density", "hd", + "cylinders", "0-79", + "rotational-period-ms", "200"); + + writer.setOptionsForFormat("ibm", options); + + when(preferences.get("format_ibm", "")).thenReturn( + "density=hd&cylinders=0-79&rotational-period-ms=200"); + assertThat(writer.getOptionsForFormat("ibm")).isEqualTo(options); + } + + @Test + public void roundTripEncodesSpecialCharacters() + { + writer = new PreferencesReaderWriter(preferences); + ImmutableMap options = ImmutableMap.of( + "comment", "hello world & goodbye", + "path", "a=b%c+d"); + + writer.setOptionsForFormat("amiga", options); + + String stored = options.entrySet() + .stream() + .map(entry -> java.net.URLEncoder.encode(entry.getKey(), + java.nio.charset.StandardCharsets.UTF_8) + "=" + + java.net.URLEncoder.encode(entry.getValue(), + java.nio.charset.StandardCharsets.UTF_8)) + .collect(java.util.stream.Collectors.joining("&")); + when(preferences.get("format_amiga", "")).thenReturn(stored); + + assertThat(writer.getOptionsForFormat("amiga")).isEqualTo(options); + } + + @Test + public void getOptionsForFormatReturnsEmptyMapWhenNotSet() + { + when(preferences.get("format_unknown", "")).thenReturn(""); + writer = new PreferencesReaderWriter(preferences); + + assertThat(writer.getOptionsForFormat("unknown")).isEmpty(); + } + + @Test + public void setPreferenceStoresValue() + { + writer = new PreferencesReaderWriter(preferences); + + writer.setPreference("last-format", "ibm"); + + verify(preferences).put("last-format", "ibm"); + } + + @Test + public void getPreferenceReturnsDefaultWhenNotSet() + { + when(preferences.get("missing", "default")).thenReturn("default"); + writer = new PreferencesReaderWriter(preferences); + + assertThat(writer.getPreference("missing", "default")).isEqualTo("default"); + } +} From ea994e151616c3a1e35b13294afdb7dd60fd6458 Mon Sep 17 00:00:00 2001 From: David Given Date: Sat, 15 Aug 2026 02:21:32 +0200 Subject: [PATCH 192/192] Try and fix the flaky test. --- .../fluxengine/gui/ConfigurationPanel.java | 2 +- .../algorithms/FluxOperationTest.java | 47 ++++++++++--------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java b/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java index 17db35e7..9a6b3005 100644 --- a/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java +++ b/java/com/cowlark/fluxengine/gui/ConfigurationPanel.java @@ -53,7 +53,7 @@ private void rebuildUi() .set(From.VIEW, (String) it.get().getSelectedItem()))) .add("span 2, growx, wrap", namedSeparator("Device properties")) .add(label("Device:")) - .add("growx, pushx", comboBox(new String[]{"1", "2"})); + .add("growx, pushx", comboBox("1", "2")); } private static UIForPanel namedSeparator(String label) diff --git a/javatests/com/cowlark/fluxengine/algorithms/FluxOperationTest.java b/javatests/com/cowlark/fluxengine/algorithms/FluxOperationTest.java index 56e8ac60..8a4b384d 100644 --- a/javatests/com/cowlark/fluxengine/algorithms/FluxOperationTest.java +++ b/javatests/com/cowlark/fluxengine/algorithms/FluxOperationTest.java @@ -130,31 +130,32 @@ public void operationsStartedAtSameTimeAreSerialised() throws Exception first.create().subscribe(); second.create().subscribe(); - try - { - /* The first operation starts immediately. */ - assertThat(first.started.await(5, TimeUnit.SECONDS)).isTrue(); - - /* Only one operation may run at a time: the second must wait - * until the first has finished. */ - assertThat(second.started.await(100, TimeUnit.MILLISECONDS)).isFalse(); - - /* Releasing the first operation lets the second run, on its own - * thread. */ - first.gate.release(); - assertThat(second.started.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(second.runThread).isNotEqualTo(first.runThread); - - second.gate.release(); - assertThat(first.finished.await(5, TimeUnit.SECONDS)).isTrue(); - assertThat(second.finished.await(5, TimeUnit.SECONDS)).isTrue(); - } finally + /* Both operations race for the lock, so either may acquire it first. + * Wait for whichever one does, then verify the other is still + * waiting. */ + long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(5); + while (first.started.getCount() == 1 && second.started.getCount() == 1) { - /* Always release both gates so a failed assertion doesn't leave - * worker threads blocked. */ - first.gate.release(); - second.gate.release(); + if (System.nanoTime() >= deadline) + throw new AssertionError("neither operation started"); + Thread.sleep(1); } + + Harness running = first.started.getCount() == 0 ? first : second; + Harness waiting = running == first ? second : first; + + /* Only one operation may run at a time: the other must wait. */ + assertThat(waiting.started.getCount()).isEqualTo(1); + + /* Releasing the running operation lets the other run, on its own + * thread. */ + running.gate.release(); + assertThat(waiting.started.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(waiting.runThread).isNotEqualTo(running.runThread); + + waiting.gate.release(); + assertThat(running.finished.await(5, TimeUnit.SECONDS)).isTrue(); + assertThat(waiting.finished.await(5, TimeUnit.SECONDS)).isTrue(); } @Test