diff --git a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/QuantityConversionException.java b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/QuantityConversionException.java index a7706df8e..e912969fa 100644 --- a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/QuantityConversionException.java +++ b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/QuantityConversionException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -213,33 +213,23 @@ public String getInteractivePrototype() { } } - /* - * NOTE: The type parameter T doesn't strictly need to extend Comparable, particularly not - * for UNPARSEABLE. For the other cases, it seemed possible that they could be used where being - * Comparable would be beneficial. - */ - public static > QuantityConversionException unparsable( - String badString, T prototype, IPersister persister) { + public static QuantityConversionException unparsable(String badString, T prototype, IPersister persister) { return new Persisted(Problem.UNPARSEABLE, badString, prototype, persister); } - public static > QuantityConversionException noUnit( - String badString, T prototype, IPersister persister) { + public static QuantityConversionException noUnit(String badString, T prototype, IPersister persister) { return new Persisted(Problem.NO_UNIT, badString, prototype, persister); } - public static > QuantityConversionException unknownUnit( - String badString, T prototype, IPersister persister) { + public static QuantityConversionException unknownUnit(String badString, T prototype, IPersister persister) { return new Persisted(Problem.UNKNOWN_UNIT, badString, prototype, persister); } - public static > QuantityConversionException tooLow( - T badValue, T min, IPersister persister) { + public static QuantityConversionException tooLow(T badValue, T min, IPersister persister) { return new Persisted(Problem.TOO_LOW, badValue, min, persister); } - public static > QuantityConversionException tooHigh( - T badValue, T max, IPersister persister) { + public static QuantityConversionException tooHigh(T badValue, T max, IPersister persister) { return new Persisted(Problem.TOO_HIGH, badValue, max, persister); } @@ -247,7 +237,7 @@ public static > QuantityConversionException tooHigh( * FIXME: This currently reports that the value is "below precision". Replace precisionLimit * with a closest valid quantity (and change the problem message)? */ - public static > QuantityConversionException belowPrecision( + public static QuantityConversionException belowPrecision( T badValue, T precisionLimit, IPersister persister) { return new Persisted(Problem.TOO_SMALL_MAGNITUDE, badValue, precisionLimit, persister); } diff --git a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/UnitLookup.java b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/UnitLookup.java index 0a46f80c3..a934b3d2d 100644 --- a/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/UnitLookup.java +++ b/core/org.openjdk.jmc.common/src/main/java/org/openjdk/jmc/common/unit/UnitLookup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -91,14 +91,9 @@ final public class UnitLookup { private static final String UNIT_ID_SEPARATOR = ":"; public static final LinearKindOfQuantity MEMORY = createMemory(); public static final LinearKindOfQuantity TIMESPAN = createTimespan(); - /* - * NOTE: These 3 (count, index, and identifier) cannot be persisted/restored due to Long(1) and - * Integer(1) not being equal or comparable. We either need to split into concrete wrappers, - * support a custom Comparator, or wrap into a (simple) IQuantity. - */ - public static final ContentType COUNT = createCount(); - public static final ContentType INDEX = createIndex(); - public static final ContentType IDENTIFIER = createIdentifier(); + public static final ContentType COUNT = createCount(); + public static final ContentType INDEX = createIndex(); + public static final ContentType IDENTIFIER = createIdentifier(); public static final KindOfQuantity TIMESTAMP = createTimestamp(TIMESPAN); public static final LinearKindOfQuantity PERCENTAGE = createPercentage(); public static final LinearKindOfQuantity NUMBER = createNumber(); @@ -426,15 +421,99 @@ private static String formatHexNumber(IQuantity quantity) { return String.format("0x%08X", quantity.longValue()); } + private static Number parseNumber(String numberStr) { + try { + return Long.parseLong(numberStr); + } catch (NumberFormatException eLong) { + return Double.parseDouble(numberStr); + } + } + // FIXME: Rename to createPrimitiveNumber? Remove? private static ContentType createRawNumber() { - ContentType contentType = new ContentType<>("raw number"); + ContentType contentType = new LeafContentType("raw number") { + @Override + public boolean validate(Number value) { + checkNull(value); + return false; + } + + @Override + public String persistableString(Number value) { + validate(value); + return value.toString(); + } + + @Override + public Number parsePersisted(String persistedValue) throws QuantityConversionException { + checkNull(persistedValue); + try { + return parseNumber(persistedValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(persistedValue, 0, this); + } + } + + @Override + public String interactiveFormat(Number value) { + validate(value); + return value.toString(); + } + + @Override + public Number parseInteractive(String interactiveValue) throws QuantityConversionException { + checkNull(interactiveValue); + try { + return parseNumber(interactiveValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(interactiveValue, 0, this); + } + } + }; contentType.addFormatter(new DisplayFormatter<>(contentType, IDisplayable.AUTO, "Value")); return contentType; } private static ContentType createRawLong() { - ContentType contentType = new ContentType<>("raw long"); + ContentType contentType = new LeafContentType("raw long") { + @Override + public boolean validate(Long value) { + checkNull(value); + return false; + } + + @Override + public String persistableString(Long value) { + validate(value); + return value.toString(); + } + + @Override + public Long parsePersisted(String persistedValue) throws QuantityConversionException { + checkNull(persistedValue); + try { + return Long.parseLong(persistedValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(persistedValue, 0L, this); + } + } + + @Override + public String interactiveFormat(Long value) { + validate(value); + return value.toString(); + } + + @Override + public Long parseInteractive(String interactiveValue) throws QuantityConversionException { + checkNull(interactiveValue); + try { + return Long.parseLong(interactiveValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(interactiveValue, 0L, this); + } + } + }; contentType.addFormatter(new DisplayFormatter<>(contentType, IDisplayable.AUTO, "Value")); return contentType; } @@ -568,25 +647,134 @@ private static LinearKindOfQuantity createPercentage() { return percentage; } - private static ContentType createCount() { - ContentType contentType = new ContentType<>("count"); -// contentType.addDisplayUnit( -// new DisplayUnit(contentType, DisplayUnit.ENGINEERING_NOTATION_IDENTIFIER, "Engineering Notation")); - contentType.addFormatter(new DisplayFormatter<>(contentType, IDisplayable.AUTO, "Value")); + private static ContentType createCount() { + ContentType contentType = new LeafContentType("count") { + @Override + public boolean validate(Long value) { + checkNull(value); + return false; + } + + @Override + public String persistableString(Long value) { + validate(value); + return value.toString(); + } + + @Override + public Long parsePersisted(String persistedValue) throws QuantityConversionException { + checkNull(persistedValue); + try { + return Long.parseLong(persistedValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(persistedValue, 0L, this); + } + } + + @Override + public String interactiveFormat(Long value) { + validate(value); + return value.toString(); + } -// contentType.addDisplayUnit( -// new DisplayUnit(contentType, DisplayUnit.SCIENTIFIC_NOTATION_IDENTIFIER, "Scientific Notation")); + @Override + public Long parseInteractive(String interactiveValue) throws QuantityConversionException { + checkNull(interactiveValue); + try { + return Long.parseLong(interactiveValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(interactiveValue, 0L, this); + } + } + }; + contentType.addFormatter(new DisplayFormatter<>(contentType, IDisplayable.AUTO, "Value")); return contentType; } - private static ContentType createIdentifier() { - ContentType contentType = new ContentType<>("identifier"); + private static ContentType createIdentifier() { + ContentType contentType = new LeafContentType("identifier") { + @Override + public boolean validate(Long value) { + checkNull(value); + return false; + } + + @Override + public String persistableString(Long value) { + validate(value); + return value.toString(); + } + + @Override + public Long parsePersisted(String persistedValue) throws QuantityConversionException { + checkNull(persistedValue); + try { + return Long.parseLong(persistedValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(persistedValue, 0L, this); + } + } + + @Override + public String interactiveFormat(Long value) { + validate(value); + return value.toString(); + } + + @Override + public Long parseInteractive(String interactiveValue) throws QuantityConversionException { + checkNull(interactiveValue); + try { + return Long.parseLong(interactiveValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(interactiveValue, 0L, this); + } + } + }; contentType.addFormatter(new DisplayFormatter<>(contentType, IDisplayable.AUTO, "Value")); return contentType; } - private static ContentType createIndex() { - ContentType contentType = new ContentType<>("index"); + private static ContentType createIndex() { + ContentType contentType = new LeafContentType("index") { + @Override + public boolean validate(Long value) { + checkNull(value); + return false; + } + + @Override + public String persistableString(Long value) { + validate(value); + return value.toString(); + } + + @Override + public Long parsePersisted(String persistedValue) throws QuantityConversionException { + checkNull(persistedValue); + try { + return Long.parseLong(persistedValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(persistedValue, 0L, this); + } + } + + @Override + public String interactiveFormat(Long value) { + validate(value); + return value.toString(); + } + + @Override + public Long parseInteractive(String interactiveValue) throws QuantityConversionException { + checkNull(interactiveValue); + try { + return Long.parseLong(interactiveValue); + } catch (NumberFormatException e) { + throw QuantityConversionException.unparsable(interactiveValue, 0L, this); + } + } + }; contentType.addFormatter(new DisplayFormatter<>(contentType, IDisplayable.AUTO, "Value")); return contentType; } diff --git a/core/tests/org.openjdk.jmc.common.test/src/main/java/org/openjdk/jmc/common/test/unit/PersisterRoundTripTest.java b/core/tests/org.openjdk.jmc.common.test/src/main/java/org/openjdk/jmc/common/test/unit/PersisterRoundTripTest.java new file mode 100644 index 000000000..2f5dcc6ce --- /dev/null +++ b/core/tests/org.openjdk.jmc.common.test/src/main/java/org/openjdk/jmc/common/test/unit/PersisterRoundTripTest.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * The contents of this file are subject to the terms of either the Universal Permissive License + * v 1.0 as shown at https://oss.oracle.com/licenses/upl + * + * or the following license: + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors may be used to + * endorse or promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.openjdk.jmc.common.test.unit; + +import org.junit.Test; +import org.openjdk.jmc.common.unit.IPersister; +import org.openjdk.jmc.common.unit.QuantityConversionException; +import org.openjdk.jmc.common.unit.UnitLookup; +import org.openjdk.jmc.test.MCTestCase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThrows; + +@SuppressWarnings("nls") +public class PersisterRoundTripTest extends MCTestCase { + + private static final String MALFORMED = "not_a_number"; + + private void assertNumberRoundTrip(IPersister persister, Number value) throws QuantityConversionException { + String persisted = persister.persistableString(value); + assertNotNull(persisted); + Number restored = persister.parsePersisted(persisted); + if (value instanceof Long || value instanceof Integer) { + assertEquals(value.longValue(), restored.longValue()); + } else { + assertEquals(value.doubleValue(), restored.doubleValue(), 0.0001); + } + } + + private void assertLongRoundTrip(IPersister persister, Long value) throws QuantityConversionException { + String persisted = persister.persistableString(value); + assertNotNull(persisted); + Long restored = persister.parsePersisted(persisted); + assertEquals(value, restored); + } + + @Test + public void testRawNumber() throws QuantityConversionException { + IPersister persister = UnitLookup.RAW_NUMBER.getPersister(); + assertNotNull(persister); + assertNumberRoundTrip(persister, 0); + assertNumberRoundTrip(persister, 42); + assertNumberRoundTrip(persister, -100); + assertNumberRoundTrip(persister, Long.MAX_VALUE); + assertNumberRoundTrip(persister, 3.14); + } + + @Test + public void testRawNumberParseError() { + IPersister persister = UnitLookup.RAW_NUMBER.getPersister(); + assertNotNull(persister); + assertThrows(QuantityConversionException.class, () -> persister.parsePersisted(MALFORMED)); + assertThrows(QuantityConversionException.class, () -> persister.parseInteractive(MALFORMED)); + } + + @Test + public void testRawLong() throws QuantityConversionException { + IPersister persister = UnitLookup.RAW_LONG.getPersister(); + assertNotNull(persister); + assertLongRoundTrip(persister, 0L); + assertLongRoundTrip(persister, 42L); + assertLongRoundTrip(persister, -100L); + assertLongRoundTrip(persister, Long.MAX_VALUE); + assertLongRoundTrip(persister, Long.MIN_VALUE); + } + + @Test + public void testRawLongParseError() { + IPersister persister = UnitLookup.RAW_LONG.getPersister(); + assertNotNull(persister); + assertThrows(QuantityConversionException.class, () -> persister.parsePersisted(MALFORMED)); + assertThrows(QuantityConversionException.class, () -> persister.parseInteractive(MALFORMED)); + } + + @Test + public void testCount() throws QuantityConversionException { + IPersister persister = UnitLookup.COUNT.getPersister(); + assertNotNull(persister); + assertLongRoundTrip(persister, 0L); + assertLongRoundTrip(persister, 1L); + assertLongRoundTrip(persister, 1000L); + assertLongRoundTrip(persister, 1000000L); + } + + @Test + public void testCountParseError() { + IPersister persister = UnitLookup.COUNT.getPersister(); + assertNotNull(persister); + assertThrows(QuantityConversionException.class, () -> persister.parsePersisted(MALFORMED)); + assertThrows(QuantityConversionException.class, () -> persister.parseInteractive(MALFORMED)); + } + + @Test + public void testIndex() throws QuantityConversionException { + IPersister persister = UnitLookup.INDEX.getPersister(); + assertNotNull(persister); + assertLongRoundTrip(persister, 0L); + assertLongRoundTrip(persister, 1L); + assertLongRoundTrip(persister, 100L); + assertLongRoundTrip(persister, 999999L); + } + + @Test + public void testIndexParseError() { + IPersister persister = UnitLookup.INDEX.getPersister(); + assertNotNull(persister); + assertThrows(QuantityConversionException.class, () -> persister.parsePersisted(MALFORMED)); + assertThrows(QuantityConversionException.class, () -> persister.parseInteractive(MALFORMED)); + } + + @Test + public void testIdentifier() throws QuantityConversionException { + IPersister persister = UnitLookup.IDENTIFIER.getPersister(); + assertNotNull(persister); + assertLongRoundTrip(persister, 1L); + assertLongRoundTrip(persister, 12345L); + assertLongRoundTrip(persister, 987654321L); + } + + @Test + public void testIdentifierParseError() { + IPersister persister = UnitLookup.IDENTIFIER.getPersister(); + assertNotNull(persister); + assertThrows(QuantityConversionException.class, () -> persister.parsePersisted(MALFORMED)); + assertThrows(QuantityConversionException.class, () -> persister.parseInteractive(MALFORMED)); + } +}