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 }