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

package ucar.ma2;

import javax.annotation.concurrent.Immutable;

import java.util.Iterator;

/**
 * Represents a set of integers, used as an index for arrays.
 * No duplicates are allowed.
 * It should be considered as a subset of the interval of integers [first(), last()] inclusive.
 * For example Range(1:11:3) represents the set of integers {1,4,7,10}
 * Note that Range no longer is always strided or monotonic.
 * Immutable.
 *

 * Elements must be nonnegative and unique.
 * EMPTY is the empty Range.
 * VLEN is for variable length dimensions.
 *
 Standard iteration is
 *

 *  for (int idx : range) {
 *    ...
 *  }
 *

 *
 * @author caron
 */

@Immutable
public class Range implements RangeIterator {
  public static final Range EMPTY = new Range(); // used for unlimited dimension = 0
  public static final Range ONE = new Range(1);
  public static final Range VLEN = new Range(-1);

  public static Range make(String name, int len) {
    try {
      return new Range(name, 0, len - 1, 1);
    } catch (InvalidRangeException e) {
      throw new RuntimeException(e); // cant happen if len > 0
    }
  }

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

  protected final int length; // number of elements
  private final int first; // first value in range
  private final int last; // last value in range, inclusive
  private final int stride; // stride, must be >= 1
  protected final String name; // optional name

  /**
   * Used for EMPTY
   */
  private Range() {
    this.length = 0;
    this.first = 0;
    this.last = 0;
    this.stride = 1;
    this.name = null;
  }

  /**
   * Create a range with unit stride.
   *
   * @param first first value in range
   * @param last  last value in range, inclusive
   * @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
   */
  public Range(int first, int last) throws InvalidRangeException {
    this(null, first, last, 1);
  }

  /**
   * Create a range starting at zero, with unit stride.
   *
   * @param length number of elements in the Range
   */
  public Range(int length) {
    assert (length != 0);
    this.name = null;
    this.first = 0;
    this.last = length - 1;
    this.stride = 1;
    this.length = length;
  }

  /**
   * Create a named range with unit stride.
   *
   * @param name  name of Range
   * @param first first value in range
   * @param last  last value in range, inclusive
   * @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
   */
  public Range(String name, int first, int last) throws InvalidRangeException {
    this(name, first, last, 1);
  }

  /**
   * Create a range with a specified values.
   *
   * @param first  first value in range
   * @param last   last value in range, inclusive
   * @param stride stride between consecutive elements, must be > 0
   * @throws InvalidRangeException elements must be nonnegative: 0 <= first <= last, stride > 0
   */
  public Range(int first, int last, int stride) throws InvalidRangeException {
    this(null, first, last, stride);
  }

  /**
   * Create a named range with a specified name and values.
   *
   * @param name   name of Range
   * @param first  first value in range
   * @param last   last value in range, inclusive
   * @param stride stride between consecutive elements, must be > 0
   * @throws InvalidRangeException elements must be nonnegative: 0 <= first <= last, stride > 0
   */
  public Range(String name, int first, int last, int stride) throws InvalidRangeException {
    if (first < 0)
      throw new InvalidRangeException("first (" + first + ") must be >= 0");
    if (last < first)
      throw new InvalidRangeException("last (" + last + ") must be >= first (" + first + ")");
    if (stride < 1)
      throw new InvalidRangeException("stride (" + stride + ") must be > 0");

    this.name = name;
    this.first = first;
    this.stride = stride;
    this.length = 1 + (last - first) / stride;
    this.last = first + (this.length - 1) * stride;
    if (stride == 1)
      assert this.last == last;
    assert this.length != 0;
  }

  protected Range(String name, int first, int last, int stride, int length) throws InvalidRangeException {
    if (first < 0)
      throw new InvalidRangeException("first (" + first + ") must be >= 0");
    if (last < first)
      throw new InvalidRangeException("last (" + last + ") must be >= first (" + first + ")");
    if (stride < 1)
      throw new InvalidRangeException("stride (" + stride + ") must be > 0");
    if (length < (1 + last - first) / stride)
      throw new InvalidRangeException("length (" + length + ") must be > (1 + last - first) / stride");

    this.name = name;
    this.first = first;
    this.last = last;
    this.stride = stride;
    this.length = length;
  }

  // copy on change
  public Range setStride(int stride) throws InvalidRangeException {
    return new Range(this.first(), this.last(), stride);
  }

  @Override
  public Range setName(String name) {
    if (name.equals(this.getName())) return this;
    try {
      return new Range(name, first, last, stride, length);
    } catch (InvalidRangeException e) {
      throw new RuntimeException(e); // cant happen
    }
  }

  /**
   * @return name, or null if none
   */
  public String getName() {
    return name;
  }

  /**
   * @return first in range
   */
  public int first() {
    return first;
  }

  /**
   * @return last in range, inclusive
   */
  public int last() {
    return last;
  }

  /**
   * @return the number of elements in the range.
   */
  public int length() {
    return length;
  }

  /**
   * @return stride, must be >= 1 when evenly strided, -1 if not
   * // * @deprecated use iterator(), dont assume evenly strided
   */
  public int stride() {
    return stride;
  }

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

  /**
   * Is want contained in this Range?
   *
   * @param want index in the original Range
   * @return true if the ith element would be returned by the Range iterator
   */
  public boolean contains(int want) {
    if (want < first())
      return false;
    if (want > last())
      return false;
    if (stride == 1) return true;
    return (want - first) % stride == 0;
  }

  /**
   * Create a new Range by composing a Range that is reletive to this Range.
   * Revised 2013/04/19 by Dennis Heimbigner to handle edge cases.
   * See the commentary associated with the netcdf-c file dceconstraints.h,
   * function dceslicecompose().
   *
   * @param r range reletive to base
   * @return combined Range, may be EMPTY
   * @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
   */
  public Range compose(Range r) throws InvalidRangeException {
    if ((length() == 0) || (r.length() == 0))
      return EMPTY;
    if (this == VLEN || r == VLEN)
      return VLEN;
/* if(false) {// Original version
    // Note that this version assumes that range r is
    // correct with respect to this.
    int first = element(r.first());
    int stride = stride() * r.stride();
    int last = element(r.last());
    return new Range(name, first, last, stride);
} else {//new version: handles versions all values of r. */
    int sr_stride = this.stride * r.stride;
    int sr_first = element(r.first()); // MAP(this,i) == element(i)
    int lastx = element(r.last());
    int sr_last = (last() < lastx ? last() : lastx); //min(last(),lastx)
    //unused int sr_length = (sr_last + 1) - sr_first;
    return new Range(name, sr_first, sr_last, sr_stride);
  }

  /**
   * Create a new Range by compacting this Range by removing the stride.
   * first = first/stride, last=last/stride, stride=1.
   *
   * @return compacted Range
   * @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
   */
  public Range compact() throws InvalidRangeException {
    if (stride == 1) return this;
    int first = first() / stride;           // LOOK WTF ?
    int last = first + length() - 1;
    return new Range(name, first, last, 1);
  }

  /**
   * Get ith element
   *
   * @param i index of the element
   * @return the i-th element of a range.
   * @throws InvalidRangeException i must be: 0 <= i < length
   */
  public int element(int i) throws InvalidRangeException {
    if (i < 0)
      throw new InvalidRangeException("i must be >= 0");
    if (i >= length)
      throw new InvalidRangeException("i must be < length");

    return first + i * stride;
  }

  /**
   * Get the index for this element: inverse of element
   *
   * @param want the element of the range
   * @return index
   * @throws InvalidRangeException if illegal elem
   */
  public int index(int want) throws InvalidRangeException {
    if (want < first)
      throw new InvalidRangeException("elem must be >= first");
    int result = (want - first) / stride;
    if (result > length)
      throw new InvalidRangeException("elem must be <= first = n * stride");
    return result;
  }

  /**
   * Create a new Range by intersecting with a Range using same interval as this Range.
   * NOTE: we dont yet support intersection when both Ranges have strides
   *
   * @param r range to intersect
   * @return intersected Range, may be EMPTY
   * @throws InvalidRangeException elements must be nonnegative
   */
  public Range intersect(Range r) throws InvalidRangeException {
    if ((length() == 0) || (r.length() == 0))
      return EMPTY;
    if (this == VLEN || r == VLEN)
      return VLEN;

    int last = Math.min(this.last(), r.last());
    int resultStride = stride * r.stride();

    int useFirst;
    if (resultStride == 1) {  // both strides are 1
      useFirst = Math.max(this.first(), r.first());

    } else if (stride == 1) { // then r has a stride

      if (r.first() >= first())
        useFirst = r.first();
      else {
        int incr = (first() - r.first()) / resultStride;
        useFirst = r.first() + incr * resultStride;
        if (useFirst < first()) useFirst += resultStride;
      }

    } else if (r.stride == 1) { // then this has a stride

      if (first() >= r.first())
        useFirst = first();
      else {
        int incr = (r.first() - first()) / resultStride;
        useFirst = first() + incr * resultStride;
        if (useFirst < r.first()) useFirst += resultStride;
      }

    } else {
      throw new UnsupportedOperationException("Intersection when both ranges have a stride");
    }

    if (useFirst > last)
      return EMPTY;
    return new Range(name, useFirst, last, resultStride);
  }

  /**
   * Determine if a given Range intersects this one.
   * NOTE: we dont yet support intersection when both Ranges have strides
   *
   * @param r range to intersect
   * @return true if they intersect
   * @throws UnsupportedOperationException if both Ranges have strides
   */
  public boolean intersects(Range r) {
    if ((length() == 0) || (r.length() == 0))
      return false;
    if (this == VLEN || r == VLEN)
      return true;

    int last = Math.min(this.last(), r.last());
    int resultStride = stride * r.stride();

    int useFirst;
    if (resultStride == 1) {   // both strides are 1
      useFirst = Math.max(this.first(), r.first());

    } else if (stride == 1) { // then r has a stride

      if (r.first() >= first())
        useFirst = r.first();
      else {
        int incr = (first() - r.first()) / resultStride;
        useFirst = r.first() + incr * resultStride;
        if (useFirst < first()) useFirst += resultStride;
      }

    } else if (r.stride() == 1) { // then this has a stride

      if (first() >= r.first())
        useFirst = first();
      else {
        int incr = (r.first() - first()) / resultStride;
        useFirst = first() + incr * resultStride;
        if (useFirst < r.first()) useFirst += resultStride;
      }

    } else {
      throw new UnsupportedOperationException("Intersection when both ranges have a stride");
    }

    return (useFirst <= last);
  }

