1 /*
2 * Oceanus: Java Utilities
3 * Copyright 2012-2026. Tony Washer
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
6 * use this file except in compliance with the License. You may obtain a copy
7 * of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14 * License for the specific language governing permissions and limitations under
15 * the License.
16 */
17 package io.github.tonywasher.joceanus.oceanus.convert;
18
19 import io.github.tonywasher.joceanus.oceanus.base.OceanusException;
20 import io.github.tonywasher.joceanus.oceanus.exc.OceanusDataException;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.InputStreamReader;
26 import java.io.OutputStreamWriter;
27 import java.nio.charset.StandardCharsets;
28 import java.util.Arrays;
29
30 /**
31 * Data Conversion utility functions.
32 */
33 public final class OceanusDataConverter {
34 /**
35 * Invalid hexadecimal length string.
36 */
37 private static final String ERROR_HEXLEN = "Invalid HexString Length: ";
38
39 /**
40 * Invalid hexadecimal error string.
41 */
42 private static final String ERROR_HEXDIGIT = "Non Hexadecimal Value: ";
43
44 /**
45 * Base64 Encoding array.
46 */
47 private static final char[] BASE64_ENCODE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".toCharArray();
48
49 /**
50 * Base64 Decoding array.
51 */
52 private static final int[] BASE64_DECODE = new int[BASE64_ENCODE.length << 1];
53
54 static {
55 for (int i = 0; i < BASE64_ENCODE.length; i++) {
56 BASE64_DECODE[BASE64_ENCODE[i]] = i;
57 }
58 }
59
60 /**
61 * Base64 triplet size.
62 */
63 private static final int BASE64_TRIPLE = 3;
64
65 /**
66 * Base64 padding character.
67 */
68 private static final char BASE64_PAD = '=';
69
70 /**
71 * Base64 shift 1.
72 */
73 private static final int BASE64_SHIFT1 = 2;
74
75 /**
76 * Base64 shift 2.
77 */
78 private static final int BASE64_SHIFT2 = 4;
79
80 /**
81 * Base64 shift 3.
82 */
83 private static final int BASE64_SHIFT3 = 6;
84
85 /**
86 * Hexadecimal Radix.
87 */
88 public static final int HEX_RADIX = 16;
89
90 /**
91 * Byte shift.
92 */
93 public static final int BYTE_SHIFT = Byte.SIZE;
94
95 /**
96 * Byte mask.
97 */
98 public static final int BYTE_MASK = 0xFF;
99
100 /**
101 * Base64 mask.
102 */
103 public static final int BASE64_MASK = 0x3F;
104
105 /**
106 * Color mask.
107 */
108 public static final int COLOR_MASK = 0x00FFFFFF;
109
110 /**
111 * Nybble shift.
112 */
113 public static final int NYBBLE_SHIFT = Byte.SIZE >> 1;
114
115 /**
116 * Nybble mask.
117 */
118 public static final int NYBBLE_MASK = 0xF;
119
120 /**
121 * RGB colour length.
122 */
123 public static final int RGB_LENGTH = 6;
124
125 /**
126 * Private constructor to avoid instantiation.
127 */
128 private OceanusDataConverter() {
129 }
130
131 /**
132 * format a byte array as a hexadecimal string.
133 *
134 * @param pBytes the byte array
135 * @return the string
136 */
137 public static String bytesToHexString(final byte[] pBytes) {
138 /* Allocate the string builder */
139 final StringBuilder myValue = new StringBuilder(2 * pBytes.length);
140
141 /* For each byte in the value */
142 for (final byte b : pBytes) {
143 /* Access the byte as an unsigned integer */
144 int myInt = b;
145 if (myInt < 0) {
146 myInt += BYTE_MASK + 1;
147 }
148
149 /* Access the high nybble */
150 int myDigit = myInt >>> NYBBLE_SHIFT;
151 char myChar = Character.forDigit(myDigit, HEX_RADIX);
152
153 /* Add it to the value string */
154 myValue.append(myChar);
155
156 /* Access the low digit */
157 myDigit = myInt
158 & NYBBLE_MASK;
159 myChar = Character.forDigit(myDigit, HEX_RADIX);
160
161 /* Add it to the value string */
162 myValue.append(myChar);
163 }
164
165 /* Return the string */
166 return myValue.toString();
167 }
168
169 /**
170 * format a long as a hexadecimal string.
171 *
172 * @param pValue the long value
173 * @return the string
174 */
175 public static String longToHexString(final long pValue) {
176 /* Access the long value */
177 long myLong = pValue;
178
179 /* Allocate the string builder */
180 final StringBuilder myValue = new StringBuilder();
181
182 /* handle negative values */
183 final boolean isNegative = myLong < 0;
184 if (isNegative) {
185 myLong = -myLong;
186 }
187
188 /* Special case for zero */
189 if (myLong == 0) {
190 myValue.append("00");
191
192 /* else need to loop through the digits */
193 } else {
194 /* While we have digits to format */
195 while (myLong > 0) {
196 /* Access the digit and move to next one */
197 final int myDigit = (int) (myLong & NYBBLE_MASK);
198 final char myChar = Character.forDigit(myDigit, HEX_RADIX);
199 myValue.insert(0, myChar);
200 myLong >>>= NYBBLE_SHIFT;
201 }
202
203 /* If we are odd length prefix a zero */
204 if ((myValue.length() & 1) != 0) {
205 myValue.insert(0, '0');
206 }
207
208 /* Reinstate negative sign */
209 if (isNegative) {
210 myValue.insert(0, '-');
211 }
212 }
213
214 /* Return the string */
215 return myValue.toString();
216 }
217
218 /**
219 * parse a byte array from a hexadecimal string.
220 *
221 * @param pHexString the hex string
222 * @return the bytes
223 * @throws OceanusException on error
224 */
225 public static byte[] hexStringToBytes(final String pHexString) throws OceanusException {
226 /* Access the length of the hex string */
227 final int myLen = pHexString.length();
228
229 /* Check that it has an even length */
230 if (myLen % 2 != 0) {
231 throw new OceanusDataException(ERROR_HEXLEN
232 + pHexString);
233 }
234
235 /* Allocate the new bytes array */
236 final byte[] myByteValue = new byte[myLen / 2];
237
238 /* Loop through the string */
239 for (int i = 0; i < myLen; i += 2) {
240 /* Access the top level byte */
241 char myChar = pHexString.charAt(i);
242 int myDigit = Character.digit(myChar, HEX_RADIX);
243
244 /* Check that the char is a valid hex digit */
245 if (myDigit < 0) {
246 throw new OceanusDataException(ERROR_HEXDIGIT
247 + pHexString);
248 }
249
250 /* Initialise result */
251 int myInt = myDigit << NYBBLE_SHIFT;
252
253 /* Access the second byte */
254 myChar = pHexString.charAt(i + 1);
255 myDigit = Character.digit(myChar, HEX_RADIX);
256
257 /* Check that the char is a valid hex digit */
258 if (myDigit < 0) {
259 throw new OceanusDataException(ERROR_HEXDIGIT
260 + pHexString);
261 }
262
263 /* Add into result */
264 myInt += myDigit;
265
266 /* Convert to byte and store */
267 if (myInt > Byte.MAX_VALUE) {
268 myInt -= BYTE_MASK + 1;
269 }
270 myByteValue[i / 2] = (byte) myInt;
271 }
272
273 /* Return the bytes */
274 return myByteValue;
275 }
276
277 /**
278 * parse a long from a hexadecimal string.
279 *
280 * @param pHexString the hex string
281 * @return the bytes
282 * @throws OceanusException on error
283 */
284 public static long hexStringToLong(final String pHexString) throws OceanusException {
285 /* Access the length of the hex string */
286 String myHexString = pHexString;
287 int myLen = myHexString.length();
288
289 /* handle negative values */
290 final boolean isNegative = myLen > 0
291 && myHexString.charAt(0) == '-';
292 if (isNegative) {
293 myHexString = myHexString.substring(1);
294 myLen--;
295 }
296
297 /* Check that it has an even length */
298 if (myLen % 2 != 0) {
299 throw new OceanusDataException(ERROR_HEXLEN
300 + pHexString);
301 }
302
303 /* Loop through the string */
304 long myValue = 0;
305 for (int i = 0; i < myLen; i++) {
306 /* Access the next character */
307 final char myChar = myHexString.charAt(i);
308 final int myDigit = Character.digit(myChar, HEX_RADIX);
309
310 /* Check that the char is a valid hex digit */
311 if (myDigit < 0) {
312 throw new OceanusDataException(ERROR_HEXDIGIT
313 + pHexString);
314 }
315
316 /* Add into the value */
317 myValue <<= NYBBLE_SHIFT;
318 myValue += myDigit;
319 }
320
321 /* Reinstate negative values */
322 if (isNegative) {
323 myValue = -myValue;
324 }
325
326 /* Return the value */
327 return myValue;
328 }
329
330 /**
331 * Convert character array to byte array.
332 *
333 * @param pChars the character array
334 * @return the byte array
335 * @throws OceanusException on error
336 */
337 public static byte[] charsToByteArray(final char[] pChars) throws OceanusException {
338 /* protect against exceptions */
339 try {
340 /* Transform the character array to a byte array */
341 final ByteArrayOutputStream baos = new ByteArrayOutputStream();
342 final OutputStreamWriter out = new OutputStreamWriter(baos, StandardCharsets.UTF_8);
343 out.write(pChars, 0, pChars.length);
344 out.flush();
345 return baos.toByteArray();
346 } catch (IOException e) {
347 throw new OceanusDataException(e.getMessage(), e);
348 }
349 }
350
351 /**
352 * Convert byte array to character array.
353 *
354 * @param pBytes the byte array
355 * @return the character array
356 * @throws OceanusException on error
357 */
358 public static char[] bytesToCharArray(final byte[] pBytes) throws OceanusException {
359 /* protect against exceptions */
360 try {
361 /* Allocate the character array allowing for one character per byte */
362 char[] myArray = new char[pBytes.length];
363
364 /* Transform the byte array to a character array */
365 final ByteArrayInputStream bais = new ByteArrayInputStream(pBytes);
366 final InputStreamReader in = new InputStreamReader(bais, StandardCharsets.UTF_8);
367 final int myLen = in.read(myArray);
368
369 /* Cut down the array to the actual length */
370 myArray = Arrays.copyOf(myArray, myLen);
371
372 /* Return the array */
373 return myArray;
374 } catch (IOException e) {
375 throw new OceanusDataException(e.getMessage(), e);
376 }
377 }
378
379 /**
380 * parse a long from a byte array.
381 *
382 * @param pBytes the eight byte array holding the long
383 * @return the long value
384 */
385 public static long byteArrayToLong(final byte[] pBytes) {
386 /* Loop through the bytes */
387 long myValue = 0;
388 for (int i = 0; i < Long.BYTES; i++) {
389 /* Access the next byte as an unsigned integer */
390 int myByte = pBytes[i];
391 myByte &= BYTE_MASK;
392
393 /* Add in to value */
394 myValue <<= BYTE_SHIFT;
395 myValue += myByte;
396 }
397
398 /* Return the value */
399 return myValue;
400 }
401
402 /**
403 * build a byte array from a long.
404 *
405 * @param pValue the long value to convert
406 * @return the byte array
407 */
408 public static byte[] longToByteArray(final long pValue) {
409 /* Loop through the bytes */
410 long myValue = pValue;
411 final byte[] myBytes = new byte[Long.BYTES];
412 for (int i = Long.BYTES; i > 0; i--) {
413 /* Store the next byte */
414 final byte myByte = (byte) (myValue & BYTE_MASK);
415 myBytes[i - 1] = myByte;
416
417 /* Adjust value */
418 myValue >>= BYTE_SHIFT;
419 }
420
421 /* Return the value */
422 return myBytes;
423 }
424
425 /**
426 * parse an integer from a byte array.
427 *
428 * @param pBytes the four byte array holding the integer
429 * @return the integer value
430 */
431 public static int byteArrayToInteger(final byte[] pBytes) {
432 /* Loop through the bytes */
433 int myValue = 0;
434 for (int i = 0; i < Integer.BYTES; i++) {
435 /* Access the next byte as an unsigned integer */
436 int myByte = pBytes[i];
437 myByte &= BYTE_MASK;
438
439 /* Add in to value */
440 myValue <<= BYTE_SHIFT;
441 myValue += myByte;
442 }
443
444 /* Return the value */
445 return myValue;
446 }
447
448 /**
449 * build a byte array from an integer.
450 *
451 * @param pValue the integer value to convert
452 * @return the byte array
453 */
454 public static byte[] integerToByteArray(final int pValue) {
455 /* Loop through the bytes */
456 final byte[] myBytes = new byte[Integer.BYTES];
457 int myValue = pValue;
458 for (int i = Integer.BYTES; i > 0; i--) {
459 /* Store the next byte */
460 final byte myByte = (byte) (myValue & BYTE_MASK);
461 myBytes[i - 1] = myByte;
462
463 /* Adjust value */
464 myValue >>= BYTE_SHIFT;
465 }
466
467 /* Return the value */
468 return myBytes;
469 }
470
471 /**
472 * parse a short from a byte array.
473 *
474 * @param pBytes the four byte array holding the integer
475 * @return the short value
476 */
477 public static short byteArrayToShort(final byte[] pBytes) {
478 /* Loop through the bytes */
479 short myValue = 0;
480 for (int i = 0; i < Short.BYTES; i++) {
481 /* Access the next byte as an unsigned integer */
482 short myByte = pBytes[i];
483 myByte &= BYTE_MASK;
484
485 /* Add in to value */
486 myValue <<= BYTE_SHIFT;
487 myValue += myByte;
488 }
489
490 /* Return the value */
491 return myValue;
492 }
493
494 /**
495 * build a byte array from a short.
496 *
497 * @param pValue the short value to convert
498 * @return the byte array
499 */
500 public static byte[] shortToByteArray(final short pValue) {
501 /* Loop through the bytes */
502 final byte[] myBytes = new byte[Short.BYTES];
503 int myValue = pValue;
504 for (int i = Short.BYTES; i > 0; i--) {
505 /* Store the next byte */
506 final byte myByte = (byte) (myValue & BYTE_MASK);
507 myBytes[i - 1] = myByte;
508
509 /* Adjust value */
510 myValue >>= BYTE_SHIFT;
511 }
512
513 /* Return the value */
514 return myBytes;
515 }
516
517 /**
518 * get Bytes from String.
519 *
520 * @param pInput the bytes to obtain the string from
521 * @return the bytes representing the bytes
522 */
523 public static String byteArrayToString(final byte[] pInput) {
524 return new String(pInput, StandardCharsets.UTF_8);
525 }
526
527 /**
528 * get Bytes from String.
529 *
530 * @param pInput the string to obtain the bytes from
531 * @return the bytes representing the string
532 */
533 public static byte[] stringToByteArray(final String pInput) {
534 return pInput.getBytes(StandardCharsets.UTF_8);
535 }
536
537 /**
538 * Convert a byte array to a Base64 string.
539 *
540 * @param pBytes the byte array (not null)
541 * @return the translated Base64 string (not null)
542 */
543 public static String byteArrayToBase64(final byte[] pBytes) {
544 /* Determine input length and allocate output buffer */
545 final int myLen = pBytes.length;
546 final StringBuilder myBuilder = new StringBuilder(myLen << 1);
547 final byte[] myTriplet = new byte[BASE64_TRIPLE];
548
549 /* Loop through the input bytes */
550 int myIn = 0;
551 while (myIn < myLen) {
552 /* Access input triplet */
553 myTriplet[0] = pBytes[myIn++];
554 myTriplet[1] = myIn < myLen
555 ? pBytes[myIn++]
556 : 0;
557 myTriplet[2] = myIn < myLen
558 ? pBytes[myIn++]
559 : 0;
560
561 /* Convert to base64 */
562 myBuilder.append(BASE64_ENCODE[(myTriplet[0] >> BASE64_SHIFT1)
563 & BASE64_MASK]);
564 myBuilder.append(BASE64_ENCODE[((myTriplet[0] << BASE64_SHIFT2) | ((myTriplet[1] & BYTE_MASK) >> BASE64_SHIFT2))
565 & BASE64_MASK]);
566 myBuilder.append(BASE64_ENCODE[((myTriplet[1] << BASE64_SHIFT1) | ((myTriplet[2] & BYTE_MASK) >> BASE64_SHIFT3))
567 & BASE64_MASK]);
568 myBuilder.append(BASE64_ENCODE[myTriplet[2]
569 & BASE64_MASK]);
570 }
571
572 /* Handle short input */
573 int myXtra = myLen
574 % myTriplet.length;
575 if (myXtra > 0) {
576 /* Determine padding length */
577 myXtra = myTriplet.length
578 - myXtra;
579
580 /* Remove redundant characters */
581 myBuilder.setLength(myBuilder.length()
582 - myXtra);
583
584 /* Replace with padding character */
585 while (myXtra-- > 0) {
586 myBuilder.append(BASE64_PAD);
587 }
588 }
589
590 /* Convert chars to string */
591 return myBuilder.toString();
592 }
593
594 /**
595 * Convert a Base64 string into a byte array.
596 *
597 * @param pBase64 the Base64 string (not null)
598 * @return the byte array (not null)
599 */
600 public static byte[] base64ToByteArray(final String pBase64) {
601 /* Access input as chars */
602 final char[] myBase64 = pBase64.toCharArray();
603 final int myLen = myBase64.length;
604
605 /* Determine number of padding bytes */
606 int myNumPadding = 0;
607 if (myBase64[myLen - 1] == BASE64_PAD) {
608 myNumPadding++;
609 if (myBase64[myLen - 2] == BASE64_PAD) {
610 myNumPadding++;
611 }
612 }
613
614 /* Allocate the output buffer and index */
615 final int myOutLen = ((myLen * BASE64_TRIPLE) >> 2)
616 - myNumPadding;
617 final byte[] myOutput = new byte[myOutLen];
618
619 /* Loop through the base64 input */
620 int myIn = 0;
621 int myOut = 0;
622 while (myOut < myOutLen) {
623 /* Build first byte */
624 final int c0 = BASE64_DECODE[myBase64[myIn++]];
625 final int c1 = BASE64_DECODE[myBase64[myIn++]];
626 myOutput[myOut++] = (byte) (((c0 << BASE64_SHIFT1) | (c1 >> BASE64_SHIFT2)) & BYTE_MASK);
627
628 /* Build second byte */
629 if (myOut < myOutLen) {
630 final int c2 = BASE64_DECODE[myBase64[myIn++]];
631 myOutput[myOut++] = (byte) (((c1 << BASE64_SHIFT2) | (c2 >> BASE64_SHIFT1)) & BYTE_MASK);
632
633 /* Build third byte */
634 if (myOut < myOutLen) {
635 final int c3 = BASE64_DECODE[myBase64[myIn++]];
636 myOutput[myOut++] = (byte) (((c2 << BASE64_SHIFT3) | c3) & BYTE_MASK);
637 }
638 }
639 }
640 return myOutput;
641 }
642 }