From b0b90add20d1a5cf834eb9224acdf27a16aa5d61 Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 19 Sep 2025 11:09:02 +0200 Subject: [PATCH 01/12] #77: Added space (up to 1M) to allow editing the metadata without fully re-saving the trace set --- .../com/riscure/trs/ReadOnlyTraceSet.java | 4 +-- .../com/riscure/trs/TRSMetaDataUtils.java | 14 ++++++++-- src/main/java/com/riscure/trs/TraceSet.java | 2 ++ .../java/com/riscure/trs/enums/TRSTag.java | 3 ++- .../TraceParameterDefinitionMap.java | 15 ++++++++--- src/test/java/TestTraceSet.java | 27 ++++++++++++++----- 6 files changed, 51 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java b/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java index b22b0c0..5fb8908 100644 --- a/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java +++ b/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java @@ -22,7 +22,7 @@ public class ReadOnlyTraceSet extends TraceSet { private static final String TRACE_INDEX_OUT_OF_BOUNDS = "Requested trace index (%d) is larger than the total number of available traces (%d)."; private static final String UNKNOWN_SAMPLE_CODING = "Error reading TRS file: unknown sample coding '%d'"; // This is excessive for the header, but it's only the initial maximum - private static final long MAX_METADATA_SIZE = 100_000_000L; + private static final long INITIAL_MEMORY_SIZE = 100_000_000L; private final int metaDataSize; private final FileInputStream readStream; @@ -44,7 +44,7 @@ public class ReadOnlyTraceSet extends TraceSet { //the file might be bigger than the buffer, in which case we partially buffer it in memory this.fileSize = channel.size(); - long initialBufferSize = Math.min(fileSize, MAX_METADATA_SIZE); + long initialBufferSize = Math.min(fileSize, INITIAL_MEMORY_SIZE); this.metaDataBuffer = channel.map(FileChannel.MapMode.READ_ONLY, 0, initialBufferSize); this.metaData = TRSMetaDataUtils.readTRSMetaData(metaDataBuffer); diff --git a/src/main/java/com/riscure/trs/TRSMetaDataUtils.java b/src/main/java/com/riscure/trs/TRSMetaDataUtils.java index dcc0ebf..75e194c 100644 --- a/src/main/java/com/riscure/trs/TRSMetaDataUtils.java +++ b/src/main/java/com/riscure/trs/TRSMetaDataUtils.java @@ -10,6 +10,8 @@ import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import static com.riscure.trs.TraceSet.DEFAULT_METADATA_SIZE; + public class TRSMetaDataUtils { private static final String IGNORED_UNKNOWN_TAG = "ignored unknown metadata tag '%02X' while reading a TRS file\n"; private static final String TAG_LENGTH_INVALID = "The length field following tag '%s' has value '%X', which is not between 0 and 0xffff"; @@ -30,7 +32,7 @@ public static void writeTRSMetaData(FileOutputStream fos, TRSMetaData metaData) fos.getChannel().position(0); } for (TRSTag tag : TRSTag.values()) { - if (tag.equals(TRSTag.TRACE_BLOCK)) continue; //TRACE BLOCK should be the last write + if (tag.equals(TRSTag.TRACE_BLOCK) || tag.equals(TRSTag.PADDING)) continue; //PADDING and TRACE BLOCK should be the last writes if (!tag.isRequired() && metaData.hasDefaultValue(tag)) continue; //ignore if default and not required fos.write(tag.getValue()); if (tag.getType() == String.class) { @@ -61,6 +63,14 @@ public static void writeTRSMetaData(FileOutputStream fos, TRSMetaData metaData) throw new TRSFormatException(String.format(UNSUPPORTED_TAG_TYPE, tag.getName(), tag.getType())); } } + // Grow the metadata up to 1M, creating an empty buffer in the trace set + // This allows us to grow the header without having to rewrite the whole file + while (fos.getChannel().position() < DEFAULT_METADATA_SIZE) { + byte[] bytes = new byte[(int) (DEFAULT_METADATA_SIZE - fos.getChannel().position())]; + fos.write(TRSTag.PADDING.getValue()); + writeLength(fos, bytes.length); + fos.write(bytes); + } fos.write(TRSTag.TRACE_BLOCK.getValue()); fos.write(TRSTag.TRACE_BLOCK.getLength()); } @@ -124,7 +134,7 @@ public static String readName(LittleEndianInputStream dis) throws IOException { private static void readAndStoreData(ByteBuffer buffer, byte tag, int length, TRSMetaData trsMD) throws TRSFormatException { - boolean hasValidLength = (0 <= length & length <= 0xffff); + boolean hasValidLength = (0 <= length & length <= 0xffffff); TRSTag trsTag; try { trsTag = TRSTag.fromValue(tag); diff --git a/src/main/java/com/riscure/trs/TraceSet.java b/src/main/java/com/riscure/trs/TraceSet.java index 751187c..1b172b0 100644 --- a/src/main/java/com/riscure/trs/TraceSet.java +++ b/src/main/java/com/riscure/trs/TraceSet.java @@ -8,6 +8,8 @@ public abstract class TraceSet implements AutoCloseable { protected static final String TRACE_SET_NOT_OPEN = "TraceSet has not been opened or has been closed."; + // We want to pre-allocate 1M for the header, so we can grow it if needed without re-writing the whole file + public static final long DEFAULT_METADATA_SIZE = 1_000_000L; //Shared variables private final Path path; diff --git a/src/main/java/com/riscure/trs/enums/TRSTag.java b/src/main/java/com/riscure/trs/enums/TRSTag.java index 32038dd..0547d70 100644 --- a/src/main/java/com/riscure/trs/enums/TRSTag.java +++ b/src/main/java/com/riscure/trs/enums/TRSTag.java @@ -54,7 +54,8 @@ public enum TRSTag { XY_SCAN_HEIGHT (0x74, "HE", false, Integer.class, 4, 0, "Number of steps in the \"y\" direction during XY scan"), XY_MEASUREMENTS_PER_SPOT (0x75, "ME", false, Integer.class, 4, 0, "Number of consecutive measurements done per spot during XY scan"), TRACE_SET_PARAMETERS (0x76, "GP", false, TraceSetParameterMap.class, 0, UnmodifiableTraceSetParameterMap.of(new TraceSetParameterMap()), "The set of custom global trace set parameters"), - TRACE_PARAMETER_DEFINITIONS (0x77, "LP", false, TraceParameterDefinitionMap.class, 0, UnmodifiableTraceParameterDefinitionMap.of(new TraceParameterDefinitionMap()), "The set of custom local trace parameters"); + TRACE_PARAMETER_DEFINITIONS (0x77, "LP", false, TraceParameterDefinitionMap.class, 0, UnmodifiableTraceParameterDefinitionMap.of(new TraceParameterDefinitionMap()), "The set of custom local trace parameters"), + PADDING (0xFF, "FF", false, String.class, 0, 0, "Empty value to allow growing the metadata"); private static final String UNKNOWN_TAG = "Unknown tag: 0x%X"; diff --git a/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java b/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java index dedd43d..ddd1af4 100644 --- a/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java +++ b/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java @@ -1,5 +1,6 @@ package com.riscure.trs.parameter.trace.definition; +import com.riscure.trs.TRSFormatException; import com.riscure.trs.TRSMetaDataUtils; import com.riscure.trs.io.LittleEndianInputStream; import com.riscure.trs.io.LittleEndianOutputStream; @@ -9,7 +10,10 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -18,6 +22,7 @@ * This explicitly implements LinkedHashMap to ensure that the data is retrieved in the same order as it was added */ public class TraceParameterDefinitionMap extends LinkedHashMap> { + private static final String NAME_TOO_LONG = "Name of length %d exceeds maximum length of %d bytes%nName will be truncated to the maximum length%n"; public TraceParameterDefinitionMap() { super(); @@ -43,7 +48,7 @@ public int totalSize() { * @return this map converted to a byte array, serialized according to the TRS V2 standard definition * @throws RuntimeException if the map failed to serialize correctly */ - public byte[] serialize() { + public byte[] serialize() throws IOException, TRSFormatException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try (LittleEndianOutputStream dos = new LittleEndianOutputStream(baos)) { //Write NE @@ -51,6 +56,12 @@ public byte[] serialize() { for (Map.Entry> entry : entrySet()) { byte[] nameBytes = entry.getKey().getBytes(StandardCharsets.UTF_8); //Write NL + if (nameBytes.length > Short.MAX_VALUE) { + System.err.printf(NAME_TOO_LONG, nameBytes.length, Short.MAX_VALUE); + nameBytes = new byte[Short.MAX_VALUE]; + CharBuffer name = CharBuffer.wrap(entry.getKey()); + StandardCharsets.UTF_8.newEncoder().encode(name, ByteBuffer.wrap(nameBytes), true); + } dos.writeShort(nameBytes.length); //Write N dos.write(nameBytes); @@ -59,8 +70,6 @@ public byte[] serialize() { } dos.flush(); return baos.toByteArray(); - } catch (IOException ex) { - throw new RuntimeException(ex); } } diff --git a/src/test/java/TestTraceSet.java b/src/test/java/TestTraceSet.java index ba6e132..f5b2325 100644 --- a/src/test/java/TestTraceSet.java +++ b/src/test/java/TestTraceSet.java @@ -17,6 +17,7 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; import java.io.ByteArrayInputStream; @@ -26,10 +27,11 @@ import java.nio.file.Path; import java.util.*; +import static com.riscure.trs.TraceSet.DEFAULT_METADATA_SIZE; import static org.junit.jupiter.api.Assertions.*; -public class TestTraceSet { +class TestTraceSet { private static Path tempDir; private static final String BYTES_TRS = "bytes.trs"; private static final String SHORTS_TRS = "shorts.trs"; @@ -208,7 +210,9 @@ void testWriteTraceSetParameters() throws IOException, TRSFormatException { } /** - * This tests adding a parameter with a name of 100000 characters + * This tests adding a parameter with a name of 100000 characters. + * Expectation: The name will be truncated to the maximum allowed length when writing, + * when reading back and comparing with the original metadata, the values will differ * * @throws IOException * @throws TRSFormatException @@ -218,15 +222,15 @@ void testWriteTraceParametersInvalidName() throws IOException, TRSFormatExceptio TRSMetaData metaData = TRSMetaData.create(); String parameterName = String.format("%100000s", "XYZ"); //CREATE TRACE - String name = UUID.randomUUID().toString() + TRS; - try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + name, metaData)) { + String name = UUID.randomUUID() + TRS; + try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath() + File.separator + name, metaData)) { TraceParameterMap parameters = new TraceParameterMap(); parameters.put(parameterName, 1); traceWithParameters.add(Trace.create("", FLOAT_SAMPLES, parameters)); } //READ BACK AND CHECK RESULT - assertThrows(TRSFormatException.class, () -> { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + assertThrows(AssertionFailedError.class, () -> { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { TraceParameterDefinitionMap parameterDefinitions = readable.getMetaData().getTraceParameterDefinitions(); parameterDefinitions.forEach((key, parameter) -> assertEquals(parameterName, key)); } @@ -684,4 +688,15 @@ void testFileReleasing() throws IOException, TRSFormatException, InterruptedExce File file = new File(filePath); assert(file.delete()); } + + @Test + void testDefaultHeaderSize() throws IOException, TRSFormatException { + Path filePath = tempDir.resolve("large_header.trs"); + TRSMetaData metaData = new TRSMetaData(); + metaData.put(TRSTag.TRS_VERSION, 2); + try (TraceSet ts = TraceSet.create(filePath.toString(), metaData)) { + ts.add(new Trace(new float[]{})); + } + assertTrue(filePath.toFile().length() > DEFAULT_METADATA_SIZE); + } } From 400b985f206b8ba4f554e72fcce02ce6d898684c Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 15:08:41 +0100 Subject: [PATCH 02/12] #77: Added file unmapping delay as the default --- src/main/java/com/riscure/trs/ReadOnlyTraceSet.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java b/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java index 5fb8908..c99716f 100644 --- a/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java +++ b/src/main/java/com/riscure/trs/ReadOnlyTraceSet.java @@ -129,6 +129,19 @@ private long calculateTraceSize() { public void close() throws IOException, TRSFormatException { super.close(); closeReader(); + awaitFileUnmapping(); + } + + private static void awaitFileUnmapping() throws IOException { + // Unfortunately, the current solution requires a garbage collect to have been performed before the issue is resolved. + // Other fixes required either a Java 8 Cleaner.clean() call not accessible from Java 21, or a Java 20 Arena.close(), + // which is not been finalized in Java 21. + System.gc(); + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new IOException(e); + } } @Override From 62789e6d544e66d5009f37b8221f5a386d8f6fe6 Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 15:10:27 +0100 Subject: [PATCH 03/12] #77: Added test to check updating metadata and bumped to V3 after adding metadata padding --- src/test/java/TestTraceSet.java | 47 ++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/test/java/TestTraceSet.java b/src/test/java/TestTraceSet.java index f5b2325..72f6f69 100644 --- a/src/test/java/TestTraceSet.java +++ b/src/test/java/TestTraceSet.java @@ -76,10 +76,7 @@ public static void createTempDir() throws IOException, TRSFormatException { } @AfterAll - public static void cleanup() throws InterruptedException { - //We need to allow a little time for java to release all handles - System.gc(); - Thread.sleep(100); + public static void cleanup() { for (File file : Objects.requireNonNull(tempDir.toFile().listFiles())) { try { Files.delete(file.toPath()); @@ -679,24 +676,50 @@ void testFileReleasing() throws IOException, TRSFormatException, InterruptedExce try (TraceSet traceSet = TraceSet.open(filePath)) { traceSet.getMetaData().getTraceSetParameters(); } - // Unfortunately, the current solution requires a garbage collect to have been performed before the issue is resolved. - // Other fixes required either a Java 8 Cleaner.clean() call not accessible from Java 21, or a Java 20 Arena.close(), - // which is not been finalized in Java 21. - System.gc(); - Thread.sleep(1000); // Assert that the opened file has been closed again, by deleting it. File file = new File(filePath); - assert(file.delete()); + assertTrue(file.delete()); } + /** + * This test checks whether version 3 correctly allocates 1MB of header space by default + */ @Test void testDefaultHeaderSize() throws IOException, TRSFormatException { Path filePath = tempDir.resolve("large_header.trs"); TRSMetaData metaData = new TRSMetaData(); - metaData.put(TRSTag.TRS_VERSION, 2); + metaData.put(TRSTag.TRS_VERSION, 3); try (TraceSet ts = TraceSet.create(filePath.toString(), metaData)) { - ts.add(new Trace(new float[]{})); + ts.add(new Trace(new float[]{0})); } assertTrue(filePath.toFile().length() > DEFAULT_METADATA_SIZE); } + + /** + * This test checks whether we can successfully add information to the header of a traceset file without + * increasing its size + */ + @Test + void testOverwritingMetadata() throws IOException, TRSFormatException { + String filename = tempDir.toAbsolutePath() + File.separator + BYTES_TRS; + long originalFileSize = new File(filename).length(); + + TraceSetParameterMap tspm; + TraceParameterDefinitionMap tpdm; + try (TraceSet readable = TraceSet.open(filename)) { + assertFalse(readable.getMetaData().getTraceSetParameters().containsKey("test")); + + tspm = readable.getMetaData().getTraceSetParameters().copy(); + tpdm = readable.getMetaData().getTraceParameterDefinitions().copy(); + + tspm.put("test", "This value should exist afterwards"); + } + + TraceSet.updateParameterMaps(filename, tspm, tpdm); + + try (TraceSet readable = TraceSet.open(filename)) { + assertTrue(readable.getMetaData().getTraceSetParameters().containsKey("test")); + } + assertEquals(originalFileSize, new File(filename).length()); + } } From 580d7f9be3662d90932d41f0a62113fe4ddaa241 Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 15:12:22 +0100 Subject: [PATCH 04/12] #77: Added implementation for updating the metadata --- .../com/riscure/trs/TRSMetaDataUtils.java | 91 +++++++++++++++++++ src/main/java/com/riscure/trs/TraceSet.java | 37 +++++++- 2 files changed, 126 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/riscure/trs/TRSMetaDataUtils.java b/src/main/java/com/riscure/trs/TRSMetaDataUtils.java index 75e194c..bc48b37 100644 --- a/src/main/java/com/riscure/trs/TRSMetaDataUtils.java +++ b/src/main/java/com/riscure/trs/TRSMetaDataUtils.java @@ -7,6 +7,7 @@ import java.io.FileOutputStream; import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; @@ -18,6 +19,64 @@ public class TRSMetaDataUtils { private static final String UNSUPPORTED_TAG_TYPE = "Unsupported tag type for tag '%s': %s"; private static final String REWINDING_STREAM = "The output stream is not at the start of the file. Rewinding stream."; + /** + * Writes the provided TRS metadata to the stream. + * + * @param raf the file output opened in random access mode + * @param metaData the metadata to write + * @throws IOException if any write error occurs + * @throws TRSFormatException if the metadata contains unsupported tags + */ + public static void writeTRSMetaData(RandomAccessFile raf, TRSMetaData metaData) throws IOException, TRSFormatException { + // We always write at the start of the file + raf.seek(0); + for (TRSTag tag : TRSTag.values()) { + if (tag.equals(TRSTag.TRACE_BLOCK) || tag.equals(TRSTag.PADDING)) continue; //PADDING and TRACE BLOCK should be the last writes + if (!tag.isRequired() && metaData.hasDefaultValue(tag)) continue; //ignore if default and not required + raf.write(tag.getValue()); + if (tag.getType() == String.class) { + String s = metaData.getString(tag); + byte[] stringBytes = s.getBytes(StandardCharsets.UTF_8); + writeLength(raf, stringBytes.length); + raf.write(stringBytes); + } else if (tag.getType() == Float.class) { + float f = metaData.getFloat(tag); + writeLength(raf, tag.getLength()); + writeInt(raf, Float.floatToIntBits(f), tag.getLength()); + } else if (tag.getType() == Boolean.class) { + int value = metaData.getBoolean(tag) ? 1 : 0; + writeLength(raf, tag.getLength()); + writeInt(raf, value, tag.getLength()); + } else if (tag.getType() == Integer.class) { + writeLength(raf, tag.getLength()); + writeInt(raf, metaData.getInt(tag), tag.getLength()); + } else if (tag.getType() == TraceSetParameterMap.class) { + byte[] serialized = metaData.getTraceSetParameters().serialize(); + writeLength(raf, serialized.length); + raf.write(serialized); + } else if (tag.getType() == TraceParameterDefinitionMap.class) { + byte[] serialized = metaData.getTraceParameterDefinitions().serialize(); + writeLength(raf, serialized.length); + raf.write(serialized); + } else { + throw new TRSFormatException(String.format(UNSUPPORTED_TAG_TYPE, tag.getName(), tag.getType())); + } + } + // Grow the metadata up to 1M, creating an empty buffer in the trace set + // This allows us to grow the header without having to rewrite the whole file + if (raf.getChannel().position() < DEFAULT_METADATA_SIZE) { + raf.write(TRSTag.PADDING.getValue()); + int expectedLength = (int) (DEFAULT_METADATA_SIZE - raf.getChannel().position()); + // The length of the padding will be the maximum size minus the current position minus the number of bytes used for the length tag minus the length of the trace block tag minus the length of the trace block length tag + int paddingLength = expectedLength - computeLengthBytes(expectedLength) - 2; + writeLength(raf, paddingLength); + byte[] bytes = new byte[paddingLength]; + raf.write(bytes); + } + raf.write(TRSTag.TRACE_BLOCK.getValue()); + raf.write(TRSTag.TRACE_BLOCK.getLength()); + } + /** * Writes the provided TRS metadata to the stream. * @@ -75,12 +134,32 @@ public static void writeTRSMetaData(FileOutputStream fos, TRSMetaData metaData) fos.write(TRSTag.TRACE_BLOCK.getLength()); } + private static int computeLengthBytes(int length) { + int lengthBytes = 0; + if (length > 0x7F) { + int lenlen = 1 + (int) (Math.log(length) / Math.log(256)); + lengthBytes++; + for (int i = 0; i < lenlen; i++) { + lengthBytes++; + } + } else { + lengthBytes++; + } + return lengthBytes; + } + private static void writeInt(FileOutputStream fos, int value, int length) throws IOException { for (int i = 0; i < length; i++) { fos.write((byte) (value >> (i * 8))); } } + private static void writeInt(RandomAccessFile raf, int value, int length) throws IOException { + for (int i = 0; i < length; i++) { + raf.write((byte) (value >> (i * 8))); + } + } + private static void writeLength(FileOutputStream fos, long length) throws IOException { if (length > 0x7F) { int lenlen = 1 + (int) (Math.log(length) / Math.log(256)); @@ -93,6 +172,18 @@ private static void writeLength(FileOutputStream fos, long length) throws IOExce } } + private static void writeLength(RandomAccessFile raf, long length) throws IOException { + if (length > 0x7F) { + int lenlen = 1 + (int) (Math.log(length) / Math.log(256)); + raf.write((byte) (0x80 + lenlen)); + for (int i = 0; i < lenlen; i++) { + raf.write((byte) (length >> (i * 8))); + } + } else { + raf.write((byte) length); + } + } + /** * Reads the meta data of a TRS file. The {@code ByteBuffer} is assumed to be positioned at the start of the file; A * {@code TRSFormatException} will probably be thrown otherwise, since it cannot be parsed. diff --git a/src/main/java/com/riscure/trs/TraceSet.java b/src/main/java/com/riscure/trs/TraceSet.java index 1b172b0..9379b39 100644 --- a/src/main/java/com/riscure/trs/TraceSet.java +++ b/src/main/java/com/riscure/trs/TraceSet.java @@ -1,10 +1,14 @@ package com.riscure.trs; +import com.riscure.trs.parameter.trace.definition.TraceParameterDefinitionMap; +import com.riscure.trs.parameter.traceset.TraceSetParameterMap; + import java.io.IOException; +import java.io.RandomAccessFile; import java.nio.file.Path; import java.util.List; -import static com.riscure.trs.enums.TRSTag.TRS_VERSION; +import static com.riscure.trs.enums.TRSTag.*; public abstract class TraceSet implements AutoCloseable { protected static final String TRACE_SET_NOT_OPEN = "TraceSet has not been opened or has been closed."; @@ -137,7 +141,36 @@ public static TraceSet create(String file) throws IOException { * @throws IOException if the file creation failed */ public static TraceSet create(String file, TRSMetaData metaData) throws IOException { - metaData.put(TRS_VERSION, 2, false); + metaData.put(TRS_VERSION, 3, false); return new WritableTraceSet(file, metaData); } + + /** + * Overwrite the metadata associated with this trace set + * If this traceset is in read mode, this is only possible under certain conditions: + * 1) The opened trace set is a V3 set + * 2) There is empty remaining space (i.e. padding) in the pre-allocated metadata + * + * TODO: We should probably limit the changes to specific tags. e.g. the number of traces should not be modified, + * TODO: but the TSPM is fine. The definition map may be updated, but the size must remain the same + */ + public static void updateParameterMaps(String file, TraceSetParameterMap tspm, TraceParameterDefinitionMap tpdm) throws IOException, TRSFormatException { + TRSMetaData metaData; + try (TraceSet ts = open(file)) { + metaData = ts.getMetaData(); + } + + if (metaData.getInt(TRS_VERSION) < 3) throw new IOException(String.format("This trace set is version %d. Only version 3 and upwards support updating metadata.", metaData.getInt(TRS_VERSION))); + // TODO check this + //if (metaDataSize > DEFAULT_METADATA_SIZE) throw new IOException("The meta data has already grown beyond the padding size. This trace set does not support updating the meta data."); + if (metaData.getTraceParameterDefinitions().totalSize() != tpdm.totalSize()) throw new IOException("The provided parameter definitions are of a different size than the current ones. While it's possible to change the definitions, the size must match."); + + metaData.put(TRACE_SET_PARAMETERS, tspm); + metaData.put(TRACE_PARAMETER_DEFINITIONS, tpdm); + + // Open the file in append mode so we can overwrite the header only + try (RandomAccessFile raf = new RandomAccessFile(file, "rw")) { + TRSMetaDataUtils.writeTRSMetaData(raf, metaData); + } + } } From b16d27b1e826adf4be72bc890118d534d2c9aabf Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 15:13:08 +0100 Subject: [PATCH 05/12] #77: Changed while to if (while doesn't do anything here) --- src/main/java/com/riscure/trs/TRSMetaDataUtils.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/riscure/trs/TRSMetaDataUtils.java b/src/main/java/com/riscure/trs/TRSMetaDataUtils.java index bc48b37..7dd2565 100644 --- a/src/main/java/com/riscure/trs/TRSMetaDataUtils.java +++ b/src/main/java/com/riscure/trs/TRSMetaDataUtils.java @@ -124,10 +124,13 @@ public static void writeTRSMetaData(FileOutputStream fos, TRSMetaData metaData) } // Grow the metadata up to 1M, creating an empty buffer in the trace set // This allows us to grow the header without having to rewrite the whole file - while (fos.getChannel().position() < DEFAULT_METADATA_SIZE) { - byte[] bytes = new byte[(int) (DEFAULT_METADATA_SIZE - fos.getChannel().position())]; + if (fos.getChannel().position() < DEFAULT_METADATA_SIZE) { fos.write(TRSTag.PADDING.getValue()); - writeLength(fos, bytes.length); + int expectedLength = (int) (DEFAULT_METADATA_SIZE - fos.getChannel().position()); + // The length of the padding will be the maximum size minus the current position minus the number of bytes used for the length tag minus the length of the trace block tag minus the length of the trace block length tag + int paddingLength = expectedLength - computeLengthBytes(expectedLength) - 2; + writeLength(fos, paddingLength); + byte[] bytes = new byte[paddingLength]; fos.write(bytes); } fos.write(TRSTag.TRACE_BLOCK.getValue()); From 42b52d2ab90075a4b4588cbab930df210b1d1e9b Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 15:13:17 +0100 Subject: [PATCH 06/12] #77: Cleanup tests --- src/test/java/TestTraceSet.java | 86 ++++++++++++--------------------- 1 file changed, 32 insertions(+), 54 deletions(-) diff --git a/src/test/java/TestTraceSet.java b/src/test/java/TestTraceSet.java index 72f6f69..29e3984 100644 --- a/src/test/java/TestTraceSet.java +++ b/src/test/java/TestTraceSet.java @@ -11,7 +11,6 @@ import com.riscure.trs.parameter.trace.TraceParameterMap; import com.riscure.trs.parameter.trace.definition.TraceParameterDefinition; import com.riscure.trs.parameter.trace.definition.TraceParameterDefinitionMap; -import com.riscure.trs.parameter.traceset.TraceSetParameter; import com.riscure.trs.parameter.traceset.TraceSetParameterMap; import com.riscure.trs.types.*; import org.junit.jupiter.api.AfterAll; @@ -50,25 +49,25 @@ class TestTraceSet { public static void createTempDir() throws IOException, TRSFormatException { tempDir = Files.createTempDirectory("TestTraceSet"); - try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + BYTES_TRS)) { + try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath() + File.separator + BYTES_TRS)) { for (int k = 0; k < NUMBER_OF_TRACES; k++) { writable.add(Trace.create(BYTE_SAMPLES)); } } - try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + SHORTS_TRS)) { + try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath() + File.separator + SHORTS_TRS)) { for (int k = 0; k < NUMBER_OF_TRACES; k++) { writable.add(Trace.create(SHORT_SAMPLES)); } } - try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + INTS_TRS)) { + try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath() + File.separator + INTS_TRS)) { for (int k = 0; k < NUMBER_OF_TRACES; k++) { writable.add(Trace.create(INT_SAMPLES)); } } - try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + FLOATS_TRS)) { + try (TraceSet writable = TraceSet.create(tempDir.toAbsolutePath() + File.separator + FLOATS_TRS)) { for (int k = 0; k < NUMBER_OF_TRACES; k++) { writable.add(Trace.create(FLOAT_SAMPLES)); } @@ -81,21 +80,19 @@ public static void cleanup() { try { Files.delete(file.toPath()); } catch (IOException e) { - System.err.printf("Failed to delete temporary file '%s'%n", file.toPath().toAbsolutePath().toString()); - e.printStackTrace(); + System.err.printf("Failed to delete temporary file '%s'%n", file.toPath().toAbsolutePath()); } } try { Files.delete(tempDir); } catch (IOException e) { - System.err.printf("Failed to delete temporary folder '%s'%n", tempDir.toFile().toPath().toAbsolutePath().toString()); - e.printStackTrace(); + System.err.printf("Failed to delete temporary folder '%s'%n", tempDir.toFile().toPath().toAbsolutePath()); } } @Test void testOpenBytes() throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + BYTES_TRS)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + BYTES_TRS)) { int numberOfTracesRead = readable.getMetaData().getInt(TRSTag.NUMBER_OF_TRACES); Encoding encoding = Encoding.fromValue(readable.getMetaData().getInt(TRSTag.SAMPLE_CODING)); assertEquals(Encoding.BYTE, encoding); @@ -110,7 +107,7 @@ void testOpenBytes() throws IOException, TRSFormatException { @Test void testOpenShorts() throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + SHORTS_TRS)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + SHORTS_TRS)) { int numberOfTracesRead = readable.getMetaData().getInt(TRSTag.NUMBER_OF_TRACES); Encoding encoding = Encoding.fromValue(readable.getMetaData().getInt(TRSTag.SAMPLE_CODING)); assertEquals(Encoding.SHORT, encoding); @@ -125,7 +122,7 @@ void testOpenShorts() throws IOException, TRSFormatException { @Test void testOpenInts() throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + INTS_TRS)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + INTS_TRS)) { int numberOfTracesRead = readable.getMetaData().getInt(TRSTag.NUMBER_OF_TRACES); Encoding encoding = Encoding.fromValue(readable.getMetaData().getInt(TRSTag.SAMPLE_CODING)); assertEquals(Encoding.INT, encoding); @@ -140,7 +137,7 @@ void testOpenInts() throws IOException, TRSFormatException { @Test void testOpenFloats() throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + FLOATS_TRS)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + FLOATS_TRS)) { int numberOfTracesRead = readable.getMetaData().getInt(TRSTag.NUMBER_OF_TRACES); Encoding encoding = Encoding.fromValue(readable.getMetaData().getInt(TRSTag.SAMPLE_CODING)); assertEquals(Encoding.FLOAT, encoding); @@ -156,13 +153,11 @@ void testOpenFloats() throws IOException, TRSFormatException { @Test void testUTF8Title() throws IOException, TRSFormatException { String title = "씨브 크레그스만"; - String name = UUID.randomUUID().toString() + TRS; - try (TraceSet ts = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + name)) { + String name = UUID.randomUUID() + TRS; + try (TraceSet ts = TraceSet.create(tempDir.toAbsolutePath() + File.separator + name)) { ts.add(Trace.create(title, new float[0], new TraceParameterMap())); - } catch (TRSFormatException e) { - throw e; } - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { assertEquals(title, readable.get(0).getTitle()); } } @@ -170,9 +165,6 @@ void testUTF8Title() throws IOException, TRSFormatException { /** * This tests adding several different types of information to the trace set header. The three parameters are chosen * to match the three major cases: Strings, primitives, and arbitrary (serializable) objects. - * - * @throws IOException - * @throws TRSFormatException */ @Test void testWriteTraceSetParameters() throws IOException, TRSFormatException { @@ -197,10 +189,10 @@ void testWriteTraceSetParameters() throws IOException, TRSFormatException { //parameters.put("XYZ offset", XYZ_TEST_VALUE); metaData.put(TRSTag.TRACE_SET_PARAMETERS, parameters); //CREATE TRACE - String name = UUID.randomUUID().toString() + TRS; - TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + name, metaData).close(); + String name = UUID.randomUUID() + TRS; + TraceSet.create(tempDir.toAbsolutePath() + File.separator + name, metaData).close(); //READ BACK AND CHECK RESULT - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { TraceSetParameterMap readTraceSetParameterMap = readable.getMetaData().getTraceSetParameters(); parameters.forEach((s, traceSetParameter) -> assertEquals(traceSetParameter, readTraceSetParameterMap.get(s))); } @@ -210,9 +202,6 @@ void testWriteTraceSetParameters() throws IOException, TRSFormatException { * This tests adding a parameter with a name of 100000 characters. * Expectation: The name will be truncated to the maximum allowed length when writing, * when reading back and comparing with the original metadata, the values will differ - * - * @throws IOException - * @throws TRSFormatException */ @Test void testWriteTraceParametersInvalidName() throws IOException, TRSFormatException { @@ -239,9 +228,6 @@ void testWriteTraceParametersInvalidName() throws IOException, TRSFormatExceptio * - if no length is specified, the first string is leading * - if a string is longer than the length specified, it should be truncated * - when truncated, a string should still be valid UTF-8 (truncated at character level, not byte level) - * - * @throws IOException - * @throws TRSFormatException */ @Test void testWriteTraceParametersVaryingStringLength() throws IOException, TRSFormatException { @@ -253,8 +239,8 @@ void testWriteTraceParametersVaryingStringLength() throws IOException, TRSFormat strings.add("ab"); strings.add("abcdefgh汉字"); //CREATE TRACE - String name = UUID.randomUUID().toString() + TRS; - try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + name, metaData)) { + String name = UUID.randomUUID() + TRS; + try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath() + File.separator + name, metaData)) { for (int k = 0; k < 25; k++) { TraceParameterMap parameters = new TraceParameterMap(); parameters.put("BYTEARRAY", new byte[]{(byte) k, (byte) k, (byte) k}); @@ -270,17 +256,14 @@ void testWriteTraceParametersVaryingStringLength() throws IOException, TRSFormat /** * This tests whether all getters are working as expected - * - * @throws IOException - * @throws TRSFormatException */ @Test void testReadTraceParametersTyped() throws IOException, TRSFormatException { TRSMetaData metaData = TRSMetaData.create(); List testParameters = new ArrayList<>(); //CREATE TRACE - String name = UUID.randomUUID().toString() + TRS; - try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + name, metaData)) { + String name = UUID.randomUUID() + TRS; + try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath() + File.separator + name, metaData)) { for (int k = 0; k < 25; k++) { TraceParameterMap parameters = new TraceParameterMap(); parameters.put("BYTE", (byte) k); @@ -308,7 +291,7 @@ void testReadTraceParametersTyped() throws IOException, TRSFormatException { } private void readBackGeneric(List testParameters, String name) throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { TraceParameterDefinitionMap parameterDefinitions = readable.getMetaData().getTraceParameterDefinitions(); for (int k = 0; k < 25; k++) { assertEquals(parameterDefinitions.size(), testParameters.get(k).size()); @@ -323,7 +306,7 @@ private void readBackGeneric(List testParameters, String name } private void readBackTyped(List testParameters, String name) throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { TraceParameterDefinitionMap parameterDefinitions = readable.getMetaData().getTraceParameterDefinitions(); for (int k = 0; k < 25; k++) { assertEquals(parameterDefinitions.size(), testParameters.get(k).size()); @@ -392,7 +375,7 @@ private void readBackTyped(List testParameters, String name) } private void readBackTypedKeys(List testParameters, String name) throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { TraceParameterDefinitionMap parameterDefinitions = readable.getMetaData().getTraceParameterDefinitions(); for (int k = 0; k < 25; k++) { assertEquals(parameterDefinitions.size(), testParameters.get(k).size()); @@ -429,8 +412,8 @@ private void readBackTypedKeys(List testParameters, String na throw new RuntimeException("Unexpected type: " + parameter.getType()); } if (parameter.getLength() > 1 && typedKey.getCls().isArray()) { - assertArrayEquals(Arrays.asList(correctValue.getOrElseThrow(typedKey)).toArray(), - Arrays.asList(trace.getParameters().getOrElseThrow(typedKey)).toArray()); + assertArrayEquals(Collections.singletonList(correctValue.getOrElseThrow(typedKey)).toArray(), + Collections.singletonList(trace.getParameters().getOrElseThrow(typedKey)).toArray()); } else { assertEquals(correctValue.get(typedKey), trace.getParameters().get(typedKey)); } @@ -441,42 +424,37 @@ private void readBackTypedKeys(List testParameters, String na /** * This tests getting a value of the wrong type correctly throws an exception - * - * @throws IOException - * @throws TRSFormatException */ @Test void testExceptionWrongType() throws IOException, TRSFormatException { TRSMetaData metaData = TRSMetaData.create(); //CREATE TRACE - String name = UUID.randomUUID().toString() + TRS; - try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + name, metaData)) { + String name = UUID.randomUUID() + TRS; + try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath() + File.separator + name, metaData)) { TraceParameterMap parameters = new TraceParameterMap(); parameters.put("BYTE", (byte) 1); traceWithParameters.add(Trace.create("", FLOAT_SAMPLES, parameters)); } //READ BACK AND CHECK RESULT - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { assertThrows(ClassCastException.class, () -> readable.get(0).getParameters().getDouble("BYTE")); } } /** * This - * @throws IOException - * @throws TRSFormatException */ @Test void testContainsNonArray() throws IOException, TRSFormatException { ByteTypeKey byteKey = new ByteTypeKey("BYTE"); - String name = UUID.randomUUID().toString() + TRS; - try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath().toString() + File.separator + name)) { + String name = UUID.randomUUID() + TRS; + try (TraceSet traceWithParameters = TraceSet.create(tempDir.toAbsolutePath() + File.separator + name)) { TraceParameterMap parameters = new TraceParameterMap(); parameters.put(byteKey, (byte) 1); traceWithParameters.add(Trace.create("", FLOAT_SAMPLES, parameters)); } //READ BACK AND CHECK RESULT - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + name)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { assertTrue(readable.get(0).getParameters().get(byteKey).isPresent()); } } @@ -504,7 +482,7 @@ void testInvalidParameterLength() { */ @Test void testModificationAfterReadback() throws IOException, TRSFormatException { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath().toString() + File.separator + BYTES_TRS)) { + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + BYTES_TRS)) { assertThrows(UnsupportedOperationException.class, () -> readable.getMetaData().getTraceSetParameters().put("SHOULD_FAIL", 0)); assertThrows(UnsupportedOperationException.class, () -> readable.getMetaData().getTraceParameterDefinitions().put("SHOULD_FAIL", new TraceParameterDefinition(ParameterType.BYTE, (short)1, (short)1))); for (int k = 0; k < NUMBER_OF_TRACES; k++) { From 6c7c3fb21c24d16cbc5dc626449f485eeebff60e Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 16:03:27 +0100 Subject: [PATCH 07/12] #77: Changed parameter type so we can just write 0s instead of an empty string --- src/main/java/com/riscure/trs/enums/TRSTag.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/riscure/trs/enums/TRSTag.java b/src/main/java/com/riscure/trs/enums/TRSTag.java index 0547d70..e99ea33 100644 --- a/src/main/java/com/riscure/trs/enums/TRSTag.java +++ b/src/main/java/com/riscure/trs/enums/TRSTag.java @@ -55,7 +55,7 @@ public enum TRSTag { XY_MEASUREMENTS_PER_SPOT (0x75, "ME", false, Integer.class, 4, 0, "Number of consecutive measurements done per spot during XY scan"), TRACE_SET_PARAMETERS (0x76, "GP", false, TraceSetParameterMap.class, 0, UnmodifiableTraceSetParameterMap.of(new TraceSetParameterMap()), "The set of custom global trace set parameters"), TRACE_PARAMETER_DEFINITIONS (0x77, "LP", false, TraceParameterDefinitionMap.class, 0, UnmodifiableTraceParameterDefinitionMap.of(new TraceParameterDefinitionMap()), "The set of custom local trace parameters"), - PADDING (0xFF, "FF", false, String.class, 0, 0, "Empty value to allow growing the metadata"); + PADDING (0xFF, "FF", false, Integer.class, 0, 0, "Empty value to allow growing the metadata"); private static final String UNKNOWN_TAG = "Unknown tag: 0x%X"; From 27041c8ab4bff7d4e35654c14de36d28b7db8689 Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 16:04:12 +0100 Subject: [PATCH 08/12] #77: Reset the trace counter in case we created a copy of another MetaData object --- src/main/java/com/riscure/trs/WritableTraceSet.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/riscure/trs/WritableTraceSet.java b/src/main/java/com/riscure/trs/WritableTraceSet.java index aef3306..0e3b4a1 100644 --- a/src/main/java/com/riscure/trs/WritableTraceSet.java +++ b/src/main/java/com/riscure/trs/WritableTraceSet.java @@ -55,6 +55,7 @@ public void add(Trace trace) throws IOException, TRSFormatException { metaData.put(TITLE_SPACE, titleLength, false); metaData.put(SAMPLE_CODING, trace.getPreferredCoding(), false); metaData.put(TRACE_PARAMETER_DEFINITIONS, TraceParameterDefinitionMap.createFrom(trace.getParameters())); + metaData.put(NUMBER_OF_TRACES, 0); TRSMetaDataUtils.writeTRSMetaData(writeStream, metaData); firstTrace = false; } From c5e090b87ef36cb217d3edb3592edb1435e7526b Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 30 Jan 2026 16:04:39 +0100 Subject: [PATCH 09/12] #77: Allow duplicating a metadata object, making it modifiable --- src/main/java/com/riscure/trs/TRSMetaData.java | 13 +++++++++++++ src/main/java/com/riscure/trs/WritableTraceSet.java | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/riscure/trs/TRSMetaData.java b/src/main/java/com/riscure/trs/TRSMetaData.java index e2fb127..fa9829b 100644 --- a/src/main/java/com/riscure/trs/TRSMetaData.java +++ b/src/main/java/com/riscure/trs/TRSMetaData.java @@ -29,6 +29,19 @@ private void init() { } } + /** + * @return a modifiable copy of this metadata object + */ + public TRSMetaData modifiable() { + TRSMetaData copy = new TRSMetaData(); + for (TRSTag tag : TRSTag.values()) { + copy.put(tag, get(tag)); + } + copy.put(TRSTag.TRACE_SET_PARAMETERS, getTraceSetParameters().copy()); + copy.put(TRSTag.TRACE_PARAMETER_DEFINITIONS, getTraceParameterDefinitions().copy()); + return copy; + } + /** * Add the data associated with the supplied tag to this metadata. * This will overwrite any existing value diff --git a/src/main/java/com/riscure/trs/WritableTraceSet.java b/src/main/java/com/riscure/trs/WritableTraceSet.java index 0e3b4a1..050436c 100644 --- a/src/main/java/com/riscure/trs/WritableTraceSet.java +++ b/src/main/java/com/riscure/trs/WritableTraceSet.java @@ -35,7 +35,7 @@ public class WritableTraceSet extends TraceSet { WritableTraceSet(String outputFileName, TRSMetaData metaData) throws FileNotFoundException { super(Paths.get(outputFileName)); - this.metaData = metaData; + this.metaData = metaData.modifiable(); this.writeStream = new FileOutputStream(outputFileName); } From ad228320a1162cd55281b74c0501cdd0342f6592 Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Wed, 12 Aug 2026 15:58:32 +0200 Subject: [PATCH 10/12] #77: Reduce default header size to 256k This will have less impact on very small trace sets, while still being plenty --- src/main/java/com/riscure/trs/TraceSet.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/riscure/trs/TraceSet.java b/src/main/java/com/riscure/trs/TraceSet.java index 9379b39..9fcc6c7 100644 --- a/src/main/java/com/riscure/trs/TraceSet.java +++ b/src/main/java/com/riscure/trs/TraceSet.java @@ -12,8 +12,8 @@ public abstract class TraceSet implements AutoCloseable { protected static final String TRACE_SET_NOT_OPEN = "TraceSet has not been opened or has been closed."; - // We want to pre-allocate 1M for the header, so we can grow it if needed without re-writing the whole file - public static final long DEFAULT_METADATA_SIZE = 1_000_000L; + // We want to pre-allocate 256k for the header, so we can grow it if needed without re-writing the whole file + public static final long DEFAULT_METADATA_SIZE = 256_000L; //Shared variables private final Path path; From c8c43c54924e7ce15c8ef2c5254c14c045583f42 Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Thu, 20 Aug 2026 08:47:12 +0200 Subject: [PATCH 11/12] #77: Fixed test to match written expectation with actual test contents --- src/test/java/TestTraceSet.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/test/java/TestTraceSet.java b/src/test/java/TestTraceSet.java index 29e3984..781a232 100644 --- a/src/test/java/TestTraceSet.java +++ b/src/test/java/TestTraceSet.java @@ -16,7 +16,6 @@ import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.opentest4j.AssertionFailedError; import java.io.ByteArrayInputStream; @@ -215,12 +214,12 @@ void testWriteTraceParametersInvalidName() throws IOException, TRSFormatExceptio traceWithParameters.add(Trace.create("", FLOAT_SAMPLES, parameters)); } //READ BACK AND CHECK RESULT - assertThrows(AssertionFailedError.class, () -> { - try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { - TraceParameterDefinitionMap parameterDefinitions = readable.getMetaData().getTraceParameterDefinitions(); - parameterDefinitions.forEach((key, parameter) -> assertEquals(parameterName, key)); + try (TraceSet readable = TraceSet.open(tempDir.toAbsolutePath() + File.separator + name)) { + TraceParameterDefinitionMap parameterDefinitions = readable.getMetaData().getTraceParameterDefinitions(); + for (var def : parameterDefinitions.keySet()) { + assertNotEquals(parameterName.length(), def.length()); } - }); + } } /** From 39edc878d9a4d35ad20e7828680301f3f65851f3 Mon Sep 17 00:00:00 2001 From: Siebe Krijgsman Date: Fri, 21 Aug 2026 15:34:18 +0200 Subject: [PATCH 12/12] Replaced printout with logging --- pom.xml | 5 +++++ .../trace/definition/TraceParameterDefinitionMap.java | 10 ++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/pom.xml b/pom.xml index c3112cc..c10265c 100644 --- a/pom.xml +++ b/pom.xml @@ -216,6 +216,11 @@ + + commons-logging + commons-logging + 1.3.6 + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java b/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java index ddd1af4..2525df0 100644 --- a/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java +++ b/src/main/java/com/riscure/trs/parameter/trace/definition/TraceParameterDefinitionMap.java @@ -6,6 +6,8 @@ import com.riscure.trs.io.LittleEndianOutputStream; import com.riscure.trs.parameter.TraceParameter; import com.riscure.trs.parameter.trace.TraceParameterMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -13,7 +15,6 @@ import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.StandardCharsets; -import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; @@ -22,6 +23,7 @@ * This explicitly implements LinkedHashMap to ensure that the data is retrieved in the same order as it was added */ public class TraceParameterDefinitionMap extends LinkedHashMap> { + private static final Log LOG = LogFactory.getLog(TraceParameterDefinitionMap.class); private static final String NAME_TOO_LONG = "Name of length %d exceeds maximum length of %d bytes%nName will be truncated to the maximum length%n"; public TraceParameterDefinitionMap() { @@ -57,7 +59,7 @@ public byte[] serialize() throws IOException, TRSFormatException { byte[] nameBytes = entry.getKey().getBytes(StandardCharsets.UTF_8); //Write NL if (nameBytes.length > Short.MAX_VALUE) { - System.err.printf(NAME_TOO_LONG, nameBytes.length, Short.MAX_VALUE); + LOG.warn(String.format(NAME_TOO_LONG, nameBytes.length, Short.MAX_VALUE)); nameBytes = new byte[Short.MAX_VALUE]; CharBuffer name = CharBuffer.wrap(entry.getKey()); StandardCharsets.UTF_8.newEncoder().encode(name, ByteBuffer.wrap(nameBytes), true); @@ -98,10 +100,10 @@ public static TraceParameterDefinitionMap deserialize(byte[] bytes) { } /** - * Create a set of definitions based on the parameters present in a trace. + * Create a map of definitions based on the parameters present in a trace. * * @param parameters the parameters of the trace - * @return a set of definitions based on the parameters present in a trace + * @return a map of definitions based on the parameters present in a trace */ public static TraceParameterDefinitionMap createFrom(TraceParameterMap parameters) { TraceParameterDefinitionMap definitions = new TraceParameterDefinitionMap();