/*
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "Ant" and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

/*
 * This package is based on the work done by Keiron Liddle, Aftex Software
 * <keiron@aftexsw.com> to whom the Ant project is very grateful for his
 * great code.
 */

/*
 * There has been further modification of this class by Unidata to
 * support setting the inputstream. This saves many extra memory
 * allocations when doing lots of uncompressions.  The read method
 * may throw a BZip2ReadException instead of an IOException because
 * the superclass catches and discards IOExceptions.
 */

package ucar.unidata.io.bzip2;

import java.io.InputStream;
import java.io.IOException;

/**
 * An input stream that decompresses from the BZip2 format (without the file
 * header chars) to be read as any other stream.
 *
 * @author Keiron Liddle
 */
public class CBZip2InputStream extends InputStream implements BZip2Constants {
  private static final int START_BLOCK_STATE = 1;
  private static final int RAND_PART_A_STATE = 2;
  private static final int RAND_PART_B_STATE = 3;
  private static final int RAND_PART_C_STATE = 4;
  private static final int NO_RAND_PART_A_STATE = 5;
  private static final int NO_RAND_PART_B_STATE = 6;
  private static final int NO_RAND_PART_C_STATE = 7;

  /*
    index of the last char in the block, so
    the block size == last + 1.
  */
  private int last;

  /*
    index in zptr[] of original string after sorting.
  */
  private int origPtr;

  /*
    always: in the range 0 .. 9.
    The current block size is 100000 * this number.
  */
  private int blockSize100k;

  private boolean blockRandomised;

  private int bsBuff;
  private int bsLive;
  private CRC mCrc = new CRC();

  private boolean[] inUse = new boolean[256];
  private int nInUse;

  private char[] seqToUnseq = new char[256];
  private char[] unseqToSeq = new char[256];

  private char[] selector = new char[MAX_SELECTORS];
  private char[] selectorMtf = new char[MAX_SELECTORS];

  private int[] tt;
  private char[] ll8;

  /*
    freq table collected to save a pass over the data
    during decompression.
  */
  private int[] unzftab = new int[256];

  private int[][] limit = new int[N_GROUPS][MAX_ALPHA_SIZE];
  private int[][] base = new int[N_GROUPS][MAX_ALPHA_SIZE];
  private int[][] perm = new int[N_GROUPS][MAX_ALPHA_SIZE];
  private int[] minLens = new int[N_GROUPS];

  private InputStream bsStream;

  private boolean streamEnd = false;
  private int currentChar = -1;
  private int currentState = START_BLOCK_STATE;

  private int storedBlockCRC, storedCombinedCRC;
  private int computedBlockCRC, computedCombinedCRC;

  int i2, count, chPrev, ch2;
  int i, tPos;
  int rNToGo = 0;
  int rTPos = 0;
  int j2;
  char z;

  public CBZip2InputStream() {
  }

  public CBZip2InputStream(InputStream zStream) {
    setStream(zStream);
  }

  public CBZip2InputStream(InputStream zStream, boolean skip) throws IOException {
    if (skip) {
      byte[] bzString = new byte[2];
      int ret = zStream.read(bzString);
      if (ret == -1)
          throw new IOException("End of stream reached skipping bytes");
    }
    setStream(zStream);
  }
  /**
   * Added 5-30-2006 to allow for resetting of the input used
   * by this object. This saves in memory allocation costs
   */
  public void setStream(InputStream zStream) {
    last=0;
    origPtr=0;
    blockSize100k=0;
    blockRandomised=false;
    bsBuff=0;
    bsLive=0;
    mCrc = new CRC();
    nInUse=0;
    bsStream=null;
    streamEnd = false;
    currentChar = -1;
    currentState = START_BLOCK_STATE;
    storedBlockCRC= storedCombinedCRC=0;
    computedBlockCRC= computedCombinedCRC=0;
    i2= count= chPrev= ch2=0;
    i= tPos=0;
    rNToGo = 0;
    rTPos = 0;
    j2=0;
    z=0;

    bsSetStream(zStream);
    initialize();
    if (!streamEnd) { // Handle if initialize does not detect valid bz2 stream
      initBlock();
      setupBlock();
    }
  }

  /**
   * Reads the stream.
   * @throws BZip2ReadException if there is a problem.  InputStream does
   *         not throw an IOException, so we throw a RuntimeException.
   */
  public int read() {
    if (streamEnd) {
      return -1;
    } else {
      int retChar = currentChar;
      switch (currentState) {
        case START_BLOCK_STATE:
          break;
        case RAND_PART_A_STATE:
          break;
        case RAND_PART_B_STATE:
          setupRandPartB();
          break;
        case RAND_PART_C_STATE:
          setupRandPartC();
          break;
        case NO_RAND_PART_A_STATE:
          break;
        case NO_RAND_PART_B_STATE:
          setupNoRandPartB();
          break;
        case NO_RAND_PART_C_STATE:
          setupNoRandPartC();
          break;
        default:
          break;
      }
      return retChar;
    }
  }

  private void initialize() {
    char magic3, magic4;
    magic3 = bsGetUChar();
    magic4 = bsGetUChar();
    if (magic3 != 'h' || magic4 < '1' || magic4 > '9') {
      bsFinishedWithStream();
      streamEnd = true;
      return;
    }

    setDecompressStructureSizes(magic4 - '0');
    computedCombinedCRC = 0;
  }

  private void initBlock() {
    char magic1, magic2, magic3, magic4;
    char magic5, magic6;
    magic1 = bsGetUChar();
    magic2 = bsGetUChar();
    magic3 = bsGetUChar();
    magic4 = bsGetUChar();
    magic5 = bsGetUChar();
    magic6 = bsGetUChar();
    if (magic1 == 0x17 && magic2 == 0x72 && magic3 == 0x45
            && magic4 == 0x38 && magic5 == 0x50 && magic6 == 0x90) {
      complete();
      return;
    }

    if (magic1 != 0x31 || magic2 != 0x41 || magic3 != 0x59
            || magic4 != 0x26 || magic5 != 0x53 || magic6 != 0x59) {
      badBlockHeader();
      streamEnd = true;
      return;
    }

    storedBlockCRC = bsGetInt32();

    if (bsR(1) == 1) {
      blockRandomised = true;
    } else {
      blockRandomised = false;
    }

    //        currBlockNo++;
    getAndMoveToFrontDecode();

    mCrc.initialiseCRC();
    currentState = START_BLOCK_STATE;
  }

  private void endBlock() {
    computedBlockCRC = mCrc.getFinalCRC();
    /* A bad CRC is considered a fatal error. */
    if (storedBlockCRC != computedBlockCRC) {
      // crcError();
      cadvise("CRC error: storedBlockCRC != computedBlockCRC");
    }

    computedCombinedCRC = (computedCombinedCRC << 1)
            | (computedCombinedCRC >>> 31);
    computedCombinedCRC ^= computedBlockCRC;
  }

  private void complete() {
    storedCombinedCRC = bsGetInt32();
    if (storedCombinedCRC != computedCombinedCRC) {
      // crcError();
      cadvise("CRC error: storedCombinedCRC != computedCombinedCRC");
    }

    bsFinishedWithStream();
    streamEnd = true;
  }

  private void blockOverrun() {
    cadvise("Block Overrun");
  }

  private void badBlockHeader() {
    cadvise("Bad Block Header");
  }

  private void crcError() {
    cadvise();
  }

  private void bsFinishedWithStream() {
    try {
      if (this.bsStream != null) {
        if (this.bsStream != System.in) {
          this.bsStream.close();
          this.bsStream = null;
        }
      }
    } catch (IOException ioe) {
      //ignore
    }
  }

  private void bsSetStream(InputStream f) {
    bsStream = f;
    bsLive = 0;
    bsBuff = 0;
  }

  private int bsR(int n) {
    int v;
    while (bsLive < n) {
      int zzi = 0;
      try {
        zzi = bsStream.read();
      } catch (IOException e) {
        compressedStreamEOF();
      }
      if (zzi == -1) {
        compressedStreamEOF();
      }
      bsBuff = (bsBuff << 8) | (zzi & 0xff);
      bsLive += 8;
    }

    v = (bsBuff >> (bsLive - n)) & ((1 << n) - 1);
    bsLive -= n;
    return v;
  }

  private char bsGetUChar() {
    return (char) bsR(8);
  }

  private int bsGetint() {
    int u = 0;
    u = (u << 8) | bsR(8);
    u = (u << 8) | bsR(8);
    u = (u << 8) | bsR(8);
    u = (u << 8) | bsR(8);
    return u;
  }

  private int bsGetIntVS(int numBits) {
    return bsR(numBits);
  }

  private int bsGetInt32() {
    return bsGetint();
  }

  private void hbCreateDecodeTables(int[] limit, int[] base,
          int[] perm, char[] length,
          int minLen, int maxLen, int alphaSize) {
    int pp, i, j, vec;

    pp = 0;
    for (i = minLen; i <= maxLen; i++) {
      for (j = 0; j < alphaSize; j++) {
        if (length[j] == i) {
          perm[pp] = j;
          pp++;
        }
      }
    }

    for (i = 0; i < MAX_CODE_LEN; i++) {
      base[i] = 0;
    }
    for (i = 0; i < alphaSize; i++) {
      base[length[i] + 1]++;
    }

    for (i = 1; i < MAX_CODE_LEN; i++) {
      base[i] += base[i - 1];
    }

    for (i = 0; i < MAX_CODE_LEN; i++) {
      limit[i] = 0;
    }
    vec = 0;

    for (i = minLen; i <= maxLen; i++) {
      vec += (base[i + 1] - base[i]);
      limit[i] = vec - 1;
      vec <<= 1;
    }
    for (i = minLen + 1; i <= maxLen; i++) {
      base[i] = ((limit[i - 1] + 1) << 1) - base[i];
    }
  }

