1 /*
2  * Copyright (c) 2020, 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 package test.java.util.HexFormat;
24 
25 import org.testng.annotations.DataProvider;
26 import org.testng.annotations.Test;
27 import org.testng.SkipException;
28 
29 import java.io.CharArrayWriter;
30 import java.io.IOException;
31 import java.io.UncheckedIOException;
32 import java.nio.CharBuffer;
33 import java.util.Arrays;
34 import java.util.HexFormat;
35 import java.util.Locale;
36 
37 import static org.testng.Assert.assertEquals;
38 import static org.testng.Assert.assertFalse;
39 import static org.testng.Assert.assertSame;
40 import static org.testng.Assert.assertThrows;
41 import static org.testng.Assert.assertTrue;
42 import static org.testng.Assert.expectThrows;
43 
44 import android.platform.test.annotations.LargeTest;
45 
46 /*
47  * @test
48  * @summary Check HexFormat formatting and parsing
49  * @run testng/othervm HexFormatTest
50  */
51 
52 @Test
53 // Android-changed: test methods are changed to public. static package private methods
54 // are not picked by the test runner.
55 public class HexFormatTest {
56     static final Class<NullPointerException> NPE = NullPointerException.class;
57 
58     @DataProvider(name = "HexFormattersParsers")
hexFormattersParsers()59     Object[][] hexFormattersParsers() {
60         return new Object[][]{
61                 {"", "", "", true,
62                         HexFormat.of().withUpperCase()},
63                 {", ", "#", "L", false,
64                         HexFormat.ofDelimiter(", ").withPrefix("#").withSuffix("L")},
65                 {"", "", "", false,
66                         HexFormat.of().withPrefix("").withSuffix("")},
67                 {".", "", "", false,
68                         HexFormat.ofDelimiter(".").withPrefix("").withSuffix("")},
69                 {", ", "0x", "", true,
70                         HexFormat.ofDelimiter(", ").withUpperCase().withPrefix("0x")},
71                 {"\u0202", "\u0203", "\u0204", false,
72                         HexFormat.ofDelimiter("\u0202").withPrefix("\u0203").withSuffix("\u0204")},
73                 {"\u0202", "", "", false,
74                         HexFormat.ofDelimiter("\u0202")},
75 
76         };
77     }
78 
79     @DataProvider(name = "HexStringsThrowing")
HexStringsThrowing()80     Object[][] HexStringsThrowing() {
81         return new Object[][]{
82                 {"0", ":", "", ""},         // wrong string length
83                 {"01:", ":", "", ""},       // wrong string length
84                 {"01:0", ":", "", ""},      // wrong string length
85                 {"0", ",", "", ""},         // wrong length and separator
86                 {"01:", ",", "", ""},       // wrong length and separator
87                 {"01:0", ",", "", ""},      // wrong length and separator
88                 {"01:00", ",", "", ""},     // wrong separator
89                 {"00]", ",", "[", "]"},     // missing prefix
90                 {"[00", ",", "[", "]"},     // missing suffix
91                 {"]", ",", "[", "]"},       // missing prefix
92                 {"[", ",", "[", "]"},       // missing suffix
93                 {"00", ",", "abc", ""},     // Prefix longer than string
94                 {"01", ",", "", "def"},     // Suffix longer than string
95                 {"abc00,", ",", "abc", ""},     // Prefix and delim but not another value
96                 {"01def,", ",", "", "def"},     // Suffix and delim but not another value
97         };
98     }
99 
100     @DataProvider(name = "BadBytesThrowing")
badBytesThrowing()101     Object[][] badBytesThrowing() {
102         return new Object[][]{
103                 {new byte[1], 0, 2},        // bad toIndex
104                 {new byte[1], 1, 2},        // bad fromIndex + toIndex
105                 {new byte[1], -1, 2},       // bad fromIndex
106                 {new byte[1], -1, 1},       // bad fromIndex
107                 {new byte[1], 0, -1},       // bad toIndex
108                 {new byte[1], 1, -1},       // bad toIndex
109         };
110     }
111 
112     @DataProvider(name = "BadParseHexThrowing")
badParseHexThrowing()113     Object[][] badParseHexThrowing() {
114         return new Object[][]{
115                 {"a", 0, 2, IndexOutOfBoundsException.class},        // bad toIndex
116                 {"b", 1, 2, IndexOutOfBoundsException.class},        // bad toIndex
117                 {"a", -1, 2, IndexOutOfBoundsException.class},       // bad fromIndex
118                 {"b", -1, 1, IndexOutOfBoundsException.class},       // bad fromIndex
119                 {"a", 0, -1, IndexOutOfBoundsException.class},       // bad toIndex
120                 {"b", 1, -1, IndexOutOfBoundsException.class},       // bad fromIndex + toIndex
121                 {"76543210", 0, 7, IllegalArgumentException.class},  // odd number of digits
122                 {"zz00", 0, 4, IllegalArgumentException.class},      // non-hex digits
123                 {"00zz", 0, 4, IllegalArgumentException.class},      // non-hex digits
124         };
125     }
126 
127     @DataProvider(name = "BadFromHexDigitsThrowing")
badHexDigitsThrowing()128     Object[][] badHexDigitsThrowing() {
129         return new Object[][]{
130                 {"a", 0, 2, IndexOutOfBoundsException.class},        // bad toIndex
131                 {"b", 1, 2, IndexOutOfBoundsException.class},        // bad fromIndex + toIndex
132                 {"a", -1, 2, IndexOutOfBoundsException.class},       // bad toIndex
133                 {"b", -1, 1, IndexOutOfBoundsException.class},       // bad fromIndex + toIndex
134                 {"a", 0, -1, IndexOutOfBoundsException.class},       // bad toIndex
135                 {"b", 1, -1, IndexOutOfBoundsException.class},       // bad fromIndex + toIndex
136         };
137     }
138 
genBytes(int origin, int len)139     static byte[] genBytes(int origin, int len) {
140         byte[] bytes = new byte[len];
141         for (int i = 0; i < len; i++)
142             bytes[i] = (byte) (origin + i);
143         return bytes;
144     }
145 
146     @Test
testToHex()147     public void testToHex() {
148         HexFormat hex = HexFormat.of();
149         for (int i = 0; i < 32; i++) {
150             char c = hex.toLowHexDigit((byte)i);
151             String expected = Integer.toHexString(i & 0xf);
152             assertEquals(c, expected.charAt(0), "toHex formatting");
153         }
154     }
155 
156     @Test
testToHexDigits()157     public void testToHexDigits() {
158         HexFormat hex = HexFormat.of();
159         for (int i = 0; i < 256; i++) {
160             String actual = hex.toHexDigits((byte)i);
161             int expected = HexFormat.fromHexDigits(actual);
162             assertEquals(expected, i, "fromHexDigits");
163             assertEquals(actual.charAt(0), hex.toHighHexDigit((byte)i),
164                     "first char mismatch");
165             assertEquals(actual.charAt(1), hex.toLowHexDigit((byte)i),
166                     "second char mismatch");
167         }
168     }
169 
170     @Test
testIsHexDigit()171     public void testIsHexDigit() {
172         for (int i = 0; i < 0x3ff; i++) {
173             boolean actual = HexFormat.isHexDigit(i);
174             boolean expected = Character.digit(i, 16) >= 0;
175             assertEquals(actual, expected, "isHexDigit: " + i);
176         }
177     }
178 
179     @Test
testFromHexDigit()180     public void testFromHexDigit() {
181         String chars = "0123456789ABCDEF0123456789abcdef";
182         for (int i = 0; i < chars.length(); i++) {
183             int v = HexFormat.fromHexDigit(chars.charAt(i));
184             assertEquals(v, i & 0xf, "fromHex decode");
185         }
186     }
187 
188     @Test
testFromHexInvalid()189     public void testFromHexInvalid() {
190         for (int i = 0; i < 65536; i++) {
191             char ch = (char)i;
192             if (ch > 0xff || Character.digit(ch, 16) < 0) {
193                 assertFalse(HexFormat.isHexDigit(ch), "isHexDigit incorrect for '" + ch + "'  = " + i);
194                 expectThrows(NumberFormatException.class,
195                         () -> HexFormat.fromHexDigit(ch));
196 
197             }
198         }
199     }
200 
201     @Test
testAppendHexByteWithStringBuilder()202     public void testAppendHexByteWithStringBuilder() {
203         HexFormat hex = HexFormat.of();
204         StringBuilder sb = new StringBuilder();
205         for (int i = 0; i < 256; i++) {
206             sb.setLength(0);
207             StringBuilder sb1 = hex.toHexDigits(sb, (byte)i);
208             assertSame(sb1, sb, "toHexDigits returned different StringBuilder");
209             assertEquals(sb.length(), 2, "wrong length after append: " + i);
210             assertEquals(sb.charAt(0), hex.toHighHexDigit((byte)i), "MSB converted wrong");
211             assertEquals(sb.charAt(1), hex.toLowHexDigit((byte)i), "LSB converted wrong");
212 
213             assertEquals(HexFormat.fromHexDigits(sb), i, "hex.format(sb, byte) wrong");
214         }
215     }
216 
217     @Test
testAppendHexByteWithCharBuffer()218     static void testAppendHexByteWithCharBuffer() {
219         HexFormat hex = HexFormat.of();
220         CharBuffer cb = CharBuffer.allocate(256);
221         for (int i = 1; i <= 128; i++) {
222             CharBuffer cb1 = hex.toHexDigits(cb, (byte)i);
223             assertTrue(cb1 == cb);
224             assertEquals(cb.position(), i * 2);
225         }
226         assertEquals(cb.remaining(), 0);
227     }
228 
229     @Test
testAppendHexByteWithCharArrayWriter()230     public void testAppendHexByteWithCharArrayWriter() {
231         HexFormat hex = HexFormat.of();
232         CharArrayWriter caw = new CharArrayWriter();
233         for (int i = 1; i <= 128; i++) {
234             CharArrayWriter caw1 = hex.toHexDigits(caw, (byte)i);
235             assertTrue(caw1 == caw);
236             assertEquals(caw.size(), i * 2);
237         }
238     }
239 
240     @Test
testFromHexPairInvalid()241     public void testFromHexPairInvalid() {
242         HexFormat hex = HexFormat.of();
243 
244         // An assortment of invalid characters
245         String chars = "-0--0-";
246         for (int i = 0; i < chars.length(); i += 2) {
247             final int ndx = i;
248             Throwable ex = expectThrows(NumberFormatException.class,
249                     () -> HexFormat.fromHexDigits(chars.subSequence(ndx, ndx+2)));
250             System.out.println(ex);
251         }
252     }
253 
254     @Test(dataProvider = "HexStringsThrowing")
testToBytesThrowing(String value, String sep, String prefix, String suffix)255     public void testToBytesThrowing(String value, String sep, String prefix, String suffix) {
256         HexFormat hex = HexFormat.ofDelimiter(sep).withPrefix(prefix).withSuffix(suffix);
257         Throwable ex = expectThrows(IllegalArgumentException.class,
258                 () -> {
259                     byte[] v = hex.parseHex(value);
260                     System.out.println("str: " + value + ", actual: " + v + ", bytes: " +
261                                     Arrays.toString(v));
262                 });
263         System.out.println("ex: " + ex);
264     }
265 
266     @Test
testFactoryNPE()267     public void testFactoryNPE() {
268         assertThrows(NPE, () -> HexFormat.ofDelimiter(null));
269         assertThrows(NPE, () -> HexFormat.of().withDelimiter(null));
270         assertThrows(NPE, () -> HexFormat.of().withPrefix(null));
271         assertThrows(NPE, () -> HexFormat.of().withSuffix(null));
272     }
273 
274     @Test
testFormatHexNPE()275     public void testFormatHexNPE() {
276         assertThrows(NPE, () -> HexFormat.of().formatHex(null));
277         assertThrows(NPE, () -> HexFormat.of().formatHex(null, 0, 1));
278         assertThrows(NPE, () -> HexFormat.of().formatHex(null, null));
279         assertThrows(NPE,  () -> HexFormat.of().formatHex(null, null, 0, 0));
280         StringBuilder sb = new StringBuilder();
281         assertThrows(NPE, () -> HexFormat.of().formatHex(sb, null));
282         assertThrows(NPE, () -> HexFormat.of().formatHex(sb, null, 0, 1));
283     }
284 
285     @Test
testParseHexNPE()286     public void testParseHexNPE() {
287         assertThrows(NPE, () -> HexFormat.of().parseHex(null));
288         assertThrows(NPE, () -> HexFormat.of().parseHex((String)null, 0, 0));
289         assertThrows(NPE, () -> HexFormat.of().parseHex((char[])null, 0, 0));
290     }
291 
292     @Test
testFromHexNPE()293     public void testFromHexNPE() {
294         assertThrows(NPE, () -> HexFormat.fromHexDigits(null));
295         assertThrows(NPE, () -> HexFormat.fromHexDigits(null, 0, 0));
296         assertThrows(NPE, () -> HexFormat.fromHexDigitsToLong(null));
297         assertThrows(NPE, () -> HexFormat.fromHexDigitsToLong(null, 0, 0));
298     }
299 
300     @Test
testToHexDigitsNPE()301     public void testToHexDigitsNPE() {
302         assertThrows(NPE, () -> HexFormat.of().toHexDigits(null, (byte)0));
303     }
304 
305     @Test(dataProvider = "BadParseHexThrowing")
badParseHex(String string, int offset, int length, Class<? extends Throwable> exClass)306     public void badParseHex(String string, int offset, int length,
307                             Class<? extends Throwable> exClass) {
308         assertThrows(exClass,
309                 () -> HexFormat.of().parseHex(string, offset, length));
310         char[] chars = string.toCharArray();
311         assertThrows(exClass,
312                 () -> HexFormat.of().parseHex(chars, offset, length));
313     }
314 
315     @Test(dataProvider = "BadFromHexDigitsThrowing")
badFromHexDigits(String string, int fromIndex, int toIndex, Class<? extends Throwable> exClass)316     public void badFromHexDigits(String string, int fromIndex, int toIndex,
317                            Class<? extends Throwable> exClass) {
318         assertThrows(exClass,
319                 () -> HexFormat.fromHexDigits(string, fromIndex, toIndex));
320         assertThrows(exClass,
321                 () -> HexFormat.fromHexDigitsToLong(string, fromIndex, toIndex));
322     }
323 
324     // Verify IAE for strings that are too long for the target primitive type
325     // or the number of requested digits is too large.
326     @Test
wrongNumberDigits()327     public void wrongNumberDigits() {
328         assertThrows(IllegalArgumentException.class,
329                 () -> HexFormat.fromHexDigits("9876543210"));
330         assertThrows(IllegalArgumentException.class,
331                 () -> HexFormat.fromHexDigits("9876543210", 0, 9));
332         assertThrows(IllegalArgumentException.class,
333                 () -> HexFormat.fromHexDigitsToLong("98765432109876543210"));
334         assertThrows(IllegalArgumentException.class,
335                 () -> HexFormat.fromHexDigitsToLong("98765432109876543210", 0, 17));
336     }
337 
338     @Test(dataProvider="HexFormattersParsers")
testFormatter(String delimiter, String prefix, String suffix, boolean uppercase, HexFormat hex)339     public void testFormatter(String delimiter, String prefix, String suffix,
340                                    boolean uppercase,
341                                    HexFormat hex) {
342         byte[] expected = genBytes('A', 15);
343         String res = hex.formatHex(expected);
344         assertTrue(res.startsWith(prefix), "Prefix not found");
345         assertTrue(res.endsWith(suffix), "Suffix not found");
346         int expectedLen = expected.length * (2 + prefix.length() +
347                 delimiter.length() + suffix.length()) - delimiter.length();
348         assertEquals(res.length(), expectedLen, "String length");
349 
350         if (expected.length > 1) {
351             // check prefix and suffix is present for each hex pair
352             for (int i = 0; i < expected.length; i++) {
353                 int valueChars = prefix.length() + 2 + suffix.length();
354                 int offset = i * (valueChars + delimiter.length());
355                 String value = res.substring(offset, offset + valueChars);
356                 assertTrue(value.startsWith(prefix), "wrong prefix");
357                 assertTrue(value.endsWith(suffix), "wrong suffix");
358 
359                 // Check case of digits
360                 String cc = value.substring(prefix.length(), prefix.length() + 2);
361                 assertEquals(cc,
362                         (uppercase) ? cc.toUpperCase(Locale.ROOT) : cc.toLowerCase(Locale.ROOT),
363                         "Case mismatch");
364                 if (i < expected.length - 1 && !delimiter.isEmpty()) {
365                     // Check the delimiter is present for each pair except the last
366                     assertEquals(res.substring(offset + valueChars,
367                             offset + valueChars + delimiter.length()), delimiter);
368                 }
369             }
370         }
371     }
372 
373     @Test(dataProvider="HexFormattersParsers")
testFormatHexString(String unused1, String unused2, String unused3, boolean unused4, HexFormat hex)374     public void testFormatHexString(String unused1, String unused2, String unused3,
375                                    boolean unused4, HexFormat hex) {
376         byte[] expected = genBytes('A', 15);
377         String s = hex.formatHex(expected);
378         System.out.println("    formatted: " + s);
379 
380         byte[] actual = hex.parseHex(s);
381         System.out.println("    parsed as: " + Arrays.toString(actual));
382         int mismatch = Arrays.mismatch(expected, actual);
383         assertEquals(actual, expected, "format/parse cycle failed, mismatch: " + mismatch);
384     }
385 
386     @Test(dataProvider="HexFormattersParsers")
testParseHexStringRange(String delimiter, String prefix, String suffix, boolean unused4, HexFormat hex)387     public void testParseHexStringRange(String delimiter, String prefix, String suffix,
388                                    boolean unused4, HexFormat hex) {
389         byte[] expected = genBytes('A', 15);
390         String s = hex.formatHex(expected);
391 
392         // Parse values 2, 3, 4 from the generated string
393         int low = 2;
394         int high = 5;
395         int stride = prefix.length() + 2 + suffix.length() + delimiter.length();
396         System.out.println("    formatted subrange: " +
397                 s.substring(low * stride, high * stride - delimiter.length()));
398         byte[] actual = hex.parseHex(s, low * stride,
399                 high * stride - delimiter.length());
400         System.out.println("    parsed as: " + Arrays.toString(actual));
401 
402         assertEquals(actual.length, (high - low), "array length");
403         int mismatch = Arrays.mismatch(expected, low, high, actual, 0, high - low);
404         assertEquals(mismatch, -1, "format/parse cycle failed, mismatch: " + mismatch);
405     }
406 
407     @Test(dataProvider="HexFormattersParsers")
testParseHexEmptyString(String delimiter, String prefix, String suffix, boolean unused4, HexFormat hex)408     public void testParseHexEmptyString(String delimiter, String prefix, String suffix,
409                                         boolean unused4, HexFormat hex) {
410         byte[] actual = hex.parseHex("");
411         assertEquals(actual.length, 0, "empty string parse");
412         actual = hex.parseHex("abc", 0, 0);
413         assertEquals(actual.length, 0, "empty string range parse");
414         actual = hex.parseHex(new char[1], 0, 0);
415         assertEquals(actual.length, 0, "empty char array subrange empty parse");
416     }
417 
418         @Test(dataProvider="HexFormattersParsers")
testFormatHexRangeString(String unused1, String unused2, String unused3, boolean unused4, HexFormat hex)419     public void testFormatHexRangeString(String unused1, String unused2, String unused3,
420                                    boolean unused4, HexFormat hex) {
421         byte[] expected = genBytes('A', 15);
422         int low = 1;
423         int high = expected.length - 2;
424         String s = hex.formatHex(expected, low, high);
425         System.out.println("    formatted: " + s);
426 
427         byte[] actual = hex.parseHex(s);
428         System.out.println("    parsed as: " + Arrays.toString(actual));
429         int mismatch = Arrays.mismatch(expected, low, high, actual, 0, high - low);
430         assertEquals(mismatch, -1, "format/parse cycle failed, mismatch: " + mismatch);
431     }
432 
433     @Test(dataProvider="HexFormattersParsers")
testFormatHexAppendable(String unused1, String unused2, String unused3, boolean unused4, HexFormat hex)434     public void testFormatHexAppendable(String unused1, String unused2, String unused3,
435                                      boolean unused4, HexFormat hex) {
436         byte[] expected = genBytes('A', 15);
437         StringBuilder sb = new StringBuilder();
438         StringBuilder s = hex.formatHex(sb, expected);
439         assertEquals(s, sb, "formatHex returned unknown StringBuilder");
440         System.out.println("    formatted: " + s);
441 
442         byte[] actual = hex.parseHex(s.toString());
443         System.out.println("    parsed as: " + Arrays.toString(actual));
444         int mismatch = Arrays.mismatch(expected, actual);
445         assertEquals(actual, expected, "format/parse cycle failed, mismatch: " + mismatch);
446     }
447 
448     @Test(dataProvider="HexFormattersParsers")
testFormatHexRangeAppendable(String unused1, String unused2, String unused3, boolean unused4, HexFormat hex)449     public void testFormatHexRangeAppendable(String unused1, String unused2, String unused3,
450                                      boolean unused4, HexFormat hex) {
451         byte[] expected = genBytes('A', 15);
452         int low = 1;
453         int high = expected.length - 2;
454         StringBuilder sb = new StringBuilder();
455         StringBuilder s = hex.formatHex(sb, expected, low, high);
456         assertEquals(s, sb, "formatHex returned unknown StringBuilder");
457         System.out.println("    formatted: " + s);
458 
459         byte[] actual = hex.parseHex(s.toString());
460         System.out.println("    parsed as: " + Arrays.toString(actual));
461         byte[] sub = Arrays.copyOfRange(expected, low, high);
462         System.out.println("actual: " + Arrays.toString(actual));
463         System.out.println("sub   : " + Arrays.toString(sub));
464         int mismatch = Arrays.mismatch(expected, low, high, actual, 0, high - low);
465 
466         assertEquals(actual, sub, "format/parse cycle failed, mismatch: " + mismatch);
467         assertEquals(mismatch, -1, "format/parse cycle failed, mismatch: " + mismatch);
468     }
469 
470     @Test(dataProvider="HexFormattersParsers")
testFormatHexCharArray(String unused1, String unused2, String unused3, boolean unused4, HexFormat hex)471     public void testFormatHexCharArray(String unused1, String unused2, String unused3,
472                                      boolean unused4, HexFormat hex) {
473         byte[] expected = genBytes('A', 15);
474         String s = hex.formatHex(expected);
475         System.out.println("    formatted: " + s);
476 
477         char[] chars = s.toCharArray();
478         byte[] actual = hex.parseHex(chars, 0, chars.length);
479         System.out.println("    parsed as: " + Arrays.toString(actual));
480         int mismatch = Arrays.mismatch(expected, actual);
481         assertEquals(actual, expected, "format/parse cycle failed, mismatch: " + mismatch);
482     }
483 
484     @Test(dataProvider="HexFormattersParsers")
testFormatHexCharArrayIndexed(String delimiter, String prefix, String suffix, boolean unused4, HexFormat hex)485     public void testFormatHexCharArrayIndexed(String delimiter, String prefix, String suffix,
486                                               boolean unused4, HexFormat hex) {
487         byte[] expected = genBytes('A', 15);
488         String s = hex.formatHex(expected);
489         System.out.println("    formatted: " + s);
490 
491 
492         // Parse values 2, 3, 4 from the generated string
493         int low = 2;
494         int high = 5;
495         int stride = prefix.length() + 2 + suffix.length() + delimiter.length();
496         System.out.println("    formatted subrange: " +
497                 s.substring(low * stride, high * stride - delimiter.length()));
498         char[] chars = s.toCharArray();
499         byte[] actual = hex.parseHex(chars, low * stride,
500                 high * stride - delimiter.length());
501         System.out.println("    parsed as: " + Arrays.toString(actual));
502 
503         assertEquals(actual.length, (high - low), "array length");
504         int mismatch = Arrays.mismatch(expected, low, high, actual, 0, high - low);
505         assertEquals(mismatch, -1, "format/parse cycle failed, mismatch: " + mismatch);
506     }
507 
508     @Test(dataProvider="HexFormattersParsers")
testFormatterToString(String delimiter, String prefix, String suffix, boolean uppercase, HexFormat hex)509     public void testFormatterToString(String delimiter, String prefix, String suffix,
510                                     boolean uppercase,
511                                     HexFormat hex) {
512         String actual = String.format(
513                 "uppercase: %s, delimiter: \"%s\", prefix: \"%s\", suffix: \"%s\"",
514                 uppercase, escapeNL(delimiter), escapeNL(prefix), escapeNL(suffix));
515         System.out.println("    hex: " + actual);
516         assertEquals(actual, hex.toString(), "Formatter toString mismatch");
517     }
518 
519     @Test(dataProvider="HexFormattersParsers")
testFormatterParameterMethods(String delimiter, String prefix, String suffix, boolean uppercase, HexFormat hex)520     public void testFormatterParameterMethods(String delimiter, String prefix, String suffix,
521                                     boolean uppercase,
522                                     HexFormat hex) {
523         assertEquals(hex.delimiter(), delimiter);
524         assertEquals(hex.prefix(), prefix);
525         assertEquals(hex.suffix(), suffix);
526         assertEquals(hex.isUpperCase(), uppercase);
527     }
528 
529     @Test(dataProvider="HexFormattersParsers")
testFormatterTestEquals(String delimiter, String prefix, String suffix, boolean uppercase, HexFormat expected)530     public void testFormatterTestEquals(String delimiter, String prefix, String suffix,
531                                     boolean uppercase,
532                                     HexFormat expected) {
533         HexFormat actual = HexFormat.of()
534                 .withDelimiter(delimiter)
535                 .withPrefix(prefix)
536                 .withSuffix(suffix);
537         actual = uppercase ? actual.withUpperCase() : actual.withLowerCase();
538 
539         assertEquals(actual.delimiter(), delimiter, "delimiter");
540         assertEquals(actual.prefix(), prefix, "prefix");
541         assertEquals(actual.suffix(), suffix, "suffix");
542         assertEquals(actual.isUpperCase(), uppercase, "uppercase");
543         assertTrue(actual.equals(expected), "equals method");
544         assertEquals(actual.hashCode(), expected.hashCode(), "hashCode");
545 
546         assertTrue(actual.equals(actual));   // equals self
547         assertFalse(actual.equals(null));    // never equals null
548     }
549 
550     @Test(dataProvider="HexFormattersParsers")
testZeroLength(String delimiter, String prefix, String suffix, boolean uppercase, HexFormat hex)551     public void testZeroLength(String delimiter, String prefix, String suffix, boolean uppercase,
552                                 HexFormat hex) {
553         // Test formatting of zero length byte arrays, should produce no output
554         StringBuilder sb = new StringBuilder();
555         assertEquals(hex.formatHex(new byte[0]), "", "Zero length");
556         assertEquals(hex.formatHex(new byte[0], 0, 0), "", "Zero length");
557 
558         hex.formatHex(sb, new byte[0]);
559         assertEquals(sb.length(), 0, "length should not change");
560         hex.formatHex(sb, new byte[0], 0, 0);
561         assertEquals(sb.length(), 0, "length should not change");
562 
563     }
escapeNL(String string)564     private static String escapeNL(String string) {
565         return string.replace("\n", "\\n")
566                 .replace("\r", "\\r");
567     }
568 
569     @Test
testfromHexDigitsToInt()570     public void testfromHexDigitsToInt() {
571         HexFormat hex = HexFormat.of();
572 
573         String allHex = "76543210";
574         final int orig = 0x76543210;
575         for (int digits = 0; digits <= 8; digits++) {
576             String s = hex.toHexDigits(orig, digits);
577             long actual = HexFormat.fromHexDigits(s, 0, digits);
578             System.out.printf("    digits: %2d, formatted: \"%s\", parsed as: 0x%08x%n",
579                     digits, s, actual);
580             assertEquals(s, allHex.substring(8 - digits, 8));
581             long expected = (digits < 8) ? orig & ~(0xffffffff << (4 * digits)) : orig;
582             assertEquals(actual, expected);
583         }
584     }
585 
586     @Test
testfromHexDigitsToLong()587     public void testfromHexDigitsToLong() {
588         HexFormat hex = HexFormat.of();
589 
590         String allHex = "fedcba9876543210";
591         final long orig = 0xfedcba9876543210L;
592         for (int digits = 0; digits <= 16; digits++) {
593             String s = hex.toHexDigits(orig, digits);
594             long actual = HexFormat.fromHexDigitsToLong(s, 0, digits);
595             System.out.printf("    digits: %2d, formatted: \"%s\", parsed as: 0x%016xL%n",
596                     digits, s, actual);
597             assertEquals(s, allHex.substring(16 - digits, 16));
598             long expected = (digits < 16) ? orig & ~(0xffffffffffffffffL << (4 * digits)) : orig;
599             assertEquals(actual, expected);
600         }
601     }
602 
603     @Test
testToHexDigitsLong()604     public void testToHexDigitsLong() {
605         HexFormat hex = HexFormat.of();
606 
607         String allHex = "fedcba9876543210";
608         final long expected = 0xfedcba9876543210L;
609         String s = hex.toHexDigits(expected);
610         long actual = HexFormat.fromHexDigitsToLong(s);
611         System.out.printf("    formatted: \"%s\", parsed as: 0x%016xL%n", s, actual);
612         assertEquals(s, allHex);
613         assertEquals(actual, expected);
614     }
615 
616     @Test(dataProvider="HexFormattersParsers")
testIOException(String delimiter, String prefix, String suffix, boolean uppercase, HexFormat hex)617     public void testIOException(String delimiter, String prefix, String suffix, boolean uppercase,
618                                HexFormat hex) {
619         Appendable throwingAppendable = new ThrowingAppendable();
620         assertThrows(UncheckedIOException.class,
621                 () -> hex.formatHex(throwingAppendable, new byte[1]));
622         assertThrows(UncheckedIOException.class,
623                 () -> hex.formatHex(throwingAppendable, new byte[1], 0, 1));
624         assertThrows(UncheckedIOException.class,
625                 () -> hex.toHexDigits(throwingAppendable, (byte)1));
626     }
627 
628     @LargeTest
629     @Test(dataProvider="HexFormattersParsers")
testOOME(String delimiter, String prefix, String suffix, boolean uppercase, HexFormat hex)630     public void testOOME(String delimiter, String prefix, String suffix, boolean uppercase,
631                          HexFormat hex) {
632         // compute the size of byte array that will exceed the buffer
633         long valueChars = prefix.length() + 2 + suffix.length();
634         long stride = valueChars + delimiter.length();
635         long max = Integer.MAX_VALUE & 0xFFFFFFFFL;
636         long len = max / stride;
637         long remainder = max - ((len - 1) * stride);
638         if (remainder > valueChars) {
639             len++;
640             remainder -= valueChars;
641         }
642         try {
643             byte[] bytes = new byte[(int) len];
644             Throwable ex = expectThrows(OutOfMemoryError.class,
645                     () -> hex.formatHex(bytes));
646             System.out.println("ex: " + ex);
647         } catch (OutOfMemoryError oome) {
648             System.out.printf("OOME: total mem: %08x, free mem: %08x, max mem: %08x%n",
649                     Runtime.getRuntime().totalMemory(),
650                     Runtime.getRuntime().freeMemory(),
651                     Runtime.getRuntime().maxMemory());
652             throw new SkipException("Insufficient Memory to test OOME");
653         }
654 
655     }
656 
657     /**
658      * Example code from the HexFormat javadoc.
659      * Showing simple usage of the API using "assert" to express the correct results
660      * when shown in the javadoc.
661      * The additional TestNG asserts verify the correctness of the same code.
662      */
663     @Test
samples()664     public void samples() {
665         {
666             // Primitive formatting and parsing.
667             HexFormat hex = HexFormat.of();
668 
669             byte b = 127;
670             String byteStr = hex.toHexDigits(b);
671             System.out.println("    " + byteStr);
672 
673             byte byteVal = (byte) HexFormat.fromHexDigits(byteStr);
674             assert(byteStr.equals("7f"));
675             assert(b == byteVal);
676             assertTrue(byteStr.equals("7f"));
677             assertTrue(b == byteVal);
678 
679 
680             char c = 'A';
681             String charStr = hex.toHexDigits(c);
682             System.out.println("    " + charStr);
683             int charVal = HexFormat.fromHexDigits(charStr);
684             assert(c == charVal);
685             assertTrue(c == charVal);
686 
687             int i = 12345;
688             String intStr = hex.toHexDigits(i);
689             System.out.println("    " + intStr);
690             int intVal = HexFormat.fromHexDigits(intStr);
691             assert(i == intVal);
692             assertTrue(i == intVal);
693 
694             long l = Long.MAX_VALUE;
695             String longStr = hex.toHexDigits(l, 16);
696             long longVal = HexFormat.fromHexDigitsToLong(longStr, 0, 16);
697             System.out.println("    " + longStr + ", " + longVal);
698             assert(l == longVal);
699             assertTrue(l == longVal);
700         }
701 
702         {
703             // RFC 4752 Fingerprint
704             HexFormat formatFingerprint = HexFormat.ofDelimiter(":").withUpperCase();
705             byte[] bytes = {0, 1, 2, 3, 124, 125, 126, 127};
706             String str = formatFingerprint.formatHex(bytes);
707             System.out.println("    Formatted: " + str);
708 
709             byte[] parsed = formatFingerprint.parseHex(str);
710             System.out.println("    Parsed: " + Arrays.toString(parsed));
711             assert(Arrays.equals(bytes, parsed));
712             assertTrue(Arrays.equals(bytes, parsed));
713         }
714 
715         {
716             // Comma separated formatting
717             HexFormat commaFormat = HexFormat.ofDelimiter(",");
718             byte[] bytes = {0, 1, 2, 3, 124, 125, 126, 127};
719             String str = commaFormat.formatHex(bytes);
720             System.out.println("    Formatted: " + str);
721 
722             byte[] parsed = commaFormat.parseHex(str);
723             System.out.println("    Parsed: " + Arrays.toString(parsed));
724             assert(Arrays.equals(bytes, parsed));
725             assertTrue(Arrays.equals(bytes, parsed));
726         }
727         {
728             // Text formatting
729             HexFormat commaFormat = HexFormat.ofDelimiter(", ").withPrefix("#");
730             byte[] bytes = {0, 1, 2, 3, 124, 125, 126, 127};
731             String str = commaFormat.formatHex(bytes);
732             System.out.println("    Formatted: " + str);
733 
734             byte[] parsed = commaFormat.parseHex(str);
735             System.out.println("    Parsed:    " + Arrays.toString(parsed));
736             assert(Arrays.equals(bytes, parsed));
737             assertTrue(Arrays.equals(bytes, parsed));
738         }
739     }
740 
741     /**
742      * A test implementation of Appendable that throws IOException on all methods.
743      */
744     static class ThrowingAppendable implements Appendable {
745         @Override
append(CharSequence csq)746         public Appendable append(CharSequence csq) throws IOException {
747             throw new IOException(".append(CharSequence) always throws");
748         }
749 
750         @Override
append(CharSequence csq, int start, int end)751         public Appendable append(CharSequence csq, int start, int end) throws IOException {
752             throw new IOException(".append(CharSequence, start, end) always throws");
753         }
754 
755         @Override
append(char c)756         public Appendable append(char c) throws IOException {
757             throw new IOException(".append(char) always throws");
758         }
759     }
760 }
761