| /* | |
| * Copyright (c) 2019, 2020, 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 4221795 6565620 6959267 7070436 7198195 8032446 8174270 8221431 8239383 | |
| * @summary Confirm Normalizer's fundamental behavior | |
| * @library /lib/testlibrary/java/lang | |
| * @modules java.base/sun.text java.base/jdk.internal.icu.text | |
| * @compile -XDignore.symbol.file ConformanceTest.java | |
| * @run main/timeout=3000 ConformanceTest | |
| */ | |
| import java.io.BufferedReader; | |
| import java.io.File; | |
| import java.io.FileInputStream; | |
| import java.io.InputStreamReader; | |
| import java.nio.charset.Charset; | |
| import java.nio.charset.CharsetDecoder; | |
| import java.util.BitSet; | |
| import java.util.StringTokenizer; | |
| import jdk.internal.icu.text.NormalizerBase; | |
| /* | |
| * Conformance test for java.text.Normalizer and sun.text.Normalizer. | |
| */ | |
| public class ConformanceTest { | |
| // | |
| // Options to be used with sun.text.Normalizer | |
| // | |
| /* | |
| * Default Unicode 3.2.0 normalization. (Provided for IDNA/StringPrep) | |
| * | |
| * - Without Corrigendum 4 fix | |
| * (Different from ICU4J 3.2's Normalizer.) | |
| * - Without Public Review Issue #29 fix | |
| * (Different from ICU4J 3.2's Normalizer.) | |
| */ | |
| private static final int UNICODE_3_2_0 = sun.text.Normalizer.UNICODE_3_2; | |
| /* | |
| * Original Unicode 3.2.0 normalization. (Provided for testing only) | |
| * | |
| * - With Corrigendum 4 fix | |
| * - With Public Revilew Issue #29 fix | |
| */ | |
| private static final int UNICODE_3_2_0_ORIGINAL = | |
| NormalizerBase.UNICODE_3_2; | |
| /* | |
| * Default normalization. In JDK 6, | |
| * - Unicode 4.0.0 | |
| * - With Corrigendum 4 fix | |
| * - Without Public Review Issue #29 fix | |
| * | |
| * In JDK 7, | |
| * - Unicode 5.1.0 | |
| * (Different from ICU4J 3.2's Normalizer.) | |
| * - With Corrigendum 4 fix | |
| * - With Public Review Issue #29 fix | |
| * | |
| * In JDK 8, | |
| * - Unicode 6.1.0 | |
| * - With Corrigendum 4 fix | |
| * - With Public Review Issue #29 fix | |
| * | |
| * When we support Unicode 4.1.0 or later, we need to do normalization | |
| * with Public Review Issue #29 fix. For more details of PRI #29, see | |
| * http://unicode.org/review/pr-29.html . | |
| */ | |
| private static final int UNICODE_LATEST = NormalizerBase.UNICODE_LATEST; | |
| // | |
| // Conformance test datafiles | |
| // | |
| /* | |
| * Conformance test datafile for Unicode 3.2.0 with Corrigendum4 | |
| * corrections. | |
| * This testdata is for sun.text.Normalize(UNICODE_3_2) | |
| * | |
| * This is NOT an original Conformace test data. Some inconvenient test | |
| * cases are commented out. About corrigendum 4, please refer | |
| * http://www.unicode.org/review/resolved-pri.html#pri29 | |
| * | |
| */ | |
| static final String DATA_3_2_0_CORRIGENDUM = | |
| "NormalizationTest-3.2.0.Corrigendum4.txt"; | |
| /* | |
| * Conformance test datafile for Unicode 3.2.0 without Corrigendum4 | |
| * corrections. This is the original Conformace test data. | |
| * | |
| * This testdata is for sun.text.Normalize(UNICODE_3_2_IDNA) | |
| */ | |
| static final String DATA_3_2_0 = "NormalizationTest-3.2.0.txt"; | |
| /* | |
| * Conformance test datafile for the latest Unicode which is supported | |
| * by J2SE. | |
| * Unicode 4.0.0 is the latest version in JDK 5.0 and JDK 6. Unicode 5.1.0 | |
| * in JDK 7, and 6.1.0 in JDK 8. This Unicode can be used via both | |
| * java.text.Normalizer and sun.text.Normalizer. | |
| * | |
| * This testdata is for sun.text.Normalize(UNICODE_LATEST) | |
| */ | |
| static final String DATA_LATEST = "NormalizationTest.txt"; | |
| /* | |
| * Conformance test datafile in ICU4J 3.2. | |
| */ | |
| static final String DATA_ICU = "ICUNormalizationTest.txt"; | |
| /* | |
| * Decorder | |
| */ | |
| static final CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder(); | |
| /* | |
| * List to pick up characters which are not listed in Part1 | |
| */ | |
| static BitSet charList = new BitSet(Character.MAX_CODE_POINT+1); | |
| /* | |
| * Shortcuts | |
| */ | |
| private static final java.text.Normalizer.Form NFC = | |
| java.text.Normalizer.Form.NFC; | |
| private static final java.text.Normalizer.Form NFD = | |
| java.text.Normalizer.Form.NFD; | |
| private static final java.text.Normalizer.Form NFKC = | |
| java.text.Normalizer.Form.NFKC; | |
| private static final java.text.Normalizer.Form NFKD = | |
| java.text.Normalizer.Form.NFKD; | |
| static final java.text.Normalizer.Form[] forms = {NFC, NFD, NFKC, NFKD}; | |
| static TestNormalizer normalizer; | |
| public static void main(String[] args) throws Exception { | |
| ConformanceTest ct = new ConformanceTest(); | |
| ct.test(); | |
| } | |
| void test() throws Exception { | |
| normalizer = new testJavaNormalizer(); | |
| test(DATA_LATEST, UNICODE_LATEST); | |
| normalizer = new testSunNormalizer(); | |
| test(DATA_3_2_0_CORRIGENDUM, UNICODE_3_2_0); | |
| test(DATA_LATEST, UNICODE_LATEST); | |
| test(DATA_ICU, UNICODE_LATEST); | |
| /* Unconformity test */ | |
| // test(DATA_3_2_0, UNICODE_LATEST); | |
| // test(DATA_LATEST, UNICODE_3_2_0); | |
| } | |
| /* | |
| * Main routine of conformance test | |
| */ | |
| private static void test(String filename, int unicodeVer) throws Exception { | |
| File f = filename.equals(DATA_LATEST) ? | |
| UCDFiles.NORMALIZATION_TEST.toFile() : | |
| new File(System.getProperty("test.src", "."), filename); | |
| FileInputStream fis = new FileInputStream(f); | |
| BufferedReader in = | |
| new BufferedReader(new InputStreamReader(fis, decoder)); | |
| System.out.println("\nStart testing for " + normalizer.name + | |
| " with " + filename + " for options: " + | |
| (((unicodeVer & NormalizerBase.UNICODE_3_2) != 0) ? | |
| "Unicode 3.2.0" : "the latest Unicode")); | |
| int lineNo = 0; | |
| String text; | |
| boolean part1test = false; | |
| boolean part1testExists = false; | |
| String[] columns = new String[6]; | |
| while ((text = in.readLine()) != null) { | |
| lineNo ++; | |
| char c = text.charAt(0); | |
| if (c == '#') { | |
| continue; | |
| } else if (c == '@') { | |
| if (text.startsWith("@Part")) { | |
| System.out.println("# Testing data in " + text); | |
| if (text.startsWith("@Part1 ")) { | |
| part1test = true; | |
| part1testExists = true; | |
| } else { | |
| part1test = false; | |
| } | |
| continue; | |
| } | |
| } | |
| prepareColumns(columns, text, filename, lineNo, part1test); | |
| testNFC(columns, unicodeVer, filename, lineNo); | |
| testNFD(columns, unicodeVer, filename, lineNo); | |
| testNFKC(columns, unicodeVer, filename, lineNo); | |
| testNFKD(columns, unicodeVer, filename, lineNo); | |
| } | |
| in.close(); | |
| fis.close(); | |
| if (part1testExists) { | |
| System.out.println("# Testing characters which are not listed in Part1"); | |
| testRemainingChars(filename, unicodeVer); | |
| part1testExists = false; | |
| } | |
| } | |
| /* | |
| * Test for NFC | |
| * | |
| * c2 == NFC(c1) == NFC(c2) == NFC(c3) | |
| * c4 == NFC(c4) == NFC(c5) | |
| */ | |
| private static void testNFC(String[] c, int unicodeVer, | |
| String file, int line) throws Exception { | |
| test(2, c, 1, 3, NFC, unicodeVer, file, line); | |
| test(4, c, 4, 5, NFC, unicodeVer, file, line); | |
| } | |
| /* | |
| * Test for NFD | |
| * | |
| * c3 == NFD(c1) == NFD(c2) == NFD(c3) | |
| * c5 == NFD(c4) == NFD(c5) | |
| */ | |
| private static void testNFD(String[] c, int unicodeVer, | |
| String file, int line) throws Exception { | |
| test(3, c, 1, 3, NFD, unicodeVer, file, line); | |
| test(5, c, 4, 5, NFD, unicodeVer, file, line); | |
| } | |
| /* | |
| * Test for NFKC | |
| * | |
| * c4 == NFKC(c1) == NFKC(c2) == NFKC(c3) == NFKC(c4) == NFKC(c5) | |
| */ | |
| private static void testNFKC(String[] c, int unicodeVer, | |
| String file, int line) throws Exception { | |
| test(4, c, 1, 5, NFKC, unicodeVer, file, line); | |
| } | |
| /* | |
| * Test for NFKD | |
| * | |
| * c5 == NFKD(c1) == NFKD(c2) == NFKD(c3) == NFKD(c4) == NFKD(c5) | |
| */ | |
| private static void testNFKD(String[] c, int unicodeVer, | |
| String file, int line) throws Exception { | |
| test(5, c, 1, 5, NFKD, unicodeVer, file, line); | |
| } | |
| /* | |
| * Test for characters which aren't listed in Part1 | |
| * | |
| * X == NFC(X) == NFD(X) == NFKC(X) == NFKD(X) | |
| */ | |
| private static void testRemainingChars(String file, | |
| int unicodeVer) throws Exception { | |
| for (int i = Character.MIN_CODE_POINT; | |
| i <= Character.MAX_CODE_POINT; | |
| i++) { | |
| if (!charList.get(i)) { | |
| String from = String.valueOf(Character.toChars(i)); | |
| String to; | |
| for (int j = 0; j < forms.length; j++) { | |
| java.text.Normalizer.Form form = forms[j]; | |
| to = normalizer.normalize(from, form, unicodeVer); | |
| if (!from.equals(to)) { | |
| error(form, from, from, to, file, -1); | |
| // } else { | |
| // okay(form, from, from, to, file, -1); | |
| } | |
| if (!normalizer.isNormalized(from, form, unicodeVer)) { | |
| error(form, from, file, -1); | |
| // } else { | |
| // okay(form, from, file, -1); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| /* | |
| * Test normalize() and isNormalized() | |
| */ | |
| private static void test(int col, String[] c, | |
| int FROM, int TO, | |
| java.text.Normalizer.Form form, int unicodeVer, | |
| String file, int line) throws Exception { | |
| for (int i = FROM; i <= TO; i++) { | |
| String got = normalizer.normalize(c[i], form, unicodeVer); | |
| if (!c[col].equals(got)) { | |
| error(form, c[i], c[col], got, file, line); | |
| // } else { | |
| // okay(form, c[i], c[col], got, file, line); | |
| } | |
| /* | |
| * If the original String equals its normalized String, it means | |
| * that the original String is normalizerd. Thus, isNormalized() | |
| * should return true. And, vice versa! | |
| */ | |
| if (c[col].equals(c[i])) { | |
| if (!normalizer.isNormalized(c[i], form, unicodeVer)) { | |
| error(form, c[i], file, line); | |
| // } else { | |
| // okay(form, c[i], file, line); | |
| } | |
| } else { | |
| if (normalizer.isNormalized(c[i], form, unicodeVer)) { | |
| error(form, c[i], file, line); | |
| // } else { | |
| // okay(form, c[i], file, line); | |
| } | |
| } | |
| } | |
| } | |
| /* | |
| * Generate an array of String from a line of conformance datafile. | |
| */ | |
| private static void prepareColumns(String[] cols, String text, | |
| String file, int line, | |
| boolean part1test) throws Exception { | |
| int index = text.indexOf('#'); | |
| if (index != -1) { | |
| text = text.substring(0, index); | |
| } | |
| StringTokenizer st = new StringTokenizer(text, ";"); | |
| int tokenCount = st.countTokens(); | |
| if (tokenCount < 5) { | |
| throw new RuntimeException("# of tokens in datafile should be 6, but got: " + tokenCount + " at line " + line + " in " + file); | |
| } | |
| StringBuffer sb = new StringBuffer(); | |
| for (int i = 1; i <= 5; i++) { | |
| StringTokenizer tst = new StringTokenizer(st.nextToken(), " "); | |
| while (tst.hasMoreTokens()) { | |
| int code = Integer.parseInt(tst.nextToken(), 16); | |
| sb.append(Character.toChars(code)); | |
| } | |
| cols[i] = sb.toString(); | |
| sb.setLength(0); | |
| } | |
| if (part1test) { | |
| charList.set(cols[1].codePointAt(0)); | |
| } | |
| } | |
| /* | |
| * Show an error message when normalize() didn't return the expected value. | |
| * (An exception is sometimes convenient. Therefore, it is commented out | |
| * for the moment.) | |
| */ | |
| private static void error(java.text.Normalizer.Form form, | |
| String from, String to, String got, | |
| String file, int line) throws Exception { | |
| System.err.println("-\t" + form.toString() + ": normalize(" + | |
| toHexString(from) + ") doesn't equal <" + toHexString(to) + | |
| "> at line " + line + " in " + file + ". Got [" + | |
| toHexString(got) + "]"); | |
| throw new RuntimeException("Normalization(" + form.toString() + ") failed"); | |
| } | |
| /* | |
| * Show an error message when isNormalize() didn't return the expected | |
| * value. | |
| * (An exception is sometimes convenient. Therefore, it is commented out | |
| * for the moment.) | |
| */ | |
| private static void error(java.text.Normalizer.Form form, String s, | |
| String file, int line) throws Exception { | |
| System.err.println("\t" + form.toString() + ": isNormalized(" + | |
| toHexString(s) + ") returned the wrong value at line " + line + | |
| " in " + file); | |
| throw new RuntimeException("Normalization(" + form.toString() +") failed"); | |
| } | |
| /* | |
| * (For debugging) | |
| * Shows a message when normalize() returned the expected value. | |
| */ | |
| private static void okay(java.text.Normalizer.Form form, | |
| String from, String to, String got, | |
| String file, int line) { | |
| System.out.println("\t" + form.toString() + ": normalize(" + | |
| toHexString(from) + ") equals <" + toHexString(to) + | |
| "> at line " + line + " in " + file + ". Got [" + | |
| toHexString(got) + "]"); | |
| } | |
| /* | |
| * (For debugging) | |
| * Shows a message when isNormalized() returned the expected value. | |
| */ | |
| private static void okay(java.text.Normalizer.Form form, String s, | |
| String file, int line) { | |
| System.out.println("\t" + form.toString() + ": isNormalized(" + | |
| toHexString(s) + ") returned the correct value at line " + | |
| line + " in " + file); | |
| } | |
| /* | |
| * Returns a spece-delimited hex String | |
| */ | |
| private static String toHexString(String s) { | |
| StringBuffer sb = new StringBuffer(" "); | |
| for (int i = 0; i < s.length(); i++) { | |
| sb.append(Integer.toHexString(s.charAt(i))); | |
| sb.append(' '); | |
| } | |
| return sb.toString(); | |
| } | |
| /* | |
| * Abstract class to call each Normalizer in java.text or sun.text. | |
| */ | |
| private abstract class TestNormalizer { | |
| String name; | |
| TestNormalizer(String str) { | |
| name = str; | |
| } | |
| String getNormalizerName() { | |
| return name; | |
| } | |
| abstract String normalize(CharSequence cs, | |
| java.text.Normalizer.Form form, | |
| int option); | |
| abstract boolean isNormalized(CharSequence cs, | |
| java.text.Normalizer.Form form, | |
| int option); | |
| } | |
| /* | |
| * For java.text.Normalizer | |
| * - normalize(CharSequence, Normalizer.Form) | |
| * - isNormalized(CharSequence, Normalizer.Form) | |
| */ | |
| private class testJavaNormalizer extends TestNormalizer { | |
| testJavaNormalizer() { | |
| super("java.text.Normalizer"); | |
| } | |
| String normalize(CharSequence cs, | |
| java.text.Normalizer.Form form, | |
| int option) { | |
| return java.text.Normalizer.normalize(cs, form); | |
| } | |
| boolean isNormalized(CharSequence cs, | |
| java.text.Normalizer.Form form, | |
| int option) { | |
| return java.text.Normalizer.isNormalized(cs, form); | |
| } | |
| } | |
| /* | |
| * For sun.text.Normalizer | |
| * - normalize(CharSequence, Normalizer.Form, int) | |
| * - isNormalized(CharSequence, Normalizer.Form, int) | |
| */ | |
| private class testSunNormalizer extends TestNormalizer { | |
| testSunNormalizer() { | |
| super("sun.text.Normalizer"); | |
| } | |
| String normalize(CharSequence cs, | |
| java.text.Normalizer.Form form, | |
| int option) { | |
| return sun.text.Normalizer.normalize(cs, form, option); | |
| } | |
| boolean isNormalized(CharSequence cs, | |
| java.text.Normalizer.Form form, | |
| int option) { | |
| return sun.text.Normalizer.isNormalized(cs, form, option); | |
| } | |
| } | |
| } |