1 package nom.tam.fits; 2 3 /* 4 * #%L 5 * nom.tam FITS library 6 * %% 7 * Copyright (C) 2004 - 2024 nom-tam-fits 8 * %% 9 * This is free and unencumbered software released into the public domain. 10 * 11 * Anyone is free to copy, modify, publish, use, compile, sell, or 12 * distribute this software, either in source code form or as a compiled 13 * binary, for any purpose, commercial or non-commercial, and by any 14 * means. 15 * 16 * In jurisdictions that recognize copyright laws, the author or authors 17 * of this software dedicate any and all copyright interest in the 18 * software to the public domain. We make this dedication for the benefit 19 * of the public at large and to the detriment of our heirs and 20 * successors. We intend this dedication to be an overt act of 21 * relinquishment in perpetuity of all present and future rights to this 22 * software under copyright law. 23 * 24 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 25 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 26 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 28 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 29 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 30 * OTHER DEALINGS IN THE SOFTWARE. 31 * #L% 32 */ 33 34 import java.io.Closeable; 35 import java.io.IOException; 36 import java.io.InputStream; 37 import java.lang.reflect.Array; 38 import java.net.HttpURLConnection; 39 import java.net.ProtocolException; 40 import java.net.URL; 41 import java.net.URLConnection; 42 import java.text.ParsePosition; 43 import java.util.ArrayList; 44 import java.util.Arrays; 45 import java.util.logging.Level; 46 import java.util.logging.Logger; 47 48 import nom.tam.util.ArrayDataOutput; 49 import nom.tam.util.AsciiFuncs; 50 import nom.tam.util.FitsDecoder; 51 import nom.tam.util.FitsEncoder; 52 import nom.tam.util.FitsIO; 53 import nom.tam.util.RandomAccess; 54 55 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 56 57 /** 58 * Static utility functions used throughout the FITS classes. 59 */ 60 public final class FitsUtil { 61 62 /** Lowest ASCII value that can be in FITS strings */ 63 static final byte BLANK_SPACE = 0x20; 64 65 /** Highest ASCII value that can be in FITS strings */ 66 static final byte MIN_ASCII_VALUE = 0x20; 67 68 /** Highest ASCII value that can be in FITS strings */ 69 static final byte MAX_ASCII_VALUE = 0x7e; 70 71 /** Highest ASCII value that can be in FITS strings */ 72 static final byte ASCII_NULL = 0x00; 73 74 /** 75 * the logger to log to. 76 */ 77 private static final Logger LOG = Logger.getLogger(FitsUtil.class.getName()); 78 79 private static boolean wroteCheckingError = false; 80 81 /** 82 * Utility class, do not instantiate it. 83 */ 84 private FitsUtil() { 85 } 86 87 /** 88 * @deprecated use {@link #addPadding(long)} instead. Calculates the amount of padding needed to complete the 89 * last FITS block at the specified current size. 90 * 91 * @return Total size of blocked FITS element, using e.v. padding to fits block size. 92 * 93 * @param size the current size. 94 */ 95 public static int addPadding(int size) { 96 return size + padding(size); 97 } 98 99 /** 100 * Calculates the amount of padding needed to complete the last FITS block at the specified current size. 101 * 102 * @return Total size of blocked FITS element, using e.v. padding to fits block size. 103 * 104 * @param size the current size. 105 * 106 * @see #padding(long) 107 * @see #pad(ArrayDataOutput, long) 108 */ 109 public static long addPadding(long size) { 110 return size + padding(size); 111 } 112 113 /** 114 * Converts an array of <code>boolean</code> or {@link Boolean} values to FITS logicals (bytes containint 'T', 'F' 115 * or '\0'). The shapes and size of the resulting array matches that of the input. Values of '\0' are converted to 116 * <code>null</code> values. 117 * 118 * @param o a new array of <code>Boolean</code> 119 * 120 * @return and array of FITS logical values with the same size and shape as the input 121 * 122 * @see #bytesToBooleanObjects(Object) 123 * @see #byteToBoolean(byte[]) 124 * 125 * @since 1.18 126 */ 127 static Object booleansToBytes(Object o) { 128 if (o == null) { 129 return FitsEncoder.byteForBoolean(null); 130 } 131 132 if (o instanceof Boolean) { 133 return FitsEncoder.byteForBoolean((Boolean) o); 134 } 135 136 if (o instanceof boolean[]) { 137 boolean[] bool = (boolean[]) o; 138 byte[] b = new byte[bool.length]; 139 for (int i = 0; i < bool.length; i++) { 140 b[i] = FitsEncoder.byteForBoolean(bool[i]); 141 } 142 return b; 143 } 144 145 if (o instanceof Boolean[]) { 146 Boolean[] bool = (Boolean[]) o; 147 byte[] b = new byte[bool.length]; 148 for (int i = 0; i < bool.length; i++) { 149 b[i] = FitsEncoder.byteForBoolean(bool[i]); 150 } 151 return b; 152 } 153 154 if (o instanceof Object[]) { 155 Object[] array = (Object[]) o; 156 Object[] b = null; 157 158 for (int i = 0; i < array.length; i++) { 159 Object e = booleansToBytes(array[i]); 160 if (b == null) { 161 b = (Object[]) Array.newInstance(e.getClass(), array.length); 162 } 163 b[i] = e; 164 } 165 166 return b; 167 } 168 169 throw new IllegalArgumentException("Not boolean values: " + o.getClass().getName()); 170 } 171 172 /** 173 * Converts an array of FITS logicals (bytes containint 'T', 'F' or '\0') to an array of {@link Boolean}. The shapes 174 * and size of the resulting array matches that of the input. Values of '\0' are converted to <code>null</code> 175 * values. 176 * 177 * @param bytes and array of FITS logical values 178 * 179 * @return a new array of <code>Boolean</code> with the same size and shape as the input 180 * 181 * @see #booleansToBytes(Object) 182 * 183 * @since 1.18 184 */ 185 static Object bytesToBooleanObjects(Object bytes) { 186 if (bytes instanceof Byte) { 187 return FitsDecoder.booleanObjectFor(((Number) bytes).intValue()); 188 } 189 190 if (bytes instanceof byte[]) { 191 byte[] b = (byte[]) bytes; 192 Boolean[] bool = new Boolean[b.length]; 193 for (int i = 0; i < b.length; i++) { 194 bool[i] = FitsDecoder.booleanObjectFor(b[i]); 195 } 196 return bool; 197 } 198 199 if (bytes instanceof Object[]) { 200 Object[] array = (Object[]) bytes; 201 Object[] bool = null; 202 203 for (int i = 0; i < array.length; i++) { 204 Object e = bytesToBooleanObjects(array[i]); 205 if (bool == null) { 206 bool = (Object[]) Array.newInstance(e.getClass(), array.length); 207 } 208 bool[i] = e; 209 } 210 211 return bool; 212 } 213 214 throw new IllegalArgumentException("Cannot convert to boolean values: " + bytes.getClass().getName()); 215 } 216 217 /** 218 * Converts an array of booleans into the bits packed into a block of bytes. 219 * 220 * @param bits an array of bits 221 * 222 * @return a new byte array containing the packed bits (in big-endian order) 223 * 224 * @see #bytesToBits(Object) 225 * 226 * @since 1.18 227 */ 228 static byte[] bitsToBytes(boolean[] bits) throws IllegalArgumentException { 229 byte[] bytes = new byte[(bits.length + Byte.SIZE - 1) / Byte.SIZE]; 230 for (int i = 0; i < bits.length; i++) { 231 if (bits[i]) { 232 int pos = Byte.SIZE - 1 - i % Byte.SIZE; 233 bytes[i / Byte.SIZE] |= 1 << pos; 234 } 235 } 236 237 return bytes; 238 } 239 240 /** 241 * Converts an array of bit segments into the bits packed into a blocks of bytes. 242 * 243 * @param bits an array of bits 244 * @param l the number of bits in a segment that are to be kept together 245 * 246 * @return a new byte array containing the packed bits (in big-endian order) 247 * 248 * @see #bytesToBits(Object) 249 * 250 * @since 1.18 251 */ 252 static byte[] bitsToBytes(boolean[] bits, int l) throws IllegalArgumentException { 253 int n = bits.length / l; // Number of bit segments 254 int bl = (l + Byte.SIZE - 1) / Byte.SIZE; // Number of bytes per segment 255 byte[] bytes = new byte[n * bl]; // The converted byte array 256 257 for (int i = 0; i < n; i++) { 258 int off = i * l; // bit offset 259 int boff = i * bl; // byte offset 260 261 for (int j = 0; j < l; j++) { 262 if (bits[off + j]) { 263 int pos = Byte.SIZE - 1 - j % Byte.SIZE; 264 bytes[boff + (j / Byte.SIZE)] |= 1 << pos; 265 } 266 } 267 } 268 269 return bytes; 270 } 271 272 /** 273 * Converts the bits packed into a block of bytes into a boolean array. 274 * 275 * @param bytes the byte array containing the packed bits (in big-endian order) 276 * @param count the number of bits to extract 277 * 278 * @return an array of boolean with the separated bit values. 279 * 280 * @see #bitsToBytes(Object) 281 * 282 * @since 1.18 283 */ 284 static boolean[] bytesToBits(byte[] bytes, int count) { 285 boolean[] bits = new boolean[count]; 286 287 for (int i = 0; i < bits.length; i++) { 288 int pos = Byte.SIZE - 1 - i % Byte.SIZE; 289 bits[i] = ((bytes[i / Byte.SIZE] >>> pos) & 1) == 1; 290 } 291 292 return bits; 293 } 294 295 /** 296 * Extracts a string from a byte array at the specified offset, maximal length and termination byte. This method 297 * trims trailing spaces but not leading ones. 298 * 299 * @param bytes an array of ASCII bytes 300 * @param offset the array index at which the string begins 301 * @param maxLen the maximum number of bytes to extract from the position 302 * @param terminator the byte value that terminates the string, such as 0x00. 303 * 304 * @return a new String with the relevant bytes, with length not exceeding the specified limit. 305 * 306 * @since 1.18 307 */ 308 static String extractString(byte[] bytes, ParsePosition pos, int maxLen, byte terminator) { 309 int offset = pos.getIndex(); 310 311 if (offset >= bytes.length) { 312 return ""; 313 } 314 315 if (offset + maxLen > bytes.length) { 316 maxLen = bytes.length - offset; 317 } 318 319 int end = -1; 320 321 // Check up to the specified length or termination 322 for (int i = 0; i < maxLen; i++) { 323 byte b = bytes[offset + i]; 324 pos.setIndex(offset + i); 325 326 if (b == terminator || b == 0) { 327 break; 328 } 329 330 if (b != BLANK_SPACE) { 331 end = i; 332 } 333 } 334 335 pos.setIndex(pos.getIndex() + 1); 336 337 byte[] sanitized = new byte[end + 1]; 338 boolean checking = FitsFactory.getCheckAsciiStrings(); 339 340 // Up to the specified length or terminator 341 for (int i = 0; i <= end; i++) { 342 byte b = bytes[offset + i]; 343 344 if (checking && (b < BLANK_SPACE || b > MAX_ASCII_VALUE)) { 345 if (!wroteCheckingError) { 346 LOG.warning("WARNING! Converting invalid table string character[s] to spaces."); 347 wroteCheckingError = true; 348 } 349 b = BLANK_SPACE; 350 } 351 352 sanitized[i] = b; 353 } 354 355 return AsciiFuncs.asciiString(sanitized); 356 } 357 358 /** 359 * Converts a FITS byte sequence to a Java string array, triming spaces at the heads and tails of each element. 360 * While FITS typically considers leading spaces significant, this library has been removing them from regularly 361 * shaped string arrays for a very long time, apparently based on request by users then... Even though it seems like 362 * a bad choice, since users could always call {@link String#trim()} if they needed to, we cannot recover the 363 * leading spaces once the string was trimmed. At this point we have no real choice but to continue the tradition, 364 * lest we want to break exising applications, which may rely on this behavior. 365 * 366 * @return Convert bytes to Strings, removing leading and trailing spaces from each entry. 367 * 368 * @param bytes byte array to convert 369 * @param maxLen the max string length 370 * 371 * @deprecated (<i>for internal use</i>) No longer used internally, will be removed in the future. 372 */ 373 public static String[] byteArrayToStrings(byte[] bytes, int maxLen) { 374 // Note that if a String in a binary table contains an internal 0, 375 // the FITS standard says that it is to be considered as terminating 376 // the string at that point, so that software reading the 377 // data back may not include subsequent characters. 378 // No warning of this truncation is given. 379 380 String[] res = new String[bytes.length / maxLen]; 381 for (int i = 0; i < res.length; i++) { 382 res[i] = extractString(bytes, new ParsePosition(i * maxLen), maxLen, (byte) '\0').trim(); 383 } 384 return res; 385 } 386 387 /** 388 * Converts a FITS representation of boolean values as bytes to a java boolean array. This implementation does not 389 * handle FITS <code>null</code> values. 390 * 391 * @return Convert an array of bytes to booleans. 392 * 393 * @param bytes the array of bytes to get the booleans from. 394 * 395 * @see FitsDecoder#booleanFor(int) 396 */ 397 static boolean[] byteToBoolean(byte[] bytes) { 398 boolean[] bool = new boolean[bytes.length]; 399 400 for (int i = 0; i < bytes.length; i++) { 401 bool[i] = FitsDecoder.booleanFor(bytes[i]); 402 } 403 return bool; 404 } 405 406 /** 407 * Parses a logical value from a string, using loose conversion. The string may contain either 'true'/'false' or 408 * 'T'/'F' (case insensitive), or else a zero (<code>false</code>) or non-zero number value (<code>true</code>). All 409 * other strings will return <code>null</code> corresponding to an undefined logical value. 410 * 411 * @param s A string 412 * 413 * @return <code>true</code>, <code>false</code>, or <code>null</code> (if undefined). 414 */ 415 @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "null has specific meaning here") 416 static Boolean parseLogical(String s) { 417 if (s == null) { 418 return null; 419 } 420 421 s = s.trim(); 422 423 if (s.isEmpty()) { 424 return null; 425 } 426 if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t")) { 427 return true; 428 } 429 if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("f")) { 430 return false; 431 } 432 433 try { 434 long l = Long.parseLong(s); 435 return l != 0; 436 } catch (NumberFormatException e) { 437 // Nothing to do.... 438 } 439 440 try { 441 double d = Double.parseDouble(s); 442 if (!Double.isNaN(d)) { 443 return d != 0; 444 } 445 } catch (NumberFormatException e) { 446 // Nothing to do.... 447 } 448 449 return null; 450 } 451 452 /** 453 * Gets the file offset for the given IO resource. 454 * 455 * @return The offset from the beginning of file (if random accessible), or -1 otherwise. 456 * 457 * @param o the stream to get the position 458 * 459 * @deprecated (<i>for internal use</i>) Visibility may be reduced to the package level in the future. 460 */ 461 public static long findOffset(Closeable o) { 462 if (o instanceof RandomAccess) { 463 return ((RandomAccess) o).getFilePointer(); 464 } 465 return -1; 466 } 467 468 /** 469 * Gets and input stream for a given URL resource. 470 * 471 * @return Get a stream to a URL accommodating possible redirections. Note that if a redirection request 472 * points to a different protocol than the original request, then the redirection is not 473 * handled automatically. 474 * 475 * @param url the url to get the stream from 476 * @param level max levels of redirection 477 * 478 * @throws IOException if the operation failed 479 */ 480 public static InputStream getURLStream(URL url, int level) throws IOException { 481 URLConnection conn = null; 482 int code = -1; 483 try { 484 conn = url.openConnection(); 485 if (conn instanceof HttpURLConnection) { 486 code = ((HttpURLConnection) conn).getResponseCode(); 487 } 488 return conn.getInputStream(); 489 } catch (ProtocolException e) { 490 LOG.log(Level.WARNING, "could not connect to " + url + (code >= 0 ? " got responce-code" + code : ""), e); 491 throw e; 492 } 493 } 494 495 /** 496 * Returns the maximum string length in an array. 497 * 498 * @return the maximum length of string in an array. 499 * 500 * @param strings array of strings to check 501 * 502 * @deprecated (<i>for internal use</i>) No longer used internally, may be removed in the future. 503 */ 504 public static int maxLength(String[] strings) { 505 int max = 0; 506 for (String element : strings) { 507 if (element != null && element.length() > max) { 508 max = element.length(); 509 } 510 } 511 return max; 512 } 513 514 /** 515 * Returns the maximum string length in an array of strings. Non-string elements nd null values are ignored. 516 * 517 * @return the maximum length of strings in an array. 518 * 519 * @param o array of strings to check 520 */ 521 static int maxStringLength(Object o) { 522 if (o instanceof String) { 523 return ((String) o).length(); 524 } 525 526 int max = 0; 527 528 if (o instanceof Object[]) { 529 for (Object e : (Object[]) o) { 530 if (e == null) { 531 continue; 532 } 533 534 int l = maxStringLength(e); 535 if (l > max) { 536 max = l; 537 } 538 } 539 } 540 541 return max; 542 } 543 544 /** 545 * Returns the minimum string length in an array of strings. Non-string elements nd null values are ignored. 546 * 547 * @return the minimum length of strings in an array. 548 * 549 * @param o strings array of strings to check 550 */ 551 static int minStringLength(Object o) { 552 if (o instanceof String) { 553 return ((String) o).length(); 554 } 555 556 int min = -1; 557 558 if (o instanceof Object[]) { 559 for (Object e : (Object[]) o) { 560 if (e == null) { 561 return 0; 562 } 563 564 int l = minStringLength(e); 565 if (l == 0) { 566 return 0; 567 } 568 569 if (min < 0 || l < min) { 570 min = l; 571 } 572 } 573 } 574 575 return min < 0 ? 0 : min; 576 } 577 578 /** 579 * Adds the necessary amount of padding needed to complete the last FITS block. 580 * 581 * @param stream stream to pad 582 * @param size the current size of the stream (total number of bytes written to it since the beginning 583 * of the FITS). 584 * 585 * @throws FitsException if the operation failed 586 * 587 * @see #pad(ArrayDataOutput, long, byte) 588 * 589 * @deprecated (<i>for internal use</i>) Visibility may be reduced to package level in the future 590 */ 591 public static void pad(ArrayDataOutput stream, long size) throws FitsException { 592 pad(stream, size, (byte) 0); 593 } 594 595 /** 596 * Adds the necessary amount of padding needed to complete the last FITS block., usign the designated padding byte 597 * value. 598 * 599 * @param stream stream to pad 600 * @param size the current size of the stream (total number of bytes written to it since the beginning 601 * of the FITS). 602 * @param fill the byte value to use for the padding 603 * 604 * @throws FitsException if the operation failed 605 * 606 * @see #pad(ArrayDataOutput, long) 607 * 608 * @deprecated (<i>for internal use</i>) Visibility may be reduced to private in the future 609 */ 610 public static void pad(ArrayDataOutput stream, long size, byte fill) throws FitsException { 611 int len = padding(size); 612 if (len > 0) { 613 byte[] buf = new byte[len]; 614 Arrays.fill(buf, fill); 615 try { 616 stream.write(buf); 617 stream.flush(); 618 } catch (Exception e) { 619 throw new FitsException("Unable to write padding", e); 620 } 621 } 622 } 623 624 /** 625 * @deprecated see Use {@link #padding(long)} instead. 626 * 627 * @return How many bytes are needed to fill a 2880 block? 628 * 629 * @param size the size without padding 630 */ 631 public static int padding(int size) { 632 return padding((long) size); 633 } 634 635 /** 636 * Calculated the amount of padding we need to add given the current size of a FITS file (under construction) 637 * 638 * @param size the current size of our FITS file before the padding 639 * 640 * @return the number of bytes of padding we need to add at the end to complete the FITS block. 641 * 642 * @see #addPadding(long) 643 * @see #pad(ArrayDataOutput, long) 644 */ 645 public static int padding(long size) { 646 int mod = (int) (size % FitsFactory.FITS_BLOCK_SIZE); 647 if (mod > 0) { 648 return FitsFactory.FITS_BLOCK_SIZE - mod; 649 } 650 return 0; 651 } 652 653 /** 654 * Attempts to reposition a FITS input ot output. The call will succeed only if the underlying input or output is 655 * random accessible. Othewise, an exception will be thrown. 656 * 657 * @deprecated This method wraps an {@link IOException} into a {@link FitsException} for no good 658 * reason really. A revision of the API could reduce the visibility of this method, 659 * and/or procees the underlying exception instead. 660 * 661 * @param o the FITS input or output 662 * @param offset the offset to position it to. 663 * 664 * @throws FitsException if the underlying input/output is not random accessible or if the requested position is 665 * invalid. 666 */ 667 @Deprecated 668 public static void reposition(FitsIO o, long offset) throws FitsException { 669 // TODO AK: argument should be RandomAccess instead of Closeable, since 670 // that's the only type we actually handle... 671 672 if (o == null) { 673 throw new FitsException("Attempt to reposition null stream"); 674 } 675 676 if (!(o instanceof RandomAccess) || offset < 0) { 677 throw new FitsException( 678 "Invalid attempt to reposition stream " + o + " of type " + o.getClass().getName() + " to " + offset); 679 } 680 681 try { 682 ((RandomAccess) o).seek(offset); 683 } catch (IOException e) { 684 throw new FitsException("Unable to repostion stream " + o + " of type " + o.getClass().getName() + " to " 685 + offset + ": " + e.getMessage(), e); 686 } 687 } 688 689 /** 690 * Converts a string to ASCII bytes in the specified array, padding (with 0x00) or truncating as necessary to 691 * provide the expected length at the specified arrya offset. 692 * 693 * @param s a string 694 * @param res the byte array into which to extract the ASCII bytes 695 * @param offset array index in the byte array at which the extracted bytes should begin 696 * @param len the maximum number of bytes to extract, truncating or padding (with 0x00) as needed. 697 * @param pad the byte value to use to pad the remainder of the string 698 */ 699 private static void stringToBytes(String s, byte[] res, int offset, int len, byte pad) { 700 int l = 0; 701 702 if (s != null) { 703 byte[] b = AsciiFuncs.getBytes(s); 704 l = Math.min(b.length, len); 705 if (l > 0) { 706 System.arraycopy(b, 0, res, offset, l); 707 } 708 } 709 710 // Terminate and pad as necessary 711 if (l < len) { 712 Arrays.fill(res, offset + l, offset + len, pad); 713 } 714 } 715 716 /** 717 * Converts a string to an array of ASCII bytes, padding (with 0x00) or truncating as necessary to provide the 718 * expected length. 719 * 720 * @param s a string 721 * @param len the number of bytes for the return value, also the maximum number of bytes that are extracted from 722 * the string 723 * 724 * @return a byte array of the specified length containing the truncated or padded string value. 725 * 726 * @see #stringsToByteArray(String[], int, byte) q 727 * 728 * @since 1.18 729 */ 730 static byte[] stringToByteArray(String s, int len) { 731 byte[] res = new byte[len]; 732 stringToBytes(s, res, 0, len, BLANK_SPACE); 733 return res; 734 } 735 736 /** 737 * Convert an array of Strings to bytes. padding (with 0x00) or truncating as necessary to provide the expected 738 * length. 739 * 740 * @return the resulting bytes 741 * 742 * @param stringArray the array with Strings 743 * @param len the number of bytes used for each string element. The string will be truncated ot padded 744 * as necessary to fit into that size. 745 * 746 * @deprecated (<i>for internal use</i>) Visibility may be reduced to package level in the future. 747 */ 748 public static byte[] stringsToByteArray(String[] stringArray, int len) { 749 return stringsToByteArray(stringArray, len, BLANK_SPACE); 750 } 751 752 /** 753 * Convert an array of Strings to bytes. padding (with 0x00) or truncating as necessary to provide the expected 754 * length. 755 * 756 * @return the resulting bytes 757 * 758 * @param stringArray the array with Strings 759 * @param len the number of bytes used for each string element. The string will be truncated ot padded as 760 * necessary to fit into that size. 761 * @param pad the byte value to use for padding strings as necessary to the requisite length. 762 */ 763 static byte[] stringsToByteArray(String[] stringArray, int len, byte pad) { 764 byte[] res = new byte[stringArray.length * len]; 765 for (int i = 0; i < stringArray.length; i++) { 766 stringToBytes(stringArray[i], res, i * len, len, pad); 767 } 768 return res; 769 } 770 771 /** 772 * Convert an array of strings to a delimited sequence of bytes, e.g. for sequentialized variable-sized storage of 773 * multiple string elements. The last string component is terminated by an ASCII NUL (0x00). 774 * 775 * @return the resulting bytes 776 * 777 * @param array the array with Strings 778 * @param maxlen the maximum string length or -1 of unlimited. 779 * @param delim the byte value that delimits string components 780 * 781 * @see #stringToByteArray(String, int) 782 * 783 * @since 1.18 784 */ 785 static byte[] stringsToDelimitedBytes(String[] array, int maxlen, byte delim) { 786 int l = array.length - 1; 787 for (String s : array) { 788 l += (s == null) ? 1 : Math.max(s.length() + 1, maxlen); 789 } 790 byte[] b = new byte[l]; 791 l = 0; 792 for (String s : array) { 793 if (s != null) { 794 stringToBytes(s, b, l, s.length(), BLANK_SPACE); 795 l += s.length(); 796 } 797 b[l++] = delim; 798 } 799 b[l - 1] = (byte) 0; 800 return b; 801 } 802 803 /** 804 * Extracts strings from a packed delimited byte sequence. Strings start either immediately after the prior string 805 * reached its maximum length, or else immediately after the specified delimiter byte value. 806 * 807 * @param bytes bytes containing the packed strings 808 * @param maxlen the maximum length of individual string components 809 * @param delim the byte value that delimits strings shorter than the maximum length 810 * 811 * @return An array of the extracted strings 812 * 813 * @see #stringsToDelimitedBytes(String[], int, byte) 814 * 815 * @since 1.18 816 */ 817 private static String[] delimitedBytesToStrings(byte[] bytes, byte delim) { 818 ArrayList<String> s = new ArrayList<>(); 819 ParsePosition pos = new ParsePosition(0); 820 while (pos.getIndex() < bytes.length) { 821 s.add(extractString(bytes, pos, bytes.length, delim)); 822 } 823 String[] a = new String[s.size()]; 824 s.toArray(a); 825 return a; 826 } 827 828 /** 829 * Extracts strings from a packed delimited byte sequence. Strings start either immediately after the prior string 830 * reached its maximum length, or else immediately after the specified delimiter byte value. 831 * 832 * @param bytes bytes containing the packed strings 833 * @param maxlen the maximum length of individual string components 834 * @param delim the byte value that delimits strings shorter than the maximum length 835 * 836 * @return An array of the extracted strings 837 * 838 * @see #stringsToDelimitedBytes(String[], int, byte) 839 * 840 * @since 1.18 841 */ 842 static String[] delimitedBytesToStrings(byte[] bytes, int maxlen, byte delim) { 843 if (maxlen <= 0) { 844 return delimitedBytesToStrings(bytes, delim); 845 } 846 847 String[] res = new String[(bytes.length + maxlen - 1) / maxlen]; 848 ParsePosition pos = new ParsePosition(0); 849 for (int i = 0; i < res.length; i++) { 850 res[i] = extractString(bytes, pos, maxlen, delim); 851 } 852 return res; 853 } 854 855 }