All Downloads are FREE. Search and download functionalities are using the official Maven repository.

nom.tam.fits.FitsUtil Maven / Gradle / Ivy

Go to download

Java library for reading and writing FITS files. FITS, the Flexible Image Transport System, is the format commonly used in the archiving and transport of astronomical data.

There is a newer version: 1.21.0
Show newest version
package nom.tam.fits;

/*
 * #%L
 * nom.tam FITS library
 * %%
 * Copyright (C) 2004 - 2024 nom-tam-fits
 * %%
 * This is free and unencumbered software released into the public domain.
 *
 * Anyone is free to copy, modify, publish, use, compile, sell, or
 * distribute this software, either in source code form or as a compiled
 * binary, for any purpose, commercial or non-commercial, and by any
 * means.
 *
 * In jurisdictions that recognize copyright laws, the author or authors
 * of this software dedicate any and all copyright interest in the
 * software to the public domain. We make this dedication for the benefit
 * of the public at large and to the detriment of our heirs and
 * successors. We intend this dedication to be an overt act of
 * relinquishment in perpetuity of all present and future rights to this
 * software under copyright law.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
 * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 * #L%
 */

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.net.HttpURLConnection;
import java.net.ProtocolException;
import java.net.URL;
import java.net.URLConnection;
import java.text.ParsePosition;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.Logger;

import nom.tam.util.ArrayDataOutput;
import nom.tam.util.AsciiFuncs;
import nom.tam.util.FitsDecoder;
import nom.tam.util.FitsEncoder;
import nom.tam.util.FitsIO;
import nom.tam.util.RandomAccess;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

/**
 * Static utility functions used throughout the FITS classes.
 */
public final class FitsUtil {

    /** Lowest ASCII value that can be in FITS strings */
    static final byte BLANK_SPACE = 0x20;

    /** Highest ASCII value that can be in FITS strings */
    static final byte MIN_ASCII_VALUE = 0x20;

    /** Highest ASCII value that can be in FITS strings */
    static final byte MAX_ASCII_VALUE = 0x7e;

    /** Highest ASCII value that can be in FITS strings */
    static final byte ASCII_NULL = 0x00;

    /**
     * the logger to log to.
     */
    private static final Logger LOG = Logger.getLogger(FitsUtil.class.getName());

    private static boolean wroteCheckingError = false;

    /**
     * Utility class, do not instantiate it.
     */
    private FitsUtil() {
    }

    /**
     * @deprecated      use {@link #addPadding(long)} instead. Calculates the amount of padding needed to complete the
     *                      last FITS block at the specified current size.
     * 
     * @return          Total size of blocked FITS element, using e.v. padding to fits block size.
     *
     * @param      size the current size.
     */
    public static int addPadding(int size) {
        return size + padding(size);
    }

    /**
     * Calculates the amount of padding needed to complete the last FITS block at the specified current size.
     * 
     * @return      Total size of blocked FITS element, using e.v. padding to fits block size.
     *
     * @param  size the current size.
     * 
     * @see         #padding(long)
     * @see         #pad(ArrayDataOutput, long)
     */
    public static long addPadding(long size) {
        return size + padding(size);
    }

    /**
     * Converts an array of boolean or {@link Boolean} values to FITS logicals (bytes containint 'T', 'F'
     * or '\0'). The shapes and size of the resulting array matches that of the input. Values of '\0' are converted to
     * null values.
     * 
     * @param  o a new array of Boolean
     * 
     * @return   and array of FITS logical values with the same size and shape as the input
     * 
     * @see      #bytesToBooleanObjects(Object)
     * @see      #byteToBoolean(byte[])
     * 
     * @since    1.18
     */
    static Object booleansToBytes(Object o) {
        if (o == null) {
            return FitsEncoder.byteForBoolean(null);
        }

        if (o instanceof Boolean) {
            return FitsEncoder.byteForBoolean((Boolean) o);
        }

        if (o instanceof boolean[]) {
            boolean[] bool = (boolean[]) o;
            byte[] b = new byte[bool.length];
            for (int i = 0; i < bool.length; i++) {
                b[i] = FitsEncoder.byteForBoolean(bool[i]);
            }
            return b;
        }

        if (o instanceof Boolean[]) {
            Boolean[] bool = (Boolean[]) o;
            byte[] b = new byte[bool.length];
            for (int i = 0; i < bool.length; i++) {
                b[i] = FitsEncoder.byteForBoolean(bool[i]);
            }
            return b;
        }

        if (o instanceof Object[]) {
            Object[] array = (Object[]) o;
            Object[] b = null;

            for (int i = 0; i < array.length; i++) {
                Object e = booleansToBytes(array[i]);
                if (b == null) {
                    b = (Object[]) Array.newInstance(e.getClass(), array.length);
                }
                b[i] = e;
            }

            return b;
        }

        throw new IllegalArgumentException("Not boolean values: " + o.getClass().getName());
    }