  private void recvDecodingTables() {
    char len[][] = new char[N_GROUPS][MAX_ALPHA_SIZE];
    int i, j, t, nGroups, nSelectors, alphaSize;
    int minLen, maxLen;
    boolean[] inUse16 = new boolean[16];

    /* Receive the mapping table */
    for (i = 0; i < 16; i++) {
      if (bsR(1) == 1) {
        inUse16[i] = true;
      } else {
        inUse16[i] = false;
      }
    }

    for (i = 0; i < 256; i++) {
      inUse[i] = false;
    }

    for (i = 0; i < 16; i++) {
      if (inUse16[i]) {
        for (j = 0; j < 16; j++) {
          if (bsR(1) == 1) {
            inUse[i * 16 + j] = true;
          }
        }
      }
    }

    makeMaps();
    alphaSize = nInUse + 2;

    /* Now the selectors */
    nGroups = bsR(3);
    nSelectors = bsR(15);
    for (i = 0; i < nSelectors; i++) {
      j = 0;
      while (bsR(1) == 1) {
        j++;
      }
      selectorMtf[i] = (char) j;
    }

    /* Undo the MTF values for the selectors. */
    {
      char[] pos = new char[N_GROUPS];
      char tmp, v;
      for (v = 0; v < nGroups; v++) {
        pos[v] = v;
      }

      for (i = 0; i < nSelectors; i++) {
        v = selectorMtf[i];
        tmp = pos[v];
        while (v > 0) {
          pos[v] = pos[v - 1];
          v--;
        }
        pos[0] = tmp;
        selector[i] = tmp;
      }
    }

    /* Now the coding tables */
    for (t = 0; t < nGroups; t++) {
      int curr = bsR(5);
      for (i = 0; i < alphaSize; i++) {
        while (bsR(1) == 1) {
          if (bsR(1) == 0) {
            curr++;
          } else {
            curr--;
          }
        }
        len[t][i] = (char) curr;
      }
    }

    /* Create the Huffman decoding tables */
    for (t = 0; t < nGroups; t++) {
      minLen = 32;
      maxLen = 0;
      for (i = 0; i < alphaSize; i++) {
        if (len[t][i] > maxLen) {
          maxLen = len[t][i];
        }
        if (len[t][i] < minLen) {
          minLen = len[t][i];
        }
      }
      hbCreateDecodeTables(limit[t], base[t], perm[t], len[t], minLen,
              maxLen, alphaSize);
      minLens[t] = minLen;
    }
  }

  private void getAndMoveToFrontDecode() {
    char[] yy = new char[256];
    int i, j, nextSym, limitLast;
    int EOB, groupNo, groupPos;

    limitLast = baseBlockSize * blockSize100k;
    origPtr = bsGetIntVS(24);

    recvDecodingTables();
    EOB = nInUse + 1;
    groupNo = -1;
    groupPos = 0;

    /*
      Setting up the unzftab entries here is not strictly
      necessary, but it does save having to do it later
      in a separate pass, and so saves a block's worth of
      cache misses.
    */
    for (i = 0; i <= 255; i++) {
      unzftab[i] = 0;
    }

    for (i = 0; i <= 255; i++) {
      yy[i] = (char) i;
    }

    last = -1;

    {
      int zt, zn, zvec, zj;
      if (groupPos == 0) {
        groupNo++;
        groupPos = G_SIZE;
      }
      groupPos--;
      zt = selector[groupNo];
      zn = minLens[zt];
      zvec = bsR(zn);
      while (zvec > limit[zt][zn]) {
        zn++;
        {
          {
            while (bsLive < 1) {
              int zzi = 0;
              try {
                zzi = bsStream.read();
              } catch (IOException e) {
                compressedStreamEOF();
              }
              if (zzi == -1) {
                compressedStreamEOF();
              }
              bsBuff = (bsBuff << 8) | (zzi & 0xff);
              bsLive += 8;
            }
          }
          zj = (bsBuff >> (bsLive - 1)) & 1;
          bsLive--;
        }
        zvec = (zvec << 1) | zj;
      }
      nextSym = perm[zt][zvec - base[zt][zn]];
    }

    while (true) {

      if (nextSym == EOB) {
        break;
      }

      if (nextSym == RUNA || nextSym == RUNB) {
        char ch;
        int s = -1;
        int N = 1;
        do {
          if (nextSym == RUNA) {
            s = s + (0 + 1) * N;
          } else if (nextSym == RUNB) {
            s = s + (1 + 1) * N;
          }
          N = N * 2;
          {
            int zt, zn, zvec, zj;
            if (groupPos == 0) {
              groupNo++;
              groupPos = G_SIZE;
            }
            groupPos--;
            zt = selector[groupNo];
            zn = minLens[zt];
            zvec = bsR(zn);
            while (zvec > limit[zt][zn]) {
              zn++;
              {
                {
                  while (bsLive < 1) {
                    int zzi = 0;
                    try {
                      zzi = bsStream.read();
                    } catch (IOException e) {
                      compressedStreamEOF();
                    }
                    if (zzi == -1) {
                      compressedStreamEOF();
                    }
                    bsBuff = (bsBuff << 8) | (zzi & 0xff);
                    bsLive += 8;
                  }
                }
                zj = (bsBuff >> (bsLive - 1)) & 1;
                bsLive--;
              }
              zvec = (zvec << 1) | zj;
            }
            nextSym = perm[zt][zvec - base[zt][zn]];
          }
        } while (nextSym == RUNA || nextSym == RUNB);

        s++;
        ch = seqToUnseq[yy[0]];
        unzftab[ch] += s;

        while (s > 0) {
          last++;
          ll8[last] = ch;
          s--;
        }

        if (last >= limitLast) {
          blockOverrun();
        }
        continue;
      } else {
        char tmp;
        last++;
        if (last >= limitLast) {
          blockOverrun();
        }

        tmp = yy[nextSym - 1];
        unzftab[seqToUnseq[tmp]]++;
        ll8[last] = seqToUnseq[tmp];

        /*
          This loop is hammered during decompression,
          hence the unrolling.

          for (j = nextSym-1; j > 0; j--) yy[j] = yy[j-1];
        */

        j = nextSym - 1;
        for (; j > 3; j -= 4) {
          yy[j] = yy[j - 1];
          yy[j - 1] = yy[j - 2];
          yy[j - 2] = yy[j - 3];
          yy[j - 3] = yy[j - 4];
        }
        for (; j > 0; j--) {
          yy[j] = yy[j - 1];
        }

        yy[0] = tmp;
        {
          int zt, zn, zvec, zj;
          if (groupPos == 0) {
            groupNo++;
            groupPos = G_SIZE;
          }
          groupPos--;
          zt = selector[groupNo];
          zn = minLens[zt];
          zvec = bsR(zn);
          while (zvec > limit[zt][zn]) {
            zn++;
            {
              {
                while (bsLive < 1) {
                  int zzi;
                  char thech = 0;
                  try {
                    thech = (char) bsStream.read();
                  } catch (IOException e) {
                    compressedStreamEOF();
                  }
                  zzi = thech;
                  bsBuff = (bsBuff << 8) | (zzi & 0xff);
                  bsLive += 8;
                }
              }
              zj = (bsBuff >> (bsLive - 1)) & 1;
              bsLive--;
            }
            zvec = (zvec << 1) | zj;
          }
          nextSym = perm[zt][zvec - base[zt][zn]];
        }
        continue;
      }
    }
  }

  private void setupBlock() {
    int[] cftab = new int[257];
    char ch;

    cftab[0] = 0;
    for (i = 1; i <= 256; i++) {
      cftab[i] = unzftab[i - 1];
    }
    for (i = 1; i <= 256; i++) {
      cftab[i] += cftab[i - 1];
    }

    for (i = 0; i <= last; i++) {
      ch = ll8[i];
      tt[cftab[ch]] = i;
      cftab[ch]++;
    }
    cftab = null;

    tPos = tt[origPtr];

    count = 0;
    i2 = 0;
    ch2 = 256;   /* not a char and not EOF */

    if (blockRandomised) {
      rNToGo = 0;
      rTPos = 0;
      setupRandPartA();
    } else {
      setupNoRandPartA();
    }
  }

  private void setupRandPartA() {
    if (i2 <= last) {
      chPrev = ch2;
      ch2 = ll8[tPos];
      tPos = tt[tPos];
      if (rNToGo == 0) {
        rNToGo = rNums[rTPos];
        rTPos++;
        if (rTPos == 512) {
          rTPos = 0;
        }
      }
      rNToGo--;
      ch2 ^= ((rNToGo == 1) ? 1 : 0);
      i2++;

      currentChar = ch2;
      currentState = RAND_PART_B_STATE;
      mCrc.updateCRC(ch2);
    } else {
      endBlock();
      initBlock();
      setupBlock();
    }
  }

  private void setupNoRandPartA() {
    if (i2 <= last) {
      chPrev = ch2;
      ch2 = ll8[tPos];
      tPos = tt[tPos];
      i2++;

      currentChar = ch2;
      currentState = NO_RAND_PART_B_STATE;
      mCrc.updateCRC(ch2);
    } else {
      endBlock();
      initBlock();
      setupBlock();
    }
  }

