View Javadoc
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 }