    /**
     * Converts an array of FITS logicals (bytes containint 'T', 'F' or '\0') to an array of {@link Boolean}. The shapes
     * and size of the resulting array matches that of the input. Values of '\0' are converted to null
     * values.
     * 
     * @param  bytes and array of FITS logical values
     * 
     * @return       a new array of Boolean with the same size and shape as the input
     * 
     * @see          #booleansToBytes(Object)
     * 
     * @since        1.18
     */
    static Object bytesToBooleanObjects(Object bytes) {
        if (bytes instanceof Byte) {
            return FitsDecoder.booleanObjectFor(((Number) bytes).intValue());
        }

        if (bytes instanceof byte[]) {
            byte[] b = (byte[]) bytes;
            Boolean[] bool = new Boolean[b.length];
            for (int i = 0; i < b.length; i++) {
                bool[i] = FitsDecoder.booleanObjectFor(b[i]);
            }
            return bool;
        }

        if (bytes instanceof Object[]) {
            Object[] array = (Object[]) bytes;
            Object[] bool = null;

            for (int i = 0; i < array.length; i++) {
                Object e = bytesToBooleanObjects(array[i]);
                if (bool == null) {
                    bool = (Object[]) Array.newInstance(e.getClass(), array.length);
                }
                bool[i] = e;
            }

            return bool;
        }

        throw new IllegalArgumentException("Cannot convert to boolean values: " + bytes.getClass().getName());
    }

    /**
     * Converts an array of booleans into the bits packed into a block of bytes.
     * 
     * @param  bits an array of bits
     * 
     * @return      a new byte array containing the packed bits (in big-endian order)
     * 
     * @see         #bytesToBits(Object)
     * 
     * @since       1.18
     */
    static byte[] bitsToBytes(boolean[] bits) throws IllegalArgumentException {
        byte[] bytes = new byte[(bits.length + Byte.SIZE - 1) / Byte.SIZE];
        for (int i = 0; i < bits.length; i++) {
            if (bits[i]) {
                int pos = Byte.SIZE - 1 - i % Byte.SIZE;
                bytes[i / Byte.SIZE] |= 1 << pos;
            }
        }

        return bytes;
    }

    /**
     * Converts an array of bit segments into the bits packed into a blocks of bytes.
     * 
     * @param  bits an array of bits
     * @param  l    the number of bits in a segment that are to be kept together
     * 
     * @return      a new byte array containing the packed bits (in big-endian order)
     * 
     * @see         #bytesToBits(Object)
     * 
     * @since       1.18
     */
    static byte[] bitsToBytes(boolean[] bits, int l) throws IllegalArgumentException {
        int n = bits.length / l; // Number of bit segments
        int bl = (l + Byte.SIZE - 1) / Byte.SIZE; // Number of bytes per segment
        byte[] bytes = new byte[n * bl]; // The converted byte array

        for (int i = 0; i < n; i++) {
            int off = i * l; // bit offset
            int boff = i * bl; // byte offset

            for (int j = 0; j < l; j++) {
                if (bits[off + j]) {
                    int pos = Byte.SIZE - 1 - j % Byte.SIZE;
                    bytes[boff + (j / Byte.SIZE)] |= 1 << pos;
                }
            }
        }

        return bytes;
    }

    /**
     * Converts the bits packed into a block of bytes into a boolean array.
     * 
     * @param  bytes the byte array containing the packed bits (in big-endian order)
     * @param  count the number of bits to extract
     * 
     * @return       an array of boolean with the separated bit values.
     * 
     * @see          #bitsToBytes(Object)
     * 
     * @since        1.18
     */
    static boolean[] bytesToBits(byte[] bytes, int count) {
        boolean[] bits = new boolean[count];

        for (int i = 0; i < bits.length; i++) {
            int pos = Byte.SIZE - 1 - i % Byte.SIZE;
            bits[i] = ((bytes[i / Byte.SIZE] >>> pos) & 1) == 1;
        }

        return bits;
    }