  /**
   * If this range is completely past the wanted range
   *
   * @param want desired range
   * @return true if  first() > want.last()
   */
  public boolean past(Range want) {
    return (first() > want.last());
  }

  /**
   * Create a new Range shifting this range by a constant factor.
   *
   * @param origin subtract this from each element
   * @return shifted range
   * @throws InvalidRangeException elements must be nonnegative, 0 <= first <= last
   */
  public Range shiftOrigin(int origin) throws InvalidRangeException {
    if (this == VLEN)
      return VLEN;

    int first = first() - origin;
    int last = last() - origin;
    return new Range(name, first, last, stride);
  }

  /**
   * Create a new Range by making the union with a Range using same interval as this Range.
   * NOTE: no strides
   *
   * @param r range to add
   * @return intersected Range, may be EMPTY
   * @throws InvalidRangeException elements must be nonnegative
   */
  public Range union(Range r) throws InvalidRangeException {
    if (length() == 0)
      return r;
    if (this == VLEN || r == VLEN)
      return VLEN;

    if (r.length() == 0)
      return this;

    int first = Math.min(this.first(), r.first());
    int last = Math.max(this.last(), r.last());
    return new Range(name, first, last);
  }

  /**
   * Find the first element in a strided array after some index start.
   * Return the smallest element k in the Range, such that

   *
k >= first
   *
k >= start
   *
k <= last
   *
k = element of this Range
   *

   *
   * @param start starting index
   * @return first in interval, else -1 if there is no such element.
   */
  public int getFirstInInterval(int start) {
    if (start > last()) return -1;
    if (start <= first) return first;
    if (stride == 1) return start;
    int offset = start - first;
    int i = offset / stride;
    i = (offset % stride == 0) ? i : i + 1; // round up
    return first + i * stride;
  }

  public String toString() {
    if (this.length == 0)
      return ":";  // EMPTY
    else if (this.length < 0)
      return ":";  // VLEN
    else
      return first + ":" + last() + (stride > 1 ? ":" + stride : "");
  }

  /**
   * Range elements with same first, last, stride are equal.
   */
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof Range)) return false;   // this catches nulls
    Range or = (Range) o;

    if ((length == 0) && (or.length == 0)) // empty ranges are equal
      return true;

    return (or.first == first) && (or.length == length) && (or.stride == stride) && (or.last == last);
  }

  /**
   * Override Object.hashCode() to implement equals.
   */
  public int hashCode() {
    int result = first;
    result = 37 * result + last;
    result = 37 * result + stride;
    result = 37 * result + length;
    return result;
  }

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

  /**
   * Iterate over Range index
   *
   * @return Iterator over element indices
   * @deprecated use iterator() or foreach
   */
  public Iterator<Integer> getIterator() {
    return new MyIterator();
  }

  @Override
  public Iterator<Integer> iterator() {
    return new MyIterator();
  }

  private class MyIterator implements java.util.Iterator<Integer> {
    private int current = 0;

    public boolean hasNext() {
      return current < length;
    }

    public Integer next() {
      return elementNC(current++);
    }
  }

  /**
   * Get ith element; skip checking, for speed.
   */
  private int elementNC(int i) {
    return first + i * stride;
  }

}


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

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.LongBuffer;

/**
 * Concrete implementation of Array specialized for longs.
 * Data storage is with 1D java array of longs.
 *
 * issues: what should we do if a conversion loses accuracy? nothing ? Exception ?
 *
 * @see Array
 * @author caron
 */
public class ArrayLong extends Array {

  /** package private. use Array.factory() */
  static ArrayLong factory(Index index, boolean isUnsigned) {
    return ArrayLong.factory(index, isUnsigned, null);
  }

  /* create new ArrayLong with given indexImpl and backing store.
   * @param index use this Index
   * @param stor. use this storage. if null, allocate.
   * @return. new ArrayLong.D<rank> or ArrayLong object.
   */
  static ArrayLong factory( Index index, boolean isUnsigned, long [] storage) {
      if (index instanceof Index0D) {
          return new ArrayLong.D0(index, isUnsigned, storage);
      } else if (index instanceof Index1D) {
          return new ArrayLong.D1(index, isUnsigned, storage);
      } else if (index instanceof Index2D) {
          return new ArrayLong.D2(index, isUnsigned, storage);
      } else if (index instanceof Index3D) {
          return new ArrayLong.D3(index, isUnsigned, storage);
      } else if (index instanceof Index4D) {
          return new ArrayLong.D4(index, isUnsigned, storage);
      } else if (index instanceof Index5D) {
          return new ArrayLong.D5(index, isUnsigned, storage);
      } else if (index instanceof Index6D) {
          return new ArrayLong.D6(index, isUnsigned, storage);
      } else if (index instanceof Index7D) {
          return new ArrayLong.D7(index, isUnsigned, storage);
      } else {
          return new ArrayLong(index, isUnsigned, storage);
      }
  }

  //////////////////////////////////////////////////////
  protected long[] storage;

  /**
  * Create a new Array of type long and the given shape.
  * dimensions.length determines the rank of the new Array.
  * @param dimensions the shape of the Array.
  */
  public ArrayLong(int [] dimensions, boolean isUnsigned) {
    super(isUnsigned ? DataType.ULONG : DataType.LONG, dimensions);
    storage = new long[(int)indexCalc.getSize()];
  }

  /**
  * Create a new Array using the given IndexArray and backing store.
  * used for sections. Trusted package private.
  * @param ima use this IndexArray as the index
  * @param data use this as the backing store
  */
  ArrayLong(Index ima, boolean isUnsigned, long [] data) {
    super(isUnsigned ? DataType.ULONG : DataType.LONG, ima);
    /* replace by something better
    if (ima.getSize() != data.length)
      throw new IllegalArgumentException("bad data length"); */
    if (data != null)
      storage = data;
    else
      storage = new long[(int)ima.getSize()];
  }

  /** create new Array with given indexImpl and same backing store */
  protected Array createView( Index index) {
    return ArrayLong.factory( index, isUnsigned(), storage);
  }

  /* Get underlying primitive array storage. CAUTION! You may invalidate your warrentee! */
  public Object getStorage() { return storage; }

      // copy from javaArray to storage using the iterator: used by factory( Object);
  protected void copyFrom1DJavaArray(IndexIterator iter, Object javaArray) {
    long[] ja = (long []) javaArray;
    for (long aJa : ja) iter.setLongNext(aJa);
  }

  // copy to javaArray from storage using the iterator: used by copyToNDJavaArray;
  protected void copyTo1DJavaArray(IndexIterator iter, Object javaArray) {
    long[] ja = (long []) javaArray;
    for (int i=0; i<ja.length; i++)
      ja[i] = iter.getLongNext();
  }

    public ByteBuffer getDataAsByteBuffer() {return getDataAsByteBuffer(null);}

    public ByteBuffer getDataAsByteBuffer(ByteOrder order) {
        ByteBuffer bb = super.getDataAsByteBuffer((int) (8 * getSize()), order);
        LongBuffer ib = bb.asLongBuffer();
        ib.put( (long[]) get1DJavaArray(getDataType())); // make sure its in canonical order
        return bb;
  }

 /** Return the element class type */
  public Class getElementType() { return long.class; }

    /** get the value at the specified index. */
  public long get(Index i) {
    return storage[i.currentElement()];
  }
    /** set the value at the sepcified index. */
  public void set(Index i, long value) {
    storage[i.currentElement()] = value;
  }

  public double getDouble(Index i) {
    return (double) storage[i.currentElement()];
  }
  public void setDouble(Index i, double value) {
    storage[i.currentElement()] = (long) value;
  }

  public float getFloat(Index i) {
    return (float) storage[i.currentElement()];
  }
  public void setFloat(Index i, float value) {
    storage[i.currentElement()] = (long) value;
  }

  public long getLong(Index i) {
    return storage[i.currentElement()];
  }
  public void setLong(Index i, long value) {
    storage[i.currentElement()] = value;
  }

  public int getInt(Index i) {
    return (int) storage[i.currentElement()];
  }
  public void setInt(Index i, int value) {
    storage[i.currentElement()] = (long) value;
  }

  public short getShort(Index i) {
    return (short) storage[i.currentElement()];
  }
  public void setShort(Index i, short value) {
    storage[i.currentElement()] = (long) value;
  }

  public byte getByte(Index i) {
    return (byte) storage[i.currentElement()];
  }
  public void setByte(Index i, byte value) {
    storage[i.currentElement()] = (long) value;
  }

  public char getChar(Index i) {
    return (char) storage[i.currentElement()];
  }
  public void setChar(Index i, char value) {
    storage[i.currentElement()] = (long) value;
  }

  /** not legal, throw ForbiddenConversionException */
  public boolean getBoolean(Index i) {
    throw new ForbiddenConversionException();
  }
  /** not legal, throw ForbiddenConversionException */
  public void setBoolean(Index i, boolean value) {
     throw new ForbiddenConversionException();
  }

  public Object getObject(Index i) {
    return storage[i.currentElement()];
  }
  public void setObject(Index i, Object value) {
    storage[i.currentElement()] = ((Number)value).longValue();
  }

    // package private : mostly for iterators
  public double getDouble(int index) {return (double) storage[index]; }
  public void setDouble(int index, double value) { storage[index] = (long) value; }

  public float getFloat(int index) { return storage[index]; }
  public void setFloat(int index, float value) { storage[index] = (long) value;}

  public long getLong(int index) {return storage[index];}
  public void setLong(int index, long value) { storage[index] = value;}

  public int getInt(int index) { return (int) storage[index]; }
  public void setInt(int index, int value) { storage[index] = (long) value;}

  public short getShort(int index) { return (short) storage[index]; }
  public void setShort(int index, short value) { storage[index] = (long) value; }

  public byte getByte(int index) { return (byte) storage[index]; }
  public void setByte(int index, byte value) {storage[index] = (long) value;}

  public char getChar(int index) { return (char) storage[index];}
  public void setChar(int index, char value) { storage[index] = (long) value; }

