1/*
2 * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 *
26 * @test
27 * @bug 4533872 4915683 4985217 5017280
28 * @summary Unit tests for supplementary character support (JSR-204)
29 */
30
31public class Supplementary {
32
33    public static void main(String[] args) {
34        test1();        // Test for codePointAt(int index)
35        test2();        // Test for codePointBefore(int index)
36        test3();        // Test for reverse()
37        test4();        // Test for appendCodePoint(int codePoint)
38        test5();        // Test for codePointCount(int beginIndex, int endIndex)
39        test6();        // Test for offsetByCodePoints(int index, int offset)
40        testDontReadOutOfBoundsTrailingSurrogate();
41    }
42
43    /* Text strings which are used as input data.
44     * The comment above each text string means the index of each 16-bit char
45     * for convenience.
46     */
47    static final String[] input = {
48      /*                               111     1     111111     22222
49         0123     4     5678     9     012     3     456789     01234 */
50        "abc\uD800\uDC00def\uD800\uD800ab\uD800\uDC00cdefa\uDC00bcdef",
51      /*                          1     1111     1111     1     222
52         0     12345     6789     0     1234     5678     9     012     */
53        "\uD800defg\uD800hij\uD800\uDC00klm\uDC00nop\uDC00\uD800rt\uDC00",
54      /*                          11     1     1111     1     112     222
55         0     12345     6     78901     2     3456     7     890     123     */
56        "\uDC00abcd\uDBFF\uDFFFefgh\uD800\uDC009ik\uDC00\uDC00lm\uDC00no\uD800",
57      /*                                    111     111111     1 22     2
58         0     1     2345     678     9     012     345678     9 01     2     */
59        "\uD800\uDC00!#$\uD800%&\uD800\uDC00;+\uDC00<>;=^\uDC00\\@\uD800\uDC00",
60
61        // includes an undefined supplementary character in Unicode 4.0.0
62      /*                                    1     11     1     1111     1
63         0     1     2345     6     789     0     12     3     4567     8     */
64        "\uDB40\uDE00abc\uDE01\uDB40de\uDB40\uDE02f\uDB40\uDE03ghi\uDB40\uDE02",
65    };
66
67
68    /* Expected results for:
69     *     test1(): for codePointAt()
70     *
71     * Each character in each array is the golden data for each text string
72     * in the above input data. For example, the first data in each array is
73     * for the first input string.
74     */
75    static final int[][] golden1 = {
76        {'a',    0xD800, 0xDC00,  0x10000, 0xE0200}, // codePointAt(0)
77        {0xD800, 0x10000, 'g',    0xDC00,  0xE0202}, // codePointAt(9)
78        {'f',    0xDC00,  0xD800, 0xDC00,  0xDE02},  // codePointAt(length-1)
79    };
80
81    /*
82     * Test for codePointAt(int index) method
83     */
84    static void test1() {
85
86        for (int i = 0; i < input.length; i++) {
87            StringBuilder sb = new StringBuilder(input[i]);
88
89            /*
90             * Normal case
91             */
92            testCodePoint(At, sb, 0, golden1[0][i]);
93            testCodePoint(At, sb, 9, golden1[1][i]);
94            testCodePoint(At, sb, sb.length()-1, golden1[2][i]);
95
96            /*
97             * Abnormal case - verify that an exception is thrown.
98             */
99            testCodePoint(At, sb, -1);
100            testCodePoint(At, sb, sb.length());
101        }
102    }
103
104
105    /* Expected results for:
106     *     test2(): for codePointBefore()
107     *
108     * Each character in each array is the golden data for each text string
109     * in the above input data. For example, the first data in each array is
110     * for the first input string.
111     */
112    static final int[][] golden2 = {
113        {'a',    0xD800, 0xDC00,  0xD800,  0xDB40},  // codePointBefore(1)
114        {0xD800, 'l',    0x10000, 0xDC00,  0xDB40},  // codePointBefore(13)
115        {'f',    0xDC00, 0xD800,  0x10000, 0xE0202}, // codePointBefore(length)
116    };
117
118    /*
119     * Test for codePointBefore(int index) method
120     */
121    static void test2() {
122
123        for (int i = 0; i < input.length; i++) {
124            StringBuilder sb = new StringBuilder(input[i]);
125
126            /*
127             * Normal case
128             */
129            testCodePoint(Before, sb, 1, golden2[0][i]);
130            testCodePoint(Before, sb, 13, golden2[1][i]);
131            testCodePoint(Before, sb, sb.length(), golden2[2][i]);
132
133            /*
134             * Abnormal case - verify that an exception is thrown.
135             */
136            testCodePoint(Before, sb, 0);
137            testCodePoint(Before, sb, sb.length()+1);
138        }
139    }
140
141
142    /* Expected results for:
143     *     test3(): for reverse()
144     *
145     * Unlike golden1 and golden2, each array is the golden data for each text
146     * string in the above input data. For example, the first array is  for
147     * the first input string.
148     */
149    static final String[] golden3 = {
150        "fedcb\uDC00afedc\uD800\uDC00ba\uD800\uD800fed\uD800\uDC00cba",
151        "\uDC00tr\uD800\uDC00pon\uDC00mlk\uD800\uDC00jih\uD800gfed\uD800",
152        "\uD800on\uDC00ml\uDC00\uDC00ki9\uD800\uDC00hgfe\uDBFF\uDFFFdcba\uDC00",
153        "\uD800\uDC00@\\\uDC00^=;><\uDC00+;\uD800\uDC00&%\uD800$#!\uD800\uDC00",
154
155        // includes an undefined supplementary character in Unicode 4.0.0
156        "\uDB40\uDE02ihg\uDB40\uDE03f\uDB40\uDE02ed\uDB40\uDE01cba\uDB40\uDE00",
157    };
158
159    // Additional input data & expected result for test3()
160    static final String[][] testdata1 = {
161        {"a\uD800\uDC00", "\uD800\uDC00a"},
162        {"a\uDC00\uD800", "\uD800\uDC00a"},
163        {"\uD800\uDC00a", "a\uD800\uDC00"},
164        {"\uDC00\uD800a", "a\uD800\uDC00"},
165        {"\uDC00\uD800\uD801", "\uD801\uD800\uDC00"},
166        {"\uDC00\uD800\uDC01", "\uD800\uDC01\uDC00"},
167        {"\uD801\uD800\uDC00", "\uD800\uDC00\uD801"},
168        {"\uD800\uDC01\uDC00", "\uDC00\uD800\uDC01"},
169        {"\uD800\uDC00\uDC01\uD801", "\uD801\uDC01\uD800\uDC00"},
170    };
171
172    /*
173     * Test for reverse() method
174     */
175    static void test3() {
176        for (int i = 0; i < input.length; i++) {
177            StringBuilder sb = new StringBuilder(input[i]).reverse();
178
179            check(!golden3[i].equals(sb.toString()),
180                 "reverse() for <" + toHexString(input[i]) + ">",
181                 sb, golden3[i]);
182        }
183
184        for (int i = 0; i < testdata1.length; i++) {
185            StringBuilder sb = new StringBuilder(testdata1[i][0]).reverse();
186
187            check(!testdata1[i][1].equals(sb.toString()),
188                 "reverse() for <" + toHexString(testdata1[i][0]) + ">",
189                 sb, testdata1[i][1]);
190        }
191    }
192
193    /**
194     * Test for appendCodePoint() method
195     */
196    static void test4() {
197        for (int i = 0; i < input.length; i++) {
198            String s = input[i];
199            StringBuilder sb = new StringBuilder();
200            int c;
201            for (int j = 0; j < s.length(); j += Character.charCount(c)) {
202                c = s.codePointAt(j);
203                StringBuilder rsb = sb.appendCodePoint(c);
204                check(sb != rsb, "appendCodePoint returned a wrong object");
205                int sbc = sb.codePointAt(j);
206                check(sbc != c, "appendCodePoint("+j+") != c", sbc, c);
207            }
208            check(!s.equals(sb.toString()),
209                  "appendCodePoint() produced a wrong result with input["+i+"]");
210        }
211
212        // test exception
213        testAppendCodePoint(-1, IllegalArgumentException.class);
214        testAppendCodePoint(Character.MAX_CODE_POINT+1, IllegalArgumentException.class);
215    }
216
217    /**
218     * Test codePointCount(int, int)
219     *
220     * This test case assumes that
221     * Character.codePointCount(CharSequence, int, int) works
222     * correctly.
223     */
224    static void test5() {
225        for (int i = 0; i < input.length; i++) {
226            String s = input[i];
227            StringBuilder sb = new StringBuilder(s);
228            int length = sb.length();
229            for (int j = 0; j <= length; j++) {
230                int result = sb.codePointCount(j, length);
231                int expected = Character.codePointCount(sb, j, length);
232                check(result != expected, "codePointCount(input["+i+"], "+j+", "+length+")",
233                      result, expected);
234            }
235            for (int j = length; j >= 0; j--) {
236                int result = sb.codePointCount(0, j);
237                int expected = Character.codePointCount(sb, 0, j);
238                check(result != expected, "codePointCount(input["+i+"], 0, "+j+")",
239                      result, expected);
240            }
241
242            // test exceptions
243            testCodePointCount(null, 0, 0, NullPointerException.class);
244            testCodePointCount(sb, -1, length, IndexOutOfBoundsException.class);
245            testCodePointCount(sb, 0, length+1, IndexOutOfBoundsException.class);
246            testCodePointCount(sb, length, length-1, IndexOutOfBoundsException.class);
247        }
248    }
249
250    /**
251     * Test offsetByCodePoints(int, int)
252     *
253     * This test case assumes that
254     * Character.codePointCount(CharSequence, int, int) works
255     * correctly.
256     */
257    static void test6() {
258        for (int i = 0; i < input.length; i++) {
259            String s = input[i];
260            StringBuilder sb = new StringBuilder(s);
261            int length = s.length();
262            for (int j = 0; j <= length; j++) {
263                int nCodePoints = Character.codePointCount(sb, j, length);
264                int result = sb.offsetByCodePoints(j, nCodePoints);
265                check(result != length,
266                      "offsetByCodePoints(input["+i+"], "+j+", "+nCodePoints+")",
267                      result, length);
268                result = sb.offsetByCodePoints(length, -nCodePoints);
269                int expected = j;
270                if (j > 0 && j < length) {
271                    int cp = sb.codePointBefore(j+1);
272                    if (Character.isSupplementaryCodePoint(cp)) {
273                        expected--;
274                    }
275                }
276                check(result != expected,
277                      "offsetByCodePoints(input["+i+"], "+j+", "+(-nCodePoints)+")",
278                      result, expected);
279            }
280            for (int j = length; j >= 0; j--) {
281                int nCodePoints = Character.codePointCount(sb, 0, j);
282                int result = sb.offsetByCodePoints(0, nCodePoints);
283                int expected = j;
284                if (j > 0 && j < length) {
285                    int cp = sb.codePointAt(j-1);
286                     if (Character.isSupplementaryCodePoint(cp)) {
287                        expected++;
288                    }
289                }
290                check(result != expected,
291                      "offsetByCodePoints(input["+i+"], 0, "+nCodePoints+")",
292                      result, expected);
293                result = sb.offsetByCodePoints(j, -nCodePoints);
294                check(result != 0,
295                      "offsetBycodePoints(input["+i+"], "+j+", "+(-nCodePoints)+")",
296                      result, 0);
297            }
298
299            // test exceptions
300            testOffsetByCodePoints(null, 0, 0, NullPointerException.class);
301            testOffsetByCodePoints(sb, -1, length, IndexOutOfBoundsException.class);
302            testOffsetByCodePoints(sb, 0, length+1, IndexOutOfBoundsException.class);
303            testOffsetByCodePoints(sb, 1, -2, IndexOutOfBoundsException.class);
304            testOffsetByCodePoints(sb, length, length-1, IndexOutOfBoundsException.class);
305            testOffsetByCodePoints(sb, length, -(length+1), IndexOutOfBoundsException.class);
306        }
307    }
308
309    static void testDontReadOutOfBoundsTrailingSurrogate() {
310        StringBuilder sb = new StringBuilder();
311        int suppl = Character.MIN_SUPPLEMENTARY_CODE_POINT;
312        sb.appendCodePoint(suppl);
313        check(sb.codePointAt(0) != (int) suppl,
314              "codePointAt(0)", sb.codePointAt(0), suppl);
315        check(sb.length() != 2, "sb.length()");
316        sb.setLength(1);
317        check(sb.length() != 1, "sb.length()");
318        check(sb.codePointAt(0) != Character.highSurrogate(suppl),
319              "codePointAt(0)",
320              sb.codePointAt(0), Character.highSurrogate(suppl));
321    }
322
323    static final boolean At = true, Before = false;
324
325    static void testCodePoint(boolean isAt, StringBuilder sb, int index, int expected) {
326        int c = isAt ? sb.codePointAt(index) : sb.codePointBefore(index);
327
328        check(c != expected,
329              "codePoint" + (isAt ? "At" : "Before") + "(" + index + ") for <"
330              + sb + ">", c, expected);
331    }
332
333    static void testCodePoint(boolean isAt, StringBuilder sb, int index) {
334        boolean exceptionOccurred = false;
335
336        try {
337            int c = isAt ? sb.codePointAt(index) : sb.codePointBefore(index);
338        }
339        catch (StringIndexOutOfBoundsException e) {
340            exceptionOccurred = true;
341        }
342        check(!exceptionOccurred,
343              "codePoint" + (isAt ? "At" : "Before") + "(" + index + ") for <"
344              + sb + "> should throw StringIndexOutOfBoundsPointerException.");
345    }
346
347    static void testAppendCodePoint(int codePoint, Class expectedException) {
348        try {
349            new StringBuilder().appendCodePoint(codePoint);
350        } catch (Exception e) {
351            if (expectedException.isInstance(e)) {
352                return;
353            }
354            throw new RuntimeException("Error: Unexpected exception", e);
355        }
356        check(true, "appendCodePoint(" + toHexString(codePoint) + ") didn't throw "
357              + expectedException.getName());
358    }
359
360    static void testCodePointCount(StringBuilder sb, int beginIndex, int endIndex,
361                                   Class expectedException) {
362        try {
363            int n = sb.codePointCount(beginIndex, endIndex);
364        } catch (Exception e) {
365            if (expectedException.isInstance(e)) {
366                return;
367            }
368            throw new RuntimeException("Error: Unexpected exception", e);
369        }
370        check(true, "codePointCount() didn't throw " + expectedException.getName());
371    }
372
373    static void testOffsetByCodePoints(StringBuilder sb, int index, int offset,
374                                       Class expectedException) {
375        try {
376            int n = sb.offsetByCodePoints(index, offset);
377        } catch (Exception e) {
378            if (expectedException.isInstance(e)) {
379                return;
380            }
381            throw new RuntimeException("Error: Unexpected exception", e);
382        }
383        check(true, "offsetByCodePoints() didn't throw " + expectedException.getName());
384    }
385
386    static void check(boolean err, String msg) {
387        if (err) {
388            throw new RuntimeException("Error: " + msg);
389        }
390    }
391
392    static void check(boolean err, String s, int got, int expected) {
393        if (err) {
394            throw new RuntimeException("Error: " + s
395                                       + " returned an unexpected value. got "
396                                       + toHexString(got)
397                                       + ", expected "
398                                       + toHexString(expected));
399        }
400    }
401
402    static void check(boolean err, String s, StringBuilder got, String expected) {
403        if (err) {
404            throw new RuntimeException("Error: " + s
405                                       + " returned an unexpected value. got <"
406                                       + toHexString(got.toString())
407                                       + ">, expected <"
408                                       + toHexString(expected)
409                                       + ">");
410        }
411    }
412
413    private static String toHexString(int c) {
414        return "0x" + Integer.toHexString(c);
415    }
416
417    private static String toHexString(String s) {
418        StringBuilder sb = new StringBuilder();
419        for (int i = 0; i < s.length(); i++) {
420            char c = s.charAt(i);
421
422            sb.append(" 0x");
423            if (c < 0x10) sb.append('0');
424            if (c < 0x100) sb.append('0');
425            if (c < 0x1000) sb.append('0');
426            sb.append(Integer.toHexString(c));
427        }
428        sb.append(' ');
429        return sb.toString();
430    }
431}
432