    /**
     * Extracts a string from a byte array at the specified offset, maximal length and termination byte. This method
     * trims trailing spaces but not leading ones.
     * 
     * @param  bytes      an array of ASCII bytes
     * @param  offset     the array index at which the string begins
     * @param  maxLen     the maximum number of bytes to extract from the position
     * @param  terminator the byte value that terminates the string, such as 0x00.
     * 
     * @return            a new String with the relevant bytes, with length not exceeding the specified limit.
     * 
     * @since             1.18
     */
    static String extractString(byte[] bytes, ParsePosition pos, int maxLen, byte terminator) {
        int offset = pos.getIndex();

        if (offset >= bytes.length) {
            return "";
        }

        if (offset + maxLen > bytes.length) {
            maxLen = bytes.length - offset;
        }

        int end = -1;

        // Check up to the specified length or termination
        for (int i = 0; i < maxLen; i++) {
            byte b = bytes[offset + i];
            pos.setIndex(offset + i);

            if (b == terminator || b == 0) {
                break;
            }

            if (b != BLANK_SPACE) {
                end = i;
            }
        }

        pos.setIndex(pos.getIndex() + 1);

        byte[] sanitized = new byte[end + 1];
        boolean checking = FitsFactory.getCheckAsciiStrings();

        // Up to the specified length or terminator
        for (int i = 0; i <= end; i++) {
            byte b = bytes[offset + i];

            if (checking && (b < BLANK_SPACE || b > MAX_ASCII_VALUE)) {
                if (!wroteCheckingError) {
                    LOG.warning("WARNING! Converting invalid table string character[s] to spaces.");
                    wroteCheckingError = true;
                }
                b = BLANK_SPACE;
            }

            sanitized[i] = b;
        }

        return AsciiFuncs.asciiString(sanitized);
    }

    /**
     * Converts a FITS byte sequence to a Java string array, triming spaces at the heads and tails of each element.
     * While FITS typically considers leading spaces significant, this library has been removing them from regularly
     * shaped string arrays for a very long time, apparently based on request by users then... Even though it seems like
     * a bad choice, since users could always call {@link String#trim()} if they needed to, we cannot recover the
     * leading spaces once the string was trimmed. At this point we have no real choice but to continue the tradition,
     * lest we want to break exising applications, which may rely on this behavior.
     * 
     * @return            Convert bytes to Strings, removing leading and trailing spaces from each entry.
     *
     * @param      bytes  byte array to convert
     * @param      maxLen the max string length
     * 
     * @deprecated        (for internal use) No longer used internally, will be removed in the future.
     */
    public static String[] byteArrayToStrings(byte[] bytes, int maxLen) {
        // Note that if a String in a binary table contains an internal 0,
        // the FITS standard says that it is to be considered as terminating
        // the string at that point, so that software reading the
        // data back may not include subsequent characters.
        // No warning of this truncation is given.

        String[] res = new String[bytes.length / maxLen];
        for (int i = 0; i < res.length; i++) {
            res[i] = extractString(bytes, new ParsePosition(i * maxLen), maxLen, (byte) '\0').trim();
        }
        return res;
    }

    /**
     * Converts a FITS representation of boolean values as bytes to a java boolean array. This implementation does not
     * handle FITS null values.
     * 
     * @return       Convert an array of bytes to booleans.
     *
     * @param  bytes the array of bytes to get the booleans from.
     * 
     * @see          FitsDecoder#booleanFor(int)
     */
    static boolean[] byteToBoolean(byte[] bytes) {
        boolean[] bool = new boolean[bytes.length];

        for (int i = 0; i < bytes.length; i++) {
            bool[i] = FitsDecoder.booleanFor(bytes[i]);
        }
        return bool;
    }