  public boolean getBoolean(int index) { throw new ForbiddenConversionException(); }
  public void setBoolean(int index, boolean value) {throw new ForbiddenConversionException(); }

  public Object getObject(int index) { return getLong(index); }
  public void setObject(int index, Object value) { storage[index] = ((Number)value).longValue(); }

  /** Concrete implementation of Array specialized for longs, rank 0. */
  public static class D0 extends ArrayLong {
    private Index0D ix;
    /** Constructor. */
    public D0 (boolean isUnsigned) {
      super(new int [] {}, isUnsigned);
      ix = (Index0D) indexCalc;
    }
    D0 (Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index0D) indexCalc;
    }
    /** get the value. */
    public long get() {
      return storage[ix.currentElement()];
    }
    /** set the value. */
    public void set(long value) {
      storage[ix.currentElement()] = value;
    }
  }

  /** Concrete implementation of Array specialized for longs, rank 1. */
  public static class D1 extends ArrayLong {
    private Index1D ix;
    /** Constructor for array of shape {len0}. */
    public D1 (int len0, boolean isUnsigned) {
      super(new int [] {len0}, isUnsigned);
      ix = (Index1D) indexCalc;
    }
    private D1(Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index1D) indexCalc;
    }
    /** get the value. */
    public long get(int i) {
      return storage[ix.setDirect(i)];
    }
    /** set the value. */
    public void set(int i, long value) {
      storage[ix.setDirect(i)] = value;
    }
  }

  /** Concrete implementation of Array specialized for longs, rank 2. */
  public static class D2 extends ArrayLong {
    private Index2D ix;
    /** Constructor for array of shape {len0,len1}. */
    public D2 (int len0, int len1, boolean isUnsigned) {
      super(new int [] {len0, len1}, isUnsigned);
      ix = (Index2D) indexCalc;
    }
    private D2 (Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index2D) indexCalc;
    }
    /** get the value. */
    public long get(int i, int j) {
      return storage[ix.setDirect(i,j)];
    }
    /** set the value. */
    public void set(int i, int j, long value) {
      storage[ix.setDirect(i,j)] = value;
    }
  }

  /** Concrete implementation of Array specialized for longs, rank 3. */
  public static class D3 extends ArrayLong {
    private Index3D ix;
    /** Constructor for array of shape {len0,len1,len2}. */
    public D3 (int len0, int len1, int len2, boolean isUnsigned) {
      super(new int [] {len0, len1, len2}, isUnsigned);
      ix = (Index3D) indexCalc;
    }
    private D3 (Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index3D) indexCalc;
    }
    /** get the value. */
    public long get(int i, int j, int k) {
      return storage[ix.setDirect(i,j,k)];
    }
    /** set the value. */
    public void set(int i, int j, int k, long value) {
      storage[ix.setDirect(i,j,k)] = value;
    }
  }

  /** Concrete implementation of Array specialized for longs, rank 4. */
  public static class D4 extends ArrayLong {
    private Index4D ix;
    /** Constructor for array of shape {len0,len1,len2,len3}. */
    public D4 (int len0, int len1, int len2, int len3, boolean isUnsigned) {
      super(new int [] {len0, len1, len2, len3}, isUnsigned);
      ix = (Index4D) indexCalc;
    }
    private D4 (Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index4D) indexCalc;
    }
    /** get the value. */
    public long get(int i, int j, int k, int l) {
      return storage[ix.setDirect(i,j,k,l)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, long value) {
      storage[ix.setDirect(i,j,k,l)] = value;
    }
  }

  /** Concrete implementation of Array specialized for longs, rank 5. */
  public static class D5 extends ArrayLong {
    private Index5D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4}. */
    public D5 (int len0, int len1, int len2, int len3, int len4, boolean isUnsigned) {
      super(new int [] {len0, len1, len2, len3, len4}, isUnsigned);
      ix = (Index5D) indexCalc;
    }
    private D5 (Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index5D) indexCalc;
    }
    /** get the value. */
    public long get(int i, int j, int k, int l, int m) {
      return storage[ix.setDirect(i,j,k,l, m)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, long value) {
      storage[ix.setDirect(i,j,k,l, m)] = value;
    }
  }

  /** Concrete implementation of Array specialized for longs, rank 6. */
  public static class D6 extends ArrayLong {
    private Index6D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4,len5,}. */
    public D6 (int len0, int len1, int len2, int len3, int len4, int len5, boolean isUnsigned) {
      super(new int [] {len0, len1, len2, len3, len4, len5}, isUnsigned);
      ix = (Index6D) indexCalc;
    }
    private D6 (Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index6D) indexCalc;
    }
    /** get the value. */
    public long get(int i, int j, int k, int l, int m, int n) {
      return storage[ix.setDirect(i,j,k,l,m,n)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, int n, long value) {
      storage[ix.setDirect(i,j,k,l,m,n)] = value;
    }
  }

  /** Concrete implementation of Array specialized for longs, rank 7. */
  public static class D7 extends ArrayLong {
    private Index7D ix;
    /** Constructor for array of shape {len0,len1,len2,len3,len4,len5,len6}. */
    public D7 (int len0, int len1, int len2, int len3, int len4, int len5, int len6, boolean isUnsigned) {
      super(new int [] {len0, len1, len2, len3, len4, len5, len6}, isUnsigned);
      ix = (Index7D) indexCalc;
    }
    private D7 (Index i, boolean isUnsigned, long[] store) {
      super(i, isUnsigned, store);
      ix = (Index7D) indexCalc;
    }
    /** get the value. */
    public long get(int i, int j, int k, int l, int m, int n, int o) {
      return storage[ix.setDirect(i,j,k,l,m,n,o)];
    }
    /** set the value. */
    public void set(int i, int j, int k, int l, int m, int n, int o, long value) {
      storage[ix.setDirect(i,j,k,l,m,n,o)] = value;
    }
  }

}


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

import ucar.ma2.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.iosp.Layout;
import ucar.nc2.iosp.IospHelper;

import java.nio.channels.WritableByteChannel;

/**
 * Use our RandomAccessFile class to read and write.
 * @author caron
 */

public class N3raf extends N3iosp  {

  protected void _open(ucar.unidata.io.RandomAccessFile raf) throws java.io.IOException {
  }

  protected void _create(ucar.unidata.io.RandomAccessFile raf) throws java.io.IOException {
  }

  /**
   * Read data subset from file for a variable, create primitive array.
   * @param index handles skipping around in the file.
   * @param dataType dataType of the variable
   * @return primitive array with data read in
   */
 protected Object readData( Layout index, DataType dataType) throws java.io.IOException {
   return IospHelper.readDataFill(raf, index, dataType, null, -1);
 }

  /**
   * Read data subset from file for a variable, to WritableByteChannel.
   * Will send as bigendian, since thats what the underlying file has.
   * @param index handles skipping around in the file.
   * @param dataType dataType of the variable
   */
  protected long readData( Layout index, DataType dataType, WritableByteChannel out) throws java.io.IOException {
    long count = 0;
    if (dataType.getPrimitiveClassType() == byte.class || dataType == DataType.CHAR) {
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        count += raf.readToByteChannel( out, chunk.getSrcPos(), chunk.getNelems());
      }

    } else if (dataType.getPrimitiveClassType() == short.class) {
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        count += raf.readToByteChannel( out, chunk.getSrcPos(), 2 * chunk.getNelems());
      }

    } else if (dataType.getPrimitiveClassType() == int.class || (dataType == DataType.FLOAT)) {
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        count += raf.readToByteChannel( out, chunk.getSrcPos(), 4 * chunk.getNelems());
      }

    } else if ((dataType == DataType.DOUBLE) || dataType.getPrimitiveClassType() == long.class) {
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        count += raf.readToByteChannel( out, chunk.getSrcPos(), 8 * chunk.getNelems());
      }
    }

    return count;
  }

   /**
    * write data to a file for a variable.
    * @param values write this data.
    * @param index handles skipping around in the file.
    * @param dataType dataType of the variable
    */
  protected void writeData( Array values, Layout index, DataType dataType) throws java.io.IOException {
    if ((dataType == DataType.BYTE) || (dataType == DataType.CHAR)) {
      IndexIterator ii = values.getIndexIterator();
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.seek ( chunk.getSrcPos());
        for (int k=0; k<chunk.getNelems(); k++)
          raf.write( ii.getByteNext());
      }
      return;

    } else if (dataType == DataType.STRING) { // LOOK not legal
      IndexIterator ii = values.getIndexIterator();
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.seek ( chunk.getSrcPos());
        for (int k=0; k<chunk.getNelems(); k++) {
          String val = (String) ii.getObjectNext();
          if (val != null) raf.write( val.getBytes(CDM.utf8Charset)); // LOOK ??
        }
      }
      return;

    } else if (dataType == DataType.SHORT) {
      IndexIterator ii = values.getIndexIterator();
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.seek ( chunk.getSrcPos());
        for (int k=0; k<chunk.getNelems(); k++)
          raf.writeShort( ii.getShortNext());
      }
      return;

    } else if (dataType == DataType.INT) {
      IndexIterator ii = values.getIndexIterator();
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.seek ( chunk.getSrcPos());
        for (int k=0; k<chunk.getNelems(); k++)
          raf.writeInt( ii.getIntNext());
      }
      return;

    } else if (dataType == DataType.FLOAT) {
      IndexIterator ii = values.getIndexIterator();
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.seek ( chunk.getSrcPos());
        for (int k=0; k<chunk.getNelems(); k++)
          raf.writeFloat( ii.getFloatNext());
      }
      return;

    } else if (dataType == DataType.DOUBLE) {
      IndexIterator ii = values.getIndexIterator();
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.seek ( chunk.getSrcPos());
        for (int k=0; k<chunk.getNelems(); k++)
          raf.writeDouble( ii.getDoubleNext());
      }
      return;
    }

    throw new IllegalStateException("dataType= "+dataType);
  }

}


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

package ucar.nc2.ft.point.standard.plug;

import ucar.nc2.ft.point.standard.*;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.Dimension;
import ucar.nc2.constants.FeatureType;

import java.util.Formatter;

/**
 * Madis Convention
 * @author caron
 * @since Apr 23, 2008
 */