  private void setupRandPartB() {
    if (ch2 != chPrev) {
      currentState = RAND_PART_A_STATE;
      count = 1;
      setupRandPartA();
    } else {
      count++;
      if (count >= 4) {
        z = ll8[tPos];
        tPos = tt[tPos];
        if (rNToGo == 0) {
          rNToGo = rNums[rTPos];
          rTPos++;
          if (rTPos == 512) {
            rTPos = 0;
          }
        }
        rNToGo--;
        z ^= ((rNToGo == 1) ? 1 : 0);
        j2 = 0;
        currentState = RAND_PART_C_STATE;
        setupRandPartC();
      } else {
        currentState = RAND_PART_A_STATE;
        setupRandPartA();
      }
    }
  }

  private void setupRandPartC() {
    if (j2 < (int) z) {
      currentChar = ch2;
      mCrc.updateCRC(ch2);
      j2++;
    } else {
      currentState = RAND_PART_A_STATE;
      i2++;
      count = 0;
      setupRandPartA();
    }
  }

  private void setupNoRandPartB() {
    if (ch2 != chPrev) {
      currentState = NO_RAND_PART_A_STATE;
      count = 1;
      setupNoRandPartA();
    } else {
      count++;
      if (count >= 4) {
        z = ll8[tPos];
        tPos = tt[tPos];
        currentState = NO_RAND_PART_C_STATE;
        j2 = 0;
        setupNoRandPartC();
      } else {
        currentState = NO_RAND_PART_A_STATE;
        setupNoRandPartA();
      }
    }
  }

  private void setupNoRandPartC() {
    if (j2 < (int) z) {
      currentChar = ch2;
      mCrc.updateCRC(ch2);
      j2++;
    } else {
      currentState = NO_RAND_PART_A_STATE;
      i2++;
      count = 0;
      setupNoRandPartA();
    }
  }

  private void setDecompressStructureSizes(int newSize100k) {
    if (!(0 <= newSize100k && newSize100k <= 9 && 0 <= blockSize100k
            && blockSize100k <= 9)) {
      // throw new IOException("Invalid block size");
    }

    blockSize100k = newSize100k;

    if (newSize100k == 0) {
      return;
    }

    //Modified  5-30-2006 by unidata to allow for reuse of the ll8 and tt buffers

    int n = baseBlockSize * newSize100k;

    if (ll8 != null && ll8.length != n) {
      ll8 = null;
    }

    if (tt != null && tt.length != n) {
      tt = null;
    }

    if (ll8 == null) {
      ll8 = new char[n];
    }
    if (tt == null) {
      tt = new int[n];
    }
  }

  private void cadvise() {
    System.out.println("CRC Error");
    //throw new CCoruptionError();
  }

  private void cadvise(String msg) {
    throw new BZip2ReadException(msg);
  }

  private void compressedStreamEOF() {
    cadvise("Compressed Stream EOF");
  }