    /**
     * Parses a logical value from a string, using loose conversion. The string may contain either 'true'/'false' or
     * 'T'/'F' (case insensitive), or else a zero (false) or non-zero number value (true). All
     * other strings will return null corresponding to an undefined logical value.
     * 
     * @param  s A string
     * 
     * @return   true, false, or null (if undefined).
     */
    @SuppressFBWarnings(value = "NP_BOOLEAN_RETURN_NULL", justification = "null has specific meaning here")
    static Boolean parseLogical(String s) {
        if (s == null) {
            return null;
        }

        s = s.trim();

        if (s.isEmpty()) {
            return null;
        }
        if (s.equalsIgnoreCase("true") || s.equalsIgnoreCase("t")) {
            return true;
        }
        if (s.equalsIgnoreCase("false") || s.equalsIgnoreCase("f")) {
            return false;
        }

        try {
            long l = Long.parseLong(s);
            return l != 0;
        } catch (NumberFormatException e) {
            // Nothing to do....
        }

        try {
            double d = Double.parseDouble(s);
            if (!Double.isNaN(d)) {
                return d != 0;
            }
        } catch (NumberFormatException e) {
            // Nothing to do....
        }

        return null;
    }

    /**
     * Gets the file offset for the given IO resource.
     * 
     * @return       The offset from the beginning of file (if random accessible), or -1 otherwise.
     *
     * @param      o the stream to get the position
     * 
     * @deprecated   (for internal use) Visibility may be reduced to the package level in the future.
     */
    public static long findOffset(Closeable o) {
        if (o instanceof RandomAccess) {
            return ((RandomAccess) o).getFilePointer();
        }
        return -1;
    }

    /**
     * Gets and input stream for a given URL resource.
     * 
     * @return             Get a stream to a URL accommodating possible redirections. Note that if a redirection request
     *                         points to a different protocol than the original request, then the redirection is not
     *                         handled automatically.
     *
     * @param  url         the url to get the stream from
     * @param  level       max levels of redirection
     *
     * @throws IOException if the operation failed
     */
    public static InputStream getURLStream(URL url, int level) throws IOException {
        URLConnection conn = null;
        int code = -1;
        try {
            conn = url.openConnection();
            if (conn instanceof HttpURLConnection) {
                code = ((HttpURLConnection) conn).getResponseCode();
            }
            return conn.getInputStream();
        } catch (ProtocolException e) {
            LOG.log(Level.WARNING, "could not connect to " + url + (code >= 0 ? " got responce-code" + code : ""), e);
            throw e;
        }
    }

    /**
     * Returns the maximum string length in an array.
     * 
     * @return             the maximum length of string in an array.
     *
     * @param      strings array of strings to check
     * 
     * @deprecated         (for internal use) No longer used internally, may be removed in the future.
     */
    public static int maxLength(String[] strings) {
        int max = 0;
        for (String element : strings) {
            if (element != null && element.length() > max) {
                max = element.length();
            }
        }
        return max;
    }

    /**
     * Returns the maximum string length in an array of strings. Non-string elements nd null values are ignored.
     * 
     * @return   the maximum length of strings in an array.
     *
     * @param  o array of strings to check
     */
    static int maxStringLength(Object o) {
        if (o instanceof String) {
            return ((String) o).length();
        }

        int max = 0;

        if (o instanceof Object[]) {
            for (Object e : (Object[]) o) {
                if (e == null) {
                    continue;
                }

                int l = maxStringLength(e);
                if (l > max) {
                    max = l;
                }
            }
        }

        return max;
    }

    /**
     * Returns the minimum string length in an array of strings. Non-string elements nd null values are ignored.
     * 
     * @return   the minimum length of strings in an array.
     *
     * @param  o strings array of strings to check
     */
    static int minStringLength(Object o) {
        if (o instanceof String) {
            return ((String) o).length();
        }

        int min = -1;

        if (o instanceof Object[]) {
            for (Object e : (Object[]) o) {
                if (e == null) {
                    return 0;
                }

                int l = minStringLength(e);
                if (l == 0) {
                    return 0;
                }

                if (min < 0 || l < min) {
                    min = l;
                }
            }
        }

        return min < 0 ? 0 : min;
    }

    /**
     * Adds the necessary amount of padding needed to complete the last FITS block.
     *
     * @param      stream        stream to pad
     * @param      size          the current size of the stream (total number of bytes written to it since the beginning
     *                               of the FITS).
     *
     * @throws     FitsException if the operation failed
     * 
     * @see                      #pad(ArrayDataOutput, long, byte)
     * 
     * @deprecated               (for internal use) Visibility may be reduced to package level in the future
     */
    public static void pad(ArrayDataOutput stream, long size) throws FitsException {
        pad(stream, size, (byte) 0);
    }

