Passa al contenuto principale

Gestione degli errori

L'implementazione Java segnala gli errori tramite eccezioni. Tutti gli errori della libreria sono istanze di OsfException, che estende RuntimeException — si tratta quindi di eccezioni non controllate: nessun obbligo di clausola throws, nessun try/catch forzato. Chi desidera intercettarle cattura in modo mirato OsfException (o una delle sue sottoclassi); chi non lo desidera le lascia propagare fino a un handler centrale.

Separato da ciò è il lettore best effort: un singolo blocco di dati difettoso o troncato non termina la lettura con un'eccezione, ma viene saltato e conteggiato in ReaderStats. Solo gli errori strutturali prima del flusso di blocchi (header, metablock) e le violazioni gravi delle precondizioni generano eccezioni. Questa separazione è il nucleo della gestione degli errori: fallire in modo netto finché il file è in linea di principio non interpretabile — proseguire non appena sono andati persi solo singoli blocchi finali.

La gerarchia di eccezioni OsfException​

package com.optimeas.osf;

public class OsfException extends RuntimeException {
// Nachricht (und optional Ursache) — die Nachricht ist nur zur Anzeige
public static final class UnsupportedType extends OsfException { }
public static final class MalformedFile extends OsfException { }
public static final class UnknownHeaderToken extends OsfException { }
public static final class MetablockCrcMismatch extends OsfException { }
}

Regole:

  • Diramare in base al tipo, visualizzare il messaggio soltanto. getMessage() è un dettaglio leggibile dall'uomo e non fa parte dell'API — la formulazione può cambiare. Chi vuole reagire in modo programmatico verifica la classe (instanceof / ordine dei catch).
  • Un OsfException senza una sottoclasse più specifica rappresenta il caso «API valida, ma usata in modo errato oppure I/O non riuscito» — soprattutto dai writer.
  • Quando la causa è una IOException sottostante, questa viene riportata come cause (getCause()), in modo che lo stack trace resti completo.

Catalogo degli errori​

In apertura e parsing (errori bloccanti)​

Queste eccezioni si verificano prima ancora che venga letto un blocco di dati utili — il file non è interpretabile come OSF e viene rifiutato per intero.

EccezioneSignificatoOrigine tipica
MalformedFileDifetto strutturale: magic header non ben formato, identificatore di versione sconosciuto, campo obbligatorio mancante nel metablock, numero non analizzabile, sizeoflengthvalue non valido (≠ 2/4), fine imprevista del flusso, nessun ritorno a capo nella finestra dell'header, JSON (OSF5) o XML (OSF4) non valido. In caso di causa di I/O riporta la IOException come cause.Parser di header, metablock e blocchi
UnknownHeaderTokenUn token del magic header la cui chiave è sconosciuta alla libreria (regola must-understand). Volutamente separato da MalformedFile, affinché un token di integrità/estensione sconosciuto non appaia come un fuorviante errore di formato numerico.Magic header
MetablockCrcMismatchIl CRC32C dichiarato nel token di header crc32c non corrisponde ai byte grezzi del metablock. Con il profilo di integrità attivo il file viene rifiutato in modalità fail-closed — i metadati sono considerati compromessi.Verifica del metablock
UnsupportedTypeIl file utilizza un tipo di dati rimosso dalla specifica (pair, triple, candata, gpsdata). Rifiuto bloccante — il vecchio layout del payload non è riproducibile da una build attuale. Serve anche come errore in fase di accesso: un getter di tipo errato su DataChannel (ad es. valori double da un canale String).Parser del metablock, DataChannel

In scrittura e nell'uso dell'API​

I writer (StreamingWriter, BlockWriter) generano un semplice OsfException a ogni precondizione violata — segnalano un errore di programmazione, non un difetto dei dati:

CausaSignificato di esempio
Indice del canale sconosciutoCampione scritto su un canale non dichiarato
Tipo non corrispondenteIl tipo di scrittura non corrisponde al tipo di dati dichiarato del canale
Tipi di blocco mistiUn canale fornisce blocchi equidistanti e con timestamp — vietato dalla specifica
Fase del ciclo di vita errataCampione scritto mentre il writer è ancora in configurazione oppure già chiuso
Nessun canale / lunghezza incoerentebegin/writeTo senza canali dichiarati; timestamps.length ≠ values.length
Profilo firmato richiestoIl profilo ed25519 (firmato) viene rifiutato in scrittura da questa libreria di livello CRC
Errore di I/OFile non apribile, errore di scrittura/force — IOException come cause

Lettore best effort: ciò che deliberatamente non è un errore​

Il lettore del flusso di blocchi si ferma dove un file è in linea di principio ancora leggibile, anziché generare un'eccezione. Il risultato viene registrato in ReaderStats, consultabile tramite manager.stats():