  private void makeMaps() {
    int i;
    nInUse = 0;
    for (i = 0; i < 256; i++) {
      if (inUse[i]) {
        seqToUnseq[nInUse] = (char) i;
        unseqToSeq[i] = (char) nInUse;
        nInUse++;
      }
    }
  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ui.widget;

import javax.swing.event.EventListenerList;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
 * This wraps a javax.swing.ProgressMonitor, which allows tasks to be canceled.
 * This class adds extra behavior to javax.swing.ProgressMonitor:
 *

 *
 Pass in the ProgressMonitorTask you want to monitor.
 *
 Throws an actionEvent (on the AWT event thread) when the task is done.
 *
 If an error, pops up an error message.
 *
 Get status: success/failed/cancel when task is done.
 *

 *

 * The ProgressMonitorTask is run in a background thread while a
 * javax.swing.ProgressMonitor dialog box shows progress. The task is checked every second
 * to see if its done or canceled.
 *

 *

 *  Example:
 *
 * AddDatasetTask task = new AddDatasetTask(datasets);
 * ProgressMonitor pm = new ProgressMonitor(task);
 * pm.addActionListener( new ActionListener() {
 * public void actionPerformed(ActionEvent e) {
 * if (e.getActionCommand().equals("success")) {
 * doGoodStuff();
 * }
 * }
 * });
 * pm.start( this, "Add Datasets", datasets.size());
 *
 * class AddDatasetTask extends ProgressMonitorTask {
 * private List datasets;
 *
 * OpenDatasetTask(List datasets) { this.datasets = datasets; }
 *
 * public void run() {
 * Iterator iter = datasets.iterator();
 * while (iter.hasNext()) {
 * AddeDataset ads = (AddeDataset) iter.next();
 * this.note = ads.filenameReletive();
 * try {
 * ads.addImageData( currentSM.serverInfo(), results, false);
 * } catch (IOException ioe) {
 * error = ioe.getMessage();
 * break;
 * }
 * if (cancel) break;
 * this.progress++;
 * }
 * success = !cancel && !isError();
 * done = true;    // do last!
 * }
 * }
 *

 *
 * @author jcaron
 * @version 1.0
 * @see ProgressMonitorTask
 */

public class ProgressMonitor {
  private javax.swing.ProgressMonitor pm;
  private ProgressMonitorTask task;
  private javax.swing.Timer timer;
  private Thread taskThread;
  private int millisToPopup = 100;
  private int millisToDecideToPopup = 100;
  private int secs = 0;

  // event handling
  private EventListenerList listenerList = new EventListenerList();

  public ProgressMonitor(ProgressMonitorTask task) {
    this(task, 1000, 1000);
  }

  public ProgressMonitor(ProgressMonitorTask task, int millisToPopup, int millisToDecideToPopup) {
    this.task = task;
    this.millisToPopup = millisToPopup;
    this.millisToDecideToPopup = millisToDecideToPopup;
  }

  public ProgressMonitorTask getTask() {
    return task;
  }

  /**
   * Add listener: action event sent when task is done. event.getActionCommand() =
   *

 "success"
   *
 "error"
   *
 "cancel"
   *
 "done" if done, but success/error/cancel not set
   *

   */
  public void addActionListener(ActionListener l) {
    listenerList.add(java.awt.event.ActionListener.class, l);
  }

  /**
   * Remove listener
   */
  public void removeActionListener(ActionListener l) {
    listenerList.remove(java.awt.event.ActionListener.class, l);
  }

  private void fireEvent(java.awt.event.ActionEvent event) {
    // Guaranteed to return a non-null array
    Object[] listeners = listenerList.getListenerList();
    // Process the listeners last to first
    for (int i = listeners.length - 2; i >= 0; i -= 2) {
      ((java.awt.event.ActionListener) listeners[i + 1]).actionPerformed(event);
    }
  }

  /**
   * Call this from awt event thread.
   * The task is run in a background thread.
   *
   * @param top              put ProgressMonitor on top of this component (may be null)
   * @param taskName         display name of task
   * @param progressMaxCount maximum number of Progress indicator
   */
  public void start(java.awt.Component top, String taskName, int progressMaxCount) {
    // create ProgressMonitor
    pm = new javax.swing.ProgressMonitor(top, taskName, "", 0, progressMaxCount);
    pm.setMillisToDecideToPopup(millisToDecideToPopup);
    pm.setMillisToPopup(millisToPopup);

    // do task in a seperate, non-event, thread
    taskThread = new Thread(task);
    taskThread.start();

    // create timer, whose events happen on the awt event Thread
    ActionListener watcher = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        secs++;
        if (pm.isCanceled()) {
          task.cancel();
        } else {
          // indicate progress
          String note = task.getNote();
          pm.setNote(note == null ? secs + " secs" : note);
          int progress = task.getProgress();
          pm.setProgress(progress <= 0 ? secs : progress);
        }

        // need to make sure task acknowledges the cancel; so dont shut down
        // until the task is done
        if (task.isDone()) {
          timer.stop();
          pm.close();
          // Toolkit.getDefaultToolkit().beep();

          if (task.isError()) {
            javax.swing.JOptionPane.showMessageDialog(null, task.getErrorMessage());
          }

          if (task.isSuccess())
            fireEvent(new ActionEvent(this, 0, "success"));
          else if (task.isError())
            fireEvent(new ActionEvent(this, 0, "error"));
          else if (task.isCancel())
            fireEvent(new ActionEvent(this, 0, "cancel"));
          else
            fireEvent(new ActionEvent(this, 0, "done"));
        }
      }
    };

    timer = new javax.swing.Timer(1000, watcher); // every second
    timer.start();
  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.httpservices;

import org.apache.http.*;
import org.apache.http.client.methods.HttpRequestWrapper;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.message.BasicHeader;
import org.apache.http.protocol.HttpContext;

import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

abstract public class HTTPUtil
{

    //////////////////////////////////////////////////
    // Constants

    static final public Charset UTF8 = Charset.forName("UTF-8");
    static final public Charset ASCII = Charset.forName("US-ASCII");
    static final public String LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
    static final public String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    static final public String DRIVELETTERS = LOWERCASE + UPPERCASE;

    //////////////////////////////////////////////////

    enum URIPart
    {
        SCHEME, USERINFO, HOST, PORT, PATH, QUERY, FRAGMENT
    }

    //////////////////////////////////////////////////
    // Interceptors

    static abstract class InterceptCommon
    {
        protected HttpContext context = null;
        protected List<Header> headers = new ArrayList<Header>();
        protected HttpRequest request = null;
        protected HttpResponse response = null;
        protected boolean printheaders = false;

        public InterceptCommon setPrint(boolean tf)
        {
            this.printheaders = tf;
            return this;
        }

        public void
        clear()
        {
            context = null;
            headers.clear();
            request = null;
            response = null;
        }

        synchronized public HttpRequest getRequest()
        {
            return this.request;
        }

        synchronized public HttpResponse getResponse()
        {
            return this.response;
        }

        synchronized public HttpContext getContext()
        {
            return this.context;
        }

        synchronized public HttpEntity getRequestEntity()
        {
            if(this.request != null
                    && this.request instanceof HttpEntityEnclosingRequest) {
                return ((HttpEntityEnclosingRequest) this.request).getEntity();
            } else
                return null;
        }

        synchronized HttpEntity getResponseEntity()
        {
            if(this.response != null) {
                return this.response.getEntity();
            } else
                return null;
        }

        synchronized public List<Header> getHeaders(String key)
        {
            List<Header> keyh = new ArrayList<Header>();
            for(Header h : this.headers) {
                if(h.getName().equalsIgnoreCase(key.trim()))
                    keyh.add(h);
            }
            return keyh;
        }

        synchronized List<Header> getHeaders()
        {
            return this.headers;
        }

        void
        printHeaders()
        {
            if(this.request != null) {
                Header[] hdrs = this.request.getAllHeaders();
                if(hdrs == null) hdrs = new Header[0];
                System.err.println("Request Headers:");
                for(Header h : hdrs) {
                    System.err.println(h.toString());
                }
            }
            if(this.response != null) {
                Header[] hdrs = this.response.getAllHeaders();
                if(hdrs == null) hdrs = new Header[0];
                System.err.println("Response Headers:");
                for(Header h : hdrs) {
                    System.err.println(h.toString());
                }
            }
            System.err.flush();
        }
    }

    static public class InterceptResponse extends InterceptCommon
            implements HttpResponseInterceptor
    {
        synchronized public void
        process(HttpResponse response, HttpContext context)
                throws HttpException, IOException
        {
            this.response = response;
            this.context = context;
            if(this.printheaders)
                printHeaders();
            else if(this.response != null) {
                Header[] hdrs = this.response.getAllHeaders();
                for(int i = 0; i < hdrs.length; i++) {
                    headers.add(hdrs[i]);
                }
            }
        }
    }

    static public class InterceptRequest extends InterceptCommon
            implements HttpRequestInterceptor
    {
        synchronized public void
        process(HttpRequest request, HttpContext context)
                throws HttpException, IOException
        {
            this.request = request;
            this.context = context;
            if(this.printheaders)
                printHeaders();
            else if(this.request != null) {
                Header[] hdrs = this.request.getAllHeaders();
                for(int i = 0; i < hdrs.length; i++) {
                    headers.add(hdrs[i]);
                }
            }
        }
    }

    //////////////////////////////////////////////////
    // Misc.

    static public byte[]
    readbinaryfile(File f)
            throws IOException
    {
        try (FileInputStream fis = new FileInputStream(f)) {
            return readbinaryfile(fis);
        }
    }

    static public byte[]
    readbinaryfile(InputStream stream)
            throws IOException
    {
        // Extract the stream into a bytebuffer
        ByteArrayOutputStream bytes = new ByteArrayOutputStream();
        byte[] tmp = new byte[1 << 16];
        for(; ; ) {
            int cnt;
            cnt = stream.read(tmp);
            if(cnt <= 0) break;
            bytes.write(tmp, 0, cnt);
        }
        return bytes.toByteArray();
    }

    static public File
    fillTempFile(String base, String content)
            throws IOException
    {
        // Locate a temp directory
        String tmppath = System.getenv("TEMP");
        if(tmppath == null || tmppath.length() == 0)
            tmppath = "/tmp";
        File tmpdir = new File(tmppath);
        if(!tmpdir.exists() || !tmpdir.canWrite())
            throw new IOException("Cannot create temp file: no tmp dir");
        try {
            String suffix;
            String prefix;
            int index = base.lastIndexOf('.');
            if(index < 0) index = base.length();
            suffix = base.substring(index, base.length());
            prefix = base.substring(0, index);
            if(prefix.length() == 0)
                throw new IOException("Malformed base: " + base);
            File f = File.createTempFile(prefix, suffix, tmpdir);
            // Fill with the content
            FileOutputStream fw = new FileOutputStream(f);
            fw.write(content.getBytes(UTF8));
            fw.close();
            return f;
        } catch (IOException e) {
            throw new IOException("Cannot create temp file", e);
        }
    }

    /**
     * @return {@code true} if the objects are equal or both null
     */
    static boolean equals(final Object obj1, final Object obj2)
    {
        return obj1 == null ? obj2 == null : obj1.equals(obj2);
    }

    /**
     * @return {@code true} if the objects are equal or both null
     */
    static boolean schemeEquals(String s1, String s2)
    {
        if(s1 == s2) return true;
        if((s1 == null) ^ (s2 == null)) return false;
        if((s1.length() == 0) ^ (s2.length() == 0)) return true;
        return s1.equals(s2);
    }

    /**
     * Convert a uri string to an instance of java.net.URI.
     * The critical thing is that this procedure can handle backslash
     * escaped uris as well as %xx escaped uris.
     *
     * @param u the uri to convert
     * @return The URI corresponding to u.
     * @throws URISyntaxException
     */
    static public URI
    parseToURI(final String u)
            throws URISyntaxException
    {
        StringBuilder buf = new StringBuilder();
        int i = 0;
        while(i < u.length()) {
            char c = u.charAt(i);
            if(c == '\\') {
                if(i + 1 == u.length())
                    throw new URISyntaxException(u, "Trailing '\' at end of url");
                buf.append("%5c");
                i++;
                c = u.charAt(i);
                buf.append(String.format("%%%02x", (int) c));
            } else
                buf.append(c);
            i++;
        }
        return new URI(buf.toString());
    }

    /**
     * Remove selected fields from a  URI producing a new URI
     *
     * @param uri      the uri to convert
     * @param excludes the parts to exclude
     * @return The new URI instance
     */
    static URI
    uriExclude(final URI uri, URIPart... excludes)
    {
        URIBuilder urib = new URIBuilder();
        EnumSet<URIPart> set = EnumSet.of(excludes[0], excludes);
        for(URIPart part : URIPart.values()) {
            if(set.contains(part)) continue;
            switch (part) {
            case SCHEME:
                urib.setScheme(uri.getScheme());
                break;
            case USERINFO:
                urib.setUserInfo(uri.getUserInfo());
                break;
            case HOST:
                urib.setHost(uri.getHost());
                break;
            case PORT:
                urib.setPort(uri.getPort());
                break;
            case PATH:
                urib.setPath(uri.getPath());
                break;
            case QUERY:
                urib.setCustomQuery(uri.getQuery());
                break;
            case FRAGMENT:
                urib.setFragment(uri.getFragment());
                break;
            }
        }
        try {
            return urib.build();
        } catch (URISyntaxException e) {
            throw new IllegalArgumentException(e.getMessage());
        }
    }

    /**
     * Temporary hack to remove Content-Encoding: XXX-Endian headers
     */
    static class ContentEncodingInterceptor extends InterceptCommon
            implements HttpResponseInterceptor
    {
        synchronized public void
        process(HttpResponse response, HttpContext context)
                throws HttpException, IOException
        {
            if(response == null) return;
            Header[] hdrs = response.getAllHeaders();
            if(hdrs == null) return;
            boolean modified = false;
            for(int i = 0; i < hdrs.length; i++) {
                Header h = hdrs[i];
                if(!h.getName().equalsIgnoreCase("content-encoding")) continue;
                String value = h.getValue();
                if(value.trim().toLowerCase().endsWith("-endian")) {
                    hdrs[i] = new BasicHeader("X-Content-Encoding", value);
                    modified = true;
                }
            }
            if(modified)
                response.setHeaders(hdrs);
            // Similarly, suppress encoding for Entity
            HttpEntity entity = response.getEntity();
            if(entity != null) {
                Header ceheader = entity.getContentEncoding();
                if(ceheader != null) {
                    String value = ceheader.getValue();
                }
            }
        }
    }

    static protected Map<HTTPSession.Prop, Object>
    merge(Map<HTTPSession.Prop, Object> globalsettings, Map<HTTPSession.Prop, Object> localsettings)
    {
        // merge global and local settings; local overrides global.
        Map<HTTPSession.Prop, Object> merge = new ConcurrentHashMap<HTTPSession.Prop, Object>();
        for(HTTPSession.Prop key : globalsettings.keySet()) {
            merge.put(key, globalsettings.get(key));
        }
        for(HTTPSession.Prop key : localsettings.keySet()) {
            merge.put(key, localsettings.get(key));
        }
        return merge;
    }

    /**
     * Convert a zero-length string to null
     *
     * @param s the string to check for length
     * @return null if s.length() == 0, s otherwise
     */
    static public String nullify(String s)
    {
        if(s != null && s.length() == 0) s = null;
        return s;
    }

    String joinList(List<String> list, String delim)
    {
        StringBuilder buf = new StringBuilder();
        for(int i = 0; i < list.size(); i++) {
            if(i > 0) buf.append(delim);
            buf.append(list.get(i));
        }
        return buf.toString();
    }

    /**
     * Join two string together to form proper path
     * WITHOUT trailing slash
     *
     * @param prefix
     * @param suffix
     * @return
     */
    static public String
    canonjoin(String prefix, String suffix)
    {
        if(prefix == null) prefix = "";
        if(suffix == null) suffix = "";
        prefix = HTTPUtil.canonicalpath(prefix);
        suffix = HTTPUtil.canonicalpath(suffix);
        StringBuilder result = new StringBuilder();
        result.append(prefix);
        int prelen = prefix.length();
        if(prelen > 0 && result.charAt(prelen - 1) != '/') {
            result.append('/');
            prelen++;
        }
        if(suffix.length() > 0 && suffix.charAt(0) == '/')
            result.append(suffix.substring(1));
        else
            result.append(suffix);
        int len = result.length();
        if(len > 0 && result.charAt(len - 1) == '/') {
            result.deleteCharAt(len - 1);
            len--;
        }
        return result.toString();
    }

    /**
     * Convert path to use '/' consistently and
     * to remove any trailing '/'
     *
     * @param path convert this path
     * @return canonicalized version
     */

    static public String
    canonicalpath(String path)
    {
        if(path == null) return null;
        StringBuilder b = new StringBuilder(path);
        canonicalpath(b);
        return b.toString();
    }

    static public void
    canonicalpath(StringBuilder s)
    {
        if(s == null || s.length() == 0)
            return;
        int index = 0;
        // "\\" -> "/"
        for(; ; ) {
            index = s.indexOf("\\", index);
            if(index < 0) break;
            s.replace(index, index + 1, "/");
        }
        boolean isabs = (s.charAt(0) == '/'); // remember
        for(; ; ) { // kill any leading '/'s
            if(s.length() == 0 || s.charAt(0) != '/') break;
            s.deleteCharAt(0);
        }
        // Do we have drive letter?
        boolean hasdrive = hasDriveLetter(s);

        if(hasdrive)
            s.setCharAt(0, Character.toLowerCase(s.charAt(0)));

        while(s.length() > 0 && s.charAt(s.length() - 1) == '/') {
            s.deleteCharAt(s.length() - 1); // kill any trailing '/'s
        }

        // Add back leading '/', if any
        if(!hasdrive && isabs)
            s.insert(0, '/');
    }

    /**
     * Convert path to remove any leading '/' or drive letter assumes canonical.
     *
     * @param path convert this path
     * @return relatived version
     */
    static public String relpath(String path)
    {
        if(path == null) return null;
        StringBuilder b = new StringBuilder(path);
        canonicalpath(b);
        if(b.length() > 0) {
            if(b.charAt(0) == '/')
                b.deleteCharAt(0);
            if(hasDriveLetter(b))
                b.delete(0, 2);
        }
        return b.toString();
    }

    /**
     * @param path to test
     * @return true if path appears to start with Windows drive letter
     */
    static public boolean
    hasDriveLetter(String path)
    {
        return (path != null && path.length() >= 2
                && path.charAt(1) == ':'
                && DRIVELETTERS.indexOf(path.charAt(0)) >= 0);
    }

    // Support function
    static protected boolean
    hasDriveLetter(StringBuilder path)
    {
        return (path.length() >= 2
                && path.charAt(1) == ':'
                && DRIVELETTERS.indexOf(path.charAt(0)) >= 0);
    }

    /**
     * @param path to test
     * @return true if path is absolute
     */
    static public boolean
    isAbsolutePath(String path)
    {
        return (path != null && path.length() > 0
                && (path.charAt(0) == '/' || hasDriveLetter(path)));
    }

    /**
     * Convert path to add a  leading '/'; assumes canonical.
     *
     * @param path convert this path
     * @return absolute version
     */
    static public String abspath(String path)
    {
        if(path == null) return "/";
        StringBuilder b = new StringBuilder(path);
        canonicalpath(b);
        if(b.charAt(0) == '/')
            b.deleteCharAt(0);
        if(b.charAt(0) != '/' || !hasDriveLetter(b))
            b.insert(0, '/');
        return b.toString();
    }

    static public String
    readtextfile(InputStream stream)
               throws IOException
    {
        InputStreamReader rdr = new InputStreamReader(stream, UTF8);
        return readtextfile(rdr);
    }

    static public String
    readtextfile(Reader rdr)
            throws IOException
    {
        StringBuilder buf = new StringBuilder();
        for(; ; ) {
            int c = rdr.read();
            if(c < 0) break;
            buf.append((char) c);
        }
        return buf.toString();
    }

    static public void
    writebinaryfile(byte[] content, File dst)
            throws IOException
    {
        FileOutputStream fos = new FileOutputStream(dst);
        fos.write(content);
        fos.close();
    }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package thredds.crawlabledataset.filter;

import thredds.crawlabledataset.CrawlableDataset;
import thredds.crawlabledataset.CrawlableDatasetFilter;

import java.util.Date;

/**
 * Accept datasets whose last modified date is at least the given number
 * of milliseconds in the past.
 *
 * @author edavis
 */
public class LastModifiedLimitFilter implements CrawlableDatasetFilter
{
  private long lastModifiedLimitInMillis;

  /**
   * Constructor.
   *
   * @param lastModifiedLimitInMillis accept datasets whose lastModified() time is at least this many msecs in the past
   */
  public LastModifiedLimitFilter(long lastModifiedLimitInMillis )
  {
    this.lastModifiedLimitInMillis = lastModifiedLimitInMillis;
  }

  /**
   * Accept datasets whose last modified date is at least the
   * last modified limit of milliseconds in the past.
   *
   * @param dataset the dataset to filter
   * @return true if the datasets last modified date is at least lastModifiedLimitInMillis in the past.
   */
  public boolean accept( CrawlableDataset dataset)
  {
    Date lastModDate = dataset.lastModified();
    if ( lastModDate != null )
    {
      long now = System.currentTimeMillis();
      if ( now - lastModDate.getTime() > lastModifiedLimitInMillis )
        return true;
    }
    return false;
  }

  public Object getConfigObject()   { return null; }
  public long getLastModifiedLimitInMillis()   { return lastModifiedLimitInMillis; }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.grib.collection;

import com.google.common.base.MoreObjects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import thredds.featurecollection.FeatureCollectionConfig;
import thredds.inventory.MFile;
import ucar.nc2.grib.GribIndexCache;
import ucar.nc2.grib.GribTables;
import ucar.nc2.grib.coord.Coordinate;
import ucar.nc2.grib.coord.CoordinateRuntime;
import ucar.nc2.grib.coord.CoordinateTime2D;
import ucar.nc2.grib.coord.CoordinateTimeAbstract;
import ucar.nc2.grib.coord.CoordinateTimeIntv;
import ucar.nc2.grib.grib1.Grib1Gds;
import ucar.nc2.grib.grib1.Grib1ParamTime;
import ucar.nc2.grib.grib1.Grib1SectionProductDefinition;
import ucar.nc2.grib.grib1.Grib1Variable;
import ucar.nc2.grib.grib1.tables.Grib1Customizer;
import ucar.nc2.grib.grib2.*;
import ucar.nc2.grib.grib2.table.Grib2Tables;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateFormatter;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.time.CalendarTimeZone;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.Parameter;
import ucar.unidata.util.StringUtil2;

import javax.annotation.concurrent.Immutable;
import java.io.Closeable;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

/**
 * A mutable class for writing indices or building GribCollectionImmutable.
 * Better to use a Builder?
 *
 * @author John
 * @since 12/1/13
 */
public class GribCollectionMutable implements Closeable {
  private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(GribCollectionMutable.class);
  static final long MISSING_RECORD = -1;

  //////////////////////////////////////////////////////////

  static MFile makeIndexMFile(String collectionName, File directory) {
    String nameNoBlanks = StringUtil2.replace(collectionName, ' ', "_");
    return new GcMFile(directory, nameNoBlanks + GribCdmIndex.NCX_SUFFIX, -1, -1, -1); // LOOK dont know lastMod, size. can it be added later?
  }

  private static final CalendarDateFormatter cf = new CalendarDateFormatter("yyyyMMdd-HHmmss", new CalendarTimeZone("UTC"));

  static String makeName(String collectionName, CalendarDate runtime) {
    String nameNoBlanks = StringUtil2.replace(collectionName, ' ', "_");
    return nameNoBlanks + "-" + cf.toString(runtime);
  }

  ////////////////////////////////////////////////////////////////
  protected final String name; // collection name; index filename must be directory/name.ncx2
  protected final FeatureCollectionConfig config;
  protected final boolean isGrib1;
  protected File directory;
  protected String orgDirectory;

  // set by the builder
  public int version; // the ncx version
  public int center, subcenter, master, local;  // GRIB 1 uses "local" for table version
  public int genProcessType, genProcessId, backProcessId;
  public List<Parameter> params;          // not used
  protected Map<Integer, MFile> fileMap;    // all the files used in the GC; key is the index in original collection, GC has subset of them
  protected List<Dataset> datasets;
  protected CoordinateRuntime masterRuntime;
  protected GribTables cust;
  protected int indexVersion;

  void setCalendarDateRange(long startMsecs, long endMsecs) {
    this.dateRange = CalendarDateRange.of( CalendarDate.of(startMsecs), CalendarDate.of(endMsecs));
  }

  protected CalendarDateRange dateRange;

  // not stored in index
  protected RandomAccessFile indexRaf; // this is the raf of the index (ncx) file
  protected String indexFilename;
  protected long lastModified;
  protected long fileSize;

  private static int countGC;

  protected GribCollectionMutable(String name, File directory, FeatureCollectionConfig config, boolean isGrib1) {
    countGC++;
    this.name = name;
    this.directory = directory;
    this.config = config;
    this.isGrib1 = isGrib1;
    if (config == null)
      logger.error("HEY GribCollection {} has empty config%n", name);
    if (name == null)
      logger.error("HEY GribCollection has null name dir={}%n", directory);
  }

  // for making partition collection
  void copyInfo(GribCollectionMutable from) {
    this.center = from.center;
    this.subcenter = from.subcenter;
    this.master = from.master;
    this.local = from.local;
    this.genProcessType = from.genProcessType;
    this.genProcessId = from.genProcessId;
    this.backProcessId = from.backProcessId;
  }

  public String getName() {
    return name;
  }

  public File getDirectory() {
    return directory;
  }

  public String getLocation() {
    if (indexRaf != null) return indexRaf.getLocation();
    return getIndexFilepathInCache();
  }

  public Collection<MFile> getFiles() {
    return fileMap.values();
  }

  public FeatureCollectionConfig getConfig() {
    return config;
  }

  /**
   * The files that comprise the collection.
   * Actual paths, including the grib cache if used.
   *
   * @return list of filename.
   */
  public List<String> getFilenames() {
    List<String> result = new ArrayList<>();
    for (MFile file : fileMap.values())
      result.add(file.getPath());
    Collections.sort(result);
    return result;
  }

  @Nullable
  File getIndexParentFile() {
    if (indexRaf == null) return null;
    Path index = Paths.get(indexRaf.getLocation());
    Path parent = index.getParent();
    return parent.toFile();
  }

  public String getFilename(int fileno) {
    return fileMap.get(fileno).getPath();
  }

  public List<Dataset> getDatasets() {
    return datasets;
  }

  Dataset makeDataset(GribCollectionImmutable.Type type) {
    Dataset result = new Dataset(type);
    datasets.add(result);
    return result;
  }

  GribCollectionMutable.Dataset getDatasetCanonical() {
    for (GribCollectionMutable.Dataset ds : datasets) {
      if (ds.gctype != GribCollectionImmutable.Type.Best) return ds;
    }
    throw new IllegalStateException("GC.getDatasetCanonical failed on=" + name);
  }

  public void setFileMap(Map<Integer, MFile> fileMap) {
    this.fileMap = fileMap;
  }

  /**
   * public by accident, do not use
   *
   * @param indexRaf the open raf of the index file
   */
  void setIndexRaf(RandomAccessFile indexRaf) {
    this.indexRaf = indexRaf;
    if (indexRaf != null) {
      this.indexFilename = indexRaf.getLocation();
    }
  }

  /**
   * get index filename
   *
   * @return index filename; may not exist; may be in disk cache
   */
  private String getIndexFilepathInCache() {
    File indexFile = GribCdmIndex.makeIndexFile(name, directory);
    return GribIndexCache.getFileOrCache(indexFile.getPath()).getPath();
  }

  // set from GribCollectionBuilderFromIndex.readFromIndex()
  File setOrgDirectory(String orgDirectory) {
    this.orgDirectory = orgDirectory;
    directory = new File(orgDirectory);
    if (!directory.exists()) {
      File indexFile = new File(indexFilename);
      File parent = indexFile.getParentFile();
      if (parent.exists())
        directory = parent;
    }
    return directory;
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  // stuff for FileCacheable

  public void close() throws java.io.IOException {

    if (indexRaf != null) {
      indexRaf.close();
      indexRaf = null;
    }
  }

  ////////////////////////////////////////////////////////////////////////////////////////////////////

  public class Dataset {
    public GribCollectionImmutable.Type gctype;
    List<GroupGC> groups;  // must be kept in order, because PartitionForVariable2D has index into it

    public Dataset(GribCollectionImmutable.Type type) {
      this.gctype = type;
      groups = new ArrayList<>();
    }

    Dataset(Dataset from) {
      this.gctype = from.gctype;
      groups = new ArrayList<>(from.groups.size());
    }

    GroupGC addGroupCopy(GroupGC from) {
      GroupGC g = new GroupGC(from);
      groups.add(g);
      return g;
    }

    public List<GroupGC> getGroups() {
      return groups;
    }
  }

  public class GroupGC implements Comparable<GroupGC> {
    GribHorizCoordSystem horizCoordSys;
    final List<VariableIndex> variList;
    List<Coordinate> coords;      // shared coordinates
    int[] filenose;               // key for GC.fileMap
    HashMap<GribCollectionMutable.VariableIndex, GribCollectionMutable.VariableIndex> varMap;
    boolean isTwoD = true;        // true except for Best (?)

    GroupGC() {
      this.variList = new ArrayList<>();
      this.coords = new ArrayList<>();
    }

    // copy constructor for PartitionBuilder
    GroupGC(GroupGC from) {
      this.horizCoordSys = from.horizCoordSys;     // reference
      this.variList = new ArrayList<>(from.variList.size());  // empty list
      this.coords = new ArrayList<>(from.coords.size());      // empty list
      this.isTwoD = from.isTwoD;
    }

    public VariableIndex addVariable(VariableIndex vi) {
      variList.add(vi);
      return vi;
    }

    public GribCollectionMutable getGribCollection() {
      return GribCollectionMutable.this;
    }

    public Iterable<VariableIndex> getVariables() {
      return variList;
    }

    public Iterable<Coordinate> getCoordinates() {
      return coords;
    }

    // unique name for Group
    public String getId() {
      return horizCoordSys.getId();
    }

    // human readable
    public String getDescription() {
      return horizCoordSys.getDescription();
    }

    public byte[] getGdsBytes() {
      return horizCoordSys.getRawGds();
    }

    public Object getGdsHash() {
       return horizCoordSys.getGdsHash();
     }

    @Override
    public int compareTo(@Nonnull GroupGC o) {
      return getDescription().compareTo(o.getDescription());
    }

    public List<MFile> getFiles() {
      List<MFile> result = new ArrayList<>();
      if (filenose == null) return result;
      for (int fileno : filenose)
        result.add(fileMap.get(fileno));
      Collections.sort(result);
      return result;
    }

    public List<String> getFilenames() {
      List<String> result = new ArrayList<>();
      if (filenose == null) return result;
      for (int fileno : filenose)
        result.add(fileMap.get(fileno).getPath());
      Collections.sort(result);
      return result;
    }

    // get the variable in this group that has same object equality as want
    public GribCollectionMutable.VariableIndex findVariableByHash(GribCollectionMutable.VariableIndex want) {
      if (varMap == null) {
        varMap = new HashMap<>(variList.size() * 2);
        for (VariableIndex vi : variList) {
          VariableIndex old = varMap.put(vi, vi);
          if (old != null) {
            logger.error("GribCollectionMutable has duplicate variable hash {} == {}", vi, old);
          }
        }
      }
      return varMap.get(want);
    }

    private CalendarDateRange dateRange = null;

    public CalendarDateRange getCalendarDateRange() {
      if (dateRange == null) {
        CalendarDateRange result = null;
        for (Coordinate coord : coords) {
          switch (coord.getType()) {
            case time:
            case timeIntv:
            case time2D:
              CoordinateTimeAbstract time = (CoordinateTimeAbstract) coord;
              CalendarDateRange range = time.makeCalendarDateRange(null);
              if (result == null) result = range;
              else result = result.extend(range);
          }
        }
        dateRange = result;
      }
      return dateRange;
    }

    public int getNFiles() {
      if (filenose == null) return 0;
      return filenose.length;
    }

    public void show(Formatter f) {
      f.format("Group %s (%d) isTwoD=%s%n", horizCoordSys.getId(), horizCoordSys.getGdsHash().hashCode(), isTwoD);
      f.format(" nfiles %d%n", filenose == null ? 0 : filenose.length);
      f.format(" hcs = %s%n", horizCoordSys.getHcs());
    }

    @Override
    public String toString() {
      return MoreObjects.toStringHelper(this)
          .add("horizCoordSys", horizCoordSys)
          .add("variList", variList)
          .add("coords", coords)
          .add("filenose", filenose)
          .add("varMap", varMap)
          .add("isTwoD", isTwoD)
          .add("dateRange", dateRange)
          .toString();
    }
  }

  GribCollectionMutable.VariableIndex makeVariableIndex(GroupGC g, GribTables customizer,
      int discipline, int center,
      int subcenter, byte[] rawPds, List<Integer> index, long recordsPos, int recordsLen) {
    return new VariableIndex(g, customizer, discipline, center, subcenter, rawPds, index, recordsPos, recordsLen);
  }

  VariableIndex makeVariableIndex(GroupGC group, GribCollectionMutable.VariableIndex from) {
    VariableIndex vip = new VariableIndex(group, from);
    group.addVariable(vip);
    return vip;
  }

  public class VariableIndex implements Comparable<VariableIndex> {
    public final GroupGC group;     // belongs to this group
    public final int tableVersion;   // grib1 only : can vary by variable
    public final int discipline, center, subcenter;     // grib2 only
    public final byte[] rawPds;      // grib1 or grib2
    public final long recordsPos;    // where the records array is stored in the index. 0 means no records
    public final int recordsLen;
    public Object gribVariable;    // use this to test for object equality

    List<Integer> coordIndex;  // indexes into group.coords

    // derived from pds
    public final int category, parameter, levelType, intvType, ensDerivedType, probType;
    private String intvName;  // eg "mixed intervals, 3 Hour, etc"
    public final String probabilityName;
    public final boolean isLayer, isEnsemble;
    public final int genProcessType;
    public final int spatialStatType;

    // stats
    public int ndups, nrecords, nmissing;

    // temporary storage while building - do not use
    List<Coordinate> coords;

    private VariableIndex(GroupGC g, GribTables customizer, int discipline, int center, int subcenter, byte[] rawPds,
                          List<Integer> index, long recordsPos, int recordsLen) {
      this.group = g;
      this.discipline = discipline;
      this.rawPds = rawPds;
      this.center = center;
      this.subcenter = subcenter;
      this.coordIndex = index;
      this.recordsPos = recordsPos;
      this.recordsLen = recordsLen;

      if (isGrib1) {
        Grib1Customizer cust = (Grib1Customizer) customizer;
        Grib1SectionProductDefinition pds = new Grib1SectionProductDefinition(rawPds);

        // quantities that are stored in the pds
        this.category = 0;
        this.tableVersion = pds.getTableVersion();
        this.parameter = pds.getParameterNumber();
        this.levelType = pds.getLevelType();
        Grib1ParamTime ptime = cust.getParamTime(pds);
        if (ptime.isInterval()) {
          this.intvType = pds.getTimeRangeIndicator();
        } else {
          this.intvType = -1;
        }
        this.isLayer = cust.isLayer(pds.getLevelType());

        this.ensDerivedType = -1;
        this.probType = -1;
        this.probabilityName = null;

        this.genProcessType = pds.getGenProcess(); // LOOK process vs process type ??
        this.isEnsemble = pds.isEnsemble();
        this.spatialStatType = -1;

        // LOOK config vs serialized config
        gribVariable = new Grib1Variable(cust, pds, (Grib1Gds) g.getGdsHash(), config.gribConfig.useTableVersion, config.gribConfig.intvMerge, config.gribConfig.useCenter);

      } else {
        Grib2Tables cust2 = (Grib2Tables) customizer;

        Grib2SectionProductDefinition pdss = new Grib2SectionProductDefinition(rawPds);
        Grib2Pds pds = pdss.getPDS();
        assert pds != null;
        this.tableVersion = -1;

        // quantities that are stored in the pds
        this.category = pds.getParameterCategory();
        this.parameter = pds.getParameterNumber();
        this.levelType = pds.getLevelType1();
        this.intvType = pds.getStatisticalProcessType();
        this.isLayer = Grib2Utils.isLayer(pds);

        if (pds.isEnsembleDerived()) {
          Grib2Pds.PdsEnsembleDerived pdsDerived = (Grib2Pds.PdsEnsembleDerived) pds;
          ensDerivedType = pdsDerived.getDerivedForecastType(); // derived type (table 4.7)
        } else {
          this.ensDerivedType = -1;
        }

        if (pds.isProbability()) {
          Grib2Pds.PdsProbability pdsProb = (Grib2Pds.PdsProbability) pds;
          probabilityName = pdsProb.getProbabilityName();
          probType = pdsProb.getProbabilityType();
        } else {
          this.probType = -1;
          this.probabilityName = null;
        }

        this.genProcessType = pds.getGenProcessType();
        this.isEnsemble = pds.isEnsemble();

        if (pds.isSpatialInterval()) {
          Grib2Pds.PdsSpatialInterval pdsSpatial = (Grib2Pds.PdsSpatialInterval) pds;
          this.spatialStatType = pdsSpatial.getSpatialStatisticalProcessType();
        } else {
          this.spatialStatType = -1;
        }

        // LOOK config vs serialized config
        gribVariable = new Grib2Variable (cust2, discipline, center, subcenter, (Grib2Gds) g.getGdsHash(), pds, config.gribConfig.intvMerge, config.gribConfig.useGenType);
      }
    }

    protected VariableIndex(GroupGC g, VariableIndex other) {
      this.group = g;
      this.tableVersion = other.tableVersion;
      this.discipline = other.discipline;
      this.center = other.center;
      this.subcenter = other.subcenter;
      this.rawPds = other.rawPds;
      this.gribVariable = other.gribVariable;
      this.coordIndex = new ArrayList<>(other.coordIndex);
      this.recordsPos = 0;
      this.recordsLen = 0;

      this.category = other.category;
      this.parameter = other.parameter;
      this.levelType = other.levelType;
      this.intvType = other.intvType;
      this.isLayer = other.isLayer;
      this.ensDerivedType = other.ensDerivedType;
      this.probabilityName = other.probabilityName;
      this.probType = other.probType;
      this.genProcessType = other.genProcessType;
      this.spatialStatType = other.spatialStatType;
      this.isEnsemble = other.isEnsemble;
    }

    public List<Coordinate> getCoordinates() {
      List<Coordinate> result = new ArrayList<>(coordIndex.size());
      for (int idx : coordIndex)
        result.add(group.coords.get(idx));
      return result;
    }

    @Nullable
    public Coordinate getCoordinate(Coordinate.Type want) {
      for (int idx : coordIndex)
        if (group.coords.get(idx).getType() == want)
          return group.coords.get(idx);
      return null;
    }

    public int getCoordinateIdx(Coordinate.Type want) {
      for (int idx : coordIndex)
        if (group.coords.get(idx).getType() == want)
          return idx;
      return -1;
    }

    @Nullable
    public String getTimeIntvName() {
      if (intvName != null) return intvName;
      CoordinateTimeIntv timeiCoord = (CoordinateTimeIntv) getCoordinate(Coordinate.Type.timeIntv);
      if (timeiCoord != null) {
        intvName = timeiCoord.getTimeIntervalName();
        return intvName;
      }

      CoordinateTime2D time2DCoord = (CoordinateTime2D) getCoordinate(Coordinate.Type.time2D);
      if (time2DCoord == null || !time2DCoord.isTimeInterval()) return null;
      intvName = time2DCoord.getTimeIntervalName();
      return intvName;
    }

    /////////////////////////////
    public String id() {
      return discipline + "-" + category + "-" + parameter;
    }

    public int getVarid() {
      return (discipline << 16) + (category << 8) + parameter;
    }

    @Override
     public String toString() {
      return MoreObjects.toStringHelper(this)
          .add("tableVersion", tableVersion)
          .add("discipline", discipline)
          .add("category", category)
          .add("parameter", parameter)
          .add("levelType", levelType)
          .add("intvType", intvType)
          .add("ensDerivedType", ensDerivedType)
          .add("probType", probType)
          .add("intvName", intvName)
          .add("probabilityName", probabilityName)
          .add("isLayer", isLayer)
          .add("genProcessType", genProcessType)
          .add("cdmHash", gribVariable.hashCode())
          .toString();
    }

    public String toStringComplete() {
      return MoreObjects.toStringHelper(this)
          .add("group", group)
          .add("tableVersion", tableVersion)
          .add("discipline", discipline)
          .add("center", center)
          .add("subcenter", subcenter)
          .add("recordsPos", recordsPos)
          .add("recordsLen", recordsLen)
          .add("gribVariable", gribVariable)
          .add("coordIndex", coordIndex)
          .add("category", category)
          .add("parameter", parameter)
          .add("levelType", levelType)
          .add("intvType", intvType)
          .add("ensDerivedType", ensDerivedType)
          .add("probType", probType)
          .add("intvName", intvName)
          .add("probabilityName", probabilityName)
          .add("isLayer", isLayer)
          .add("isEnsemble", isEnsemble)
          .add("genProcessType", genProcessType)
          .add("spatialStatType", spatialStatType)
          .toString();
    }

    public String toStringShort() {
      try (Formatter sb = new Formatter()) {
        sb.format("Variable {%d-%d-%d", discipline, category, parameter);
        sb.format(", levelType=%d", levelType);
        sb.format(", intvType=%d", intvType);
        if (intvName != null && intvName.length() > 0) {
          sb.format(" intv=%s", intvName);
        }
        if (probabilityName != null && probabilityName.length() > 0) {
          sb.format(" prob=%s", probabilityName);
        }
        sb.format(" cdmHash=%d}", gribVariable.hashCode());
        return sb.toString();
      }
    }

    @Override
    public int compareTo(@Nonnull VariableIndex o) {
      int r = discipline - o.discipline;  // LOOK add center, subcenter, version?
      if (r != 0) return r;
      r = category - o.category;
      if (r != 0) return r;
      r = parameter - o.parameter;
      if (r != 0) return r;
      r = levelType - o.levelType;
      if (r != 0) return r;
      r = intvType - o.intvType;
      return r;
    }

    @Override
    public boolean equals(Object o) {
      if (this == o) return true;
      if (!(o instanceof VariableIndex)) return false;

      VariableIndex that = (VariableIndex) o;
      return gribVariable.equals(that.gribVariable);
    }

    @Override
    public int hashCode() {
      return gribVariable.hashCode();
    }

  }  // VariableIndex

  @Immutable
  public static class Record {
    public final int fileno;    // which file
    public final long pos;      // offset on file where data starts
    public final long bmsPos;   // if non-zero, offset where bms starts
    public final int scanMode;  // from gds

    public Record(int fileno, long pos, long bmsPos, int scanMode) {
      this.fileno = fileno;
      this.pos = pos;
      this.bmsPos = bmsPos;
      this.scanMode = scanMode;
    }

    @Override
    public String toString() {
      return MoreObjects.toStringHelper(this)
          .add("fileno", fileno)
          .add("pos", pos)
          .add("bmsPos", bmsPos)
          .add("scanMode", scanMode)
          .toString();
    }
  }

  public void showIndex(Formatter f) {
    f.format("Class (%s)%n", getClass().getName());
    f.format("%s%n%n", toString());

    for (Dataset ds : datasets) {
      f.format("Dataset %s%n", ds.gctype);
      for (GroupGC g : ds.groups) {
        f.format(" Group %s%n", g.horizCoordSys.getId());
        for (VariableIndex v : g.variList) {
          f.format("  %s%n", v.toStringShort());
        }
      }
    }
    if (fileMap == null) {
      f.format("Files empty%n");
    } else {
      f.format("Files (%d)%n", fileMap.size());
      for (int index : fileMap.keySet()) {
        f.format("  %d: %s%n", index, fileMap.get(index));
      }
      f.format("%n");
    }
  }

  @Override
  public String toString() {
    return MoreObjects.toStringHelper(this)
        .add("name", name)
        .add("config", config)
        .add("isGrib1", isGrib1)
        .add("directory", directory)
        .add("orgDirectory", orgDirectory)
        .add("version", version)
        .add("center", center)
        .add("subcenter", subcenter)
        .add("master", master)
        .add("local", local)
        .add("genProcessType", genProcessType)
        .add("genProcessId", genProcessId)
        .add("backProcessId", backProcessId)
        .add("params", params)
        .add("fileMap", fileMap)
        .add("datasets", datasets)
        .add("masterRuntime", masterRuntime)
        .add("cust", cust)
        .add("indexVersion", indexVersion)
        .add("dateRange", dateRange)
        .add("indexRaf", indexRaf)
        .add("indexFilename", indexFilename)
        .add("lastModified", lastModified)
        .add("fileSize", fileSize)
        .toString();
  }

  public String showLocation() {
    return "name="+name+" directory="+directory;
  }

  public GroupGC makeGroup() {
    return new GroupGC();
  }

}


/////////////////////////////////////////////////////////////////////////////
// This file is part of the "Java-DAP" project, a Java implementation
// of the OPeNDAP Data Access Protocol.
//
// Copyright (c) 2010, OPeNDAP, Inc.
// Copyright (c) 2002,2003 OPeNDAP, Inc.
//
// Author: James Gallagher <jgallagher@opendap.org>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms,
// with or without modification, are permitted provided
// that the following conditions are met:
//
// - Redistributions of source code must retain the above copyright
//   notice, this list of conditions and the following disclaimer.
//
// - Redistributions in binary form must reproduce the above copyright
//   notice, this list of conditions and the following disclaimer in the
//   documentation and/or other materials provided with the distribution.
//
// - Neither the name of the OPeNDAP nor the names of its contributors may
//   be used to endorse or promote products derived from this software
//   without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
// TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
/////////////////////////////////////////////////////////////////////////////

package opendap.dap;

import opendap.dap.parsers.DDSXMLParser;

import java.util.Enumeration;
import java.io.PrintWriter;

/**
 * Contains methods used only by the OPeNDAP constructor classes
 * (DStructure, DSequence, DGrid, and
 * DList).
 *
 * @author jehamby
 * @version $Revision: 19676 $
 * @see DStructure
 * @see DSequence
 * @see DGrid
 */
abstract public class DConstructor extends BaseType {

    /**
     * Constructs a new DConstructor.
     */
    public DConstructor() {
        super();
    }

    /**
     * Constructs a new DConstructor with the given name.
     *
     * @param n The name of the variable.
     */
    public DConstructor(String n) {
        super(n);
    }

    /**
     * Adds a variable to the container.
     *
     * @param v    the variable to add.
     * @param part The part of the constructor data to be modified.
     */
    abstract public void addVariable(BaseType v, int part);

    /**
     * Adds a variable to the container.  Same as addVariable(v, 0).
     *
     * @param v the variable to add.
     */
    public final void addVariable(BaseType v) {
        addVariable(v, 0);
    }

    /**
     * Gets the named variable.
     *
     * @param name the name of the variable.
     * @return the named variable.
     * @throws NoSuchVariableException if the named variable does not
     *                                 exist in this container.
     */
    abstract public BaseType getVariable(String name)
            throws NoSuchVariableException;

    /**
     * Gets the indexed variable. For a DGrid the index 0 returns the DArray and
     * indexes 1 and higher return the associated map Vectors.
     *
     * @param index the index of the variable in the Vector Vars.
     * @return the named variable.
     * @throws NoSuchVariableException if the named variable does not
     *                                 exist in this container.
     */
    abstract public BaseType getVar(int index)
            throws NoSuchVariableException;

    /**
     * Get the number of contained variables (for use with getVar()
     * @return the number of contained variables
     */
    abstract public int getVarCount();

    /**
     * Return an Enumeration that can be used to iterate over all of the
     * members of the class. Each implementation must define what this means.
     * The intent of this method is to support operations on all members of a
     * Structure, Seqeunce or Grid that can be performed equally. So it is
     * not necessary that this methods be usable, for example, when the
     * caller needs to know that it s dealing with the Array part of a grid.
     *
     * @return An Enumeration object.
     */
    abstract public Enumeration getVariables();

    /**
     *
     * @param bt The BasType object to search.
     * @return true if some child of the passed BaseType has attributes
     * @opendap.ddx.experimental
     */
    protected boolean someChildHasAttributes(BaseType bt) {

        boolean foundit = false;

        if (bt.hasAttributes())
            return (true);

        if (bt instanceof DConstructor) {

            Enumeration e = ((DConstructor) bt).getVariables();

            while (e.hasMoreElements()) {
                BaseType thisBT = (BaseType) e.nextElement();

                foundit = foundit || someChildHasAttributes(thisBT);
            }
        }

        return (foundit);
    }

    /**
     *
     * @param pw Where to print
     * @param pad Padding for iondentation (makes the output easier for humans
     * to read).
     * @param constrained If true then only projected variables (and their
     * Attributes) will be printed.
     * @opendap.ddx.experimental
     */
    //Coverity[CALL_SUPER]
    public void printXML(PrintWriter pw, String pad, boolean constrained) {

        Enumeration e = getAttributeNames();
        Enumeration ve = getVariables();

        boolean hasAttributes = e.hasMoreElements();
        boolean hasVariables = ve.hasMoreElements();

        pw.print(pad + "<" + getTypeName());
        if (getEncodedName() != null) {
            pw.print(" name=\"" +
                    DDSXMLParser.normalizeToXML(getClearName()) + "\"");
        }

        if (hasAttributes || hasVariables) {

            pw.println(">");

            while (e.hasMoreElements()) {
                String aName = (String) e.nextElement();

                Attribute a = getAttribute(aName);
                if(a!=null)
                    a.printXML(pw, pad + "\t", constrained);

            }

            while (ve.hasMoreElements()) {
                BaseType bt = (BaseType) ve.nextElement();
                bt.printXML(pw, pad + "\t", constrained);
            }

            pw.println(pad + "</" + getTypeName() + ">");

        } else {

            pw.println("/>");

        }

    }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.iosp.bufr;

import java.util.Formatter;

/**
 * Count size of compressed fields
 *
 * @author caron
 * @since Jul 4, 2008
 */
public class BitCounterCompressed implements BitCounter {

  private final DataDescriptor dkey; // the field to count
  private final int nrows;           // number of (obs) in the compression
  private final int bitOffset;       // starting position of the compressed data, reletive to start of data section
  private int dataWidth;             // bitWidth of incremental values
  private BitCounterCompressed[][] nested;     // used if the dkey is a structure = nested[innerRows][dkey.subkeys.size]

  /**
   * This counts the size of an array of Structures or Sequences, ie Structure(n)
   *
   * @param dkey                 is a structure or a sequence - so has subKeys
   * @param n                    numbers of rows in the table
   * @param bitOffset number of bits taken up by the count variable (non-zero only for sequences)
   */
  public BitCounterCompressed(DataDescriptor dkey, int n, int bitOffset) {
    this.dkey = dkey;
    this.nrows = n;
    this.bitOffset = bitOffset;
  }

  void setDataWidth( int dataWidth) {
    this.dataWidth = dataWidth;
  }

  public int getStartingBitPos() {
    return bitOffset;
  }

  public int getBitPos(int msgOffset) {
    return bitOffset + dkey.bitWidth + 6 + dataWidth * msgOffset;
  }

  public int getTotalBits() {
    if (nested == null)
      return dkey.bitWidth + 6 + dataWidth * nrows;
    else {
      int totalBits = 0;
      for (BitCounterCompressed[] counters : nested) {
        if (counters == null) continue;
        for (BitCounterCompressed counter : counters)
          if (counter != null) totalBits += counter.getTotalBits();
      }
      if (dkey.replicationCountSize > 0)
        totalBits += dkey.replicationCountSize + 6; // 6 boit count, 6 bit extra
      return totalBits;
    }
  }

  public BitCounterCompressed[] getNestedCounters(int innerIndex) {
    return nested[innerIndex];
  }

  public void addNestedCounters(int innerDimensionSize) {
    nested = new BitCounterCompressed[innerDimensionSize][dkey.getSubKeys().size()];
  }

  /**
   * Number of nested fields
   * @return 1 if no nested fields, otherwise count of nested fields
   */
  public int ncounters() {
    if (nested == null)
      return 1;
    else {
      int ncounters = 0;
      for (BitCounterCompressed[] counters : nested) {
        if (counters == null) continue;
        for (BitCounterCompressed counter : counters)
          if (counter != null) ncounters += counter.ncounters();
      }
      return ncounters;
    }
  }

  public void show(Formatter out, int indent) {
    for (int i=0; i<indent; i++) out.format(" ");
    out.format("%8d %8d %4d %s %n", getTotalBits(), bitOffset, dataWidth, dkey.name);
    if (nested != null) {
      for (BitCounterCompressed[] counters : nested) {
        if (counters == null) continue;
        for (BitCounterCompressed counter : counters)
          if (counter != null) counter.show(out, indent+2);
      }
    }
  }

  @Override
  public int getNumberRows() {
    return nrows;
  }
}