Skip to content

Commit

Permalink
The constructor of NumberFormatSymbols creates defensive copies of al…
Browse files Browse the repository at this point in the history
…l collections. Making NumberFormatSymbols effectively immutable.
  • Loading branch information
wrandelshofer committed Oct 20, 2024
1 parent 583e836 commit 8dfe653
Show file tree
Hide file tree
Showing 3 changed files with 377 additions and 339 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Objects;
Expand All @@ -32,6 +30,137 @@ public record NumberFormatSymbols(Set<Character> decimalSeparator, Set<Character
Set<String> exponentSeparator, Set<Character> minusSign, Set<Character> plusSign,
Set<String> infinity, Set<String> nan, List<Character> digits) {

/**
* Canonical constructor.
*
* @param decimalSeparator each character in this string defines a decimal separator
* @param groupingSeparator each character in this string defines a decimal separator
* @param exponentSeparator each string in this collection defines an exponent separator
* @param minusSign each character in this string defines a minus sign
* @param plusSign each character in this string defines a plus sign
* @param infinity each string in this collection defines an infinity string
* @param nan each string in this collection defines a NaN string
* @param digits the first 10 characters in this string define the digit characters from 0 to 9
*/
public NumberFormatSymbols(Set<Character> decimalSeparator, Set<Character> groupingSeparator, Set<String> exponentSeparator, Set<Character> minusSign, Set<Character> plusSign, Set<String> infinity, Set<String> nan, List<Character> digits) {
if (Objects.requireNonNull(digits, "digits").size() != 10)
throw new IllegalArgumentException("digits list must have size 10");
this.decimalSeparator = Set.copyOf(Objects.requireNonNull(decimalSeparator, "decimalSeparator"));
this.groupingSeparator = Set.copyOf(Objects.requireNonNull(groupingSeparator, "groupingSeparator"));
this.exponentSeparator = Set.copyOf(Objects.requireNonNull(exponentSeparator, "exponentSeparator"));
this.minusSign = Set.copyOf(Objects.requireNonNull(minusSign, "minusSign"));
this.plusSign = Set.copyOf(Objects.requireNonNull(plusSign, "plusSign"));
this.infinity = Set.copyOf(Objects.requireNonNull(infinity, "infinity"));
this.nan = Set.copyOf(Objects.requireNonNull(nan, "nan"));
this.digits = List.copyOf(digits);
}

/**
* Convenience constructor.
*
* @param decimalSeparators each character in this string defines a decimal separator
* @param groupingSeparators each character in this string defines a decimal separator
* @param exponentSeparators each string in this collection defines an exponent separator
* @param minusSigns each character in this string defines a minus sign
* @param plusSigns each character in this string defines a plus sign
* @param infinity each string in this collection defines an infinity string
* @param nan each string in this collection defines a NaN string
* @param digits the first 10 characters in this string define the digit characters from 0 to 9
*/
public NumberFormatSymbols(String decimalSeparators, String groupingSeparators,
Collection<String> exponentSeparators, String minusSigns, String plusSigns,
Collection<String> infinity, Collection<String> nan, String digits) {
this(toSet(decimalSeparators),
toSet(groupingSeparators),
new LinkedHashSet<>(exponentSeparators),
toSet(minusSigns),
toSet(plusSigns),
new LinkedHashSet<>(infinity),
new LinkedHashSet<>(nan),
toList(expandDigits(digits)));
}

private static String expandDigits(String digits) {
if (digits.length() == 10) return digits;
if (digits.length() != 1)
throw new IllegalArgumentException("digits must have length 1 or 10, digits=\"" + digits + "\"");
StringBuilder buf = new StringBuilder(10);
char zeroChar = digits.charAt(0);
for (int i = 0; i < 10; i++) {
buf.append((char) (zeroChar + i));
}
return buf.toString();
}

/**
* Creates a new instance from the provided {@link DecimalFormatSymbols}.
*
* @param symbols the decimal format symbols
* @return a new instance
*/
public static NumberFormatSymbols fromDecimalFormatSymbols(DecimalFormatSymbols symbols) {
Objects.requireNonNull(symbols, "symbols");
List<Character> digits = new ArrayList<>(10);
char zeroDigit = symbols.getZeroDigit();
for (int i = 0; i < 10; i++) {
digits.add((char) (zeroDigit + i));
}
return new NumberFormatSymbols(
Set.of(symbols.getDecimalSeparator()),
Set.of(symbols.getGroupingSeparator()),
Set.of(symbols.getExponentSeparator()),
Set.of(symbols.getMinusSign()),
Set.of(),
Set.of(symbols.getInfinity()),
Set.of(symbols.getNaN()),
digits
);
}

/**
* Creates a new instance with the following default symbols.
* <dl>
* <dt>decimalSeparator </dt><dd>{@code .}</dd>
* <dt>groupingSeparator</dt><dd>none</dd>
* <dt>exponentSeparator</dt><dd>{@code e}, {@code E}</dd>
* <dt>minusSign </dt><dd>{@code -}</dd>
* <dt>plusSign </dt><dd>{@code +}</dd>
* <dt>infinity </dt><dd>{@code Infinity}</dd>
* <dt>nan </dt><dd>{@code NaN}</dd>
* <dt>digits </dt><dd>{@code 0} ... {@code 9}</dd>
* </dl>
*
* @return a new instance
*/
public static NumberFormatSymbols fromDefault() {
return new NumberFormatSymbols(
Set.of('.'),
Set.of(),
Set.of("e", "E"),
Set.of('-'),
Set.of('+'),
Set.of("Infinity"),
Set.of("NaN"),
Arrays.asList('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
);
}

private static List<Character> toList(String chars) {
List<Character> list = new ArrayList<>(10);
for (char ch : chars.toCharArray()) {
list.add(ch);
}
return list;
}

private static Set<Character> toSet(String chars) {
Set<Character> set = new LinkedHashSet<>(chars.length() * 2);
for (char ch : chars.toCharArray()) {
set.add(ch);
}
return set;
}

/**
* Creates a new instance with the specified decimal separator symbols.
*
Expand All @@ -50,20 +179,20 @@ public NumberFormatSymbols withDecimalSeparator(Set<Character> newValue) {
}

/**
* Creates a new instance with the specified grouping separator symbols.
* Creates a new instance with the specified digits.
*
* @param newValue the grouping separator symbols
* @param newValue the digits
* @return a new instance
*/
public NumberFormatSymbols withGroupingSeparator(Set<Character> newValue) {
public NumberFormatSymbols withDigits(List<Character> newValue) {
return new NumberFormatSymbols(decimalSeparator,
newValue,
groupingSeparator,
exponentSeparator,
minusSign,
plusSign,
infinity,
nan,
digits);
newValue);
}

/**
Expand All @@ -84,52 +213,52 @@ public NumberFormatSymbols withExponentSeparator(Set<String> newValue) {
}

/**
* Creates a new instance with the specified minus sign symbols.
* Creates a new instance with the specified grouping separator symbols.
*
* @param newValue the minus sign symbols
* @param newValue the grouping separator symbols
* @return a new instance
*/
public NumberFormatSymbols withMinusSign(Set<Character> newValue) {
public NumberFormatSymbols withGroupingSeparator(Set<Character> newValue) {
return new NumberFormatSymbols(decimalSeparator,
groupingSeparator,
exponentSeparator,
newValue,
exponentSeparator,
minusSign,
plusSign,
infinity,
nan,
digits);
}

/**
* Creates a new instance with the specified plus sign symbols.
* Creates a new instance with the specified infinity symbols.
*
* @param newValue the plus sign symbols
* @param newValue the infinity symbols
* @return a new instance
*/
public NumberFormatSymbols withPlusSign(Set<Character> newValue) {
public NumberFormatSymbols withInfinity(Set<String> newValue) {
return new NumberFormatSymbols(decimalSeparator,
groupingSeparator,
exponentSeparator,
minusSign,
plusSign,
newValue,
infinity,
nan,
digits);
}

/**
* Creates a new instance with the specified infinity symbols.
* Creates a new instance with the specified minus sign symbols.
*
* @param newValue the infinity symbols
* @param newValue the minus sign symbols
* @return a new instance
*/
public NumberFormatSymbols withInfinity(Set<String> newValue) {
public NumberFormatSymbols withMinusSign(Set<Character> newValue) {
return new NumberFormatSymbols(decimalSeparator,
groupingSeparator,
exponentSeparator,
minusSign,
plusSign,
newValue,
plusSign,
infinity,
nan,
digits);
}
Expand All @@ -152,150 +281,19 @@ public NumberFormatSymbols withNaN(Set<String> newValue) {
}

/**
* Creates a new instance with the specified digits.
* Creates a new instance with the specified plus sign symbols.
*
* @param newValue the digits
* @param newValue the plus sign symbols
* @return a new instance
*/
public NumberFormatSymbols withDigits(List<Character> newValue) {
public NumberFormatSymbols withPlusSign(Set<Character> newValue) {
return new NumberFormatSymbols(decimalSeparator,
groupingSeparator,
exponentSeparator,
minusSign,
plusSign,
newValue,
infinity,
nan,
newValue);
}

/**
* Canonical constructor.
*
* @param decimalSeparator each character in this string defines a decimal separator
* @param groupingSeparator each character in this string defines a decimal separator
* @param exponentSeparator each string in this collection defines an exponent separator
* @param minusSign each character in this string defines a minus sign
* @param plusSign each character in this string defines a plus sign
* @param infinity each string in this collection defines an infinity string
* @param nan each string in this collection defines a NaN string
* @param digits the first 10 characters in this string define the digit characters from 0 to 9
*/
public NumberFormatSymbols(Set<Character> decimalSeparator, Set<Character> groupingSeparator, Set<String> exponentSeparator, Set<Character> minusSign, Set<Character> plusSign, Set<String> infinity, Set<String> nan, List<Character> digits) {
if (Objects.requireNonNull(digits, "digits").size() != 10)
throw new IllegalArgumentException("digits list must have size 10");
this.decimalSeparator = Objects.requireNonNull(decimalSeparator, "decimalSeparator");
this.groupingSeparator = Objects.requireNonNull(groupingSeparator, "groupingSeparator");
this.exponentSeparator = Objects.requireNonNull(exponentSeparator, "exponentSeparator");
this.minusSign = Objects.requireNonNull(minusSign, "minusSign");
this.plusSign = Objects.requireNonNull(plusSign, "plusSign");
this.infinity = Objects.requireNonNull(infinity, "infinity");
this.nan = Objects.requireNonNull(nan, "nan");
this.digits = digits;
}

/**
* Convenience constructor.
*
* @param decimalSeparators each character in this string defines a decimal separator
* @param groupingSeparators each character in this string defines a decimal separator
* @param exponentSeparators each string in this collection defines an exponent separator
* @param minusSigns each character in this string defines a minus sign
* @param plusSigns each character in this string defines a plus sign
* @param infinity each string in this collection defines an infinity string
* @param nan each string in this collection defines a NaN string
* @param digits the first 10 characters in this string define the digit characters from 0 to 9
*/
public NumberFormatSymbols(String decimalSeparators, String groupingSeparators,
Collection<String> exponentSeparators, String minusSigns, String plusSigns,
Collection<String> infinity, Collection<String> nan, String digits) {
this(toSet(decimalSeparators),
toSet(groupingSeparators),
new LinkedHashSet<>(exponentSeparators),
toSet(minusSigns),
toSet(plusSigns),
new LinkedHashSet<>(infinity),
new LinkedHashSet<>(nan),
toList(expandDigits(digits)));
}

private static String expandDigits(String digits) {
if (digits.length() == 10) return digits;
if (digits.length() != 1)
throw new IllegalArgumentException("digits must have length 1 or 10, digits=\"" + digits + "\"");
StringBuilder buf = new StringBuilder(10);
char zeroChar = digits.charAt(0);
for (int i = 0; i < 10; i++) {
buf.append((char) (zeroChar + i));
}
return buf.toString();
}

private static Set<Character> toSet(String chars) {
Set<Character> set = new LinkedHashSet<>(chars.length() * 2);
for (char ch : chars.toCharArray()) {
set.add(ch);
}
return set;
}

private static List<Character> toList(String chars) {
List<Character> list = new ArrayList<>(10);
for (char ch : chars.toCharArray()) {
list.add(ch);
}
return list;
}

/**
* Creates a new instance from the provided {@link DecimalFormatSymbols}.
*
* @param symbols the decimal format symbols
* @return a new instance
*/
public static NumberFormatSymbols fromDecimalFormatSymbols(DecimalFormatSymbols symbols) {
Objects.requireNonNull(symbols, "symbols");
List<Character> digits = new ArrayList<>(10);
char zeroDigit = symbols.getZeroDigit();
for (int i = 0; i < 10; i++) {
digits.add((char) (zeroDigit + i));
}
return new NumberFormatSymbols(
Collections.singleton(symbols.getDecimalSeparator()),
Collections.singleton(symbols.getGroupingSeparator()),
Collections.singleton(symbols.getExponentSeparator()),
Collections.singleton(symbols.getMinusSign()),
Collections.emptySet(),
Collections.singleton(symbols.getInfinity()),
Collections.singleton(symbols.getNaN()),
digits
);
}

/**
* Creates a new instance with default symbols.
* <dl>
* <dt>decimalSeparator </dt><dd>{@code .}</dd>
* <dt>groupingSeparator</dt><dd>none</dd>
* <dt>exponentSeparator</dt><dd>{@code e}, {@code E}</dd>
* <dt>minusSign </dt><dd>{@code -}</dd>
* <dt>plusSign </dt><dd>{@code +}</dd>
* <dt>infinity </dt><dd>{@code Infinity}</dd>
* <dt>nan </dt><dd>{@code NaN}</dd>
* <dt>digits </dt><dd>{@code 0} ... {@code 9}</dd>
* </dl>
*
* @return a new instance
*/
public static NumberFormatSymbols fromDefault() {
return new NumberFormatSymbols(
Collections.singleton('.'),
Collections.emptySet(),
new HashSet<>(Arrays.asList("e", "E")),
Collections.singleton('-'),
Collections.singleton('+'),
Collections.singleton("Infinity"),
Collections.singleton("NaN"),
Arrays.asList('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')
);
digits);
}
}
Loading

0 comments on commit 8dfe653

Please sign in to comment.