public class Madis extends TableConfigurerImpl  {

  public boolean isMine(FeatureType wantFeatureType, NetcdfDataset ds) {
    if ((wantFeatureType != FeatureType.ANY_POINT) && (wantFeatureType != FeatureType.STATION) && (wantFeatureType != FeatureType.POINT)
            && (wantFeatureType != FeatureType.STATION_PROFILE))
      return false;

    if (!ds.hasUnlimitedDimension()) return false;
    if (ds.findDimension("recNum") == null) return false;

    if (ds.findVariable("staticIds") == null) return false;
    if (ds.findVariable("nStaticIds") == null) return false;
    if (ds.findVariable("lastRecord") == null) return false;
    if (ds.findVariable("prevRecord") == null) return false;

    VNames vn = getVariableNames(ds, null);
    if (ds.findVariable(vn.lat) == null) return false;
    if (ds.findVariable(vn.lon) == null) return false;
    if (ds.findVariable(vn.obsTime) == null) return false;

    return true;
  }

  /*

  <stationCollection>

      <lastLink>lastRecord</lastLink>

        <stationId>:stationIdVariable</stationId>
        <stationWmoId>wmoId</stationWmoId>
        <coordAxis type="time">timeObs</coordAxis>
        <coordAxis type="lat">latitude</coordAxis>
        <coordAxis type="lon">longitude</coordAxis>
        <coordAxis type="height">elevation</coordAxis>
        <prevLink>prevRecord</prevLink>

    <cdmDataType>:thredds_data_type</cdmDataType>
  </stationCollection>
   */

  public TableConfig getConfig(FeatureType wantFeatureType, NetcdfDataset ds, Formatter errlog) {

    Dimension obsDim = Evaluator.getDimension(ds, "recNum", errlog);
    if (obsDim == null) {
      errlog.format("MADIS: must have an Observation dimension: named recNum");
      return null;
    }
    VNames vn = getVariableNames(ds, errlog);

    String levVarName = null;
    String levDimName = null;
    boolean hasStruct = Evaluator.hasNetcdf3RecordStructure(ds);
    FeatureType ft = Evaluator.getFeatureType(ds, ":thredds_data_type", errlog);
    if (null == ft) {
      if ((ds.findDimension("manLevel") != null) && (ds.findVariable("prMan") != null)) {
        ft = FeatureType.STATION_PROFILE;
        levVarName = "prMan";
        levDimName = "manLevel";
      } else if ((ds.findDimension("level") != null) && (ds.findVariable("levels") != null)) {
        ft = FeatureType.STATION_PROFILE;
        levVarName = "levels";
        levDimName = "level";
      }
    }
    if (null == ft) ft = FeatureType.POINT;

    // points
    if ((wantFeatureType == FeatureType.POINT) || (ft == FeatureType.POINT)) {
      TableConfig ptTable = new TableConfig(Table.Type.Structure, hasStruct ? "record" : obsDim.getShortName() );
      ptTable.structName = "record";
      ptTable.featureType = FeatureType.POINT;
      ptTable.structureType = hasStruct ? TableConfig.StructureType.Structure : TableConfig.StructureType.PsuedoStructure;

      ptTable.dimName = obsDim.getShortName();
      ptTable.time = vn.obsTime;
      ptTable.timeNominal = vn.nominalTime;
      ptTable.lat = vn.lat;
      ptTable.lon = vn.lon;
      ptTable.elev = vn.elev;

      return ptTable;
     }

    if (ft == FeatureType.STATION) {
      TableConfig stnTable = new TableConfig(Table.Type.Construct, "station");
      stnTable.featureType = FeatureType.STATION;

      TableConfig obs = new TableConfig(Table.Type.ParentId, "record");
      obs.parentIndex = vn.stnId;
      obs.dimName = Evaluator.getDimensionName(ds, "recNum", errlog);
      obs.time = vn.obsTime;
      obs.timeNominal = vn.nominalTime;

      obs.stnId = vn.stnId;
      obs.stnDesc = vn.stnDesc;
      obs.lat = vn.lat;
      obs.lon = vn.lon;
      obs.elev = vn.elev;

      stnTable.addChild(obs);
      return stnTable;
    }

    else if (ft == FeatureType.STATION_PROFILE) {
      TableConfig stnTable = new TableConfig(Table.Type.Construct, "station");
      stnTable.featureType = FeatureType.STATION_PROFILE;

      TableConfig obs = new TableConfig(Table.Type.ParentId, "record");
      obs.parentIndex = vn.stnId;
      obs.dimName = Evaluator.getDimensionName(ds, "recNum", errlog);
      obs.time = vn.obsTime;
      obs.timeNominal = vn.nominalTime;

      obs.stnId = vn.stnId;
      obs.stnDesc = vn.stnDesc;
      obs.lat = vn.lat;
      obs.lon = vn.lon;
      obs.stnAlt = vn.elev;

      stnTable.addChild(obs);

      TableConfig lev = new TableConfig(Table.Type.MultidimInner, "mandatory");
      lev.elev = levVarName;
      lev.outerName = obs.dimName;
      lev.innerName = levDimName;
      obs.addChild(lev);

      return stnTable;
    }

    return null;
  }

  protected static class VNames {
    String lat, lon, elev, obsTime, nominalTime;
    String stnId, stnDesc;
  }

  protected VNames getVariableNames(NetcdfDataset ds, Formatter errlog) {
    VNames vn = new VNames();

    String val = ds.findAttValueIgnoreCase(null, "stationLocationVariables", null);
    if (val == null)
      val = ds.findAttValueIgnoreCase(null, "latLonVars", null);
    if (val == null) {
      if (errlog != null) errlog.format(" Cant find global attribute stationLocationVariables%n");
      vn.lat = "latitude";
      vn.lon = "longitude";
    } else {
      String[] vals = val.split(",");
      if (vals.length > 0) vn.lat = vals[0];
      if (vals.length > 1) vn.lon = vals[1];
      if (vals.length > 2) vn.elev = vals[2];
    }

    val = ds.findAttValueIgnoreCase(null, "timeVariables", null);
    if (val == null) {
      if (errlog != null) errlog.format(" Cant find global attribute timeVariables%n");
      vn.obsTime = "observationTime";
      vn.nominalTime = "reportTime";
    } else {
      String[] vals = val.split(",");
      if (vals.length > 0) vn.obsTime = vals[0];
      if (vals.length > 1) vn.nominalTime = vals[1];
    }

    val = ds.findAttValueIgnoreCase(null, "stationDescriptionVariable", null);
    if (val == null) {
      if (errlog != null) errlog.format(" Cant find global attribute stationDescriptionVariable%n");
      vn.stnDesc = "stationName";
    } else {
      vn.stnDesc = val;
    }

    val = ds.findAttValueIgnoreCase(null, "stationIdVariable", null);
    if (val == null)
      val = ds.findAttValueIgnoreCase(null, "idVariables", null);
    if (val == null) {
      if (errlog != null) errlog.format(" Cant find global attribute stationIdVariable%n");
      vn.stnId = "stationId";
    } else {
      vn.stnId = val;
    }

    if (null != ds.findVariable("altitude"))
      vn.elev = "altitude";
    else if (null != ds.findVariable("elevation"))
      vn.elev = "elevation";

    return vn;
  }

}


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

// $Id: NcSDCharArray.java 51 2006-07-12 17:13:13Z caron $

package thredds.server.opendap;

import ucar.ma2.*;
import ucar.nc2.*;

import opendap.dap.*;
import opendap.servers.*;

import java.io.IOException;
import java.io.EOFException;
import java.io.DataOutputStream;
import java.util.*;

/**
 * Wraps a netcdf char variable with rank > 1 as an SDArray.
 *
 * @author jcaron
 */
public class NcSDCharArray extends SDArray implements HasNetcdfVariable {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(NcSDCharArray.class);

  private static final boolean debugRead = false, debugReadDetail = false;
  private Variable ncVar = null;
  private int strLen = 1;

  /**
   * Constructor: Wraps a netcdf char variable (rank > 1) in a DODS SDArray.
   *
   * @param v : netcdf Variable
   */
  NcSDCharArray(Variable v) {
      super(Variable.getDAPName(v));
    this.ncVar = v;
    if (v.getRank() < 1)
      throw new IllegalArgumentException("NcSDCharArray: rank must be > 1, var = " + v.getFullName());

    // set dimensions, eliminate last one
    List dims = v.getDimensions();
    for (int i = 0; i < dims.size(); i++) {
      Dimension dim = (Dimension) dims.get(i);
      if (i < dims.size() - 1)
        appendDim(dim.getLength(), dim.getShortName());
      else
        strLen = dim.getLength();
    }

    // set String type
    addVariable(new NcSDString(Variable.getDAPName(v), null));
  }

  public Variable getVariable() { return ncVar; }

  /**
   * Read the data values (parameters are ignored).
   * Use the start, stop and stride values that were set by the constraint evaluator.
   *
   * @param datasetName not used
   * @param specialO    not used
   * @return false (no more data to be read)
   * @throws IOException
   * @throws EOFException
   */
  public boolean read(String datasetName, Object specialO) throws IOException {
    boolean hasStride = false;
    Array a;
    try {

      if (debugRead) {
        System.out.println("NcSDCharArray read " + ncVar.getFullName());
        for (int i = 0; i < numDimensions(); i++) {
          DArrayDimension d = getDimension(i);
          System.out.println(" " + d.getEncodedName() + " " + getStart(i) + " " + getStop(i) + " " + getStride(i));
        }
      }

      // set up the netcdf read
      int n = numDimensions();
      int[] origin = new int[n + 1];
      int[] shape = new int[n + 1];

      for (int i = 0; i < n; i++) {
        origin[i] = getStart(i);
        shape[i] = getStop(i) - getStart(i) + 1;
        hasStride = hasStride || (getStride(i) > 1);
      }
      origin[n] = 0;
      shape[n] = strLen;

      a = ncVar.read(origin, shape);
      if (debugRead) System.out.println("  Read = " + a.getSize() + " elems of type = " + a.getElementType());

      // deal with strides using a section
      if (hasStride) {
        List<Range> ranges = new ArrayList<>();
        for (int i = 0; i < n; i++) {
          int s = getStride(i);
          if (s > 1) {  // otherwise null, means "take all elements"
            ranges.add(new Range(0, shape[i], s));
            if (debugRead) System.out.println(" Section dim " + i + " stride = " + s);
          }
        }
        ranges.add(null); //  get all
        a = a.section(ranges);
        if (debugRead) System.out.println("   section size " + a.getSize());
      }

    } catch (InvalidDimensionException e) {
      log.error("read char array", e);
      throw new IllegalStateException("NcSDCharArray InvalidDimensionException");
    } catch (InvalidRangeException e) {
      log.error("read char array", e);
      throw new IllegalStateException("NcSDCharArray InvalidRangeException");
    }

    setData(a);
    return (false);
  }

