On Sun, 24 Aug 2025 10:13:25 GMT, Nir Lisker <nlis...@openjdk.org> wrote:

> Refactoring of all `StringConverter`s and their tests. General notes:
> * The documentation language has been unified and `null` parameter rules have 
> been documented.
> *  Tests have been cleaned up in the vein of 
> https://github.com/openjdk/jfx/pull/1759 and unneeded `@BeforeAll`s were 
> removed.
> * Internal fields were made `private final` to guarantee immutability.
> 
>  Incremental commits are provided for easier reviewing:
> 
> ### Parent classes
> * `StringConverter`: updated documentation
> * `BaseStringConverter`: a new internal class that implements repeated code 
> from converter implementations and serves as an intermediate superclass. It 
> does empty and `null` string checks that are handled uniformly, except for 
> `DefaultStringConverter`, which has a different formatting mechanism.
> 
> ### Primitive-related converters
> * All primitive (wrapper) converters also document their formatting and 
> parsing mechanism since these are "well-established".
> 
> ### Format converter
> * Checked for `null` during constriction time to avoid runtime NPEs.
> * There is no test class for this converter. A followup might be desirable.
> * A followup should deprecate for removal `protected Format getFormat()` (as 
> in [JDK-8314597](https://bugs.openjdk.org/browse/JDK-8314597) and 
> [JDK-8260475](https://bugs.openjdk.org/browse/JDK-8260475).
> 
> ### Number and subclasses converters
> * The intermediate `locale` and `pattern` fields were removed (along with 
> their tests). The class generated a new formatter from these on each call. 
> This only makes sense for mutable fields where the resulting formatter can 
> change, but here the formatter can be computed once on construction and 
> stored.
> * The only difference between these classes is a single method for creating a 
> format from a `null` pattern, which was encapsulated in the 
> `getSpecializedNumberFormat` method.
> * The terminally deprecated `protected NumberFormat getNumberFormat()` was 
> removed. Can be split to its own issue if preferred. In my opinion, it 
> shouldn't exist even internally since testing the internal formatter doesn't 
> help. The only tests here should be for to/from strings, and these are 
> lacking. A followup can be filed for adding more conversion tests.
> 
> ### Date/Time converters
> * Added a documentation note advising users to use the `java.time` classes 
> instead of the old `Date` class.
> * As with Number converters, only the `dateFormat` field was kept, which is 
> created once on construction instead of on each call.
> * As with Number converters, the `getSpecialzied...

Looks fine, left some readability/style comments

modules/javafx.base/src/main/java/javafx/util/converter/BaseTemporalStringConverter.java
 line 50:

> 48:     protected BaseTemporalStringConverter(FormatStyle dateStyle, 
> FormatStyle timeStyle, Locale locale, Chronology chrono) {
> 49:         locale = Objects.requireNonNullElse(locale, DEFAULT_LOCALE);
> 50:         chrono = Objects.requireNonNullElse(chrono, DEFAULT_CHRONO);

Parameter reassignment here.  It all looks like fields, but only the last two 
are.  Fields could be prefixed with `this.` to make this clearer, but IMHO 
parameter assignment is always bad.

modules/javafx.base/src/main/java/javafx/util/converter/BaseTemporalStringConverter.java
 line 68:

> 66:                                              .toFormatter()
> 67:                                              .withChronology(chrono)
> 68:                                              
> .withDecimalStyle(DecimalStyle.of(locale));

This is IMHO a bad way to indent things; not only is the indent no longer a 
multiple of 4, it also makes diffs larger than they need to be when say 
`DateTimeFormatterBuilder` is renamed or put in a variable.

modules/javafx.base/src/main/java/javafx/util/converter/BaseTemporalStringConverter.java
 line 86:

> 84:                             .withChronology(chrono)
> 85:                             .withDecimalStyle(DecimalStyle.of(locale));
> 86:     }

Weird level of indent, perhaps use 4 or 8?

modules/javafx.base/src/main/java/javafx/util/converter/CurrencyStringConverter.java
 line 39:

> 37:     public CurrencyStringConverter() {
> 38:         super();
> 39:     }

`super()` here is just noise

modules/javafx.base/src/main/java/javafx/util/converter/DateStringConverter.java
 line 43:

> 41:     /// and the user's [Locale].
> 42:     public DateStringConverter() {
> 43:         super();

`super()` here is just noise

modules/javafx.base/src/main/java/javafx/util/converter/DateTimeStringConverter.java
 line 99:

> 97:     ///        both date and time styles.
> 98:     public DateTimeStringConverter(Locale locale, String pattern) {
> 99:         dateFormat = create(locale, DateFormat.DEFAULT, 
> DateFormat.DEFAULT, pattern);

Just noting inconsistent style here (see below `this.dateFormat = ` or 
`dateFormat = `) -- I personally favor prefixing with `this.` when it is a 
write action.

modules/javafx.base/src/main/java/javafx/util/converter/DateTimeStringConverter.java
 line 112:

> 110: 
> 111:     private DateFormat create(Locale locale, int dateStyle, int 
> timeStyle, String pattern) {
> 112:         locale = Objects.requireNonNullElse(locale, DEFAULT_LOCALE);

See previous comment about using `Objects.requireNonNullElse` and doing 
parameter reassignment.

modules/javafx.base/src/main/java/javafx/util/converter/DateTimeStringConverter.java
 line 119:

> 117:     }
> 118: 
> 119:     // treat as protected

Seeing this a lot, what do you want to convey here?  I'm just wondering what 
this would mean to a future maintainer... "don't accidentally make it protected 
because then it would be API"?  Can add that to a lot of methods in FX then...

If you really want to treat it as protected, then I expect a full javadoc.  If 
you just want an inbetween class with helpful methods that you don't want to 
make public, then making the class and all its methods package private is a 
standard pattern (see StringBuilder / StringBuffer)... So I'm just wondering 
what a future maintainer will use this comment for?

modules/javafx.base/src/main/java/javafx/util/converter/DateTimeStringConverter.java
 line 129:

> 127:             return dateFormat.parse(string);
> 128:         } catch (ParseException e) {
> 129:             throw new RuntimeException(e);

Should be `IllegalArgumentException`

modules/javafx.base/src/main/java/javafx/util/converter/FormatStringConverter.java
 line 42:

> 40:     private final Format format;
> 41: 
> 42:     /// Creates a `StringConverter` for arbitrary types that uses the 
> given `Format`.

Shouldn't that be `FormatStringConverter` that it creates?

To be honest, I never bother repeating this on constructors, so I just write 
"Creates a new instance ... "

modules/javafx.base/src/main/java/javafx/util/converter/FormatStringConverter.java
 line 57:

> 55:         T result = (T) format.parseObject(string, pos);
> 56:         if (pos.getIndex() != string.length()) {
> 57:             throw new RuntimeException("Parsed string not according to 
> the format");

Throw `IllegalArgumentException`?

modules/javafx.base/src/main/java/javafx/util/converter/FormatStringConverter.java
 line 67:

> 65:     }
> 66: 
> 67:     /// {@return the `Format` instance for formatting and parsing in this 
> `StringConverter`}

`FormatStringConverter` ?  Or just leave that part of...

modules/javafx.base/src/main/java/javafx/util/converter/LocalDateTimeStringConverter.java
 line 82:

> 80:     ///        [IsoChronology#INSTANCE] will be used.
> 81:     public LocalDateTimeStringConverter(FormatStyle dateStyle, 
> FormatStyle timeStyle, Locale locale, Chronology chronology) {
> 82:         // JEP-513 could make this look better by moving the null checks 
> before super

This seems like an irrelevant comment.  I doubt it would even look better if 
you did this (as you'd require variables).  How about just making it nice to 
read like:


        super(
            Objects.requireNonNullElse(dateStyle, FormatStyle.SHORT),
            Objects.requireNonNullElse(timeStyle, FormatStyle.SHORT), 
            locale, 
            chronology
        );


Also, you're using here an indent that is not a multiple of 4.

modules/javafx.base/src/main/java/javafx/util/converter/NumberStringConverter.java
 line 82:

> 80: 
> 81:     private NumberFormat createFormat(Locale locale, String pattern) {
> 82:         locale = Objects.requireNonNullElse(locale, Locale.getDefault());

Parameter reassignment is really bad form (Eclipse has a warning for it if 
you'd care to enable it).

Also I'm not sure I like `Objects.requireNonNullElse`; it's actually longer 
than:

    this.locale = locale == null ? Locale.getDefault() : locale;

modules/javafx.base/src/main/java/javafx/util/converter/NumberStringConverter.java
 line 97:

> 95:             return numberFormat.parse(string);
> 96:         } catch (ParseException e) {
> 97:             throw new RuntimeException(e);

This really should be `IllegalArgumentException`.

modules/javafx.base/src/main/java/javafx/util/converter/NumberStringConverter.java
 line 106:

> 104:     }
> 105: 
> 106:     /// {@return the `NumberFormat` used for formatting and parsing in 
> this `NumberStringConverter`}

Is this even a valid comment with the return tag embedded in curly braces?  
It's also internal, so can remove.

modules/javafx.base/src/main/java/javafx/util/converter/PercentageStringConverter.java
 line 38:

> 36:     /// Creates a `PercentageStringConverter` that uses a 
> formatter/parser based on the user's [Locale].
> 37:     public PercentageStringConverter() {
> 38:         super();

`super()` here is just noise

modules/javafx.base/src/main/java/javafx/util/converter/TimeStringConverter.java
 line 2:

> 1: /*
> 2: // * Copyright (c) 2010, 2024, Oracle and/or its affiliates. All rights 
> reserved.

This looks like it shouldn't have been committed.

modules/javafx.base/src/main/java/javafx/util/converter/TimeStringConverter.java
 line 43:

> 41:     /// time style, and the user's {@link Locale}.
> 42:     public TimeStringConverter() {
> 43:         super();

`super()` is just noise here

modules/javafx.base/src/test/java/test/javafx/util/converter/DateStringConverterTest.java
 line 48:

> 46: public class DateStringConverterTest {
> 47: 
> 48:     private static final Locale DEFALUT_LOCALE = 
> Locale.getDefault(Locale.Category.FORMAT);

Typo: `DEFAULT_LOCALE`

modules/javafx.base/src/test/java/test/javafx/util/converter/DateStringConverterTest.java
 line 75:

> 73:                 new TestCase(new 
> DateStringConverter(DateFormat.getDateInstance(DateFormat.LONG)),
> 74:                         DEFALUT_LOCALE, DateFormat.DEFAULT, null, 
> DateFormat.getDateInstance(DateFormat.LONG))
> 75:                 );

Wouldn't this look better:

        return List.of(
                new TestCase(new DateStringConverter(), DEFALUT_LOCALE, 
DateFormat.DEFAULT, null, null),
                new TestCase(new DateStringConverter(DateFormat.SHORT), 
DEFALUT_LOCALE, DateFormat.SHORT, null, null),
                new TestCase(new DateStringConverter(Locale.UK), Locale.UK, 
DateFormat.DEFAULT, null, null),
                new TestCase(new DateStringConverter(Locale.UK, 
DateFormat.SHORT), Locale.UK, DateFormat.SHORT, null, null),
                new TestCase(new DateStringConverter("dd MM yyyy"), 
DEFALUT_LOCALE, DateFormat.DEFAULT, "dd MM yyyy", null),
                new TestCase(
                        new 
DateStringConverter(DateFormat.getDateInstance(DateFormat.LONG)), 
DEFALUT_LOCALE, 
                        DateFormat.DEFAULT, null, 
DateFormat.getDateInstance(DateFormat.LONG)
                )
        );

modules/javafx.base/src/test/java/test/javafx/util/converter/DateTimeStringConverterTest.java
 line 73:

> 71:                 new TestCase(new DateTimeStringConverter(),
> 72:                         Locale.getDefault(Locale.Category.FORMAT), 
> DateFormat.DEFAULT, DateFormat.DEFAULT,
> 73:                         null, null, VALID_DATE_WITH_SECONDS),

Why not put the last closing bracket on a new line aligned with `new` just like 
everyone always does with the curly braces?  Much more readable, and doesn't 
require the empty line between blocks to "make" it readable. So:


        return List.of(
                new TestCase(new DateTimeStringConverter(),
                        Locale.getDefault(Locale.Category.FORMAT), 
DateFormat.DEFAULT, DateFormat.DEFAULT,
                        null, null, VALID_DATE_WITH_SECONDS
                ),
                // add more here...                        
        );

modules/javafx.base/src/test/java/test/javafx/util/converter/DateTimeStringConverterTest.java
 line 94:

> 92:                         Locale.getDefault(Locale.Category.FORMAT), 
> DateFormat.DEFAULT, DateFormat.DEFAULT,
> 93:                         null, 
> DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.FULL), 
> VALID_DATE_WITH_SECONDS)
> 94:                 );

This makes it looks like it is the closing bracket related to `new TestCase` 
for example, while it is from the `List.of`

modules/javafx.base/src/test/java/test/javafx/util/converter/LocalDateStringConverterTest.java
 line 72:

> 70:         NO_PARAM,
> 71:         WITH_FORMATTER_PARSER,
> 72:         WITH_FORMAT_STYLES,

I know enums allow it, but trailing comma is still ugly

modules/javafx.base/src/test/java/test/javafx/util/converter/LocalDateTimeStringConverterTest.java
 line 78:

> 76:         NO_PARAM,
> 77:         WITH_FORMATTER_PARSER,
> 78:         WITH_FORMAT_STYLES,

Trailing comma

modules/javafx.base/src/test/java/test/javafx/util/converter/LocalDateTimeStringConverterTest.java
 line 134:

> 132:             case WITH_FORMATTER_PARSER -> new 
> LocalDateTimeStringConverter(aFormatter, aParser);
> 133:             case WITH_FORMAT_STYLES -> new 
> LocalDateTimeStringConverter(FormatStyle.SHORT, FormatStyle.SHORT,
> 134:                                 Locale.UK, IsoChronology.INSTANCE);

Odd indent level, 4 or 8?

modules/javafx.base/src/test/java/test/javafx/util/converter/LocalTimeStringConverterTest.java
 line 72:

> 70:         NO_PARAM,
> 71:         WITH_FORMATTER_PARSER,
> 72:         WITH_FORMAT_STYLES,

Trailing comma

modules/javafx.base/src/test/java/test/javafx/util/converter/NumberStringConverterTest.java
 line 44:

> 42:     private static final String PATTERN = "#,##,###,####";
> 43: 
> 44:     private final NumberStringConverter usLocaleConverter = new 
> NumberStringConverter(Locale.US);

can be `static` ?

modules/javafx.base/src/test/java/test/javafx/util/converter/NumberStringConverterTest.java
 line 50:

> 48:         NumberFormat numberFormat = 
> NumberFormat.getNumberInstance(Locale.getDefault());
> 49: 
> 50:         var nsc = new NumberStringConverter();

Why not `var converter`, `var c` or even just inlining this?  I think these 
abbreviations are not helpful.  This will make the tests all much more similar 
to each other.

modules/javafx.base/src/test/java/test/javafx/util/converter/TimeStringConverterTest.java
 line 86:

> 84:                         Locale.getDefault(Locale.Category.FORMAT), 
> DateFormat.DEFAULT, null,
> 85:                         DateFormat.getTimeInstance(DateFormat.FULL), 
> VALID_TIME_WITH_SECONDS)
> 86:                 );

See previous comment, this bracket seems related to `new TestCase`.

-------------

Marked as reviewed by jhendrikx (Reviewer).

PR Review: https://git.openjdk.org/jfx/pull/1880#pullrequestreview-3149487568
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296798578
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296799109
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296799512
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296786737
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296791683
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296792639
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296792949
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296793928
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296794534
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296785487
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296786007
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296786266
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296800138
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296787950
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296789162
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296789669
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296789894
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296794828
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296795007
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296796717
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296796208
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296797782
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296798081
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296801685
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296801878
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296802012
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296802251
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296791382
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296791086
PR Review Comment: https://git.openjdk.org/jfx/pull/1880#discussion_r2296798294

Reply via email to