SituazioneComportamento
Il file termina a metà di un blocco (interruzione dell'alimentazione, troncamento)Vengono forniti tutti i blocchi completi, stats.truncationSeen() diventa true, l'iterazione termina in modo pulito
Corpo del blocco difettoso/alteratoArresto best effort proprio in quel punto: truncationSeen() = true, i blocchi restanti già letti restano validi
Indice di canale sconosciuto nel flusso di blocchiSenza definizione la larghezza del campo di lunghezza è ignota → arresto (truncationSeen()) anziché tentativo di congettura
Tipo di dati futuro sconosciutoIl canale è UNSUPPORTED; i suoi blocchi vengono saltati in base alla lunghezza, tutti gli altri canali si caricano normalmente
Il CRC di frame di un blocco non corrispondeIl blocco viene scartato, stats.blocksCrcFailed() aumenta, la lettura prosegue
Blocco di firma (canale riservato 0xFFFE)Non verificabile da questa libreria di livello CRC → saltato, stats.blocksSignatureSkipped() aumenta
Tipi di blocco riservati/vuotiConsumati come saltati; nessun errore

Solo gli errori prima del flusso di blocchi (header, CRC del metablock, parsing del metablock) generano eccezioni — in quei punti non è accettabile alcuna interpretazione parziale.

Stato di integrità — ReaderStats.verificationStatus()​

Dopo il caricamento, stats.verificationStatus() riassume il risultato dell'integrità in una stringa stabile (vocabolario dalla specifica):

ValoreSignificatoReazione consigliata
"none"Il file non riporta alcun profilo di integritàNessuna — elaborazione normale
"crc_valid"Profilo crc, ogni CRC di blocco verificatoTrattare i dati come integri
"invalid"Profilo crc, almeno un CRC di blocco non è riuscito (blocksCrcFailed() > 0)Avvisare/rifiutare; i blocchi interessati mancano nei dati
"signature_unverifiable"File firmato (ed25519) che questa libreria di livello CRC non è in grado di verificareNon indicarlo come «firmato e attendibile»; i dati utili sono comunque leggibili

verificationStatus() deriva esclusivamente dal profilo dichiarato e dai contatori — un "invalid" significa concretamente che i blocchi con CRC errato sono stati saltati (non forniti).

try/catch nella pratica​

import com.optimeas.osf.*;

try {
DataManager mgr = DataManager.loadFromFile(Path.of("messung.osf"));

ReaderStats st = mgr.stats();
if (st.truncationSeen()) {
log.warn("Datei am Ende abgeschnitten — {} Blöcke gelesen", st.blocksRead());
}
switch (st.verificationStatus()) {
case "invalid" -> log.error("CRC-Fehler: {} Blöcke verworfen", st.blocksCrcFailed());
case "signature_unverifiable" -> log.warn("Signatur nicht prüfbar");
default -> { /* none / crc_valid — ok */ }
}

double[] werte = mgr.channelByName("temperatur")
.orElseThrow()
.asDoubles(); // wirft UnsupportedType bei Typ-Mismatch
} catch (OsfException.MetablockCrcMismatch e) {
// Metadaten kompromittiert — Datei ablehnen
} catch (OsfException e) {
// MalformedFile, UnknownHeaderToken, UnsupportedType, I/O …
log.error("OSF konnte nicht geladen werden: {}", e.getMessage(), e);
}

Regole pratiche:

  • Catturare il caso specifico prima di quello generale. Prima le sottoclassi specifiche (MetablockCrcMismatch, UnknownHeaderToken), poi OsfException come rete di sicurezza.
  • Dopo il caricamento controllare stats() — un load riuscito non significa che ogni blocco sia arrivato. Troncamento e errori CRC sono silenziosi e compaiono solo nei contatori.
  • Le chiamate ai getter su DataChannel possono generare UnsupportedType se il tipo richiesto non corrisponde al canale — verificare dataType() in anticipo oppure catturare in modo mirato.

Ciclo di vita del writer​

Lo StreamingWriter impone una macchina a stati CONFIGURE → STREAMING → CLOSED. Una chiamata nella fase errata — campione prima di begin(), scrittura dopo close() — genera OsfException. close() è idempotente (una seconda chiamata è un no-op) e passa in modo affidabile a CLOSED anche in caso di errore di I/O durante il flush finale, così che try-with-resources (il writer è AutoCloseable) non lasci il file aperto.

Per ulteriori dettagli vedere Lettura, Scrittura, Architettura e il manuale del formato OSF. Torna alla panoramica Java.

Questo documento è distribuito con licenza CC BY 4.0. Attribuzione: optiMEAS GmbH e optiMEAS Switzerland GmbH.