  public void setData(Array data) {
    PrimitiveVector pv = getPrimitiveVector();
    if (debugRead)
      System.out.println(" PrimitiveVector type = " + pv.getTemplate() +
          " pv type = " + pv.getClass().getName());

    // this is the case of netcdf char arrays with rank > 1;
    // these become DODS Arrays of Strings
    ArrayChar ca = (ArrayChar) data;
    ArrayChar.StringIterator siter = ca.getStringIterator();
    int nelems = siter.getNumElems();
    if (debugRead) System.out.println(" set Strings = " + nelems);

    BaseTypePrimitiveVector btpv = (BaseTypePrimitiveVector) pv;
    btpv.setLength(nelems);
    for (int i = 0; i < nelems; i++) {
      String val = siter.next();
      NcSDString ds = new NcSDString("", val);
      btpv.setValue(i, ds);
      if (debugReadDetail) System.out.println("  s = " + val + " == " + ds.getValue());
    }
    if (debugRead) System.out.println("  PrimitiveVector len = " + pv.getLength() + " type = " + pv.getTemplate());

    setRead(true);
  }

  public void serialize(DataOutputStream sink, StructureData sdata, StructureMembers.Member m) throws IOException {
    setData( sdata.getArray( m));
    externalize( sink);
  }
}


/////////////////////////////////////////////////////////////////////////////
// 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 java.io.*;

/**
 * A vector of booleans.
 *
 * @author ndp
 * @version $Revision: 15901 $
 * @see PrimitiveVector
 */
public class BooleanPrimitiveVector extends PrimitiveVector
{
    /**
     * the array of float values.
     */
    private boolean vals[];

    /**
     * Constructs a new BooleanPrimitiveVector.
     *
     * @param var the template BaseType to use.
     */
    public BooleanPrimitiveVector(BaseType var) {
        super(var);
    }

    /**
     * Returns the number of elements in the array.
     *
     * @return the number of elements in the array.
     */
    public int getLength() {
        return vals.length;
    }

    /**
     * Sets the number of elements in the array.  Allocates a new primitive
     * array of the desired size.  Note that if this is called multiple times,
     * the old array and its contents will be lost.
     *

     * Only called inside of deserialize method or in derived
     * classes on server.
     *
     * @param len the number of elements in the array.
     */
    public void setLength(int len) {
        vals = new boolean[len];
    }

    /**
     * Return the i'th value as a double.
     *
     * @param i the index of the value to return.
     * @return the i'th value.
     */
    public final boolean getValue(int i) {
        return vals[i];
    }

    /**
     * Set the i'th value of the array.
     *
     * @param i      the index of the value to set.
     * @param newVal the new value.
     */
    public final void setValue(int i, boolean newVal) {
        vals[i] = newVal;
    }

    /**
     * Prints the value of all variables in this vector.  This
     * method is primarily intended for debugging OPeNDAP applications and
     * text-based clients such as geturl.
     *
     * @param os    the PrintWriter on which to print the value.
     * @param space this value is passed to the printDecl method,
     *              and controls the leading spaces of the output.
     * @see BaseType#printVal(PrintWriter, String, boolean)
     */
    public void printVal(PrintWriter os, String space) {
        int len = vals.length;
        for (int i = 0; i < len - 1; i++) {
            os.print(vals[i]);
            os.print(", ");
        }
        // print last value, if any, without trailing comma
        if (len > 0)
            os.print(vals[len - 1]);
    }

    /**
     * Prints the value of a single variable in this vector.
     * method is used by DArray's printVal method.
     *
     * @param os    the PrintWriter on which to print the value.
     * @param index the index of the variable to print.
     * @see DArray#printVal(PrintWriter, String, boolean)
     */
    public void printSingleVal(PrintWriter os, int index) {
        os.print(vals[index]);
    }

    /**
     * Reads data from a DataInputStream. This method is only used
     * on the client side of the OPeNDAP client/server connection.
     *
     * @param source   a DataInputStream to read from.
     * @param sv       The ServerVersion returned by the server.
     *                 (used by DSequence to determine which protocol version was
     *                 used).
     * @param statusUI The StatusUI object to use for GUI updates
     *                 and user cancellation notification (may be null).
     * @throws DataReadException when invalid data is read, or if the user
     *                           cancels the download.
     * @throws EOFException      if EOF is found before the variable is completely
     *                           deserialized.
     * @throws IOException       thrown on any other InputStream exception.
     * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    public synchronized void deserialize(DataInputStream source,
                                         ServerVersion sv,
                                         StatusUI statusUI)
            throws IOException, EOFException, DataReadException {
        for (int i = 0; i < vals.length; i++) {
            vals[i] = source.readBoolean();
            if (statusUI != null) {
                statusUI.incrementByteCount(1);
                if (statusUI.userCancelled())
                    throw new DataReadException("User cancelled");
            }
        }
    }

    /**
     * Writes data to a DataOutputStream. This method is used
     * on the server side of the OPeNDAP client/server connection, and possibly
     * by GUI clients which need to download OPeNDAP data, manipulate it, and
     * then resave it as a binary file.
     *
     * @param sink a DataOutputStream to write to.
     * @throws IOException thrown on any OutputStream exception.
     */
    public void externalize(DataOutputStream sink) throws IOException {
        for (int i = 0; i < vals.length; i++) {
            sink.writeBoolean(vals[i]);
        }
    }

    /**
     * Write a subset of the data to a DataOutputStream.
     *
     * @param sink    a DataOutputStream to write to.
     * @param start  starting index (i=start)
     * @param stop   ending index (i<=stop)
     * @param stride index stride (i+=stride)
     * @throws IOException thrown on any OutputStream exception.
     */
    public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
        for (int i = start; i <= stop; i += stride)
            sink.writeBoolean(vals[i]);
    }

    /**
     * Returns (a reference to) the internal storage for this PrimitiveVector
     * object.
     *
WARNING:

     * Because this method breaks encapsulation rules the user must beware!
     * If we (the OPeNDAP prgramming team) choose to change the internal
     * representation(s) of these types your code will probably break.
     *

     * This method is provided as an optimization to eliminate massive
     * copying of data.
     *
     * @return The internal array of boolean values.
     */
    public Object getInternalStorage() {
        return (vals);
    }

    /**
     * Set the internal storage for PrimitiveVector.
     *
WARNING:

     * Because this method breaks encapsulation rules the user must beware!
     * If we (the OPeNDAP prgramming team) choose to change the internal
     * representation(s) of these types your code will probably break.
     *

     * This method is provided as an optimization to eliminate massive
     * copying of data.
     */
    public void setInternalStorage(Object o) {
        vals = (boolean []) o;
    }

    /**
     * Create a new primitive vector using a subset of the data.
     *
     * @param start  starting index (i=start)
     * @param stop  ending index (i<=stop)
     * @param stride index stride (i+=stride)
     * @return new primitive vector, of type BooleanPrimitiveVector.
     */
    public PrimitiveVector subset(int start, int stop, int stride) {
        BooleanPrimitiveVector n = new BooleanPrimitiveVector(getTemplate());
        stride = Math.max(stride, 1);
        stop = Math.max(start, stop);
        int length = 1 + (stop - start) / stride;
        n.setLength(length);

        int count = 0;
        for (int i = start; i <= stop; i += stride) {
            n.setValue(count, vals[i]);
            count++;
        }
        return n;
    }

      /**
     * Returns the OPeNDAP type name of the class instance as a String.
     *
     * @return the OPeNDAP type name of the class instance as a String.
     */
    public String getTypeName() {
        return "BooleanPrimitiveVector";
    }

/**
   * Prints the value of the variable, with its declaration.  This
   * function is primarily intended for debugging OPeNDAP applications and
   * text-based clients such as geturl.
   *
   * @param os           the PrintWriter on which to print the value.
   * @param space        this value is passed to the printDecl method,
   *                     and controls the leading spaces of the output.
   * @param print_decl_p a boolean value controlling whether the
   *                     variable declaration is printed as well as the value.
   */
   public void printVal(PrintWriter os, String space,
                                boolean print_decl_p) {}

    /**
     * Returns a clone of this BooleanPrimitiveVector.
     * See DAPNode.cloneDag()
     *
     * @param map track previously cloned nodes
     * @return a clone of this BooleanPrimitiveVector.
     */
    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
        BooleanPrimitiveVector v = (BooleanPrimitiveVector) super.cloneDAG(map);
        if (vals != null) {
            v.vals = new boolean[vals.length];
            System.arraycopy(vals, 0, v.vals, 0, vals.length);
        }
        return v;
    }

}


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

import ucar.ma2.DataType;
import ucar.nc2.Variable;

/**
 * @author caron
 * @since Dec 20, 2007
 */
public class H4type {

  // p 110 table 9a : probably the "class" of the number type
  static String getNumberType(byte type) {
    switch (type) {
      case 0:
        return "NONE";
      case 1:
        return "IEEE";
      case 2:
        return "VAX";
      case 3:
        return "CRAY";
      case 4:
        return "PC";
      case 5:
        return "CONVEX";
      default:
        throw new IllegalStateException("unknown type= " + type);
    }
  }

