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