private void readHeader()

in endorsed/src/org.apache.sis.storage/main/org/apache/sis/storage/esri/RawRasterStore.java [340:514]


    private void readHeader() throws URISyntaxException, IOException, DataStoreException {
        assert Thread.holdsLock(this);
        @SuppressWarnings("LocalVariableHidesMemberVariable")
        final ChannelDataInput input = this.input;
        if (input == null) {
            throw new DataStoreClosedException(canNotRead());
        }
        int     nrows          = 0;
        int     ncols          = 0;
        int     nbands         = 1;
        int     nbits          = Byte.SIZE;
        boolean signed         = false;
        long    skipBytes      = 0;
        int     bandRowBytes   = 0;
        int     totalRowBytes  = 0;
        int     bandGapBytes   = 0;
        double  ulxmap         = 0;
        double  ulymap         = 0;
        double  xdim           = 1;
        double  ydim           = 1;
        int     geomask        = 0;   // Mask telling whether ulxmap, ulymap, xdim, ydim were specified (in that order).
        RawRasterLayout layout = RawRasterLayout.BIL;
        ByteOrder byteOrder    = ByteOrder.nativeOrder();
        final AuxiliaryContent header = readAuxiliaryFile(RawRasterStoreProvider.HDR, false);
        if (header == null) {
            throw new DataStoreException(cannotReadAuxiliaryFile(RawRasterStoreProvider.HDR));
        }
        for (CharSequence line : CharSequences.splitOnEOL(header)) {
            final int length   = line.length();
            final int keyStart = CharSequences.skipLeadingWhitespaces(line, 0, length);
            final int keyEnd   = CharSequences.indexOf(line, ' ', keyStart, length);
            if (keyStart >= 0) {
                // Note: text after value is considered comment according ESRI specification.
                int valStart = CharSequences.skipLeadingWhitespaces(line, keyEnd, length);
                int valEnd   = CharSequences.indexOf(line, ' ', valStart, length);
                if (valEnd < 0) {
                    valEnd = CharSequences.skipTrailingWhitespaces(line, valStart, length);
                    if (valEnd <= valStart) continue;
                }
                final String keyword = line.subSequence(keyStart, keyEnd).toString();
                final String value   = line.subSequence(valStart, valEnd).toString();
                try {
                    switch (keyword.toUpperCase(Locale.US)) {
                        case NROWS:         nrows         = parseStrictlyPositive(keyword, value); break;
                        case NCOLS:         ncols         = parseStrictlyPositive(keyword, value); break;
                        case NBANDS:        nbands        = parseStrictlyPositive(keyword, value); break;
                        case NBITS:         nbits         = parseStrictlyPositive(keyword, value); break;
                        case BANDROWBYTES:  bandRowBytes  = parseStrictlyPositive(keyword, value); break;
                        case TOTALROWBYTES: totalRowBytes = parseStrictlyPositive(keyword, value); break;
                        case BANDGAPBYTES:  bandGapBytes  = Integer.parseInt(value); break;
                        case SKIPBYTES:     skipBytes     = Long.parseLong(value); break;
                        case ULXMAP:        ulxmap        = Double.parseDouble(value); geomask |= 1; break;
                        case ULYMAP:        ulymap        = Double.parseDouble(value); geomask |= 2; break;
                        case XDIM:          xdim          = Double.parseDouble(value); geomask |= 4; break;
                        case YDIM:          ydim          = Double.parseDouble(value); geomask |= 8; break;
                        case NODATA:        nodataValue   = Double.parseDouble(value); break;
                        case PIXELTYPE:     signed        = indexOf(keyword, value, "SIGNED", "SIGNEDINT") >= 0; break;
                        case LAYOUT:        layout        = RawRasterLayout.valueOf(value.toUpperCase(Locale.US)); break;
                        case BYTEORDER: {
                            switch (indexOf(keyword, value, "I", "M")) {
                                case 0:  byteOrder = ByteOrder.LITTLE_ENDIAN; break;
                                case 1:  byteOrder = ByteOrder.BIG_ENDIAN; break;
                                default: throw new DataStoreContentException(errors().getString(
                                            Errors.Keys.IllegalPropertyValue_2, keyword, value));
                            }
                        }
                        /*
                         * No default. The specification said that any line in the file that
                         * does not begin with a keyword is treated as a comment and ignored.
                         */
                    }
                } catch (IllegalArgumentException e) {      // Include NumberFormatException.
                    throw new DataStoreContentException(errors().getString(
                            Errors.Keys.IllegalPropertyValue_2, keyword, value), e);
                }
            }
        }
        input.buffer.order(byteOrder);
        /*
         * Validate parameters, compute default values then create the grid geometry.
         * If one of ULXMAP or ULYMAP is specified, then both of them shall be specified.
         * If one of XDIM or YDIM is specified, then all of ULXMAP, ULYMAP, XDIM and YDIM shall be specified.
         */
        if (nrows == 0 || ncols == 0) {
            throw missingProperty(header, (nrows == 0) ? NROWS : NCOLS);
        }
        // Invoke following method now because it does argument validation.
        final var dataType = DataType.forNumberOfBits(nbits, false, signed);
        final int bytesPerSample = dataType.bytes();
        switch (geomask) {
            case 0:  ulymap = ncols - 1; break;     // No property specified.
            case 3:  break;                         // ULXMAP and ULYMAP specified.
            case 15: break;                         // ULXMAP, ULYMAP, XDIM and YDIM specified.
            default: {
                final String keyword;
                switch (Integer.lowestOneBit(~geomask)) {
                    case 1:  keyword = ULXMAP; break;
                    case 2:  keyword = ULYMAP; break;
                    case 4:  keyword = XDIM;   break;
                    case 8:  keyword = YDIM;   break;
                    default: keyword = "?";    break;       // Should not happen.
                }
                throw missingProperty(header, keyword);
            }
        }
        readPRJ(RawRasterStore.class, "getGridGeometry");
        final GridGeometry gg = new GridGeometry(new GridExtent(ncols, nrows), CELL_ANCHOR,
                new AffineTransform2D(xdim, 0, 0, -ydim, ulxmap, ulymap), crs);
        /*
         * Create a sample model for the data layout. This block encapsulates all layout information
         * except `skipBytes` and `bandGapBytes`, which need to be taken in account at reading time.
         * Note that there is many ways to create a sample model. For example, a `BandedSampleModel`
         * could store 3 bands in the same array or in 3 different arrays. The choices made in this
         * block must be consistent with the expectations of `read(…)` method implementation.
         */
        SampleModel sampleModel = null;
        final int bt = dataType.toDataBufferType();
        switch (layout) {
            case BIL: {
                ignoredProperty(BANDGAPBYTES, bandGapBytes);
                if (bandRowBytes  == 0)  bandRowBytes = ceilDiv(multiplyExact(ncols, nbits), Byte.SIZE);
                if (totalRowBytes == 0) totalRowBytes = multiplyExact(nbands, bandRowBytes);
                if (bytesPerSample != 0) {
                    final int   bandStride     = wholeDiv(bandRowBytes,  bytesPerSample);
                    final int   scanlineStride = wholeDiv(totalRowBytes, bytesPerSample);
                    final int[] bankIndices    = new int[nbands];
                    final int[] bandOffsets    = new int[nbands];
                    for (int i=1; i<nbands; i++) {
                        bandOffsets[i] = multiplyExact(bandStride, i);
                    }
                    sampleModel = new ComponentSampleModel(bt, ncols, nrows, 1, scanlineStride, bankIndices, bandOffsets);
                }
                break;
            }
            case BIP: {
                ignoredProperty(BANDGAPBYTES, bandGapBytes);
                ignoredProperty(BANDROWBYTES, bandRowBytes);
                if (totalRowBytes == 0) {
                    totalRowBytes = ceilDiv(multiplyExact(multiplyExact(ncols, nbands), nbits), Byte.SIZE);
                }
                if (bytesPerSample != 0) {
                    final int   scanlineStride = wholeDiv(totalRowBytes, bytesPerSample);
                    final int[] bandOffsets    = ArraysExt.range(0, nbands);
                    sampleModel = new PixelInterleavedSampleModel(bt, ncols, nrows, nbands, scanlineStride, bandOffsets);
                }
                break;
            }
            case BSQ: {
                ignoredProperty(BANDROWBYTES, bandRowBytes);
                if (totalRowBytes == 0) {
                    totalRowBytes = ncols;
                }
                if (bytesPerSample != 0) {
                    final int   scanlineStride = wholeDiv(totalRowBytes, bytesPerSample);
                    final int[] bankIndices    = ArraysExt.range(0, nbands);
                    final int[] bandOffsets    = new int[nbands];
                    sampleModel = new BandedSampleModel(bt, ncols, nrows, scanlineStride, bankIndices, bandOffsets);
                }
                break;
            }
            default: throw new AssertionError(layout);
        }
        if (bytesPerSample == 0) {
            if (nbands != 1) {
                throw new DataStoreContentException(errors().getString(Errors.Keys.InconsistentAttribute_2, nbits, NBITS));
            }
            sampleModel = new MultiPixelPackedSampleModel(bt, ncols, nrows, nbits, totalRowBytes, 0);
        }
        /*
         * Prepare the reader as the last step because non-null `reader` field is used
         * as a sentinel value meaning that the initialization has been completed.
         */
        reader = new RawRasterReader(gg, dataType, sampleModel, bandGapBytes, input);
        reader.setOrigin(skipBytes);
    }