  /* type info codes from hntdefs.h
    #define DFNT_UCHAR8      3
    #define DFNT_CHAR8       4
    #define DFNT_FLOAT32     5
    #define DFNT_FLOAT64     6

    #define DFNT_INT8       20
    #define DFNT_UINT8      21
    #define DFNT_INT16      22
    #define DFNT_UINT16     23
    #define DFNT_INT32      24
    #define DFNT_UINT32     25
    #define DFNT_INT64      26
    #define DFNT_UINT64     27
  */
  static DataType setDataType(short type, Variable v) {
    DataType dt;
    switch (type) {
      case 3:
        dt = DataType.UBYTE;
        break;
      case 4:
        dt =  DataType.CHAR;
        break;
      case 5:
        dt =  DataType.FLOAT;
        break;
      case 6:
        dt =  DataType.DOUBLE;
        break;
      case 20:
        dt =  DataType.BYTE;
        break;
      case 21:
        dt =  DataType.UBYTE;
        break;
      case 22:
        dt =  DataType.SHORT;
        break;
      case 23:
        dt =  DataType.USHORT;
        break;
      case 24:
        dt =  DataType.INT;
        break;
      case 25:
        dt =  DataType.UINT;
        break;
      case 26:
        dt =  DataType.LONG;
        break;
      case 27:
        dt =  DataType.ULONG;
        break;
      default:
        throw new IllegalStateException("unknown type= " + type);
    }

    if (v != null) {
      v.setDataType(dt);
    }

    return dt;
  }

}

/*

/* $Id: hntdefs.h,v 1.5 2000/05/23 18:03:30 acheng Exp $

/*+ hnt.h
   *** This file contains all the number-type definitions for HDF

#ifndef _HNT_H
#define _HNT_H

/* masks for types
#define DFNT_HDF      0x00000000    /* standard HDF format
#define DFNT_NATIVE   0x00001000    /* native format
#define DFNT_CUSTOM   0x00002000    /* custom format
#define DFNT_LITEND   0x00004000    /* Little Endian format
#define DFNT_MASK     0x00000fff    /* format mask

/* type info codes

#define DFNT_NONE        0  /* indicates that number type not set
#define DFNT_QUERY       0  /* use this code to find the current type
#define DFNT_VERSION     1  /* current version of NT info

#define DFNT_FLOAT32     5
#define DFNT_FLOAT       5  /* For backward compat; don't use
#define DFNT_FLOAT64     6
#define DFNT_DOUBLE      6  /* For backward compat; don't use
#define DFNT_FLOAT128    7  /* No current plans for support

#define DFNT_INT8       20
#define DFNT_UINT8      21

#define DFNT_INT16      22
#define DFNT_UINT16     23
#define DFNT_INT32      24
#define DFNT_UINT32     25
#define DFNT_INT64      26
#define DFNT_UINT64     27
#define DFNT_INT128     28  /* No current plans for support
#define DFNT_UINT128    30  /* No current plans for support

#define DFNT_UCHAR8      3  /* 3 chosen for backward compatibility
#define DFNT_UCHAR       3  /* uchar=uchar8 for backward combatibility
#define DFNT_CHAR8       4  /* 4 chosen for backward compatibility
#define DFNT_CHAR        4  /* uchar=uchar8 for backward combatibility
#define DFNT_CHAR16     42  /* No current plans for support
#define DFNT_UCHAR16    43  /* No current plans for support

/* Type info codes for Native Mode datasets
#define DFNT_NFLOAT32   (DFNT_NATIVE | DFNT_FLOAT32)
#define DFNT_NFLOAT64   (DFNT_NATIVE | DFNT_FLOAT64)
#define DFNT_NFLOAT128  (DFNT_NATIVE | DFNT_FLOAT128)   /* Unsupported

#define DFNT_NINT8      (DFNT_NATIVE | DFNT_INT8)
#define DFNT_NUINT8     (DFNT_NATIVE | DFNT_UINT8)
#define DFNT_NINT16     (DFNT_NATIVE | DFNT_INT16)
#define DFNT_NUINT16    (DFNT_NATIVE | DFNT_UINT16)
#define DFNT_NINT32     (DFNT_NATIVE | DFNT_INT32)
#define DFNT_NUINT32    (DFNT_NATIVE | DFNT_UINT32)
#define DFNT_NINT64     (DFNT_NATIVE | DFNT_INT64)
#define DFNT_NUINT64    (DFNT_NATIVE | DFNT_UINT64)
#define DFNT_NINT128    (DFNT_NATIVE | DFNT_INT128)     /* Unsupported
#define DFNT_NUINT128   (DFNT_NATIVE | DFNT_UINT128)    /* Unsupported

#define DFNT_NCHAR8     (DFNT_NATIVE | DFNT_CHAR8)
#define DFNT_NCHAR      (DFNT_NATIVE | DFNT_CHAR8)  /* backward compat
#define DFNT_NUCHAR8    (DFNT_NATIVE | DFNT_UCHAR8)
#define DFNT_NUCHAR     (DFNT_NATIVE | DFNT_UCHAR8)     /* backward compat
#define DFNT_NCHAR16    (DFNT_NATIVE | DFNT_CHAR16)     /* Unsupported
#define DFNT_NUCHAR16   (DFNT_NATIVE | DFNT_UCHAR16)    /* Unsupported

/* Type info codes for Little Endian data
#define DFNT_LFLOAT32   (DFNT_LITEND | DFNT_FLOAT32)
#define DFNT_LFLOAT64   (DFNT_LITEND | DFNT_FLOAT64)
#define DFNT_LFLOAT128  (DFNT_LITEND | DFNT_FLOAT128)   /* Unsupported

#define DFNT_LINT8      (DFNT_LITEND | DFNT_INT8)
#define DFNT_LUINT8     (DFNT_LITEND | DFNT_UINT8)
#define DFNT_LINT16     (DFNT_LITEND | DFNT_INT16)
#define DFNT_LUINT16    (DFNT_LITEND | DFNT_UINT16)
#define DFNT_LINT32     (DFNT_LITEND | DFNT_INT32)
#define DFNT_LUINT32    (DFNT_LITEND | DFNT_UINT32)
#define DFNT_LINT64     (DFNT_LITEND | DFNT_INT64)
#define DFNT_LUINT64    (DFNT_LITEND | DFNT_UINT64)
#define DFNT_LINT128    (DFNT_LITEND | DFNT_INT128)     /* Unsupported
#define DFNT_LUINT128   (DFNT_LITEND | DFNT_UINT128)    /* Unsupported

#define DFNT_LCHAR8     (DFNT_LITEND | DFNT_CHAR8)
#define DFNT_LCHAR      (DFNT_LITEND | DFNT_CHAR8)  /* backward compat
#define DFNT_LUCHAR8    (DFNT_LITEND | DFNT_UCHAR8)
#define DFNT_LUCHAR     (DFNT_LITEND | DFNT_UCHAR8)     /* backward compat
#define DFNT_LCHAR16    (DFNT_LITEND | DFNT_CHAR16)     /* Unsupported
#define DFNT_LUCHAR16   (DFNT_LITEND | DFNT_UCHAR16)    /* Unsupported

/* class info codes for int
#define        DFNTI_MBO       1    /* Motorola byte order 2's compl
#define        DFNTI_VBO       2    /* Vax byte order 2's compl
#define        DFNTI_IBO       4    /* Intel byte order 2's compl

/* class info codes for float
#define        DFNTF_NONE      0    /* indicates subclass is not set
#define        DFNTF_HDFDEFAULT 1   /* hdf default float format is ieee
#define        DFNTF_IEEE      1    /* IEEE format
#define        DFNTF_VAX       2    /* Vax format
#define        DFNTF_CRAY      3    /* Cray forma
#define        DFNTF_PC        4    /* PC floats - flipped IEEE
#define        DFNTF_CONVEX    5    /* CONVEX native format
#define        DFNTF_VP        6    /* Fujitsu VP native format
#define        DFNTF_CRAYMPP   7    /* Cray MPP format

/* class info codes for char
#define        DFNTC_BYTE      0    /* bitwise/numeric field
#define        DFNTC_ASCII     1    /* ASCII
#define        DFNTC_EBCDIC    5    /* EBCDIC

/* array order
#define        DFO_FORTRAN     1    /* column major order
#define        DFO_C           2    /* row major order

/******************************************************************
/* Sizes of number types
/******************************************************************

/* first the standard sizes of number types

#    define SIZE_FLOAT32    4
#    define SIZE_FLOAT64    8
#    define SIZE_FLOAT128  16   /* No current plans for support

#    define SIZE_INT8       1
#    define SIZE_UINT8      1
#    define SIZE_INT16      2
#    define SIZE_UINT16     2
#    define SIZE_INT32      4
#    define SIZE_UINT32     4
#    define SIZE_INT64      8
#    define SIZE_UINT64     8
#    define SIZE_INT128    16   /* No current plans for support
#    define SIZE_UINT128   16   /* No current plans for support

#    define SIZE_CHAR8      1
#    define SIZE_CHAR       1   /* For backward compat char8 == char
#    define SIZE_UCHAR8     1
#    define SIZE_UCHAR      1   /* For backward compat uchar8 == uchar
#    define SIZE_CHAR16     2   /* No current plans for support
#    define SIZE_UCHAR16    2   /* No current plans for support

/* then the native sizes of number types

/* Unusual number sizes
/* Cray (UNICOS) native number sizes:
	Char = 8 bits, unsigned
	Short=64 int=64 long=64 float=64 double=64 bits
	Long double=128 bits
	Char pointers = 64 bits
	Int pointers = 64 bits
/* T3D/T3E (CRAYMPP) native number sizes:
	Char = 8 bits, unsigned
	Short=32 int=64 long=64 float=32 double=64 bits
	Long double=64 bits
	Char pointers = 64 bits
	Int pointers = 64 bits
	Big endian, IEEE floating point
/* IA64 (IA64) native number sizes:
	Char = 8 bits, signed
	Short=16 int=32 long=64 float=32 double=64 bits
	Long double=64 bits
	Char pointers = 64 bits
	Int pointers = 64 bits
	Little endian, IEEE floating point

#if !defined(UNICOS)
#    define SIZE_NFLOAT32    4
#    define SIZE_NFLOAT64    8
#    define SIZE_NFLOAT128  16  /* No current plans for support

#    define SIZE_NINT8       1
#    define SIZE_NUINT8      1
#if defined(CRAYMPP)
#    define SIZE_NINT16      4
#    define SIZE_NUINT16     4
#else
#    define SIZE_NINT16      2
#    define SIZE_NUINT16     2
#endif
#    define SIZE_NINT32      4
#    define SIZE_NUINT32     4
#    define SIZE_NINT64      8
#    define SIZE_NUINT64     8
#    define SIZE_NINT128    16  /* No current plans for support
#    define SIZE_NUINT128   16  /* No current plans for support

#    define SIZE_NCHAR8      1
#    define SIZE_NCHAR       1  /* For backward compat char8 == char
#    define SIZE_NUCHAR8     1
#    define SIZE_NUCHAR      1  /* For backward compat uchar8 == uchar
#if defined(CRAYMPP)
#    define SIZE_NCHAR16     4  /* No current plans for support
#    define SIZE_NUCHAR16    4  /* No current plans for support
#else
#    define SIZE_NCHAR16     2  /* No current plans for support
#    define SIZE_NUCHAR16    2  /* No current plans for support
#endif
#else  /* !!!!!! SOMEBODY NEEDS TO CHECK THESE !!!!!
#    define SIZE_NFLOAT32    8
#    define SIZE_NFLOAT64    8
#    define SIZE_NFLOAT128  16  /* No current plans for support

#    define SIZE_NINT8       1
#    define SIZE_NUINT8      1
#    define SIZE_NINT16      8
#    define SIZE_NUINT16     8
#    define SIZE_NINT32      8
#    define SIZE_NUINT32     8
#    define SIZE_NINT64      8
#    define SIZE_NUINT64     8
#    define SIZE_NINT128    16  /* No current plans for support
#    define SIZE_NUINT128   16  /* No current plans for support
#    define SIZE_NCHAR8      1
#    define SIZE_NCHAR       1
#    define SIZE_NCHAR       1  /* For backward compat char8 == char
#    define SIZE_NUCHAR8     1
#    define SIZE_NUCHAR      1  /* For backward compat uchar8 == uchar
#    define SIZE_NCHAR16     2  /* No current plans for support
#    define SIZE_NUCHAR16    2  /* No current plans for support
#endif /* UNICOS */

