diff --git a/src/java.base/share/classes/java/util/Currency.java b/src/java.base/share/classes/java/util/Currency.java index 9f8b94acbd7..c825f3ca19e 100644 --- a/src/java.base/share/classes/java/util/Currency.java +++ b/src/java.base/share/classes/java/util/Currency.java @@ -40,6 +40,7 @@ import java.util.regex.Pattern; import java.util.regex.Matcher; import java.util.spi.CurrencyNameProvider; import java.util.stream.Collectors; +import java.util.stream.Stream; import jdk.internal.util.StaticProperty; import sun.util.locale.provider.CalendarDataUtility; @@ -432,61 +433,82 @@ public final class Currency implements Serializable { } /** - * Gets the set of available currencies. The returned set of currencies - * contains all of the available currencies, which may include currencies - * that represent obsolete ISO 4217 codes. The set can be modified + * {@return a set of available currencies} The returned set of currencies + * contains all the available currencies, which may include currencies + * that represent obsolete ISO 4217 codes. If there is no currency available + * in the runtime, the returned set is empty. The set can be modified * without affecting the available currencies in the runtime. * - * @return the set of available currencies. If there is no currency - * available in the runtime, the returned set is empty. + * @apiNote Consider using {@link #availableCurrencies()} which returns + * a stream of the available currencies. + * @see #availableCurrencies() * @since 1.7 */ public static Set getAvailableCurrencies() { - synchronized(Currency.class) { - if (available == null) { - var sysTime = System.currentTimeMillis(); - available = new HashSet<>(256); - // Add simple currencies first - for (char c1 = 'A'; c1 <= 'Z'; c1 ++) { - for (char c2 = 'A'; c2 <= 'Z'; c2 ++) { - int tableEntry = getMainTableEntry(c1, c2); - if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK - && tableEntry != INVALID_COUNTRY_ENTRY) { - char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); - int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; - int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; - StringBuilder sb = new StringBuilder(); - sb.append(c1); - sb.append(c2); - sb.append(finalChar); - available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode)); - } else if ((tableEntry & COUNTRY_TYPE_MASK) == SPECIAL_CASE_COUNTRY_MASK - && tableEntry != INVALID_COUNTRY_ENTRY - && tableEntry != COUNTRY_WITHOUT_CURRENCY_ENTRY) { - int index = SpecialCaseEntry.toIndex(tableEntry); - SpecialCaseEntry scEntry = specialCasesList.get(index); + return new HashSet<>(getCurrencies()); + } - if (scEntry.cutOverTime == Long.MAX_VALUE - || sysTime < scEntry.cutOverTime) { - available.add(getInstance(scEntry.oldCurrency, - scEntry.oldCurrencyFraction, - scEntry.oldCurrencyNumericCode)); - } else { - available.add(getInstance(scEntry.newCurrency, - scEntry.newCurrencyFraction, - scEntry.newCurrencyNumericCode)); - } + /** + * {@return a stream of available currencies} The returned stream of currencies + * contains all the available currencies, which may include currencies + * that represent obsolete ISO 4217 codes. If there is no currency + * available in the runtime, the returned stream is empty. + * + * @implNote Unlike {@link #getAvailableCurrencies()}, this method does + * not create a defensive copy of the {@code Currency} set. + * @see #getAvailableCurrencies() + * @since 25 + */ + public static Stream availableCurrencies() { + return getCurrencies().stream(); + } + + // Returns the set of available Currencies which are lazily initialized + private static synchronized HashSet getCurrencies() { + if (available == null) { + var sysTime = System.currentTimeMillis(); + available = HashSet.newHashSet(256); + + // Add simple currencies first + for (char c1 = 'A'; c1 <= 'Z'; c1 ++) { + for (char c2 = 'A'; c2 <= 'Z'; c2 ++) { + int tableEntry = getMainTableEntry(c1, c2); + if ((tableEntry & COUNTRY_TYPE_MASK) == SIMPLE_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY) { + char finalChar = (char) ((tableEntry & SIMPLE_CASE_COUNTRY_FINAL_CHAR_MASK) + 'A'); + int defaultFractionDigits = (tableEntry & SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_MASK) >> SIMPLE_CASE_COUNTRY_DEFAULT_DIGITS_SHIFT; + int numericCode = (tableEntry & NUMERIC_CODE_MASK) >> NUMERIC_CODE_SHIFT; + StringBuilder sb = new StringBuilder(); + sb.append(c1); + sb.append(c2); + sb.append(finalChar); + available.add(getInstance(sb.toString(), defaultFractionDigits, numericCode)); + } else if ((tableEntry & COUNTRY_TYPE_MASK) == SPECIAL_CASE_COUNTRY_MASK + && tableEntry != INVALID_COUNTRY_ENTRY + && tableEntry != COUNTRY_WITHOUT_CURRENCY_ENTRY) { + int index = SpecialCaseEntry.toIndex(tableEntry); + SpecialCaseEntry scEntry = specialCasesList.get(index); + + if (scEntry.cutOverTime == Long.MAX_VALUE + || sysTime < scEntry.cutOverTime) { + available.add(getInstance(scEntry.oldCurrency, + scEntry.oldCurrencyFraction, + scEntry.oldCurrencyNumericCode)); + } else { + available.add(getInstance(scEntry.newCurrency, + scEntry.newCurrencyFraction, + scEntry.newCurrencyNumericCode)); } } } + } - // Now add other currencies - for (OtherCurrencyEntry entry : otherCurrenciesList) { - available.add(getInstance(entry.currencyCode)); - } + // Now add other currencies + for (OtherCurrencyEntry entry : otherCurrenciesList) { + available.add(getInstance(entry.currencyCode)); } } - return new HashSet<>(available); + return available; } /** diff --git a/test/jdk/java/util/Currency/AvailableCurrenciesTest.java b/test/jdk/java/util/Currency/AvailableCurrenciesTest.java new file mode 100644 index 00000000000..2886e4b41f8 --- /dev/null +++ b/test/jdk/java/util/Currency/AvailableCurrenciesTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code 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 + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8347949 + * @summary Ensure underlying element equality of available currency methods + * @run junit AvailableCurrenciesTest + */ + +import java.util.Currency; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class AvailableCurrenciesTest { + + // Validate the equality of the set and stream of available currencies + @Test + public void streamEqualsSetTest() { + var currencies = Currency.getAvailableCurrencies(); + assertEquals(currencies, Currency.availableCurrencies().collect(Collectors.toSet()), + "availableCurrencies() and getAvailableCurrencies() do not have the same elements"); + } + + // Ensure there are no duplicates in the available currencies + @Test + public void noDuplicatesTest() { + assertEquals(Currency.getAvailableCurrencies().size(), + Currency.availableCurrencies().distinct().count(), + "Duplicate currencies returned by availableCurrencies()"); + } +}