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