/* then the sizes of little-endian number types
#    define SIZE_LFLOAT32    4
#    define SIZE_LFLOAT64    8
#    define SIZE_LFLOAT128  16  /* No current plans for support

#    define SIZE_LINT8       1
#    define SIZE_LUINT8      1
#    define SIZE_LINT16      2
#    define SIZE_LUINT16     2
#    define SIZE_LINT32      4
#    define SIZE_LUINT32     4
#    define SIZE_LINT64      8
#    define SIZE_LUINT64     8
#    define SIZE_LINT128    16  /* No current plans for support
#    define SIZE_LUINT128   16  /* No current plans for support

#    define SIZE_LCHAR8      1
#    define SIZE_LCHAR       1  /* For backward compat char8 == char
#    define SIZE_LUCHAR8     1
#    define SIZE_LUCHAR      1  /* For backward compat uchar8 == uchar
#    define SIZE_LCHAR16     2  /* No current plans for support
#    define SIZE_LUCHAR16    2  /* No current plans for support

    /* sizes of different number types
#       define MACHINE_I8_SIZE     1
#       define MACHINE_I16_SIZE    2
#       define MACHINE_I32_SIZE    4
#       define MACHINE_F32_SIZE    4
#       define MACHINE_F64_SIZE    8

    /* maximum size of the atomic data types
#       define MAX_NT_SIZE      16
#endif /* _HNT_H */

/* -----------  Reserved classes and names for vdatas/vgroups -----

/* The names of the Vgroups created by the GR interface, from mfgr.h
#define GR_NAME "RIG0.0"          /* name of the Vgroup containing all the images
#define RI_NAME "RI0.0"           /* name of a Vgroup containing information about one image
#define RIGATTRNAME  "RIATTR0.0N" /* name of a Vdata containing an   attribute
#define RIGATTRCLASS "RIATTR0.0C" /* class of a Vdata containing an   attribute

/* Vdata and Vgroup attributes use the same class as that of SD attr,
 *  _HDF_ATTRIBUTE  "Attr0.0"  8/1/96

/* classes of the Vdatas/Vgroups created by the SD interface, from local_nc.h
#define _HDF_ATTRIBUTE         "Attr0.0"  /* class of a Vdata containing SD interface attribute
#define _HDF_VARIABLE          "Var0.0"   /* class of a Vgroup representing an SD NDG
#define _HDF_DIMENSION         "Dim0.0"   /* class of a Vgroup representing an SD dimension
#define _HDF_UDIMENSION        "UDim0.0"  /* class of a Vgroup representing an SD UNLIMITED dimension
#define DIM_VALS          "DimVal0.0"     /* class of a Vdata containing an SD dimension size and fake values
#define DIM_VALS01        "DimVal0.1"     /* class of a Vdata containing an SD dimension size

#define _HDF_CDF               "CDF0.0"

/* DATA is defined in DTM. Change DATA to DATA0
#define DATA              "Data0.0"
#define DATA0             "Data0.0"
#define ATTR_FIELD_NAME   "VALUES"

/* The following vdata class name is reserved by the Chunking interface.
   originally defined in 'hchunks.h'. The full class name
   currently is "_HDF_CHK_TBL_0". -GV 9/25/97
#ifdef   _HCHUNKS_MAIN /* Private to 'hchunks.c'
#define _HDF_CHK_TBL_CLASS "_HDF_CHK_TBL_" /* 13 bytes
#define _HDF_CHK_TBL_CLASS_VER  0          /* zero version number for class
#endif /* _HCHUNKS_MAIN_

/* ------------  pre-defined attribute names ----------------
/* For MFGR interface
#define FILL_ATTR    "FillValue"   /* name of an attribute containing the fill value

/* For SD interface
#define _FillValue      "_FillValue" /* name of an attribute to set fill value for an SDS
#define _HDF_LongName CDM.LONG_NAME /* data/dimension label string
#define _HDF_Units    CDM.UNITS     /* data/dimension unit string
#define _HDF_Format   "format"    /* data/dimension format string
#define _HDF_CoordSys "coordsys"  /* data coordsys string
#define _HDF_ValidRange     "valid_range" /* valid range of data values
#define _HDF_ScaleFactor    "scale_factor" /* data calibration factor
#define _HDF_ScaleFactorErr "scale_factor_err" /* data calibration factor error
#define _HDF_AddOffset      "add_offset" /* calibration offset
#define _HDF_AddOffsetErr   "add_offset_err" /*  calibration offset error
#define _HDF_CalibratedNt   "calibrated_nt"  /* data type of uncalibrated data
#define _HDF_ValidMax       "valid_max"
#define _HDF_ValidMin       "valid_min"
#define _HDF_Remarks        "remarks"        /* annotation, by DFAN
#define _HDF_AnnoLabel      "anno_label"     /* annotation label, by DFAN */


package thredds.server.catalog.tracker;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.*;

/**
 * Track the list of catalogs.
 * Used to check if any have changed, without having to read the catalog tree.
 *
 * @author John
 * @since 6/22/2015
 */
public class CatalogTracker {
  static private final Logger logger = LoggerFactory.getLogger(CatalogTracker.class);

  private static final String dbname = "/catTracker.dat";
  private final String filepath;
  private final int expectedSize;
  private long nextCatId;
  private Map<String, CatalogExt> catalogs;
  private boolean changed;

  public CatalogTracker(String pathname, boolean startOver, int expectedSize, long nextCatId) {
    this.filepath = pathname + dbname;
    this.expectedSize = expectedSize > 0 ? expectedSize : 100;
    this.nextCatId = nextCatId;

    File file = new File(filepath);
    if (startOver) reinit();
    if (!file.exists() || startOver || readCatalogs() <= 0) {
      changed = true;
      catalogs = new HashMap<>(2*expectedSize);
    }
  }

  private void reinit() {
    File file = new File(filepath);
    if (file.exists()) {
       boolean wasDeleted = file.delete();
       if (!wasDeleted) {
         throw new IllegalStateException("DatasetTrackerMapDB not able to delete "+ filepath);
       }
     }
    changed = true;
    catalogs = new HashMap<>(2*expectedSize);
  }

  public long put(CatalogExt cat) {
    changed = true;
    if (cat.setCatId(nextCatId))
      nextCatId++;
    catalogs.put(cat.getCatRelLocation(), cat);
    return cat.getCatId();
  }

  public CatalogExt get(String path) {
    return catalogs.get(path);
  }

  public CatalogExt removeCatalog(String catPath) {
    changed = true;
    return catalogs.remove( catPath);
  }

    // return sorted catalogs
  public Iterable<? extends CatalogExt> getCatalogs() {
    if (catalogs == null) readCatalogs();

    List<CatalogExt> result = new ArrayList<>();
    for (CatalogExt ext : catalogs.values())
      result.add(ext);
    Collections.sort(result, (o1, o2) -> o1.getCatRelLocation().compareTo(o2.getCatRelLocation()));    // java 8 lambda, baby
    return result;
  }

  private int readCatalogs() {
    catalogs = new HashMap<>();
    int count = 0;
    try (DataInputStream in = new DataInputStream(new FileInputStream(filepath))) {
      while (in.available() > 0) {
        CatalogExt ext = new CatalogExt();
        ext.readExternal(in);
        catalogs.put(ext.getCatRelLocation(), ext);
        count++;
      }

    } catch (IOException e) {
      logger.error("read "+filepath, e);
      return 0;
    }
    return count;
  }

  public void save() throws IOException {
    if (!changed) return;
    try (DataOutputStream out = new DataOutputStream(new FileOutputStream(filepath))) {
      for (CatalogExt ext : catalogs.values()) {
        ext.writeExternal(out);
      }
    }
    changed = false;
  }

  public int size() {
    return catalogs.size();
  }

  public long getNextCatId() {
    return nextCatId;
  }
}


/* Copyright 2012, UCAR/Unidata.
   See the LICENSE file for more information.
*/

package dap4.servlet;

import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import dap4.dap4lib.XURI;

import java.io.*;
import java.net.URISyntaxException;
import java.util.*;

import dap4.servlet.URLMap.Result;

/**
 * Define a URL map that assumes that there some prefix
 * of the urlpath that can be replaced by a path prefix
 * and that everything else is the same.
 * The mapping is carried out from a properties file
 * that has lines of the form <url path prefix>=<file path prefix>
 */
public class URLMapDefault implements URLMap
{

    //////////////////////////////////////////////////
    // Instance Variables