    /**
     * Adds the necessary amount of padding needed to complete the last FITS block., usign the designated padding byte
     * value.
     *
     * @param      stream        stream to pad
     * @param      size          the current size of the stream (total number of bytes written to it since the beginning
     *                               of the FITS).
     * @param      fill          the byte value to use for the padding
     *
     * @throws     FitsException if the operation failed
     * 
     * @see                      #pad(ArrayDataOutput, long)
     * 
     * @deprecated               (for internal use) Visibility may be reduced to private in the future
     */
    public static void pad(ArrayDataOutput stream, long size, byte fill) throws FitsException {
        int len = padding(size);
        if (len > 0) {
            byte[] buf = new byte[len];
            Arrays.fill(buf, fill);
            try {
                stream.write(buf);
                stream.flush();
            } catch (Exception e) {
                throw new FitsException("Unable to write padding", e);
            }
        }
    }

    /**
     * @deprecated      see Use {@link #padding(long)} instead.
     * 
     * @return          How many bytes are needed to fill a 2880 block?
     *
     * @param      size the size without padding
     */
    public static int padding(int size) {
        return padding((long) size);
    }

    /**
     * Calculated the amount of padding we need to add given the current size of a FITS file (under construction)
     * 
     * @param  size the current size of our FITS file before the padding
     * 
     * @return      the number of bytes of padding we need to add at the end to complete the FITS block.
     * 
     * @see         #addPadding(long)
     * @see         #pad(ArrayDataOutput, long)
     */
    public static int padding(long size) {
        int mod = (int) (size % FitsFactory.FITS_BLOCK_SIZE);
        if (mod > 0) {
            return FitsFactory.FITS_BLOCK_SIZE - mod;
        }
        return 0;
    }

    /**
     * Attempts to reposition a FITS input ot output. The call will succeed only if the underlying input or output is
     * random accessible. Othewise, an exception will be thrown.
     *
     * @deprecated               This method wraps an {@link IOException} into a {@link FitsException} for no good
     *                               reason really. A revision of the API could reduce the visibility of this method,
     *                               and/or procees the underlying exception instead.
     *
     * @param      o             the FITS input or output
     * @param      offset        the offset to position it to.
     *
     * @throws     FitsException if the underlying input/output is not random accessible or if the requested position is
     *                               invalid.
     */
    @Deprecated
    public static void reposition(FitsIO o, long offset) throws FitsException {
        // TODO AK: argument should be RandomAccess instead of Closeable, since
        // that's the only type we actually handle...

        if (o == null) {
            throw new FitsException("Attempt to reposition null stream");
        }

        if (!(o instanceof RandomAccess) || offset < 0) {
            throw new FitsException(
                    "Invalid attempt to reposition stream " + o + " of type " + o.getClass().getName() + " to " + offset);
        }

        try {
            ((RandomAccess) o).seek(offset);
        } catch (IOException e) {
            throw new FitsException("Unable to repostion stream " + o + " of type " + o.getClass().getName() + " to "
                    + offset + ": " + e.getMessage(), e);
        }
    }

    /**
     * Converts a string to ASCII bytes in the specified array, padding (with 0x00) or truncating as necessary to
     * provide the expected length at the specified arrya offset.
     * 
     * @param s      a string
     * @param res    the byte array into which to extract the ASCII bytes
     * @param offset array index in the byte array at which the extracted bytes should begin
     * @param len    the maximum number of bytes to extract, truncating or padding (with 0x00) as needed.
     * @param pad    the byte value to use to pad the remainder of the string
     */
    private static void stringToBytes(String s, byte[] res, int offset, int len, byte pad) {
        int l = 0;

        if (s != null) {
            byte[] b = AsciiFuncs.getBytes(s);
            l = Math.min(b.length, len);
            if (l > 0) {
                System.arraycopy(b, 0, res, offset, l);
            }
        }

        // Terminate and pad as necessary
        if (l < len) {
            Arrays.fill(res, offset + l, offset + len, pad);
        }
    }

    /**
     * Converts a string to an array of ASCII bytes, padding (with 0x00) or truncating as necessary to provide the
     * expected length.
     * 
     * @param  s   a string
     * @param  len the number of bytes for the return value, also the maximum number of bytes that are extracted from
     *                 the string
     * 
     * @return     a byte array of the specified length containing the truncated or padded string value.
     * 
     * @see        #stringsToByteArray(String[], int, byte) q
     * 
     * @since      1.18
     */
    static byte[] stringToByteArray(String s, int len) {
        byte[] res = new byte[len];
        stringToBytes(s, res, 0, len, BLANK_SPACE);
        return res;
    }

