Architecture of the Java Implementation
This page describes the internal structure of the Java implementation: the layer model, the modules and how they interact, the data model, and the central design decisions. It is aimed at developers who embed the library and at those who want to contribute to it. The overview page gives a quick tour; the detail topics reading, writing, error handling, tools and build have their own pages.
Guiding principles
The implementation follows four principles:
- Modern, self-contained Java 21. Idiomatic Java on the current LTS
level — records, sealed types,
switchpatterns — with no cross-language bridges. Behaviour is defined solely by the OSF format specification, not by porting a reference. - Strict encapsulation via JPMS. The Java Platform Module System
descriptor exports only
com.optimeas.osf; the internal packagecom.optimeas.osf.internalstays sealed even against reflection. The public surface is therefore small and stable. - Best-effort reading. Truncated files (power loss on an embedded writer) yield every fully readable block instead of an error; unknown future data types are skipped rather than aborting the load.
- Lean, mainstream dependencies. Jackson for the OSF5 JSON, the
StAX API bundled in the JDK for the OSF4 XML,
java.util.zip(OSFZ decompression + CRC32C), and SLF4J as the logging facade. No heavy frameworks.
Layer model
Most applications work exclusively on the high level (DataManager for
reading, one of the two writers for writing). The low level — block
reader, channel assembler, OSFZ stream, encoder — lives in the
encapsulated package com.optimeas.osf.internal and is invisible from
the outside; the whole read pipeline is orchestrated by DataManager.
Modules and responsibilities
The Maven reactor com.optimeas.osf:osf-parent groups three modules:
| Module | Artifact | Role |
|---|---|---|
| Core library | com.optimeas.osf:osf-java | Reading (OSF4 + OSF5 + OSFZ), both OSF5 writers, the crc integrity profile |
| Command line | osf-cli | Inspecting and converting OSF files; executable jar |
| Viewer | osf-viewer | JavaFX application for multi-channel signal display |
This page describes the core module. The public surface of the core
(package com.optimeas.osf):
| Type | Content | Layer |
|---|---|---|
DataManager | load + typed channel list + telemetry | High |
DataChannel | one channel's assembled samples; Kind, Segment | High |
StreamingWriter | power-loss-safe OSF5 writer (fsync per block) | High |
BlockWriter | in-memory accumulating OSF5 writer; fromManager | High |
MagicHeader / MagicHeaderParser | magic-header line + integrity tokens | Parser |
Metablock / MetablockParser / ChannelDef | definitions; JSON and XML parser | Parser |
DataType / ChannelType | wire enums + fromWireName | Foundation |
OsfVersion | on-disk version (OSF4 / OSF5) | Foundation |
GpsLocation | GPS sample (record: latitude/longitude/altitude) | Foundation |
IntegrityProfile | integrity level (NONE / CRC32C / ED25519) | Foundation |
ReaderStats | read telemetry (blocks, truncation, compression) | Foundation |
OsfException | exception hierarchy (see below) | Foundation |
Internal building blocks (package com.optimeas.osf.internal, not
exported): BlockReader + Block (raw block stream), ChannelAssembler
(block → channel), OsfzInputStream (transparent OSFZ decompression),
BlockEncoder + BlockChunking (OSF5 block encoder + chunking
arithmetic), Integrity (CRC32C framing), MetablockBuilder,
JsonMetablockParser / XmlMetablockParser, and LittleEndian
(byte-order helper). Details on the Internals page.
JPMS encapsulation
The module descriptor module-info.java draws a hard boundary:
module com.optimeas.osf {
requires com.fasterxml.jackson.databind;
requires org.slf4j;
requires java.xml; // StAX for the OSF4 XML metablock
exports com.optimeas.osf;
// com.optimeas.osf.internal is intentionally NOT exported.
}
Only com.optimeas.osf is exported. The internal package is encapsulated
on two levels: the compiler denies access to non-exported types, and —
because there is no opens directive — it also stays sealed against
reflection at runtime. Application code can therefore neither import nor
reflectively address the internal classes.
This has a visible consequence in the data model: DataChannel does
carry a nominally public constructor for the ChannelAssembler, but
its parameter type (Block.Values) lives in the internal package. From
outside the module the constructor thus cannot be called — DataChannel
instances are only ever produced through the DataManager.
Three data models — who sees what
The library deliberately has three representations of the same data, depending on the level of abstraction:
-
Metablock(MetablockParser) — the definitions: file-level metadata (Map<String,String>) and channel definitions (ChannelDef). OSF4 (XML, via StAX) and OSF5 (JSON, via Jackson) differ only in serialization; both parsers populate the same model symmetrically. -
Block(internal) — the stream view: a decoded block with a channel index and block kind (bcStartData,bcContinuedData,bcAbsTimeStampData,bcContinuedRelStampData). Payloads are held as unpacked, typedBlock.Valuesrecords so no datatype information is lost. This model is encapsulated and never appears in the public API. -
DataChannel— the channel view: one flat run of samples per channel with parallel absolute timestamps, the block boundaries resolved. A single class type whose storage layout is distinguished by aKinddiscriminator:KindStorage EQUIDISTANTflat sample run + List<Segment>; timestamps reconstructedTIMESTAMPEDnumeric/GPS with explicit parallel timestamps VARIABLEstring or binary samples, always timestamped
Naming note: ChannelDef is the channel definition from the
metablock; DataChannel is the assembled samples. The typed accessors
asDoubles(), asLongs(), asBooleans(), asStrings(), asBinaries()
and asGps() project the stored run; if the dataType() does not match
the requested view, the accessor throws OsfException.UnsupportedType.
Naming and API conventions
- Types in PascalCase (
DataManager,BlockWriter). - Methods and accessors in camelCase without a
getprefix (loadFromFile,channelByName,timestampsNs,asDoubles); records carry component-named accessors (name(),index()). - Enum constants in UPPER_SNAKE_CASE (
EQUIDISTANT,OSF4,CRC32C,GPS_LOCATION); every wire enum carries the exact wire spelling viawireName()and is resolved through the infallible factoryfromWireName(String). - Value carriers are records where immutable (
GpsLocation,DataChannel.Segment). - Fallible operations throw from an exception hierarchy under
OsfException(RuntimeException); the read/write API has no checked exceptions. - Lookups return
Optional<DataChannel>(channelByName,channelByIndex) rather thannull. - Construction goes through static factories (
DataManager.loadFromFile,DataManager.load,BlockWriter.fromManager) or builder-style writer configuration (add…Channel→ append samples → write phase). - Timestamps are
longnanoseconds since the Unix epoch (UTC) throughout; sample rates aredoublein Hz.
Central design decisions
Encapsulation over a wide API
The public surface is deliberately confined to one package. The whole
read path — block decoding, channel assembly, OSFZ decompression, CRC
checking — sits behind DataManager and is unreachable through JPMS.
This keeps the promised API small, so internal refactors break no
consumer code.
Best-effort and forward compatibility
Real OSF files are produced on devices that can lose power at any moment, and with spec revisions the reader does not yet know. Three behavioural rules follow:
- Truncation is not an error. If the file ends mid-block, the reader
yields all complete blocks and sets
ReaderStats.truncationSeen()totruerather than throwing. - The unknown is tolerated. An unknown (future) data type parses as
DataType.UNSUPPORTED, an unknown channel type asChannelType.UNSUPPORTED; the file still loads and the original spelling is preserved in the channel'sattributesentry. - Removed spec elements are hard errors. Data types removed from the
specification (
pair,triple,candata,gpsdata) are rejected withOsfException.UnsupportedType— their payload layout cannot be reproduced, and silent guessing would be data corruption.
Two writers, not one
StreamingWriter (embedded: fsync per block via FileChannel.force,
constant memory, crash-safe) and BlockWriter (analyst: accumulates in
memory, emits at the end, can auto-bump sizeoflengthvalue from 2 to 4)
have irreconcilable invariants — a single writer would have watered down
both profiles. Yet both share the same chunking arithmetic
(BlockChunking) and, for identical channels, samples,
sizeoflengthvalue and created_utc, produce byte-identical OSF5.
Details on the Writing page.
Transparent OSFZ on read only
OSFZ (= gzip- or zlib-compressed OSF) is detected and decompressed
transparently on read: DataManager.load wraps the source in an
OsfzInputStream before the magic-header parse and fills
ReaderStats.compressed() / compressionFormat(). On write the
library deliberately never compresses inline; compression is a
downstream step.
The crc integrity profile
If the magic header carries a crc32c token, DataManager.load verifies
the metablock's CRC32C fail-closed against the header value and rejects a
mismatch with OsfException.MetablockCrcMismatch; block frames are
validated by CRC32C as well. Signed files
(IntegrityProfile.ED25519) are read transparently — signature blocks
are skipped and counted — but the signatures are not verified.
Thread safety
| Class | Contract |
|---|---|
DataManager (loaded) | immutable → readable from any number of threads |
DataChannel | immutable; do not mutate the backing arrays |
StreamingWriter / BlockWriter | not thread-safe; serialize calls externally |
| different writers on different files | fine in parallel |
Directory layout
implementations/java/
├── pom.xml — reactor (osf-parent), three modules
├── osf-java/ — core library
│ ├── pom.xml
│ └── src/main/java/
│ ├── module-info.java — JPMS descriptor
│ └── com/optimeas/osf/
│ ├── *.java — public API surface
│ └── internal/ — encapsulated building blocks
├── osf-cli/ — command-line tool (picocli)
└── osf-viewer/ — JavaFX viewer
Further reading
- Reading — DataManager, DataChannel, OSFZ
- Writing — StreamingWriter, BlockWriter
- Error handling — the OsfException hierarchy
- Tools — osf-cli and osf-viewer
- Building & embedding — Maven, JPMS, CI
- Cookbook — recipes for common tasks
- Internals — encoder, chunking, assembler
- Format specification
This document is licensed under CC BY 4.0. Attribution: optiMEAS GmbH and optiMEAS Switzerland GmbH.