ICU-22917 Output to files, cleanup, update docs

This commit is contained in:
Mihai Nita 2024-11-01 12:54:37 -07:00
parent 8dc2ec0bb0
commit fbfbe6c7aa
5 changed files with 621 additions and 444 deletions

View file

@ -0,0 +1,150 @@
---
layout: default
title: Updating MeasureUnit with new CLDR data
parent: Release & Milestone Tasks
grand_parent: Contributors
nav_order: 120
---
<!--
© 2020 and later: Unicode, Inc. and others.
License & terms of use: http://www.unicode.org/copyright.html
-->
# Updating MeasureUnit with new CLDR data
{: .no_toc }
## Contents
{: .no_toc .text-delta }
1. TOC
{:toc}
---
This document explains how to update the C++ and Java version of the MeasureUnit
class with new CLDR data.
Code is generated by running MeasureUnitTest.java unit tests, which writes
generated code to System.out. Two ways to access this:
1. Within **eclipse**:
- Open MeasureUnitTest.java, run it by clicking on the green play button on
menu bar.
- Copy the generated code from the eclipse console to the clipboard.
2. With **ant**:
- Run: `ant checkTest
-Dtestclass='com.ibm.icu.dev.test.format.MeasureUnitTest'`
- Open the checkTest output: `out/junit-results/checkTest/html/index.html`
- Navigate to "System.out" at the bottom of the MeasureUnitTest page to find
the generated code, and copy to the clipboard.
After syncing CLDR data with ICU do the following. This documentation assumes
that you are updating the MeasureUnit clases for ICU 68.
* Check out
$GIT_ROOT/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/MeasureUnitTest.java
* Open MeasureUnitTest.java.
* Find the `testZZZ` test, its code should all be commented out. This test will
execute last and will run the desired code.
Make sure DRAFT_VERSIONS at top of MeasureUnitTest.java is set correctly.
These are the ICU versions that have draft methods.
## Update MeasureUnit.java
* Change `testZZZ` to run `generateConstants(“68”); // ICU 68.`
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Open MeasureUnit.java:
$GIT_ROOT/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java
* Look for line containing:
`// Start generated MeasureUnit constants`
* Look for line containing:
`// End generated MeasureUnit constants`
* Replace all the generated code in between with the contents of the clipboard
* Run the MeasureUnitTest.java to ensure that the new code is backward
compatible. These compatibility tests are called something like
`TestCompatible65`, which tests backward compatibility with ICU 65.
* Create a compatibility test for ICU 68. Change `testZZZ` to run
`generateBackwardCompatibilityTest(“68”)`
* Run tests.
* Copy generated test (see instructions above) into MeasureUnitTest.java
* Run tests again to ensure that new code is backward compatible with itself
## Update ICU4C
* checkout ICU4C
### Update measunit.h
* Change testZZZ to run `generateCXXHConstants(“68”); // ICU 68`.
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Open $GIT_ROOT/icu4c/source/i18n/unicode/measunit.h. Look for line containing:
`// Start generated createXXX methods`
* Look for line:
`// End generated createXXX methods`
* Replace all the generated code in between with the contents of the clipboard
### Update measunit.cpp
* Change testZZZ to run generateCXXConstants();
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Open $GIT_ROOT/icu4c/source/i18n/measunit.cpp. Look for line containing:
`// Start generated code for measunit.cpp`
* Look for lines
`// End generated code for measunit.cpp`
* Replace all the generated code in between with the contents of the clipboard
### Run C++ tests
* Run `./intltest format/MeasureFormatTest` from `test/intltest` to ensure new
code is backward compatible.
* Create a compatibility test for ICU 68. Change `testZZZ` in eclipse to run
`generateCXXBackwardCompatibilityTest(“68”)`
* Run tests.
* Copy generated test (see instructions above) into
$GIT_ROOT/icu4c/source/test/intltest/measfmttest.cpp. Make other necessary
changes to make test compile. You can find these changes by searching for
`TestCompatible65()`
* Run tests again to ensure that new code is backward compatible with itself
## Finishing changes
These last changes are necessary to permanently record the ICU version number of
any new measure units. Without these changes any new functions for this release
will be considered new for the next release too.
* Change `testZZZ` to run `updateJAVAVersions(“68”);`
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Append the clipboard contents to the values of the JAVA_VERSIONS variable
near the top of MeasureUnitTest.java.
**Important:** what you are copying are just the new functions for the current
ICU version, in this case 68. Therefore append, do not replace.
## Updating units.txt and unitConstants
The standard ldml2icu process is used to update ICU's resource files (see
[cldr-icu-readme.txt](https://github.com/unicode-org/icu/blob/main/icu4c/source/data/cldr-icu-readme.txt)).
CLDR's units.xml defines conversion rates in terms of some constants defined in
`unitConstants`.
For efficiency and simplicity, ICU does not read `unitConstants` from the
resource file. If any new constants are added, some code changes would be
needed. This would be caught by `testUnitConstantFreshness` unit test in
`units_test.cpp`.
They are hard-coded:
* Java: `UnitConverter.java` has the constant names in
`UnitConverter.Factor.addEntity()` and constant values in
`UnitConverter.Factor.getConversionRate()`.
* C++: `units_converter.cpp` has the constant names in
`addSingleFactorConstant()`, with the constant values in `double
constantsValues[]` in the `units_converter.h` header file.

View file

@ -12,9 +12,11 @@ License & terms of use: http://www.unicode.org/copyright.html
-->
# Updating MeasureUnit with new CLDR data
{: .no_toc }
## Contents
{: .no_toc .text-delta }
1. TOC
@ -22,117 +24,94 @@ License & terms of use: http://www.unicode.org/copyright.html
---
This document explains how to update the C++ and Java version of the MeasureUnit
This document explains how to update the C++ and Java version of the `MeasureUnit`
class with new CLDR data.
Code is generated by running MeasureUnitTest.java unit tests, which writes
generated code to System.out. Two ways to access this:
This document applies to ICU 77 and later.
For older versions see updating-measure-unit-old.md
1. Within **eclipse**:
- Open MeasureUnitTest.java, run it by clicking on the green play button on
menu bar.
- Copy the generated code from the eclipse console to the clipboard.
Make sure `DRAFT_VERSION_SET` at top of
`./icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/MeasureUnitGeneratorTest.java`
is set correctly. \
These are the ICU versions that have draft methods.
2. With **ant**:
- Run: `ant checkTest
-Dtestclass='com.ibm.icu.dev.test.format.MeasureUnitTest'`
- Open the checkTest output: `out/junit-results/checkTest/html/index.html`
- Navigate to "System.out" at the bottom of the MeasureUnitTest page to find
the generated code, and copy to the clipboard.
The code is generated by running `MeasureUnitGeneratorTest.java` unit tests, which writes
generated code to various file.
After syncing CLDR data with ICU do the following. This documentation assumes
that you are updating the MeasureUnit clases for ICU 68.
1. With **maven** (command line):
- Change folder to `{icuRoot}/icu4j`
- run `mvn install -DskipTests -DskipITs`
- run `mvn install -q -Dtest=MeasureUnitGeneratorTest -DgenerateMeasureUnitUpdate -f main/common_tests`
* Check out
$GIT_ROOT/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/MeasureUnitTest.java
* Open MeasureUnitTest.java.
* Find the `testZZZ` test, its code should all be commented out. This test will
execute last and will run the desired code.
2. Within **Eclipse**:
- Open `MeasureUnitGeneratorTest.java`, find the `generateUnitTestsUpdate` methods
and run it by clicking on the green play button on menu bar. \
Choose "JUnit Test" if asked. \
This will not generate the update, but it will run the test and create a "Run Configuration". \
Open it (Main menu -- "Run" -- "Run Configurations"), select the one named
`MeasureUnitGeneratorTest.generateUnitTestsUpdate`, go to the "Arguments" tab and add
`-DgenerateMeasureUnitUpdate` to the "VM Arguments" text area.
Make sure DRAFT_VERSIONS at top of MeasureUnitTest.java is set correctly.
These are the ICU versions that have draft methods.
Both methods will generate files with in `icu4j/main/common_tests/target/` folder. \
The file names and the logging to the standard output will guide you.
## Update MeasureUnit.java
It currently looks something like this:
```
Copy the generated code fragments from / to
/some/absolute/path/icu4j/main/common_tests/target/MeasureUnit.java \
/some/absolute/path/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java
* Change `testZZZ` to run `generateConstants(“68”); // ICU 68.`
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Open MeasureUnit.java:
$GIT_ROOT/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java
* Look for line containing:
Copy the generated code fragments from / to
/some/absolute/path/icu4j/main/common_tests/target/MeasureUnitCompatibilityTest.java \
/some/absolute/path/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/MeasureUnitCompatibilityTest.java
`// Start generated MeasureUnit constants`
* Look for line containing:
Copy the generated code fragments from / to
/some/absolute/path/icu4j/main/common_tests/target/measunit.h \
/some/absolute/path/icu4c/source/i18n/unicode/measunit.h
`// End generated MeasureUnit constants`
* Replace all the generated code in between with the contents of the clipboard
* Run the MeasureUnitTest.java to ensure that the new code is backward
compatible. These compatibility tests are called something like
`TestCompatible65`, which tests backward compatibility with ICU 65.
* Create a compatibility test for ICU 68. Change `testZZZ` to run
`generateBackwardCompatibilityTest(“68”)`
* Run tests.
* Copy generated test (see instructions above) into MeasureUnitTest.java
* Run tests again to ensure that new code is backward compatible with itself
Copy the generated code fragments from / to
/some/absolute/path/icu4j/main/common_tests/target/measunit.cpp \
/some/absolute/path/icu4c/source/i18n/measunit.cpp
## Update ICU4C
Copy the generated code fragments from / to
/some/absolute/path/icu4j/main/common_tests/target/measfmttest.cpp \
/some/absolute/path/icu4c/source/test/intltest/measfmttest.cpp
* checkout ICU4C
Copy the generated code fragments from / to
/some/absolute/path/icu4j/main/common_tests/target/MeasureUnitGeneratorTest.java \
/some/absolute/path/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/MeasureUnitGeneratorTest.java
```
### Update measunit.h
Some kind of diff tool or editor (for example `vi -d`) work nicely.
* Change testZZZ to run `generateCXXHConstants(“68”); // ICU 68`.
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Open $GIT_ROOT/icu4c/source/i18n/unicode/measunit.h. Look for line containing:
Look for line containing `// Start generated ...` and `// End generated ...`
These lines exist in both the original files, and the generated one. \
Replace all the generated code in between with the contents of the clipboard.
`// Start generated createXXX methods`
* Look for line:
If the generated code has no `// Start` ... `// End ...` pair then the new
code should be appended at some fixed place (details below).
`// End generated createXXX methods`
* Replace all the generated code in between with the contents of the clipboard
* **`MeasureUnit.java`:** replace range.
* **`MeasureUnitCompatibilityTest.java`:** append the new generated method at the end. \
It is named something like `TestCompatible<version>()`. \
Don't add it if it already exists.
* **`measunit.h`:** replace range.
* **`measunit.cpp`:** replace range.
* **`measfmttest.cpp`:** append the new generated method after the last
`MeasureFormatTest::TestCompatible<version>()` method. \
Don't add it if it already exists. \
WARNING: here you should add the method in two places. The method proper, with code,
as generated, and the declaration in the class definition.
* **`MeasureUnitGeneratorTest.java`:** append the new pairs of measure + version at
the end of the `JAVA_VERSIONS` structure. \
Don't add them if they already exist.
### Update measunit.cpp
## Run tests for both `icu4c` and `icu4j`
* Change testZZZ to run generateCXXConstants();
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Open $GIT_ROOT/icu4c/source/i18n/measunit.cpp. Look for line containing:
## Updating `units.txt` and `unitConstants`
`// Start generated code for measunit.cpp`
* Look for lines
`// End generated code for measunit.cpp`
* Replace all the generated code in between with the contents of the clipboard
### Run C++ tests
* Run `./intltest format/MeasureFormatTest` from `test/intltest` to ensure new
code is backward compatible.
* Create a compatibility test for ICU 68. Change `testZZZ` in eclipse to run
`generateCXXBackwardCompatibilityTest(“68”)`
* Run tests.
* Copy generated test (see instructions above) into
$GIT_ROOT/icu4c/source/test/intltest/measfmttest.cpp. Make other necessary
changes to make test compile. You can find these changes by searching for
`TestCompatible65()`
* Run tests again to ensure that new code is backward compatible with itself
## Finishing changes
These last changes are necessary to permanently record the ICU version number of
any new measure units. Without these changes any new functions for this release
will be considered new for the next release too.
* Change `testZZZ` to run `updateJAVAVersions(“68”);`
* Run MeasureUnitTest.java, copy the generated code (see instructions above).
* Append the clipboard contents to the values of the JAVA_VERSIONS variable
near the top of MeasureUnitTest.java.
**Important:** what you are copying are just the new functions for the current
ICU version, in this case 68. Therefore append, do not replace.
## Updating units.txt and unitConstants
The standard ldml2icu process is used to update ICU's resource files (see
[cldr-icu-readme.txt](https://github.com/unicode-org/icu/blob/main/icu4c/source/data/cldr-icu-readme.txt)).
The standard `ldml2icu` process is used to update ICU's resource files (see
[`cldr-icu-readme.txt`](https://github.com/unicode-org/icu/blob/main/icu4c/source/data/cldr-icu-readme.txt)).
CLDR's units.xml defines conversion rates in terms of some constants defined in
`unitConstants`.
@ -142,6 +121,7 @@ needed. This would be caught by `testUnitConstantFreshness` unit test in
`units_test.cpp`.
They are hard-coded:
* Java: `UnitConverter.java` has the constant names in
`UnitConverter.Factor.addEntity()` and constant values in
`UnitConverter.Factor.getConversionRate()`.

View file

@ -16,11 +16,11 @@ import com.ibm.icu.dev.test.CoreTestFmwk;
import com.ibm.icu.util.MeasureUnit;
/**
* These class contains only compatibility tests,
* generated by MeasureUnitGeneratorTest.
* This class contains only compatibility tests generated by MeasureUnitGeneratorTest.
* Do not add any other tests here.
*/
@RunWith(JUnit4.class)
public class MeasureUnitCompatibleTest extends CoreTestFmwk {
public class MeasureUnitCompatibilityTest extends CoreTestFmwk {
@Test
public void TestCompatible53() {
@ -2560,7 +2560,8 @@ public class MeasureUnitCompatibleTest extends CoreTestFmwk {
assertEquals("", 190, units.length);
}
public void TestCompatible76() {
@Test
public void TestCompatible76() {
MeasureUnit[] units = {
MeasureUnit.G_FORCE,
MeasureUnit.METER_PER_SECOND_SQUARED,
@ -2755,6 +2756,11 @@ public class MeasureUnitCompatibleTest extends CoreTestFmwk {
MeasureUnit.TABLESPOON,
MeasureUnit.TEASPOON,
};
assertEquals("", 193, units.length);
assertEquals("", 192, units.length);
}
/*
* This class contains only compatibility tests generated by MeasureUnitGeneratorTest.
* Do not add any other tests here.
*/
}

View file

@ -8,6 +8,10 @@
*/
package com.ibm.icu.dev.test.format;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -27,25 +31,27 @@ import com.ibm.icu.dev.test.CoreTestFmwk;
import com.ibm.icu.impl.Pair;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.NoUnit;
import com.ibm.icu.util.VersionInfo;
/**
* This is not a real test class. It is only used to
* generate updated unit tests code based on new CLDR data.
* Do not add any other tests here.
*
* See https://sites.google.com/site/icusite/processes/release/tasks/standards?pli=1
* See https://unicode-org.github.io/icu/processes/release/tasks/updating-measure-unit.html
* for information on how to update with each new release.
* @author markdavis
*/
@RunWith(JUnit4.class)
public class MeasureUnitGeneratorTest extends CoreTestFmwk {
static class OrderedPair<F extends Comparable, S extends Comparable> extends Pair<F, S> implements Comparable<OrderedPair<F, S>> {
private static class OrderedPair<F extends Comparable<F>, S extends Comparable<S>> extends Pair<F, S> implements Comparable<OrderedPair<F, S>> {
OrderedPair(F first, S second) {
private OrderedPair(F first, S second) {
super(first, second);
}
public static <F extends Comparable, S extends Comparable> OrderedPair<F, S> of(F first, S second) {
private static <F extends Comparable<F>, S extends Comparable<S>> OrderedPair<F, S> of(F first, S second) {
if (first == null || second == null) {
throw new IllegalArgumentException("OrderedPair.of requires non null values.");
}
@ -264,7 +270,7 @@ public class MeasureUnitGeneratorTest extends CoreTestFmwk {
// modify certain CLDR unit names before generating functions
// that create/get the corresponding MeasureUnit objects
private static final Map<String,String> CLDR_NAME_REMAP = new HashMap();
private static final Map<String,String> CLDR_NAME_REMAP = new HashMap<>();
static {
TIME_CODES.add("year");
@ -293,22 +299,40 @@ public class MeasureUnitGeneratorTest extends CoreTestFmwk {
CLDR_NAME_REMAP.put("pound-force-foot", "pound-foot");
}
private static final String ICU_ROOT = findIcuRoot();
private static String findIcuRoot() {
URL x = MeasureUnitGeneratorTest.class.getResource(".");
String classFile = x.getFile();
int idx = classFile.indexOf("/icu4j/main/common_tests/target/");
if (idx != -1) {
return classFile.substring(0, idx);
} else {
return "${icuroot}";
}
}
@Test
public void testZZZ() {
public void generateUnitTestsUpdate() throws IOException {
// various generateXXX calls go here, see
// docs/processes/release/tasks/updating-measure-unit.md
// use this test to run each of the following in succession
generateConstants("76"); // for MeasureUnit.java, update generated MeasureUnit constants
generateBackwardCompatibilityTest("76"); // for MeasureUnitTest.java, create TestCompatible74
generateCXXHConstants("76"); // for measunit.h, update generated createXXX methods
generateCXXConstants(); // for measunit.cpp, update generated code
generateCXXBackwardCompatibilityTest("74"); // for measfmttest.cpp, create TestCompatible74
updateJAVAVersions("74"); // for MeasureUnitTest.java, JAVA_VERSIONS
if (System.getProperty("generateMeasureUnitUpdate") != null) {
final String icuVersion = Integer.toString(VersionInfo.ICU_VERSION.getMajor());
System.out.println();
System.out.println("WARNING: open the pairs of files listed below and copy code fragments, not full files!");
System.out.println("Some kind of diff tool / editor would work best.");
generateConstants(icuVersion); // update generated MeasureUnit constants
generateBackwardCompatibilityTest(icuVersion); // create TestCompatible<icu_ver>
generateCXXHConstants(icuVersion); // update generated createXXX methods
generateCXXConstants(); // update generated code
generateCXXBackwardCompatibilityTest(icuVersion); // create TestCompatible<icu_ver>
updateJAVAVersions(icuVersion); // JAVA_VERSIONS
}
}
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static Map<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> getUnitsToPerParts() {
private static Map<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> getUnitsToPerParts() {
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
Map<MeasureUnit, Pair<String, String>> unitsToPerStrings =
new HashMap<>();
@ -342,95 +366,96 @@ public class MeasureUnitGeneratorTest extends CoreTestFmwk {
return unitsToPerUnits;
}
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void generateCXXHConstants(String thisVersion) {
Map<String, MeasureUnit> seen = new HashMap<>();
System.out.println("// Start generated createXXX methods");
System.out.println();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (type.equals("currency")) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String code = unit.getSubtype();
String name = toCamelCase(unit);
String javaName = toJAVAName(unit);
checkForDup(seen, name, unit);
if (isDraft(javaName)) {
System.out.println("#ifndef U_HIDE_DRAFT_API");
private static void generateCXXHConstants(String thisVersion) throws IOException {
String fullOutputPath = "${icuroot}/icu4c/source/i18n/unicode/measunit.h";
try (PrintStream out = createAndStartOutputFile(fullOutputPath)) {
Map<String, MeasureUnit> seen = new HashMap<>();
out.println("// Start generated createXXX methods");
out.println();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (type.equals("currency")) {
continue;
}
System.out.println(" /**");
System.out.println(" * Returns by pointer, unit of " + type + ": " + code + ".");
System.out.println(" * Caller owns returned value and must free it.");
System.out.printf(" * Also see {@link #get%s()}.\n", name);
System.out.println(" * @param status ICU error code.");
if (isDraft(javaName)) {
System.out.println(" * @draft ICU " + getVersion(javaName, thisVersion));
} else {
System.out.println(" * @stable ICU " + getVersion(javaName, thisVersion));
}
System.out.println(" */");
System.out.printf(" static MeasureUnit *create%s(UErrorCode &status);\n", name);
System.out.println();
System.out.println(" /**");
System.out.println(" * Returns by value, unit of " + type + ": " + code + ".");
System.out.printf(" * Also see {@link #create%s()}.\n", name);
String getterVersion = getVersion(javaName, thisVersion);
if (Integer.parseInt(getterVersion) < 64) {
getterVersion = "64";
}
if (isDraft(javaName)) {
System.out.println(" * @draft ICU " + getterVersion);
} else {
System.out.println(" * @stable ICU " + getterVersion);
}
System.out.println(" */");
System.out.printf(" static MeasureUnit get%s();\n", name);
if (isDraft(javaName)) {
System.out.println("#endif /* U_HIDE_DRAFT_API */");
}
System.out.println("");
// Hack: METRIC-TON unit changed its name from "metric-ton" to "tonne"
// In order to preserve the existing APIs for "metric-ton" we need to
// add those APIs manually
if (name.equals("Tonne")) {
addCXXHForMetricTon();
for (MeasureUnit unit : entry.getValue()) {
String code = unit.getSubtype();
String name = toCamelCase(unit);
String javaName = toJAVAName(unit);
checkForDup(seen, name, unit);
if (isDraft(javaName)) {
out.println("#ifndef U_HIDE_DRAFT_API");
}
out.println(" /**");
out.println(" * Returns by pointer, unit of " + type + ": " + code + ".");
out.println(" * Caller owns returned value and must free it.");
out.printf(" * Also see {@link #get%s()}.\n", name);
out.println(" * @param status ICU error code.");
if (isDraft(javaName)) {
out.println(" * @draft ICU " + getVersion(javaName, thisVersion));
} else {
out.println(" * @stable ICU " + getVersion(javaName, thisVersion));
}
out.println(" */");
out.printf(" static MeasureUnit *create%s(UErrorCode &status);\n", name);
out.println();
out.println(" /**");
out.println(" * Returns by value, unit of " + type + ": " + code + ".");
out.printf(" * Also see {@link #create%s()}.\n", name);
String getterVersion = getVersion(javaName, thisVersion);
if (Integer.parseInt(getterVersion) < 64) {
getterVersion = "64";
}
if (isDraft(javaName)) {
out.println(" * @draft ICU " + getterVersion);
} else {
out.println(" * @stable ICU " + getterVersion);
}
out.println(" */");
out.printf(" static MeasureUnit get%s();\n", name);
if (isDraft(javaName)) {
out.println("#endif /* U_HIDE_DRAFT_API */");
}
out.println("");
// Hack: METRIC-TON unit changed its name from "metric-ton" to "tonne"
// In order to preserve the existing APIs for "metric-ton" we need to
// add those APIs manually
if (name.equals("Tonne")) {
addCXXHForMetricTon(out);
}
}
}
out.println("// End generated createXXX methods");
}
System.out.println("// End generated createXXX methods");
}
// Add the headers for "metric-ton"
// The tool won't create them any more
private static void addCXXHForMetricTon() {
System.out.println(" /**");
System.out.println(" * Returns by pointer, unit of mass: metric-ton");
System.out.println(" * (renamed to tonne in CLDR 42 / ICU 72).");
System.out.println(" * Caller owns returned value and must free it.");
System.out.println(" * Note: In ICU 74 this will be deprecated in favor of");
System.out.println(" * createTonne(), which is currently draft but will");
System.out.println(" * become stable in ICU 74, and which uses the preferred naming.");
System.out.println(" * Also see {@link #getMetricTon()} and {@link #createTonne()}.");
System.out.println(" * @param status ICU error code.");
System.out.println(" * @stable ICU 54");
System.out.println(" */");
System.out.println(" static MeasureUnit *createMetricTon(UErrorCode &status);");
System.out.println("");
System.out.println(" /**");
System.out.println(" * Returns by value, unit of mass: metric-ton");
System.out.println(" * (renamed to tonne in CLDR 42 / ICU 72).");
System.out.println(" * Note: In ICU 74 this will be deprecated in favor of");
System.out.println(" * getTonne(), which is currently draft but will");
System.out.println(" * become stable in ICU 74, and which uses the preferred naming.");
System.out.println(" * Also see {@link #createMetricTon()} and {@link #getTonne()}.");
System.out.println(" * @stable ICU 64");
System.out.println(" */");
System.out.println(" static MeasureUnit getMetricTon();");
System.out.println("");
// The tool won't create them any more
private static void addCXXHForMetricTon(PrintStream out) {
out.println(" /**");
out.println(" * Returns by pointer, unit of mass: metric-ton");
out.println(" * (renamed to tonne in CLDR 42 / ICU 72).");
out.println(" * Caller owns returned value and must free it.");
out.println(" * Note: In ICU 74 this will be deprecated in favor of");
out.println(" * createTonne(), which is currently draft but will");
out.println(" * become stable in ICU 74, and which uses the preferred naming.");
out.println(" * Also see {@link #getMetricTon()} and {@link #createTonne()}.");
out.println(" * @param status ICU error code.");
out.println(" * @stable ICU 54");
out.println(" */");
out.println(" static MeasureUnit *createMetricTon(UErrorCode &status);");
out.println("");
out.println(" /**");
out.println(" * Returns by value, unit of mass: metric-ton");
out.println(" * (renamed to tonne in CLDR 42 / ICU 72).");
out.println(" * Note: In ICU 74 this will be deprecated in favor of");
out.println(" * getTonne(), which is currently draft but will");
out.println(" * become stable in ICU 74, and which uses the preferred naming.");
out.println(" * Also see {@link #createMetricTon()} and {@link #getTonne()}.");
out.println(" * @stable ICU 64");
out.println(" */");
out.println(" static MeasureUnit getMetricTon();");
out.println("");
}
private static void checkForDup(
@ -442,196 +467,192 @@ public class MeasureUnitGeneratorTest extends CoreTestFmwk {
}
}
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void updateJAVAVersions(String thisVersion) {
System.out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (type.equals("currency")) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String javaName = toJAVAName(unit);
checkForDup(seen, javaName, unit);
if (!JAVA_VERSION_MAP.containsKey(javaName)) {
System.out.printf(" {\"%s\", \"%s\"},\n", javaName, thisVersion);
private static void updateJAVAVersions(String thisVersion) throws IOException {
String fullOutputPath = "${icuroot}/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/MeasureUnitGeneratorTest.java";
try (PrintStream out = createAndStartOutputFile(fullOutputPath)) {
out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (type.equals("currency")) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String javaName = toJAVAName(unit);
checkForDup(seen, javaName, unit);
if (!JAVA_VERSION_MAP.containsKey(javaName)) {
out.printf(" {\"%s\", \"%s\"},\n", javaName, thisVersion);
}
}
}
}
}
static TreeMap<String, List<MeasureUnit>> getAllUnits() {
private static TreeMap<String, List<MeasureUnit>> getAllUnits() {
final Comparator<MeasureUnit> measureUnitComparator =
(MeasureUnit o1, MeasureUnit o2) -> o1.getSubtype().compareTo(o2.getSubtype());
TreeMap<String, List<MeasureUnit>> allUnits = new TreeMap<>();
for (String type : MeasureUnit.getAvailableTypes()) {
ArrayList<MeasureUnit> units = new ArrayList<>(MeasureUnit.getAvailable(type));
Collections.sort(
units,
new Comparator<MeasureUnit>() {
@Override
public int compare(MeasureUnit o1, MeasureUnit o2) {
return o1.getSubtype().compareTo(o2.getSubtype());
}
});
Collections.sort(units, measureUnitComparator);
allUnits.put(type, units);
}
return allUnits;
}
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void generateCXXConstants() {
System.out.println("// Start generated code for measunit.cpp");
System.out.println("");
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
private static void generateCXXConstants() throws IOException {
String fullOutputPath = "${icuroot}/icu4c/source/i18n/measunit.cpp";
try (PrintStream out = createAndStartOutputFile(fullOutputPath)) {
out.println("// Start generated code for measunit.cpp");
out.println("");
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
// Hack: for C++, add base unit here, but ignore them when printing the create methods.
// Also keep track of the base unit offset to make the C++ default constructor faster.
allUnits.put("none", Arrays.asList(new MeasureUnit[] {NoUnit.BASE}));
int baseTypeIdx = -1;
int baseSubTypeIdx = -1;
// Hack: for C++, add base unit here, but ignore them when printing the create methods.
// Also keep track of the base unit offset to make the C++ default constructor faster.
allUnits.put("none", Arrays.asList(new MeasureUnit[] {NoUnit.BASE}));
int baseTypeIdx = -1;
int baseSubTypeIdx = -1;
System.out.println("// Maps from Type ID to offset in gSubTypes.");
System.out.println("static const int32_t gOffsets[] = {");
int index = 0;
int typeCount = 0;
int currencyIndex = -1;
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
System.out.printf(" %d,\n", index);
if (entry.getKey() == "currency") {
currencyIndex = typeCount;
out.println("// Maps from Type ID to offset in gSubTypes.");
out.println("static const int32_t gOffsets[] = {");
int index = 0;
int typeCount = 0;
int currencyIndex = -1;
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
out.printf(" %d,\n", index);
if (entry.getKey() == "currency") {
currencyIndex = typeCount;
}
typeCount++;
index += entry.getValue().size();
}
typeCount++;
index += entry.getValue().size();
}
assertTrue("currency present", currencyIndex >= 0);
System.out.printf(" %d\n", index);
System.out.println("};");
System.out.println();
System.out.println("static const int32_t kCurrencyOffset = " + currencyIndex + ";");
System.out.println();
System.out.println("// Must be sorted alphabetically.");
System.out.println("static const char * const gTypes[] = {");
boolean first = true;
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
if (!first) {
System.out.println(",");
}
System.out.print(" \"" + entry.getKey() + "\"");
first = false;
}
System.out.println();
System.out.println("};");
System.out.println();
System.out.println("// Must be grouped by type and sorted alphabetically within each type.");
System.out.println("static const char * const gSubTypes[] = {");
first = true;
int offset = 0;
int typeIdx = 0;
Map<MeasureUnit, Integer> measureUnitToOffset = new HashMap<>();
Map<MeasureUnit, Pair<Integer, Integer>> measureUnitToTypeSubType =
new HashMap<>();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
int subTypeIdx = 0;
for (MeasureUnit unit : entry.getValue()) {
assertTrue("currency present", currencyIndex >= 0);
out.printf(" %d\n", index);
out.println("};");
out.println();
out.println("static const int32_t kCurrencyOffset = " + currencyIndex + ";");
out.println();
out.println("// Must be sorted alphabetically.");
out.println("static const char * const gTypes[] = {");
boolean first = true;
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
if (!first) {
System.out.println(",");
}
if (unit != null) {
System.out.print(" \"" + unit.getSubtype() + "\"");
} else {
assertEquals("unit only null for \"none\" type", "none", entry.getKey());
System.out.print(" \"\"");
out.println(",");
}
out.print(" \"" + entry.getKey() + "\"");
first = false;
measureUnitToOffset.put(unit, offset);
measureUnitToTypeSubType.put(unit, Pair.of(typeIdx, subTypeIdx));
if (unit == NoUnit.BASE) {
baseTypeIdx = typeIdx;
baseSubTypeIdx = subTypeIdx;
}
offset++;
subTypeIdx++;
}
typeIdx++;
}
System.out.println();
System.out.println("};");
System.out.println();
// Build unit per unit offsets to corresponding type sub types sorted by
// unit first and then per unit.
TreeMap<OrderedPair<Integer, Integer>, Pair<Integer, Integer>> unitPerUnitOffsetsToTypeSubType
= new TreeMap<>();
for (Map.Entry<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> entry
: getUnitsToPerParts().entrySet()) {
Pair<MeasureUnit, MeasureUnit> unitPerUnit = entry.getValue();
unitPerUnitOffsetsToTypeSubType.put(
OrderedPair.of(
measureUnitToOffset.get(unitPerUnit.first),
measureUnitToOffset.get(unitPerUnit.second)),
measureUnitToTypeSubType.get(entry.getKey()));
}
// Print out the fast-path for the default constructor
System.out.println("// Shortcuts to the base unit in order to make the default constructor fast");
System.out.println("static const int32_t kBaseTypeIdx = " + baseTypeIdx + ";");
System.out.println("static const int32_t kBaseSubTypeIdx = " + baseSubTypeIdx + ";");
System.out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (type.equals("currency") || type.equals("none")) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String name = toCamelCase(unit);
Pair<Integer, Integer> typeSubType = measureUnitToTypeSubType.get(unit);
if (typeSubType == null) {
throw new IllegalStateException();
out.println();
out.println("};");
out.println();
out.println("// Must be grouped by type and sorted alphabetically within each type.");
out.println("static const char * const gSubTypes[] = {");
first = true;
int offset = 0;
int typeIdx = 0;
Map<MeasureUnit, Integer> measureUnitToOffset = new HashMap<>();
Map<MeasureUnit, Pair<Integer, Integer>> measureUnitToTypeSubType =
new HashMap<>();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
int subTypeIdx = 0;
for (MeasureUnit unit : entry.getValue()) {
if (!first) {
out.println(",");
}
if (unit != null) {
out.print(" \"" + unit.getSubtype() + "\"");
} else {
assertEquals("unit only null for \"none\" type", "none", entry.getKey());
out.print(" \"\"");
}
first = false;
measureUnitToOffset.put(unit, offset);
measureUnitToTypeSubType.put(unit, Pair.of(typeIdx, subTypeIdx));
if (unit == NoUnit.BASE) {
baseTypeIdx = typeIdx;
baseSubTypeIdx = subTypeIdx;
}
offset++;
subTypeIdx++;
}
checkForDup(seen, name, unit);
System.out.printf("MeasureUnit *MeasureUnit::create%s(UErrorCode &status) {\n", name);
System.out.printf(" return MeasureUnit::create(%d, %d, status);\n",
typeSubType.first, typeSubType.second);
System.out.println("}");
System.out.println();
System.out.printf("MeasureUnit MeasureUnit::get%s() {\n", name);
System.out.printf(" return MeasureUnit(%d, %d);\n",
typeSubType.first, typeSubType.second);
System.out.println("}");
System.out.println();
// Hack: METRIC-TON unit changed its name from "metric-ton" to "tonne"
// In order to preserve the existing APIs for "metric-ton" we need to
// add those APIs manually
if (name.equals("Tonne")) {
addCXXForMetricTon(typeSubType);
typeIdx++;
}
out.println();
out.println("};");
out.println();
// Build unit per unit offsets to corresponding type sub types sorted by
// unit first and then per unit.
TreeMap<OrderedPair<Integer, Integer>, Pair<Integer, Integer>> unitPerUnitOffsetsToTypeSubType
= new TreeMap<>();
for (Map.Entry<MeasureUnit, Pair<MeasureUnit, MeasureUnit>> entry
: getUnitsToPerParts().entrySet()) {
Pair<MeasureUnit, MeasureUnit> unitPerUnit = entry.getValue();
unitPerUnitOffsetsToTypeSubType.put(
OrderedPair.of(
measureUnitToOffset.get(unitPerUnit.first),
measureUnitToOffset.get(unitPerUnit.second)),
measureUnitToTypeSubType.get(entry.getKey()));
}
// Print out the fast-path for the default constructor
out.println("// Shortcuts to the base unit in order to make the default constructor fast");
out.println("static const int32_t kBaseTypeIdx = " + baseTypeIdx + ";");
out.println("static const int32_t kBaseSubTypeIdx = " + baseSubTypeIdx + ";");
out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (type.equals("currency") || type.equals("none")) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String name = toCamelCase(unit);
Pair<Integer, Integer> typeSubType = measureUnitToTypeSubType.get(unit);
if (typeSubType == null) {
throw new IllegalStateException();
}
checkForDup(seen, name, unit);
out.printf("MeasureUnit *MeasureUnit::create%s(UErrorCode &status) {\n", name);
out.printf(" return MeasureUnit::create(%d, %d, status);\n",
typeSubType.first, typeSubType.second);
out.println("}");
out.println();
out.printf("MeasureUnit MeasureUnit::get%s() {\n", name);
out.printf(" return MeasureUnit(%d, %d);\n",
typeSubType.first, typeSubType.second);
out.println("}");
out.println();
// Hack: METRIC-TON unit changed its name from "metric-ton" to "tonne"
// In order to preserve the existing APIs for "metric-ton" we need to
// add those APIs manually
if (name.equals("Tonne")) {
addCXXForMetricTon(typeSubType, out);
}
}
}
out.println("// End generated code for measunit.cpp");
}
System.out.println("// End generated code for measunit.cpp");
}
// Add the API skeletons for "metric-ton"
// The tool won't create them any more
private static void addCXXForMetricTon(Pair<Integer, Integer> typeSubType) {
// The tool won't create them any more
private static void addCXXForMetricTon(Pair<Integer, Integer> typeSubType, PrintStream out) {
String name = "MetricTon";
System.out.printf("MeasureUnit *MeasureUnit::create%s(UErrorCode &status) {\n", name);
System.out.printf(" return MeasureUnit::create(%d, %d, status);\n",
out.printf("MeasureUnit *MeasureUnit::create%s(UErrorCode &status) {\n", name);
out.printf(" return MeasureUnit::create(%d, %d, status);\n",
typeSubType.first, typeSubType.second);
System.out.println("}");
System.out.println();
System.out.printf("MeasureUnit MeasureUnit::get%s() {\n", name);
System.out.printf(" return MeasureUnit(%d, %d);\n",
out.println("}");
out.println();
out.printf("MeasureUnit MeasureUnit::get%s() {\n", name);
out.printf(" return MeasureUnit(%d, %d);\n",
typeSubType.first, typeSubType.second);
System.out.println("}");
System.out.println();
out.println("}");
out.println();
}
private static String toCamelCase(MeasureUnit unit) {
@ -662,61 +683,64 @@ public class MeasureUnitGeneratorTest extends CoreTestFmwk {
return result.toString();
}
static boolean isTypeHidden(String type) {
private static boolean isTypeHidden(String type) {
return "currency".equals(type);
}
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void generateBackwardCompatibilityTest(String version) {
Map<String, MeasureUnit> seen = new HashMap<>();
System.out.println();
System.out.printf(" public void TestCompatible%s() {\n", version.replace(".", "_"));
System.out.println(" MeasureUnit[] units = {");
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
int count = 0;
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
if (isTypeHidden(entry.getKey())) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String javaName = toJAVAName(unit);
checkForDup(seen, javaName, unit);
System.out.printf(" MeasureUnit.%s,\n", javaName);
count++;
private static void generateBackwardCompatibilityTest(String version) throws IOException {
String fullOutputPath = "${icuroot}/icu4j/main/common_tests/src/test/java/com/ibm/icu/dev/test/format/MeasureUnitCompatibilityTest.java";
try (PrintStream out = createAndStartOutputFile(fullOutputPath)) {
Map<String, MeasureUnit> seen = new HashMap<>();
out.println();
out.printf(" @Test\n");
out.printf(" public void TestCompatible%s() {\n", version.replace(".", "_"));
out.println(" MeasureUnit[] units = {");
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
int count = 0;
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
if (isTypeHidden(entry.getKey())) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String javaName = toJAVAName(unit);
checkForDup(seen, javaName, unit);
out.printf(" MeasureUnit.%s,\n", javaName);
count++;
}
}
out.println(" };");
out.printf(" assertEquals(\"\", %d, units.length);\n", count);
out.println(" }");
}
System.out.println(" };");
System.out.printf(" assertEquals(\"\", %d, units.length);\n", count);
System.out.println(" }");
}
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void generateCXXBackwardCompatibilityTest(String version) {
System.out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
System.out.printf("void MeasureFormatTest::TestCompatible%s() {\n", version.replace(".", "_"));
System.out.println(" UErrorCode status = U_ZERO_ERROR;");
System.out.println(" LocalPointer<MeasureUnit> measureUnit;");
System.out.println(" MeasureUnit measureUnitValue;");
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
if (isTypeHidden(entry.getKey())) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String camelCase = toCamelCase(unit);
checkForDup(seen, camelCase, unit);
System.out.printf(" measureUnit.adoptInstead(MeasureUnit::create%s(status));\n", camelCase);
System.out.printf(" measureUnitValue = MeasureUnit::get%s();\n", camelCase);
private static void generateCXXBackwardCompatibilityTest(String version) throws IOException {
String fullOutputPath = "${icuroot}/icu4c/source/test/intltest/measfmttest.cpp";
try (PrintStream out = createAndStartOutputFile(fullOutputPath)) {
out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
out.printf("void MeasureFormatTest::TestCompatible%s() {\n", version.replace(".", "_"));
out.println(" UErrorCode status = U_ZERO_ERROR;");
out.println(" LocalPointer<MeasureUnit> measureUnit;");
out.println(" MeasureUnit measureUnitValue;");
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
if (isTypeHidden(entry.getKey())) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String camelCase = toCamelCase(unit);
checkForDup(seen, camelCase, unit);
out.printf(" measureUnit.adoptInstead(MeasureUnit::create%s(status));\n", camelCase);
out.printf(" measureUnitValue = MeasureUnit::get%s();\n", camelCase);
}
}
out.println(" assertSuccess(\"\", status);");
out.println("}");
}
System.out.println(" assertSuccess(\"\", status);");
System.out.println("}");
}
static String toJAVAName(MeasureUnit unit) {
private static String toJAVAName(MeasureUnit unit) {
String code = unit.getSubtype();
String type = unit.getType();
@ -734,53 +758,54 @@ public class MeasureUnitGeneratorTest extends CoreTestFmwk {
return name;
}
// DO NOT DELETE THIS FUNCTION! It may appear as dead code, but we use this to generate code
// for MeasureFormat during the release process.
static void generateConstants(String thisVersion) {
System.out.println(" // Start generated MeasureUnit constants");
System.out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (isTypeHidden(type)) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String name = toJAVAName(unit);
String code = unit.getSubtype();
checkForDup(seen, name, unit);
System.out.println(" /**");
System.out.println(" * Constant for unit of " + type +
": " +
code);
// Special case JAVA had old constants for time from before.
if ("duration".equals(type) && TIME_CODES.contains(code)) {
System.out.println(" * @stable ICU 4.0");
}
else if (isDraft(name)) {
System.out.println(" * @draft ICU " + getVersion(name, thisVersion));
} else {
System.out.println(" * @stable ICU " + getVersion(name, thisVersion));
}
System.out.println(" */");
if ("duration".equals(type) && TIME_CODES.contains(code)) {
System.out.println(" public static final TimeUnit " + name + " = (TimeUnit) MeasureUnit.internalGetInstance(\"" +
type +
"\", \"" +
code +
"\");");
} else {
System.out.println(" public static final MeasureUnit " + name + " = MeasureUnit.internalGetInstance(\"" +
type +
"\", \"" +
code +
"\");");
}
System.out.println();
private static void generateConstants(String thisVersion) throws IOException {
String fullOutputPath = "${icuroot}/icu4j/main/core/src/main/java/com/ibm/icu/util/MeasureUnit.java";
try (PrintStream out = createAndStartOutputFile(fullOutputPath)) {
out.println(" // Start generated MeasureUnit constants");
out.println();
Map<String, MeasureUnit> seen = new HashMap<>();
TreeMap<String, List<MeasureUnit>> allUnits = getAllUnits();
for (Map.Entry<String, List<MeasureUnit>> entry : allUnits.entrySet()) {
String type = entry.getKey();
if (isTypeHidden(type)) {
continue;
}
for (MeasureUnit unit : entry.getValue()) {
String name = toJAVAName(unit);
String code = unit.getSubtype();
checkForDup(seen, name, unit);
out.println(" /**");
out.println(" * Constant for unit of " + type +
": " +
code);
// Special case JAVA had old constants for time from before.
if ("duration".equals(type) && TIME_CODES.contains(code)) {
out.println(" * @stable ICU 4.0");
}
else if (isDraft(name)) {
out.println(" * @draft ICU " + getVersion(name, thisVersion));
} else {
out.println(" * @stable ICU " + getVersion(name, thisVersion));
}
out.println(" */");
if ("duration".equals(type) && TIME_CODES.contains(code)) {
out.println(" public static final TimeUnit " + name + " = (TimeUnit) MeasureUnit.internalGetInstance(\"" +
type +
"\", \"" +
code +
"\");");
} else {
out.println(" public static final MeasureUnit " + name + " = MeasureUnit.internalGetInstance(\"" +
type +
"\", \"" +
code +
"\");");
}
out.println();
}
}
out.println(" // End generated MeasureUnit constants");
}
System.out.println(" // End generated MeasureUnit constants");
}
private static String getVersion(String javaName, String thisVersion) {
@ -799,4 +824,20 @@ public class MeasureUnitGeneratorTest extends CoreTestFmwk {
return DRAFT_VERSION_SET.contains(version);
}
private static PrintStream createAndStartOutputFile(String fullOutputFileName) throws IOException {
if (fullOutputFileName.startsWith("${icuroot}")) {
fullOutputFileName = fullOutputFileName.replace("${icuroot}", ICU_ROOT);
}
File outputFile = new File("target", new File(fullOutputFileName).getName());
System.out.printf("%nCopy the generated code fragments from / to\n %s \\\n %s%n",
outputFile.getAbsoluteFile(), fullOutputFileName);
return new PrintStream(outputFile, "utf-8");
}
/*
* This is not a real test class. It is only used to
*generate updated unit tests code based on new CLDR data.
* Do not add any other tests here.
*/
}

View file

@ -955,7 +955,7 @@ public class MeasureUnitTest extends CoreTestFmwk {
@Test
public void testCLDRUnitAvailability() {
Set<MeasureUnit> knownUnits = new HashSet<>();
Class cMeasureUnit, cTimeUnit;
Class<?> cMeasureUnit, cTimeUnit;
try {
cMeasureUnit = Class.forName("com.ibm.icu.util.MeasureUnit");
cTimeUnit = Class.forName("com.ibm.icu.util.TimeUnit");
@ -1439,7 +1439,7 @@ public class MeasureUnitTest extends CoreTestFmwk {
if (unit.getType() == "currency") {
continue;
}
if (unit.getIdentifier().equals("portion-per-1e9")) {
logKnownIssue("ICU-22781", "Handle concentr/perbillion in ICU");
continue;