    /**
     * Convert an array of Strings to bytes. padding (with 0x00) or truncating as necessary to provide the expected
     * length.
     *
     * @return                 the resulting bytes
     *
     * @param      stringArray the array with Strings
     * @param      len         the number of bytes used for each string element. The string will be truncated ot padded
     *                             as necessary to fit into that size.
     * 
     * @deprecated             (for internal use) Visibility may be reduced to package level in the future.
     */
    public static byte[] stringsToByteArray(String[] stringArray, int len) {
        return stringsToByteArray(stringArray, len, BLANK_SPACE);
    }

    /**
     * Convert an array of Strings to bytes. padding (with 0x00) or truncating as necessary to provide the expected
     * length.
     *
     * @return             the resulting bytes
     *
     * @param  stringArray the array with Strings
     * @param  len         the number of bytes used for each string element. The string will be truncated ot padded as
     *                         necessary to fit into that size.
     * @param  pad         the byte value to use for padding strings as necessary to the requisite length.
     */
    static byte[] stringsToByteArray(String[] stringArray, int len, byte pad) {
        byte[] res = new byte[stringArray.length * len];
        for (int i = 0; i < stringArray.length; i++) {
            stringToBytes(stringArray[i], res, i * len, len, pad);
        }
        return res;
    }

    /**
     * Convert an array of strings to a delimited sequence of bytes, e.g. for sequentialized variable-sized storage of
     * multiple string elements. The last string component is terminated by an ASCII NUL (0x00).
     *
     * @return        the resulting bytes
     *
     * @param  array  the array with Strings
     * @param  maxlen the maximum string length or -1 of unlimited.
     * @param  delim  the byte value that delimits string components
     * 
     * @see           #stringToByteArray(String, int)
     * 
     * @since         1.18
     */
    static byte[] stringsToDelimitedBytes(String[] array, int maxlen, byte delim) {
        int l = array.length - 1;
        for (String s : array) {
            l += (s == null) ? 1 : Math.max(s.length() + 1, maxlen);
        }
        byte[] b = new byte[l];
        l = 0;
        for (String s : array) {
            if (s != null) {
                stringToBytes(s, b, l, s.length(), BLANK_SPACE);
                l += s.length();
            }
            b[l++] = delim;
        }
        b[l - 1] = (byte) 0;
        return b;
    }

    /**
     * Extracts strings from a packed delimited byte sequence. Strings start either immediately after the prior string
     * reached its maximum length, or else immediately after the specified delimiter byte value.
     * 
     * @param  bytes  bytes containing the packed strings
     * @param  maxlen the maximum length of individual string components
     * @param  delim  the byte value that delimits strings shorter than the maximum length
     * 
     * @return        An array of the extracted strings
     *
     * @see           #stringsToDelimitedBytes(String[], int, byte)
     * 
     * @since         1.18
     */
    private static String[] delimitedBytesToStrings(byte[] bytes, byte delim) {
        ArrayList s = new ArrayList<>();
        ParsePosition pos = new ParsePosition(0);
        while (pos.getIndex() < bytes.length) {
            s.add(extractString(bytes, pos, bytes.length, delim));
        }
        String[] a = new String[s.size()];
        s.toArray(a);
        return a;
    }

    /**
     * Extracts strings from a packed delimited byte sequence. Strings start either immediately after the prior string
     * reached its maximum length, or else immediately after the specified delimiter byte value.
     * 
     * @param  bytes  bytes containing the packed strings
     * @param  maxlen the maximum length of individual string components
     * @param  delim  the byte value that delimits strings shorter than the maximum length
     * 
     * @return        An array of the extracted strings
     *
     * @see           #stringsToDelimitedBytes(String[], int, byte)
     * 
     * @since         1.18
     */
    static String[] delimitedBytesToStrings(byte[] bytes, int maxlen, byte delim) {
        if (maxlen <= 0) {
            return delimitedBytesToStrings(bytes, delim);
        }

        String[] res = new String[(bytes.length + maxlen - 1) / maxlen];
        ParsePosition pos = new ParsePosition(0);
        for (int i = 0; i < res.length; i++) {
            res[i] = extractString(bytes, pos, maxlen, delim);
        }
        return res;
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy