From 396c1504728fb92554a3111c693cec8af2a48fd8 Mon Sep 17 00:00:00 2001 From: Bane Stojkovic Date: Fri, 6 Nov 2026 09:57:02 +0100 Subject: [PATCH] datasheet fix --- .../datasheets/RadialElectrolytic.datasheet | 8 +- .../datasheets/RadialFilmCapacitor.datasheet | 4 +- .../diylc/components/DatasheetRowsTest.java | 133 ++++++++++++++++++ diylc/diylc-swing/pom.xml | 2 +- diylc/docs/plans/audio-coverage-roadmap.md | 63 +++------ 5 files changed, 159 insertions(+), 51 deletions(-) create mode 100644 diylc/diylc-library/src/test/java/org/diylc/components/DatasheetRowsTest.java diff --git a/diylc/diylc-library/src/main/resources/datasheets/RadialElectrolytic.datasheet b/diylc/diylc-library/src/main/resources/datasheets/RadialElectrolytic.datasheet index 4c8532fb..1a040b0c 100644 --- a/diylc/diylc-library/src/main/resources/datasheets/RadialElectrolytic.datasheet +++ b/diylc/diylc-library/src/main/resources/datasheets/RadialElectrolytic.datasheet @@ -25,7 +25,7 @@ Elna Silmic II,16 V,330 uF,20,12.5,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,16 V,470 uF,25,12.5,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,16 V,1000 uF,31.5,16,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,16 V,2200 uF,40,18,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 -Elna Silmic II,25 V,4.7uF,11,5,2,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 +Elna Silmic II,25 V,4.7 uF,11,5,2,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,25 V,10 uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,25 V,22 uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,25 V,33 uF,11.5,8,3.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 @@ -35,7 +35,7 @@ Elna Silmic II,25 V,220 uF,20,12.5,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,25 V,330 uF,25,12.5,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,25 V,470 uF,25,16,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,25 V,1000 uF,35.5,16,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 -Elna Silmic II,35 V,4.7uF,11,5,2,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 +Elna Silmic II,35 V,4.7 uF,11,5,2,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,35 V,10 uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,35 V,22 uF,11.5,8,3.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,35 V,33 uF,12.5,10,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 @@ -47,7 +47,7 @@ Elna Silmic II,35 V,470 uF,31.5,16,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,35 V,1000 uF,35.5,18,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,50 V,2.2 uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,50 V,3.3 uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 -Elna Silmic II,50 V,4.7uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 +Elna Silmic II,50 V,4.7 uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,50 V,10 uF,11.5,8,3.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,50 V,22 uF,12.5,10,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,50 V,33 uF,16,10,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 @@ -59,7 +59,7 @@ Elna Silmic II,50 V,470 uF,35.5,16,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,50 V,1000 uF,40,18,7.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,63 V,2.2 uF,11,5,2,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,63 V,3.3 uF,11,5,2,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 -Elna Silmic II,63 V,4.7uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 +Elna Silmic II,63 V,4.7 uF,11,6.3,2.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,63 V,10 uF,11.5,8,3.5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,63 V,22 uF,16,10,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 Elna Silmic II,63 V,33 uF,20,10,5,#876B68,#544341,#FFFFFF,#CCCCCC,#544341 diff --git a/diylc/diylc-library/src/main/resources/datasheets/RadialFilmCapacitor.datasheet b/diylc/diylc-library/src/main/resources/datasheets/RadialFilmCapacitor.datasheet index 787d9280..a4eaa659 100644 --- a/diylc/diylc-library/src/main/resources/datasheets/RadialFilmCapacitor.datasheet +++ b/diylc/diylc-library/src/main/resources/datasheets/RadialFilmCapacitor.datasheet @@ -1404,8 +1404,8 @@ WIMA MKS-4,250 V,82 uF B,57,35,52.5,#FF6666,#A03140,#000000,BOX WIMA MKS-4,250 V,100 uF A,57,45,52.5,#FF6666,#A03140,#000000,BOX WIMA MKS-4,250 V,120 uF B,57,45,52.5,#FF6666,#A03140,#000000,BOX WIMA MKS-4,250 V,150 uF,57,45,52.5,#FF6666,#A03140,#000000,BOX -WIMA MKS-4,400 V,0.01µF A,10,3,7.5,#FF6666,#A03140,#000000,BOX -WIMA MKS-4,400 V,0.01µF B,13,4,10,#FF6666,#A03140,#000000,BOX +WIMA MKS-4,400 V,0.01 uF A,10,3,7.5,#FF6666,#A03140,#000000,BOX +WIMA MKS-4,400 V,0.01 uF B,13,4,10,#FF6666,#A03140,#000000,BOX WIMA MKS-4,400 V,0.015 uF A,10,3,7.5,#FF6666,#A03140,#000000,BOX WIMA MKS-4,400 V,0.015 uF B,13,4,10,#FF6666,#A03140,#000000,BOX WIMA MKS-4,400 V,0.022 uF A,10,4,7.5,#FF6666,#A03140,#000000,BOX diff --git a/diylc/diylc-library/src/test/java/org/diylc/components/DatasheetRowsTest.java b/diylc/diylc-library/src/test/java/org/diylc/components/DatasheetRowsTest.java new file mode 100644 index 00000000..cd93471a --- /dev/null +++ b/diylc/diylc-library/src/test/java/org/diylc/components/DatasheetRowsTest.java @@ -0,0 +1,133 @@ +/* + + DIY Layout Creator (DIYLC). + Copyright (c) 2009-2025 held jointly by the individual authors. + + This file is part of DIYLC. + + DIYLC is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + DIYLC is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with DIYLC. If not, see . + +*/ +package org.diylc.components; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Modifier; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.diylc.TestConfiguration; +import org.diylc.core.IDIYComponent; +import org.diylc.core.IDatasheetSupport; +import org.diylc.core.annotations.ComponentDescriptor; +import org.diylc.presenter.DatasheetService; +import org.diylc.utils.ReflectionUtils; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Applies every row of every datasheet to its component, so a malformed row fails the build rather + * than the user's Apply Model, or an AI edit that picks the model. The components are found through + * the class index, so a new datasheet is covered without touching this test. + */ +public class DatasheetRowsTest { + + @BeforeClass + public static void setUp() { + TestConfiguration.initialize(); + } + + @Test + public void everyRowApplies() throws ReflectiveOperationException { + List failures = new ArrayList(); + for (Class> type : datasheetComponents()) { + List datasheet = DatasheetService.getInstance().loadDatasheet(type); + Assert.assertNotNull(type.getSimpleName(), datasheet); + Assert.assertFalse(type.getSimpleName(), datasheet.isEmpty()); + for (String[] row : datasheet) { + IDatasheetSupport component = + (IDatasheetSupport) type.getDeclaredConstructor().newInstance(); + try { + component.applyModel(row); + } catch (RuntimeException e) { + failures.add(type.getSimpleName() + ": " + String.join(",", row) + " -> " + e); + } + } + } + Assert.assertTrue(String.join("\n", failures), failures.isEmpty()); + } + + /** + * DatasheetService decodes with the platform charset, where a latin-1 byte such as the micro + * sign turns into U+FFFD instead of failing, so units are written as uF. + */ + @Test + public void datasheetsAreAscii() throws IOException { + List failures = new ArrayList(); + for (Class> type : datasheetComponents()) { + String fileName = type.getSimpleName() + ".datasheet"; + try (InputStream in = type.getResourceAsStream("/datasheets/" + fileName)) { + Assert.assertNotNull(fileName, in); + int line = 1; + int b; + while ((b = in.read()) != -1) { + if (b == '\n') { + line++; + } else if (b > 0x7f) { + failures.add(fileName + ":" + line); + } + } + } + } + Assert.assertTrue("non-ASCII bytes at " + failures, failures.isEmpty()); + } + + /** A datasheet is found by the class name, so a file no component claims is never loaded. */ + @Test + public void everyDatasheetFileHasAComponent() throws URISyntaxException { + URL folder = DatasheetRowsTest.class.getResource("/datasheets"); + Assert.assertNotNull(folder); + Set claimed = new HashSet(); + for (Class> type : datasheetComponents()) { + claimed.add(type.getSimpleName() + ".datasheet"); + } + for (String fileName : new File(folder.toURI()).list()) { + Assert.assertTrue(fileName + " belongs to no datasheet component", + claimed.contains(fileName)); + } + } + + @SuppressWarnings("unchecked") + private static List>> datasheetComponents() { + List>> types = + new ArrayList>>(); + for (Class type : ReflectionUtils.getIndexedClasses(IDIYComponent.class)) { + ComponentDescriptor descriptor = type.getAnnotation(ComponentDescriptor.class); + if (descriptor != null && descriptor.enableDatasheet() + && !Modifier.isAbstract(type.getModifiers())) { + Assert.assertTrue(type.getSimpleName() + " enables a datasheet without IDatasheetSupport", + IDatasheetSupport.class.isAssignableFrom(type)); + types.add((Class>) type); + } + } + Assert.assertFalse("no datasheet components in the class index", types.isEmpty()); + return types; + } +} diff --git a/diylc/diylc-swing/pom.xml b/diylc/diylc-swing/pom.xml index 47eeb30f..58833ef5 100644 --- a/diylc/diylc-swing/pom.xml +++ b/diylc/diylc-swing/pom.xml @@ -7,7 +7,7 @@ org.diylc diylc-parent - 6.9.0 + 6.10.0 diylc-swing diff --git a/diylc/docs/plans/audio-coverage-roadmap.md b/diylc/docs/plans/audio-coverage-roadmap.md index b8509453..db486ad4 100644 --- a/diylc/docs/plans/audio-coverage-roadmap.md +++ b/diylc/docs/plans/audio-coverage-roadmap.md @@ -209,7 +209,7 @@ Two cautions, both learned from §6.5: - **The column format is positional and unforgiving.** `CapacitorDatasheetService` reads `model[1].split(" ")[0..1]` for voltage and `model[2].split(" ")[0..1]` for value, so a cell must be `" "` with exactly one space. `4.7uF` throws - `ArrayIndexOutOfBoundsException`; there is no validation anywhere. + `ArrayIndexOutOfBoundsException`; `DatasheetRowsTest` catches it. - **`DatasheetService` reads with `new InputStreamReader(in)` and no charset**, so files are decoded with the platform default. Keep datasheet files ASCII — write `uF`, never `µF`. Per JEP 400 the default is UTF-8 on the supported JVM, so a latin-1 byte becomes U+FFFD rather than failing loudly. @@ -282,7 +282,7 @@ block for the unreleased version. `NEW_FEATURE` for components and variants, `BU Enum constants, catalogue data, building blocks and uncommenting. Cheapest fidelity per hour in the roadmap, and where D6 and D7 send most of the work. -§6.1 and §6.5 are correctness fixes and should go first regardless of the rest. +§6.1 is a correctness fix and should go first regardless of the rest. ### 6.1 Restore the `ElectrolyticCanCapacitor` alias — **bug, not a gap** @@ -385,36 +385,12 @@ value `" "`. **~6-10 h each, `RadialCeramicDiskCapacitor` first.** -### 6.5 Fix six malformed datasheet rows — **bug** +### 6.5 Fix six malformed datasheet rows — **done** -Six of the 3,539 rows have a value cell the parser cannot read. Found by validating every row -against `parseCapacitorDatasheet`'s own logic: - -| File | Rows | Cell | Failure | -|---|---|---|---| -| `RadialElectrolytic.datasheet` | 4 (Elna Silmic II 4.7 uF at 25/35/50/63 V) | `4.7uF` | no space, so `split(" ")` yields one element and `capacitanceParts[1]` throws `ArrayIndexOutOfBoundsException` | -| `RadialFilmCapacitor.datasheet` | 2 (WIMA MKS-4 400 V) | `0.01µF A`, `0.01µF B` | latin-1 `0xb5` in a file read with the platform charset (§5.3), no space, plus an ` A`/` B` suffix no other row has; `parseCapacitance` throws `IllegalArgumentException` | - -User-visible effect: picking any of those six models from *Apply Model* throws instead of applying. - -Latent effect: the same cells break `DatasheetService.lookup`, whose `Double.parseDouble` runs over -every row in a `model|voltage` bucket. Three of the five affected buckets have the bad row first, so -the whole bucket would throw — **80 rows across 5 buckets, including all 41 WIMA MKS-4 400 V rows**. -That path has no production caller today (§5.3), so this is a trap set for whoever wires up -value-based auto-sizing, not a live fault. - -1. Rewrite the four `4.7uF` cells as `4.7 uF`. -2. Replace the two `0.01µF A` / `0.01µF B` cells with ASCII. The ` A`/` B` suffix looks like an attempt - to give one value two body sizes (10x3 at 7.5 mm pitch, 13x4 at 10 mm pitch) — a real need the - positional format cannot express, since model and voltage are the whole lookup key. **Recommend - splitting them into two models**, `WIMA MKS-4` and `WIMA MKS-4 (10mm)`, matching how - `Epcos MKT (5mm)` / `(7.5mm)` / `(10mm)` and the WIMA 2.5 mm/5 mm entries already encode pitch in - the model name. -3. Add this row validation as a test beside `FactoryVariantsTest` — same shape, same - collect-all-problems-then-assert style — so the next malformed cell fails the build. **This is - the part that matters**; the six fixes are typos, the missing validation is why they survived. - -**~3 h including the test.** +The four Elna Silmic II `4.7uF` cells and the two WIMA MKS-4 `0.01µF A`/`B` cells now read `4.7 uF` +and `0.01 uF A`/`B`. The ` A`/` B` suffix stays: it is how the other WIMA rows give one value two +case sizes, and only the value before it is applied. `DatasheetRowsTest` applies every row of every +datasheet and rejects non-ASCII bytes, so the next malformed cell fails the build. ### 6.6 Make factory building blocks reach existing users — **done** @@ -570,20 +546,19 @@ joins the §6.7 list rather than this one once someone asks for it. | Step | Items | Rough effort | |---|---|---| -| 1 | §6.5 six malformed datasheet rows, plus the validation test | 3 h | -| 2 | §6.1 alias fix, verified by regression run | 2 h | -| 3 | §6.7 push-pull pot block, as the D7 worked example | 3 h | -| 4 | §6.2 revive three, decide on the fourth | 3 h | -| 5 | §6.3 UX4, then UX5 / B8G / B9D | 6-8 h | -| 6 | §6.4 datasheets, `RadialCeramicDiskCapacitor` first | 6-10 h each | -| 7 | §6.7 remaining blocks: transformer symbol, treble bleed, tube gain stage, harnesses | 2-3 h each | -| 8 | §7.1 `Transformer`, with §7.3 Choke as its third type | 20-24 h | -| 9 | §7.2 Speaker, physical plus symbol | 10-12 h | -| 10 | §7.4 Footswitch | 10-12 h | -| 11 | §7.5 `Photoresistor`, then the Vactrol block | 6 h | +| 1 | §6.1 alias fix, verified by regression run | 2 h | +| 2 | §6.7 push-pull pot block, as the D7 worked example | 3 h | +| 3 | §6.2 revive three, decide on the fourth | 3 h | +| 4 | §6.3 UX4, then UX5 / B8G / B9D | 6-8 h | +| 5 | §6.4 datasheets, `RadialCeramicDiskCapacitor` first | 6-10 h each | +| 6 | §6.7 remaining blocks: transformer symbol, treble bleed, tube gain stage, harnesses | 2-3 h each | +| 7 | §7.1 `Transformer`, with §7.3 Choke as its third type | 20-24 h | +| 8 | §7.2 Speaker, physical plus symbol | 10-12 h | +| 9 | §7.4 Footswitch | 10-12 h | +| 10 | §7.5 `Photoresistor`, then the Vactrol block | 6 h | -Steps 1-4 are about a day and a half: both known bugs cleared and the highest-demand composable -shipped as data rather than as a class. Step 8 is the one that matters most. +Steps 1-3 are about a day: the alias bug cleared and the highest-demand composable shipped as data +rather than as a class. Step 7 is the one that matters most. Three items that looked like components when this roadmap started are now data (§6.7, §7.6, §8.9), and one is split into a primitive plus a block (§7.5). That is D7 working as intended; expect the