    SortedMap<String, String> url2path = new TreeMap<String, String>();
    SortedMap<String, String> path2url = new TreeMap<String, String>();

    //////////////////////////////////////////////////
    // Constructor(s)

    public URLMapDefault()
    {
    }

    //////////////////////////////////////////////////
    // Accessors

    @Override
    public void addEntry(String urlpath, String fileprefix)
            throws DapException
    {
        // Canonicalize the urlpath
        String urlprefix = DapUtil.canonicalpath(urlpath);
        // Canonicalize the file path
        fileprefix = DapUtil.canonicalpath(fileprefix);
        url2path.put(urlprefix, fileprefix); // overwrite
        path2url.put(fileprefix, urlprefix); // overwrite
    }

    //////////////////////////////////////////////////
    // File loader

    public void load(String filepath)
            throws IOException
    {
        String[] lines;
        try (InputStream is = new FileInputStream(filepath);) {
            String content = DapUtil.readtextfile(is);
            lines = content.split("[\n]");
        }
        for(String line : lines) {
            String[] pieces = line.split("[=]");
            if(pieces.length != 2)
                throw new IOException("File: " + filepath + "; malformed line: " + line);
            addEntry(pieces[0], pieces[1]);
        }
    }

    //////////////////////////////////////////////////
    // URLMap API

    @Override
    public Result
    mapURL(String urlpath)
            throws DapException
    {
        // Canonicalize the urlpath
        urlpath = DapUtil.canonicalpath(urlpath);
        Result result = longestmatch(url2path, urlpath);
        return result;
    }

    @Override
    public Result
    mapPath(String path)
            throws DapException
    {
        // canonicalize path
        path = DapUtil.canonicalpath(path);
        Result result = longestmatch(path2url, path);
        return result;
    }

    protected Result
    longestmatch(SortedMap<String, String> map, String prefix)
    {
        Result result = new Result();
        List<String> matches = new ArrayList<>();
        for(Map.Entry<String, String> entry : map.entrySet()) {
            if(prefix.startsWith(entry.getKey()))
                matches.add(entry.getKey());
        }
        String longestmatch = null;
        if(map.get(prefix) != null)
            longestmatch = prefix;
        for(int i = 0; i < matches.size(); i++) {
            String candidate = matches.get(i);
            if(prefix.startsWith(candidate)) {
                if(longestmatch == null || candidate.length() > longestmatch.length())
                    longestmatch = candidate;
            }
        }
        if(longestmatch != null)
            result.prefix = map.get(longestmatch);
        else
            return null;

        /*
        // lastKey returns everything less than path, but
        // we need less-or-equal, so we have to do a separate
        // check for exact match
        result.prefix = map.get(prefix);
        if(result.prefix == null) {
            SortedMap<String, String> submap = map.headMap(prefix);
            if(submap.size() == 0)
                return null; // prefix is not here in any form
            longestmatch = submap.lastKey();
            result.prefix = (String) submap.get(longestmatch);
        } */
        result.suffix = prefix.substring(longestmatch.length());
        if(result.prefix.endsWith("/"))
            result.prefix = result.prefix.substring(0,result.prefix.length()-1);
        if(result.suffix.startsWith("/"))
            result.suffix = result.suffix.substring(1);
        return result;
    }

    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        for(String url : url2path.keySet()) {
            buf.append(url + " <=> " + url2path.get(url));
        }
        return buf.toString();
    }
}


package thredds.server.catalog.tracker;

import net.openhft.chronicle.map.ChronicleMap;
import net.openhft.chronicle.map.ChronicleMapBuilder;
import org.jdom2.Element;
import org.jdom2.output.XMLOutputter;
import thredds.client.catalog.Access;
import thredds.client.catalog.Dataset;
import thredds.client.catalog.ServiceType;
import thredds.server.catalog.DatasetScan;
import thredds.server.catalog.FeatureCollectionRef;

import java.io.File;
import java.io.IOException;
import java.util.Formatter;
import java.util.Map;

/**
 * DatasetTracker using ChronicleMap
 *
 * @author John
 * @since 6/8/2015
 */
public class DatasetTrackerChronicle implements DatasetTracker {
  static private org.slf4j.Logger catalogInitLog = org.slf4j.LoggerFactory.getLogger("catalogInit");
  static private final String datasetName = "/chronicle.datasets.dat";
  // average size (bytes) of key for database, which is the path to a given dataset.
  // LOOK: is 512 a good average size? There is no length on file path, so hard to set a maximum.
  static private final int averagePathLength = 512;

  // delete old databases
  public static void cleanupBefore(String pathname, long trackerNumber) {
    for (long tnum = trackerNumber - 1; tnum > 0; tnum--) {
      File oldDatabaseFile = new File(pathname + datasetName + "." + tnum);
      if (!oldDatabaseFile.exists()) break;
      if (oldDatabaseFile.delete()) {
        catalogInitLog.info("DatasetTrackerChronicle deleted {} ", oldDatabaseFile.getAbsolutePath());
      } else {
        catalogInitLog.error("DatasetTrackerChronicle not able to delete {} ", oldDatabaseFile.getAbsolutePath());
      }
    }
  }

  private boolean alreadyExists;
  private boolean changed;
  private File dbFile;
  private long maxDatasets;
  private ChronicleMap<String, DatasetExt> datasetMap;

  public DatasetTrackerChronicle(String pathname, long maxDatasets, long number) {
    dbFile = new File(pathname + datasetName + "." + number);
    alreadyExists = dbFile.exists();
    this.maxDatasets = maxDatasets;

    try {
      open();
      catalogInitLog.info("DatasetTrackerChronicle opened success on '" + dbFile.getAbsolutePath() + "'");

    } catch (Throwable e) {
      catalogInitLog.error("DatasetTrackerChronicle failed on '" + dbFile.getAbsolutePath() + "', delete catalog cache and reload ", e);
      reinit();
    }
  }

  public void save() throws IOException {
    if (changed) {
      System.out.printf("datasetMap was saved%n");
      datasetMap.close();
      open();
    }
  }

  public void close() throws IOException {
    if (datasetMap != null) {
      datasetMap.close();
      System.out.printf("datasetMap.close() was called%n");
      datasetMap = null;
    }
  }

  public boolean exists() {
    return alreadyExists;
  }

  public boolean reinit() {
    if (dbFile.exists()) {
      boolean wasDeleted = dbFile.delete();
      if (!wasDeleted) {
        catalogInitLog.error("DatasetTrackerChronicle not able to delete {} ", dbFile.getAbsolutePath());
        return false;
      }
    }

    try {
      open();
      alreadyExists = false;
      return true;

    } catch (Throwable e) {
      catalogInitLog.error("DatasetTrackerChronicle failed on '" + dbFile.getAbsolutePath() + "', delete catalog cache and reload ", e);
      return false;
    }
  }

  private void open() throws IOException {
    ChronicleMapBuilder<String, DatasetExt> builder = ChronicleMapBuilder.of(String.class, DatasetExt.class)
            .averageValueSize(200).entries(maxDatasets).averageKeySize(averagePathLength);
    datasetMap = builder.createPersistedTo(dbFile);
    changed = false;
  }

  public boolean trackDataset(long catId, Dataset dataset, DatasetTracker.Callback callback) {
    if (callback != null) {
      callback.hasDataset(dataset);
      boolean track = false;
      if (dataset.getRestrictAccess() != null) {
        callback.hasRestriction(dataset);
        track = true;
      }
      if (dataset.getNcmlElement() != null) {
        callback.hasNcml(dataset);
        track = true;
      }
      if (track) callback.hasTrackedDataset(dataset);
    }

    boolean hasRestrict = dataset.getRestrictAccess() != null;
    boolean hasNcml = (dataset.getNcmlElement() != null) && !(dataset instanceof DatasetScan) && !(dataset instanceof FeatureCollectionRef);
    if (!hasRestrict && !hasNcml) return false;

    String path = null;
    if (dataset instanceof DatasetScan) {
      path = ((DatasetScan) dataset).getPath();

    } else if (dataset instanceof FeatureCollectionRef) {
      path = ((FeatureCollectionRef) dataset).getPath();

    } else { // regular dataset
      for (Access access : dataset.getAccess()) {
        ServiceType st = access.getService().getType();
        if (st == null || !st.isStandardTdsService()) // skip non-TDS services
          continue;

        String accessPath = access.getUrlPath();
        if (accessPath == null) {
          catalogInitLog.warn("trackDataset {} access {} has null path", dataset, access);
          continue;
        }

        if (path == null) path = accessPath;
        else if (!path.equals(accessPath)) { //LOOK must put all for restrict
          System.out.printf(" paths differ: %s%n %s%n%n", path, accessPath);
          catalogInitLog.warn(" paths differ: {} != {}", path, accessPath);
        }
      }
      // if this is a regular dataset which uses default services, those services are not
      // part of the configuration catalog, and thus the above check will fail due to
      // a lack of access methods. Get path from the urlPath of the dataset.
      if (path == null) {
        path = dataset.getUrlPath();
      }
    }

    if (path == null) {
      catalogInitLog.debug("trackDataset {} has null path", dataset);
      return false;
    }

    String ncml = null;
    if (hasNcml) {
      // want the ncml string representation
      Element ncmlElem = dataset.getNcmlElement();
      XMLOutputter xmlOut = new XMLOutputter();
      ncml = xmlOut.outputString(ncmlElem);
    }

    // changed = true;
    DatasetExt dsext = new DatasetExt(catId, dataset.getRestrictAccess(), ncml);
    datasetMap.put(path, dsext);
    changed = true;
    return true;
  }

  public String findResourceControl(String path) {
    DatasetExt dext = datasetMap.get(path);
    if (dext == null) return null;
    return dext.getRestrictAccess();
  }

  public String findNcml(String path) {
    DatasetExt dext = datasetMap.get(path);
    if (dext == null) return null;
    return dext.getNcml();
  }

  @Override
  public void showDB(Formatter f) {
    f.format("ChronicleMap %s%n", dbFile.getPath());
    int count = 0;
    for (Map.Entry<String, DatasetExt> entry : datasetMap.entrySet()) {
      f.format("%4d: '%s' == %s%n", count++, entry.getKey(), entry.getValue());
      if (count % 10 == 0) f.format("%n");
    }
  }

}