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

package dap4.core.util;

import dap4.core.dmr.DapDimension;

import java.util.ArrayList;
import java.util.List;

/**
 * A Slice is used for two purposes
 * <ol>
 * <li> To specify a subset of data to extract.
 * <li> To specify a subset of data as specified in a constraint.
 * </ol>
 * In case two, the "last" value of the slice may be undefined
 * (e.q. specifying [0:]) and so they must be filled in at some point.
 * Note that first cannot be undefined because it always defaults to zero;
 * similarly stride defaults to one.
 * Slice Supports iteration.
 * Modifiied 10/15/2016 to support zero sized slices.
 */

public class Slice
{

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

    // Define a constant to indicate the slice is undefined (i.e.
    // its "last" value is the UNDEFINED value).
    // This will primarily be used in the constraint expression parser

    //static public final long UNDEFINED = 0x7FFFFFFFFFFFFFFFL;
    static public final long UNDEFINED = -1L;

    // Define maximum legal dimension based on the spec

    static public final long MAXLENGTH = 0x3FFFFFFFFFFFFFFFL;

    static public enum Sort
    {
        Single, Multi;
    }

    // Define a set of slices indicating the canonical scalar set
    static public List<Slice> SCALARSLICES;
    static public Slice SCALARSLICE;

    static {
        try {
            SCALARSLICE = new Slice(0, 1, 1, 1).finish();
            SCALARSLICES = new ArrayList<Slice>();
            SCALARSLICES.add(SCALARSLICE);
        } catch (DapException de) {
            SCALARSLICES = null;
        }
    }

    //////////////////////////////////////////////////
    // Instance variables

    // Is this a Slice or a Multislice
    protected Sort sort = Sort.Single;

    /**
     * First index
     */
    long first = UNDEFINED;

    /**
     * Last+1 index; (stop - first) should be size of the slice.
     */
    long stop = UNDEFINED;

    /**
     * Stride
     */
    long stride = UNDEFINED;

    /**
     * Max size  (typically the size of the underlying dimension)
     */
    long maxsize = MAXLENGTH;

    /**
     * Indicate if this is known to be a whole slice
     * Boolean.TRUE => yes
     * Boolean.FALSE => no
     * null => unknown
     */
    Boolean whole = null;

    /**
     * Indicate that this slice's first/last/stride were
     * specifically set; typically from a constraint.
     * Defaults to true.
     */
    protected boolean constrained = true;

    //////////////////////////////////////////////////
    // Constructors

    public Slice()
    {
    }

    public Slice(long first, long stop, long stride)
            throws DapException
    {
        this(first, stop, stride, UNDEFINED);
    }

    public Slice(long first, long stop, long stride, long maxsize)
            throws DapException
    {
        this();
        setIndices(first, stop, stride, maxsize);
    }

    public Slice(Slice s)
            throws DapException
    {
        this();
        setIndices(s.getFirst(), s.getStop(), s.getStride(), s.getMax());
        setConstrained(s.isConstrained());
        setWhole(s.isWhole());
    }

    public Slice(DapDimension dim)
            throws DapException
    {
        this();
        setIndices(0, dim.getSize(), 1, dim.getSize());
        setWhole(true);
        setConstrained(false);
    }

    //////////////////////////////////////////////////
    // Slice specific API

    /**
     * Perform sanity checks on a slice and repair where possible.
     *
     * @return this (fluent interface)
     * @throws dap4.core.util.DapException if slice is malformed
     */
    public Slice
    finish()
            throws DapException
    {
        // Attempt to repair undefined values
        if(this.first == UNDEFINED) this.first = 0;   // default
        if(this.stride == UNDEFINED) this.stride = 1; // default
        if(this.stop == UNDEFINED && this.maxsize != UNDEFINED)
            this.stop = this.maxsize;
        if(this.stop == UNDEFINED && this.maxsize == UNDEFINED)
            this.stop = this.first + 1;
        if(this.maxsize == UNDEFINED && this.stop != UNDEFINED)
            this.maxsize = this.stop;
        // else (this.stop != UNDEFINED && this.maxsize != UNDEFINED)
        assert (this.first != UNDEFINED);
        assert (this.stride != UNDEFINED);
        assert (this.stop != UNDEFINED);
        // sanity checks
        if(this.first > this.maxsize)
            throw new DapException("Slice: first index > max size");
        if(this.stop > (this.maxsize+1))
            throw new DapException("Slice: stop > max size");
        if(this.first < 0)
            throw new DapException("Slice: first index < 0");
        if(this.stop < 0)
            throw new DapException("Slice: stop index < 0");
        if(this.stride <= 0)
            throw new DapException("Slice: stride index <= 0");
        if(this.first > this.stop)
            throw new DapException("Slice: first index > last");
        return this; // fluent interface
    }

    public SliceIterator
    iterator()
    {
        return new SliceIterator(this);
    }

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

    public Sort getSort()
    {
        return this.sort;
    }

    public long getFirst()
    {
        return this.first;
    }

    public long getStop()
    {
        return this.stop;
    }

    public long getLast()
    {
        return ((this.stop - this.first) == 0 ? 0 : this.stop - 1);
    }

    public long getStride()
    {
        return this.stride;
    }

    public long getSize() // not same as getcount and not same as maxsize
    {
        return (this.stop - this.first);
    }

    public long getMax() // not same as getcount and not same as maxsize
    {
        return this.maxsize;
    }

    public Slice setMaxSize(long size)
            throws DapException
    {
        return setIndices(this.first, this.stop, this.stride, size);
    }

    public Slice setIndices(long first, long stop, long stride)
            throws DapException
    {
        return setIndices(first, stop, stride, UNDEFINED);
    }

    public Slice setIndices(long first, long stop, long stride, long maxsize)
            throws DapException
    {
        this.first = first;
        this.stop = stop;
        this.stride = stride;
        this.maxsize = maxsize;
        return this.finish(); // fluent interface
    }

    public Boolean isWhole()
    {
        return whole;
    }

    public Slice setWhole(Boolean tf)
    {
        whole = tf;
        return this; // fluent interface
    }

    public Boolean isConstrained()
    {
        return constrained;
    }

    public Slice setConstrained(Boolean tf)
    {
        constrained = tf;
        return this; // fluent interface
    }

    //////////////////////////////////////////////////
    // Misc. methods

    /**
     * Compute the number of elements in
     * the slice. Note that this is different from
     * getStop() because stride is taken into account.
     */
    public long
    getCount()
    {
        assert this.first != UNDEFINED && this.stride != UNDEFINED && this.stop != UNDEFINED;
        long count = (this.stop) - this.first;
        count = (count + this.stride - 1);
        count /= this.stride;
        return count;
    }

    /**
     * Compare two slices for equality
     *
     * @param o the other slice to compare with
     * @return true if this and other are the same, false otherwise
     */
    @Override
    public boolean equals(Object o)
    {
        if(!(o instanceof Slice)) return false;
        Slice other = (Slice) o;
        if(other == this) return true;
        return other.getFirst() == this.getFirst()
                && other.getStop() == this.getStop()
                && other.getStride() == this.getStride();
    }

    @Override
    public int hashCode()
    {
        return (int) (getFirst() << 20 | getStop() << 10 | getStride());
    }

    @Override
    public String toString()
    {
        return toString(true);
    }

    public String toString(boolean withbrackets)
    {
        StringBuilder buf = new StringBuilder();
        if(withbrackets)
            buf.append("[");
        if((this.stop - this.first) == 0)
            buf.append("0");
        else if(this.stride == 1)
            buf.append(String.format("%d:%d", this.first, this.stop - 1));
        else
            buf.append(String.format("%d:%d:%d", this.first, this.stride, this.stop - 1));
        buf.append(String.format("|%d", (this.stop - this.first)));
        if(withbrackets)
            buf.append("]");
        return buf.toString();
    }

    /**
     * Convert this slice to a string
     * suitable for use in a constraint
     *
     * @return constraint usable string
     * @throws DapException
     */
    public String toConstraintString()
            throws DapException
    {
        assert this.first != UNDEFINED && this.stride != UNDEFINED && this.stop != UNDEFINED;
        if((this.stop - this.first) == 0) {
            return String.format("[0]");
        } else if(this.stride == 1) {
            if((this.stop - this.first) == 1)
                return String.format("[%d]", this.first);
            else
                return String.format("[%d:%d]", this.first, this.stop - 1);
        } else
            return String.format("[%d:%d:%d]", this.first, this.stride, this.stop - 1);
    }

    // Static Utilities

    /**
     * Take two slices and compose src wrt target
     * Assume neither argument is null. This code
     * should match ucar.ma2.Section in thredds
     * and dceconstraint.c in the netcdf-c library.
     *
     * @param target
     * @param src
     * @return new, composed Range
     * @throws DapException
     */
    static public Slice
    compose(Slice target, Slice src)
            throws DapException
    {
        long sr_stride = target.getStride() * src.getStride();
        long sr_first = MAP(target, src.getFirst());
        long lastx = MAP(target, src.getLast());
        long sr_last = (target.getLast() < lastx ? target.getLast() : lastx); //min(last(),lastx)
        return new Slice(sr_first, sr_last + 1, sr_stride, sr_last + 1).finish();
    }

    /**
     * Map ith element of one range wrt a target range
     *
     * @param target the target range
     * @param i      index of the element
     * @return the i-th element of a range
     * @throws DapException if index is invalid
     */
    static long
    MAP(Slice target, long i)
            throws DapException
    {
        if(i < 0)
            throw new DapException("Slice.compose: i must be >= 0");
        if(i > target.getStop())
            throw new DapException("i must be < stop");
        return target.getFirst() + i * target.getStride();
    }

    public List<Slice>
    getSubSlices()
    {
        List<Slice> list = new ArrayList<>();
        list.add(this);
        return list;
    }

    public Slice
    getSubSlice(int i)
    {
        if(i != 0)
            throw new IndexOutOfBoundsException();
        return this;
    }

}


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

package dap4.core.util;

import java.io.*;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Arrays;

abstract public class DapDump
{
    //////////////////////////////////////////////////
    // Provide a simple dump of binary data
    // (Static method)

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

    static int MAXLIMIT = 20000;
    //////////////////////////////////////////////////
    // Provide a simple dump of binary data

    static public void
    dumpbytes(ByteBuffer buf0, boolean skipdmr)
    {
        int savepos = buf0.position();
        int limit0 = buf0.limit();
        int skipcount = 0;
        if(limit0 > MAXLIMIT) limit0 = MAXLIMIT;
        if(limit0 >= buf0.limit()) limit0 = buf0.limit();
        if(skipdmr) {
            ByteOrder saveorder = buf0.order();
            buf0.order(ByteOrder.BIG_ENDIAN); // must read in network order
            skipcount = buf0.getInt(); //dmr count
            buf0.order(saveorder);
            skipcount &= 0xFFFFFF; // mask off the flags to get true count
            skipcount += 4; // skip the count also
        }
        byte[] bytes = new byte[(limit0 + 8) - skipcount];
        Arrays.fill(bytes, (byte) 0);
        buf0.position(savepos + skipcount);
        buf0.get(bytes, 0, limit0 - skipcount);
        buf0.position(savepos);

        System.err.println("order="+buf0.order());

        ByteBuffer buf = ByteBuffer.wrap(bytes).order(buf0.order());
        dumpbytes(buf);
    }

    /**
     * Dump the contents of a buffer from 0 to position
     *
     * @param buf0 byte buffer to dump
     */
    static public void
    dumpbytes(ByteBuffer buf0)
    {
        int stop = buf0.limit();
        int size = stop + 8;
        int savepos = buf0.position();
        assert savepos == 0;
        byte[] bytes = new byte[size];
        Arrays.fill(bytes, (byte) 0);
        buf0.get(bytes,0,stop);
        buf0.position(savepos);
        ByteBuffer buf = ByteBuffer.wrap(bytes).order(buf0.order());
        buf.position(0);
        buf.limit(size);
        int i = 0;
        try {
            for(i = 0; buf.position() < stop; i++) {
                savepos = buf.position();
                int iv = buf.getInt();
                buf.position(savepos);
                long lv = buf.getLong();
                buf.position(savepos);
                short sv = buf.getShort();
                buf.position(savepos);
                byte b = buf.get();
                int ub = ((int)b) & 0x000000FF;
                long uiv = ((long) iv) & 0xFFFFFFFFL;
                int usv = ((int) sv) & 0xFFFF;
                int ib = (int) b;
                char c = (char) ub;
                String s = Character.toString(c);
                if(c == '\r') s = "\\r";
                else if(c == '\n') s = "\\n";
                else if(c < ' ' || c >= 0x7f) s = "?";
                System.err.printf("[%03d] %02x %03d %4d '%s'", i, ub, ub, ib, s);
                System.err.printf("\t%12d 0x%08x", iv, uiv);
                System.err.printf("\t%5d\t0x%04x", sv, usv);
                System.err.println();
                System.err.flush();
            }

        } catch (Exception e) {
            System.err.println("failure:" + e);
        } finally {
            System.err.flush();
            //new Exception().printStackTrace(System.err);
	        System.err.flush();
        }
    }

    static public void
    dumpbytestream(OutputStream stream, ByteOrder order, String tag)
    {
        if(stream instanceof ByteArrayOutputStream) {
            byte[] content = ((ByteArrayOutputStream) stream).toByteArray();
            dumpbytestream(content, order, tag);
        }
    }

    static public void
    dumpbytestream(ByteBuffer buf, ByteOrder order, String tag)
    {
        dumpbytestream(buf.array(),0,buf.position(),order,tag);
    }

    static public void
    dumpbytestream(byte[] content, ByteOrder order, String tag)
    {
        dumpbytestream(content,0,content.length,order,tag);
    }

    static public void
    dumpbytestream(byte[] content, int start, int len, ByteOrder order, String tag)
    {
        System.err.println("++++++++++ " + tag + " ++++++++++ ");
        ByteBuffer tmp = ByteBuffer.wrap(content).order(order);
        tmp.position(start);
        tmp.limit(len);
        DapDump.dumpbytes(tmp);
        System.err.println("++++++++++ " + tag + " ++++++++++ ");
        System.err.flush();
    }

}


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

import javax.annotation.concurrent.Immutable;
import java.util.Collection;
import java.util.Map;

/**
 * Type-safe enumeration of Earth Ellipsoids. Follows EPSG.
 *
 * @author john caron
 * @see http://www.epsg.org/
 * @see "http://www.epsg.org/"
 */
@Immutable
public class EarthEllipsoid extends Earth {

  // must be done first, as other static fields depend on it
  private static final Map<String, EarthEllipsoid> hash = new java.util.LinkedHashMap<>(10);

  /**
   * Ellipsoid for WGS84 (edavis - correct 1/f as per EPSG database ver 6.14)
   */
  public static final EarthEllipsoid WGS84 = new EarthEllipsoid("WGS84", 7030, 6378137.0, 298.257223563);

  /**
   * Airy 1830 ellipsoid from EPSG database version 6.14.
   */
  public static final EarthEllipsoid Airy1830 = new EarthEllipsoid("Airy 1830", 7001, 6377563.396, 299.3249646);

  // From Grib1: IAU in 1965 (6378.160 km, 6356.775 km, f = 1/297.0)
  // From Grib2: 2 oblate spheroid with size as determined by IAU in 1965 (major axis = 6 378 160.0 m, minor axis = 6 356 775.0 m, f = 1/297.0)
  public static final EarthEllipsoid IAU = new EarthEllipsoid("IAU 1965", -1, 6378160.0, 297.0);

  // From Grib2: 4: oblate spheroid as defined in IAG-GRS80 model (major axis = 6 378 137.0 m, minor axis = 6 356 752.314 m, f = 1/298.257 222 101)
  public static final EarthEllipsoid IAG_GRS80 = new EarthEllipsoid("IIAG-GRS80", -1, 6378137.0, 298.257222101);

  /**
   * get a collection of all defined EarthEllipsoid objects
   *
   * @return all defined EarthEllipsoid objects
   */
  public static Collection<EarthEllipsoid> getAll() {
    return hash.values();
  }

  /**
   * Find the EarthEllipsoid that matches this name.
   *
   * @param name : name to match
   * @return EarthEllipsoid or null if no match.
   */
  public static EarthEllipsoid getType(String name) {
    if (name == null)
      return null;
    return hash.get(name);
  }

  /**
   * Find the EarthEllipsoid that matches this EPSG Id.
   *
   * @param epsgId : epsg Id to match
   * @return EarthEllipsoid or null if no match.
   */
  public static EarthEllipsoid getType(int epsgId) {
    Collection<EarthEllipsoid> all = getAll();
    for (EarthEllipsoid ellipsoid : all) {
      if (ellipsoid.epsgId == epsgId) {
        return ellipsoid;
      }
    }
    return null;
  }

  ///////////////////////////////////////////////////////////
  private final int epsgId;

  /**
   * Constructor.
   *
   * @param name   EPSG name
   * @param epsgId EPSG id
   * @param a      semimajor (equatorial) radius, in meters.
   * @param invF   inverse flattening.
   */
  private EarthEllipsoid(String name, int epsgId, double a, double invF) {
    this(name, epsgId, a, 0, invF);
  }

  /**
   * Constructor.
   *
   * @param name   EPSG name
   * @param epsgId EPSG id
   * @param a      semimajor (equatorial) radius, in meters.
   * @param b      semiminor (polar) radius, in meters.
   * @param invF   inverse flattening.
   */
  public EarthEllipsoid(String name, int epsgId, double a, double b, double invF) {
    super(a, b, invF, name);
    this.epsgId = epsgId;
    hash.put(name, this);
  }

  /**
   * EPSG name
   *
   * @return the EPSG name
   */
  public String getName() {
    return name;
  }

  /**
   * EPSG id
   *
   * @return the EPSG id
   */
  public int getEpsgId() {
    return epsgId;
  }

  /**
   * Same as EPSG name
   *
   * @return the EPSG name
   */
  public String toString() {
    return name;
  }

  /**
   * Override Object.hashCode() to be consistent with this equals.
   *
   * @return hashCode
   */
  public int hashCode() {
    return name.hashCode();
  }

  /**
   * Objects with same name are equal.
   *
   * @param o test this for equals
   * @return +,0,-
   */
  public boolean equals(Object o) {
    if (this == o)
      return true;
    if (!(o instanceof EarthEllipsoid))
      return false;
    EarthEllipsoid oe = (EarthEllipsoid) o;
    return oe.name.equals(name);
  }
}


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

import com.google.protobuf.ByteString;
import ucar.ma2.*;
import ucar.nc2.iosp.IospHelper;
import ucar.nc2.util.Misc;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * DataCol message encoding.
 *
 * @author caron
 * @since 10/30/2015.
 */
public class NcStreamDataCol {

  /*
  message DataCol {
    string name = 1;      // fullname for top, shortname for member
    DataType dataType = 2;
    Section section = 3;
    bool bigend = 4;
    uint32 version = 5;
    bool isVlen = 7;
    uint32 nelems = 9;

    // oneof
    bytes primdata = 10;              // rectangular, primitive array
    repeated string stringdata = 11;  // string dataType
    repeated uint32 vlens = 12;       // isVlen true
    repeated bytes opaquedata = 13;   // opaque dataType

    // structures
    ArrayStructureCol structdata = 14;  // structure/seq dataType
  }

  message ArrayStructureCol {
    repeated DataCol memberData = 1;
  }

  primdata has nelems * sizeof(dataType) bytes, turn into multidim array of primitives with section info and bigend
  stringdata has nelems strings, turn into multidim array of String with section info
  vlens has section.size array lengths; section does not include the last (vlen) dimension; data in primdata
  opaquedata has nelems opaque objects, turn into multidim array of Opaque with section info
  structdata has nelems StructureData objects, turn into multidim array of StructureData with section info and bigend

*/

  public NcStreamProto.DataCol encodeData2(String name, boolean isVlen, Section section, Array data) {
    NcStreamProto.DataCol.Builder builder = NcStreamProto.DataCol.newBuilder();
    DataType dataType = data.getDataType();

    builder.setName(name);
    builder.setDataType(NcStream.convertDataType(data.getDataType()));
    builder.setBigend(ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN);
    builder.setVersion(NcStream.ncstream_data_version);

    if (!isVlen) {
      builder.setNelems((int) data.getSize());
      builder.setSection(NcStream.encodeSection(section));
    }

    if (isVlen) {
      builder.setIsVlen(true);
      encodeVlenData(builder, section, data);

    } else if (dataType == DataType.STRING) {
      if (data instanceof ArrayChar) { // is this possible ?
        ArrayChar cdata = (ArrayChar) data;
        for (String s : cdata)
          builder.addStringdata(s);
        Section ssection = section.removeLast();
        builder.setSection(NcStream.encodeSection(ssection));

      } else if (data instanceof ArrayObject) {
        IndexIterator iter = data.getIndexIterator();
        while (iter.hasNext())
          builder.addStringdata((String) iter.next());
      } else {
        throw new IllegalStateException("Unknown class for STRING =" + data.getClass().getName());
      }

    } else if (dataType == DataType.OPAQUE) {
      if (data instanceof ArrayObject) {
        IndexIterator iter = data.getIndexIterator();
        while (iter.hasNext()) {
          ByteBuffer bb = (ByteBuffer) iter.next();

          // Need to use duplicate so that internal state of bb isn't affected
          builder.addOpaquedata(ByteString.copyFrom(bb.duplicate()));
        }
      } else {
        throw new IllegalStateException("Unknown class for OPAQUE =" + data.getClass().getName());
      }

    } else if (dataType == DataType.STRUCTURE) {
      builder.setStructdata(encodeStructureData(data));

    } else if (dataType == DataType.SEQUENCE) {
      throw new UnsupportedOperationException("Not implemented yet SEQUENCE =" + data.getClass().getName());

    } else { // normal case
      builder.setPrimdata(copyArrayToByteString(data));
    }

    return builder.build();
  }

  void encodeVlenData(NcStreamProto.DataCol.Builder builder, Section section, Array data) {
    if (data instanceof ArrayObject) {
      IndexIterator iter = data.getIndexIterator();
      int count = 0;
      int nelems = 0;
      while (iter.hasNext()) {
        Array varray = (Array) iter.next();
        int vlensize = (int) varray.getSize();
        builder.addVlens(vlensize);
        nelems += vlensize;
        count++;
      }
      builder.setNelems(nelems);
      Section ssection = section.removeVlen();
      builder.setSection(NcStream.encodeSection(ssection));
      assert ssection.computeSize() == count;

      int nbytes = nelems * data.getDataType().getSize();
      ByteBuffer bb = ByteBuffer.allocate(nbytes);
      bb.order(ByteOrder.nativeOrder());

      iter = data.getIndexIterator();
      while (iter.hasNext()) {
        Array varray = (Array) iter.next();
        copyArrayToBB(varray, true, bb);
      }
      bb.flip();
      builder.setPrimdata(ByteString.copyFrom(bb));

      // If the vlen is rank one, the data array may just be an Array of the appropriate type, since the ArrayObject is not needed:
    } else  {
      builder.setPrimdata(copyArrayToByteString(data));

      // returning as a regular array, not vlen
      builder.setIsVlen(false);
      builder.setNelems((int) data.getSize());
      builder.setSection(NcStream.encodeSection(new Section(data.getShape())));
    }

    // otherwise WTF ?
    // throw new IllegalStateException("Unknown class for OPAQUE =" + data.getClass().getName());
  }

  public static ByteString copyArrayToByteString(Array data) {
    int nbytes = (int) data.getSizeBytes();
    if (nbytes < 0) {
      System.out.printf("copyArrayToByteString neg byte size %d dataType = %d data size %d shape = %s%n",
              nbytes, data.getDataType().getSize(), data.getSize(), Misc.showInts(data.getShape()));
    }
    ByteBuffer bb = ByteBuffer.allocate(nbytes);
    bb.order(ByteOrder.nativeOrder());
    copyArrayToBB(data, false, bb);
    bb.flip();
    return ByteString.copyFrom(bb);
  }

  public static void copyArrayToBB(Array data, boolean isVlen, ByteBuffer out) {
    IndexIterator iterA = data.getIndexIterator();

    // VLEN
    if (isVlen && data instanceof ArrayObject) {
      while (iterA.hasNext()) {
        Object inner = iterA.next();
        assert (inner instanceof Array);
        copyArrayToBB((Array) inner, isVlen, out);
      }
      return;
    }

    Class classType = data.getElementType();

    if (classType == double.class) {
      while (iterA.hasNext())
        out.putDouble(iterA.getDoubleNext());

    } else if (classType == float.class) {
      while (iterA.hasNext())
        out.putFloat(iterA.getFloatNext());

    } else if (classType == long.class) {
      while (iterA.hasNext())
        out.putLong(iterA.getLongNext());

    } else if (classType == int.class) {
      while (iterA.hasNext())
        out.putInt(iterA.getIntNext());

    } else if (classType == short.class) {
      while (iterA.hasNext())
        out.putShort(iterA.getShortNext());

    } else if (classType == char.class) {
      byte[] pa = IospHelper.convertCharToByte((char[]) data.get1DJavaArray(DataType.CHAR));
      out.put(pa, 0, pa.length);

    } else if (classType == byte.class) {
      while (iterA.hasNext())
        out.put(iterA.getByteNext());

    } else
      throw new UnsupportedOperationException("Class type = " + classType.getName());

  }

  ///////////////////////////////////////////////////////////////////////////////////////////////
  // Structures

  private class MemberData {
    StructureMembers.Member member;
    Section section;
    DataType dtype;
    boolean isVlen;
    int nelems;

    ByteBuffer bb;
    List<String> stringList;
    List<ByteString> opaqueList;
    List<Array> vlenList;
    List<Integer> vlens;
    List<MemberData> members;

    public MemberData(StructureMembers.Member member, int[] parent) {
      this.member = member;
      this.section = new Section(parent);
      try {
        int[] mshape = member.getShape();
        //if (mshape.length == 0) // scalar
        //  this.section.appendRange(Range.ONE);
        //else
        // compose with the parent
        for (int s : mshape) {
          if (s < 0) continue;
          this.section.appendRange(s);
        }
      } catch (InvalidRangeException e) {
        throw new RuntimeException(e);
      }

      this.dtype = member.getDataType();
      this.isVlen = member.isVariableLength();
      this.nelems = (int) section.computeSize();

      if (isVlen) {
        vlenList = new ArrayList<>(nelems);
      } else if (dtype == DataType.STRING) {
        stringList = new ArrayList<>(nelems * member.getSize());
      } else if (dtype == DataType.OPAQUE) {
        opaqueList = new ArrayList<>(nelems * member.getSize());

      } else if (dtype == DataType.STRUCTURE) { // LOOK not doing sequences yet
        members = new ArrayList<>();
        for (StructureMembers.Member m : member.getStructureMembers().getMembers()) {
          members.add(new MemberData(m, section.getShape()));
        }
      } else {
        bb = ByteBuffer.allocate(nelems * member.getSizeBytes());
        bb.order(ByteOrder.nativeOrder());
      }
    }

    int addVlens(Array va) {
      int total = 0;
      if (va instanceof ArrayObject) {
        while (va.hasNext()) {
          Object inner = va.next();
          assert (inner instanceof Array);
          total += addVlens((Array) inner);
        }
      } else {
        vlens.add((int) va.getSize());
        total += va.getSize();
      }
      return total;
    }

    int finishVlens() {
      vlens = new ArrayList<>(nelems*member.getSize());
      int total = 0;
      for (Array va : vlenList) {
        total += addVlens(va);
      }
      this.nelems = total;

      bb = ByteBuffer.allocate(total * member.getSizeBytes());
      bb.order(ByteOrder.nativeOrder());
      for (Array va : vlenList) {
        copyArrayToBB(va, true, bb);
      }
      return total;
    }

  }

  NcStreamProto.ArrayStructureCol.Builder encodeStructureData(Array data) {
    assert data instanceof ArrayStructure;
    ArrayStructure as = (ArrayStructure) data;
    int nelems = (int) as.getSize();

    // create MemberData to hold extracted data
    List<MemberData> memberDataList = new ArrayList<>();
    StructureMembers sm = as.getStructureMembers();
    for (StructureMembers.Member m : sm.getMembers()) {
      memberDataList.add(new MemberData(m, as.getShape()));
    }

    // data extraction
    for (int recno = 0; recno < nelems; recno++) {
      for (MemberData md : memberDataList) {
        if (md.member.isVariableLength()) {
          md.vlenList.add( as.getArray(recno, md.member));
        } else {
          extractData(as, recno, md);
        }
      }
    }

    // construct the result recursively
    return buildNestedStructureData(memberDataList);
  }

  NcStreamProto.ArrayStructureCol.Builder buildNestedStructureData(List<MemberData> mdataList) {
    NcStreamProto.ArrayStructureCol.Builder result = NcStreamProto.ArrayStructureCol.newBuilder();

    for (MemberData nestedMemberData : mdataList) {
      NcStreamProto.DataCol.Builder nestedBuilder = NcStreamProto.DataCol.newBuilder();
      nestedBuilder.setName(nestedMemberData.member.getName());
      nestedBuilder.setDataType(NcStream.convertDataType(nestedMemberData.member.getDataType()));
      nestedBuilder.setNelems(nestedMemberData.nelems);
      nestedBuilder.setSection(NcStream.encodeSection(nestedMemberData.section));

      if (nestedMemberData.member.isVariableLength()) {
        nestedMemberData.finishVlens();
        nestedBuilder.addAllVlens(nestedMemberData.vlens);
        nestedMemberData.bb.flip();
        nestedBuilder.setPrimdata(ByteString.copyFrom(nestedMemberData.bb));
        nestedBuilder.setNelems(nestedMemberData.nelems);
        nestedBuilder.setIsVlen(true);

      } else if (nestedMemberData.member.getDataType() == DataType.STRING) {
        nestedBuilder.addAllStringdata(nestedMemberData.stringList);

      } else if (nestedMemberData.member.getDataType() == DataType.OPAQUE) {
        nestedBuilder.addAllOpaquedata(nestedMemberData.opaqueList);

      } else if (nestedMemberData.member.getDataType() == DataType.STRUCTURE) {
        nestedBuilder.setStructdata(buildNestedStructureData(nestedMemberData.members)); // recurse

      } else {
        nestedMemberData.bb.flip();
        nestedBuilder.setPrimdata(ByteString.copyFrom(nestedMemberData.bb));
      }

      result.addMemberData(nestedBuilder);
    }

    return result;
  }

  ///////////////////////////////////////////////////////////
  // extract data from ArrayStructure and put into MemberData

  void extractData(ArrayStructure as, int recno, MemberData md) {
    StructureMembers.Member m = md.member;
    ByteBuffer bb = md.bb;
    Class classType = md.dtype.getPrimitiveClassType();

    if (m.isScalar()) {
      if (classType == double.class)
        bb.putDouble(as.getScalarDouble(recno, m));

      else if (classType == float.class)
        bb.putFloat(as.getScalarFloat(recno, m));

      else if (classType == byte.class)
        bb.put(as.getScalarByte(recno, m));

      else if (classType == short.class)
        bb.putShort(as.getScalarShort(recno, m));

      else if (classType == int.class)
        bb.putInt(as.getScalarInt(recno, m));

      else if (classType == long.class)
        bb.putLong(as.getScalarLong(recno, m));

      else if (md.dtype == DataType.CHAR)
        bb.put((byte) as.getScalarChar(recno, m)); // look this just truncates to first byte

      else if (md.dtype == DataType.STRING)
        md.stringList.add(as.getScalarString(recno, m));

      else if (md.dtype == DataType.OPAQUE)
        md.opaqueList.add(ByteString.copyFrom((ByteBuffer) as.getScalarObject(recno, m)));

      else if (md.dtype == DataType.STRUCTURE)
        extractStructureData(md, as.getScalarStructure(recno, m));

    } else {
      if (classType == double.class) {
        double[] data = as.getJavaArrayDouble(recno, m);
        for (double aData : data) bb.putDouble(aData);

      } else if (classType == float.class) {
        float[] data = as.getJavaArrayFloat(recno, m);
        for (float aData : data) bb.putFloat(aData);

      } else if (classType == byte.class) {
        byte[] data = as.getJavaArrayByte(recno, m);
        for (byte aData : data) bb.put(aData);

      } else if (classType == short.class) {
        short[] data = as.getJavaArrayShort(recno, m);
        for (short aData : data) bb.putShort(aData);

      } else if (classType == int.class) {
        int[] data = as.getJavaArrayInt(recno, m);
        for (int aData : data) bb.putInt(aData);

      } else if (classType == long.class) {
        long[] data = as.getJavaArrayLong(recno, m);
        for (long aData : data) bb.putLong(aData);

      } else if (md.dtype == DataType.CHAR) {
        char[] data = as.getJavaArrayChar(recno, m);
        for (char aData : data) bb.put((byte) aData);

      } else if (md.dtype == DataType.STRING) {
        String[] data = as.getJavaArrayString(recno, m);
        Collections.addAll(md.stringList, data);

      } else if (md.dtype == DataType.OPAQUE) {
        ArrayObject ao = as.getArrayObject(recno, m);
        while (ao.hasNext()) md.opaqueList.add(ByteString.copyFrom((ByteBuffer) ao.next()));

      } else if (md.dtype == DataType.STRUCTURE) {
        ArrayStructure nested = as.getArrayStructure(recno, m);
        for (int i = 0; i < nested.getSize(); i++) extractStructureData(md, nested.getStructureData(i));
      }
    }
  }

  void extractStructureData(MemberData md, StructureData sdata) {

    for (MemberData nested : md.members) {
      StructureMembers.Member m = nested.member;
      ByteBuffer bb = nested.bb;
      Class classType = nested.dtype.getPrimitiveClassType();

      if (m.isScalar()) {
        if (classType == double.class)
          bb.putDouble(sdata.getScalarDouble(m));

        else if (classType == float.class)
          bb.putFloat(sdata.getScalarFloat(m));

        else if (classType == byte.class)
          bb.put(sdata.getScalarByte(m));

        else if (classType == short.class)
          bb.putShort(sdata.getScalarShort(m));

        else if (classType == int.class)
          bb.putInt(sdata.getScalarInt(m));

        else if (classType == long.class)
          bb.putLong(sdata.getScalarLong(m));

        else if (md.dtype == DataType.CHAR)
          bb.put((byte) sdata.getScalarChar(m));

        else if (md.dtype == DataType.STRING)
          md.stringList.add(sdata.getScalarString(m));

        else if (md.dtype == DataType.OPAQUE)
          md.opaqueList.add(ByteString.copyFrom((ByteBuffer) sdata.getScalarObject(m)));

        else if (md.dtype == DataType.STRUCTURE)
          extractStructureData(md, sdata.getScalarStructure(m));

      } else {
        if (classType == double.class) {
          double[] data = sdata.getJavaArrayDouble(m);
          for (double aData : data) bb.putDouble(aData);

        } else if (classType == float.class) {
          float[] data = sdata.getJavaArrayFloat(m);
          for (float aData : data) bb.putFloat(aData);

        } else if (classType == byte.class) {
          byte[] data = sdata.getJavaArrayByte(m);
          for (byte aData : data) bb.put(aData);

        } else if (classType == short.class) {
          short[] data = sdata.getJavaArrayShort(m);
          for (short aData : data) bb.putShort(aData);

        } else if (classType == int.class) {
          int[] data = sdata.getJavaArrayInt(m);
          for (int aData : data) bb.putInt(aData);

        } else if (classType == long.class) {
          long[] data = sdata.getJavaArrayLong(m);
          for (long aData : data) bb.putLong(aData);

        } else if (md.dtype == DataType.CHAR) {
          char[] data = sdata.getJavaArrayChar(m);
          for (char aData : data) bb.put((byte) aData);

        } else if (md.dtype == DataType.STRING) {
          String[] data = sdata.getJavaArrayString(m);
          Collections.addAll(md.stringList, data);

        } else if (md.dtype == DataType.OPAQUE) {
          Array ao = sdata.getArray(m);
          while (ao.hasNext()) md.opaqueList.add(ByteString.copyFrom((ByteBuffer) ao.next()));

        } else if (md.dtype == DataType.STRUCTURE) {
          ArrayStructure nestedAS = sdata.getArrayStructure(m);
          for (int i = 0; i < nestedAS.getSize(); i++) extractStructureData(md, nestedAS.getStructureData(i));
        }
      }
    }

  }

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

  public Array decode(NcStreamProto.DataCol dproto, Section parentSection) throws IOException {

    ByteOrder bo = dproto.getBigend() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;

    DataType dataType = NcStream.convertDataType(dproto.getDataType());
    Section section = (dataType == DataType.SEQUENCE) ? new Section() : NcStream.decodeSection(dproto.getSection());
    if (!dproto.getIsVlen()) {
      assert dproto.getNelems() == section.computeSize();
    }

    // special cases
    if (dproto.getIsVlen()) {
      if (parentSection == null)
        return decodeVlenData(dproto);
      else
        return decodeVlenData(dproto, parentSection);

    } else if (dataType == DataType.STRING) {
      Array data = Array.factory(dataType, section.getShape());
      IndexIterator ii = data.getIndexIterator();
      for (String s : dproto.getStringdataList()) {
        ii.setObjectNext(s);
      }
      return data;

    } else if (dataType == DataType.STRUCTURE) {
      return decodeStructureData(dproto, parentSection);

    } else if (dataType == DataType.OPAQUE) {
      Array data = Array.factory(dataType, section.getShape());
      IndexIterator ii = data.getIndexIterator();
      for (ByteString s : dproto.getOpaquedataList()) {
        ii.setObjectNext(s.asReadOnlyByteBuffer());
      }
      return data;

    } else { // common case
      ByteBuffer bb = dproto.getPrimdata().asReadOnlyByteBuffer();
      bb.order(bo);
      return Array.factory(dataType, section.getShape(), bb);
    }

  }

  // top level vlen
  public Array decodeVlenData(NcStreamProto.DataCol dproto) throws IOException {
    DataType dataType = NcStream.convertDataType(dproto.getDataType());
    ByteBuffer bb = dproto.getPrimdata().asReadOnlyByteBuffer();
    ByteOrder bo = dproto.getBigend() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
    bb.order(bo);
    Array alldata = Array.factory(dataType, new int[]{dproto.getNelems()}, bb); // flat array
    IndexIterator all = alldata.getIndexIterator();

    Section section = NcStream.decodeSection(dproto.getSection());
    Array[] data = new Array[(int) section.computeSize()];

    // divide the primitive data into variable length arrays
    int count = 0;
    for (int len : dproto.getVlensList()) {
      Array primdata = Array.factory(dataType, new int[]{len});
      IndexIterator prim = primdata.getIndexIterator();
      for (int i=0; i<len; i++) {
        prim.setObjectNext( all.getObjectNext()); // generic
      }
      data[count++] = primdata;
    }

    // return Array.makeObjectArray(dataType, data[0].getClass(), section.getShape(), data);
    return Array.makeVlenArray(section.getShape(), data);
  }

  // vlen inside a Structure
  private Array decodeVlenData(NcStreamProto.DataCol dproto, Section parentSection) throws IOException {
    DataType dataType = NcStream.convertDataType(dproto.getDataType());
    ByteBuffer bb = dproto.getPrimdata().asReadOnlyByteBuffer();
    ByteOrder bo = dproto.getBigend() ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
    bb.order(bo);
    Array alldata = Array.factory(dataType, new int[]{dproto.getNelems()}, bb); // 1D array
    IndexIterator all = alldata.getIndexIterator();

    int psize = (int) parentSection.computeSize();

    Section section = NcStream.decodeSection(dproto.getSection());
    Section vsection = section.removeFirst(parentSection);
    int vsectionSize = (int) vsection.computeSize(); // the # of varlen Arrays at the inner structure
    // LOOK check for scalar

    // divide the primitive data into variable length arrays
    int countInner = 0;
    Array[] pdata = new Array[psize];
    for (int pCount=0; pCount<psize; pCount++) {
      Array[] vdata = new Array[vsectionSize];
      for (int vCount=0; vCount<vsectionSize; vCount++) {
        int vlen = dproto.getVlens(countInner++);
        Array primdata = Array.factory(dataType, new int[]{vlen});
        IndexIterator prim = primdata.getIndexIterator();
        for (int i = 0; i < vlen; i++) {
          prim.setObjectNext(all.getObjectNext()); // generic
        }
        vdata[vCount] = primdata;
      }
      pdata[pCount] = Array.makeVlenArray(vsection.getShape(), vdata);
    }

    // ArrayObject(parentShape)
    return Array.makeVlenArray(parentSection.getShape(), pdata);
  }

  private Array decodeStructureData(NcStreamProto.DataCol dproto, Section parentSection) throws IOException {
    NcStreamProto.ArrayStructureCol structData = dproto.getStructdata();
    Section section = NcStream.decodeSection(dproto.getSection());
    int nelems = dproto.getNelems();
    assert nelems == section.computeSize();

    // accumulate parent sections
    parentSection = section.prepend(parentSection);

    StructureMembers members = new StructureMembers(dproto.getName());
    for (NcStreamProto.DataCol memberData : structData.getMemberDataList()) {
      decodeMemberData(members, memberData, parentSection);
    }

    return new ArrayStructureMA(members, section.getShape());
  }

  private void decodeMemberData(StructureMembers members, NcStreamProto.DataCol memberData, Section parentSection) throws IOException {
    String name = memberData.getName();
    DataType dataType = NcStream.convertDataType(memberData.getDataType());
    Section section = NcStream.decodeSection(memberData.getSection());
    if (!memberData.getIsVlen()) {
      assert memberData.getNelems() == section.computeSize();
    }
    // the dproto section includes parents, remove them
    Section msection = section.removeFirst(parentSection);
    if (memberData.getIsVlen())
      msection = msection.appendRange(Range.VLEN);

    StructureMembers.Member result = members.addMember(name, null, null, dataType, msection.getShape());
    Array data = decode(memberData, parentSection);
    result.setDataArray(data);
  }

}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
// Sample code from John that parses HTML and looks for URLs

package thredds.util;

import ucar.nc2.constants.CDM;

import javax.swing.text.AttributeSet;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Enumeration;

public class DodsURLExtractor {
  private HTMLEditorKit.Parser parser;
  private ArrayList urlList;
  private URL baseURL;

  private boolean wantURLS = false;
  private String title;
  private boolean isTitle;

  private StringBuffer textBuffer;
  private boolean wantText = false;
  private boolean debug = false;

  /**
   * Constructor
   */
  public DodsURLExtractor() {
    ParserGetter kit = new ParserGetter();
    parser = kit.getParser();
  }

  /**
   * Extract all A-HREF contained URLS from the given URL and return in List
   */
  public ArrayList extract(String url) throws IOException {
    if (debug) System.out.println(" URLextract=" + url);

    baseURL = new URL(url);
    InputStream in = baseURL.openStream();
    InputStreamReader r = new InputStreamReader(filterTag(in), CDM.UTF8);
    HTMLEditorKit.ParserCallback callback = new CallerBacker();

    urlList = new ArrayList();
    wantURLS = true;
    wantText = false;
    parser.parse(r, callback, false);

    return urlList;
  }

  /**
   * Extract text content from the given URL and return in String
   */
  public String getTextContent(String url) throws IOException {
    if (debug) System.out.println(" URL.getTextContent=" + url);

    baseURL = new URL(url);
    InputStream in = baseURL.openStream();
    InputStreamReader r = new InputStreamReader(filterTag(in), CDM.UTF8);
    HTMLEditorKit.ParserCallback callback = new CallerBacker();

    textBuffer = new StringBuffer(3000);
    wantURLS = false;
    wantText = true;
    parser.parse(r, callback, false);

    return textBuffer.toString();
  }

  // workaround for HTMLEditorKit.Parser, cant deal with "content-encoding"
  private InputStream filterTag(InputStream in) throws IOException {
    BufferedReader buffIn = new BufferedReader(new InputStreamReader(in, CDM.UTF8));
    ByteArrayOutputStream bos = new ByteArrayOutputStream(10000);

    String line = buffIn.readLine();
    while (line != null) {
      String lline = line.toLowerCase();
      if (lline.contains("<meta "))  // skip meta tags
        continue;
      //System.out.println("--"+line);
      bos.write(line.getBytes(CDM.utf8Charset));
      line = buffIn.readLine();
    }
    buffIn.close();

    return new ByteArrayInputStream(bos.toByteArray());
  }

  private class CallerBacker extends HTMLEditorKit.ParserCallback {

    private boolean wantTag(HTML.Tag tag) {
      return (tag == HTML.Tag.H1 || tag == HTML.Tag.H2
              || tag == HTML.Tag.H3 || tag == HTML.Tag.H4
              || tag == HTML.Tag.H5 || tag == HTML.Tag.H6);
    }

    public void handleStartTag(HTML.Tag tag, MutableAttributeSet attributes,
                               int position) {
      isTitle = (tag == HTML.Tag.TITLE);

      //System.out.println(" "+tag);
      if (wantURLS && tag == HTML.Tag.A)
        extractHREF(attributes);
    }

    public void handleEndTag(HTML.Tag tag, int position) {
      isTitle = false;
    }

    public void handleSimpleTag(HTML.Tag tag, MutableAttributeSet attributes,
                                int position) {
      isTitle = false; // (tag == HTML.Tag.TITLE); // ??

      //System.out.println(" "+tag);
      if (wantURLS && tag == HTML.Tag.A)
        extractHREF(attributes);
    }

    private void extractHREF(AttributeSet attributes) {
      Enumeration e = attributes.getAttributeNames();
      while (e.hasMoreElements()) {
        Object name = e.nextElement();
        String value = (String) attributes.getAttribute(name);
        //System.out.println(" name= <"+name+ ">"+" value= <"+value+ ">");
        try {
          if (name == HTML.Attribute.HREF) {
            URL u = baseURL.toURI().resolve(value).toURL();
            String urlName = u.toString();
            if (urlList != null)
              urlList.add(u.toString());
            if (debug) System.out.println(" extracted URL= <" + urlName + ">");
          }
        } catch (MalformedURLException ex) {
          System.err.println(ex);
          System.err.println(baseURL);
          System.err.println(value);
          ex.printStackTrace();
        } catch (URISyntaxException ex) {
          System.err.println(ex);
          System.err.println(baseURL);
          System.err.println(value);
          ex.printStackTrace();
        }
      } // while
    } // extractHREF

    public void handleText(char[] text, int position) {
      if (isTitle) title = new String(text);

      if (wantText) {
        textBuffer.append(text);
        textBuffer.append(' ');
      }
    }

  } // Callerbacker

  private static class ParserGetter extends HTMLEditorKit {
    // purely to make this method public
    public HTMLEditorKit.Parser getParser() {
      return super.getParser();
    }
  }
}


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

package ucar.nc2.iosp;

import ucar.ma2.Array;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.Section;
import ucar.ma2.StructureDataIterator;
import ucar.nc2.NetcdfFile;
import ucar.nc2.ParsedSectionSpec;
import ucar.nc2.Structure;
import ucar.nc2.util.CancelTask;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.Format;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.channels.WritableByteChannel;
import java.util.Formatter;

/**
 * Abstract base class for IOSP implementations that provides default implementations
 * of readToByteChannel(...) and readSection(...).
 *
 *
Implementations should make sure to handle the RandomAccessFile properly by
 * doing one of the following:
 *
 *

 *
 Write your own open(...) and close() methods that keep track of the
 *     RandomAccessFile, be sure to close the RandomAccessFile in your close()
 *     method.

 *
 Write your own open(...) and close() methods that call the open(...)
 *     and close() methods defined here, use the "raf" variable also defined
 *     here.

 *
 Don't write an open(...) or close() method, so that those defined
 *     here are used.

 *

 *
 */
public abstract class AbstractIOServiceProvider implements IOServiceProvider {

  /**
   * Subclasses that use AbstractIOServiceProvider.open(...) or .close()
   * should use this (instead of their own private variable).
   */
  protected ucar.unidata.io.RandomAccessFile raf;
  protected String location;
  protected int rafOrder = RandomAccessFile.BIG_ENDIAN;
  protected NetcdfFile ncfile;

  @Override
  public void open( RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask ) throws IOException {
    this.raf = raf;
    this.location = (raf != null) ? raf.getLocation() : null;
    this.ncfile = ncfile;
  }

  @Override
  public void close() throws java.io.IOException {
    if (raf != null)
      raf.close();
    raf = null;
  }

  // release any resources like file handles
  public void release() throws IOException {
    if (raf != null)
      raf.close();
    raf = null;
  }

  // reacquire any resources like file handles
  public void reacquire() throws IOException {
    raf = RandomAccessFile.acquire(location);
    this.raf.order(rafOrder);
  }

  // default implementation, reads into an Array, then writes to WritableByteChannel
  // subclasses should override if possible
  // LOOK DataOutputStream uses big-endian
  @Override
  public long readToByteChannel(ucar.nc2.Variable v2, Section section, WritableByteChannel channel)
          throws java.io.IOException, ucar.ma2.InvalidRangeException {

    Array data = readData(v2, section);
    return IospHelper.copyToByteChannel(data,  channel);
  }

  public long readToOutputStream(ucar.nc2.Variable v2, Section section, OutputStream out)
          throws java.io.IOException, ucar.ma2.InvalidRangeException {

    Array data = readData(v2, section);
    return IospHelper.copyToOutputStream(data,  out);
  }

  public long streamToByteChannel(ucar.nc2.Variable v2, Section section, WritableByteChannel channel)
      throws java.io.IOException, ucar.ma2.InvalidRangeException {

    Array data = readData(v2, section);
    return IospHelper.copyToByteChannel(data,  channel);
  }

  @Override
  public ucar.ma2.Array readSection(ParsedSectionSpec cer) throws IOException, InvalidRangeException {
    return IospHelper.readSection(cer);  //  IOSPs can optimize by overriding
  }

  @Override
  public StructureDataIterator getStructureIterator(Structure s, int bufferSize) throws java.io.IOException {
    return null;
  }

  @Override
  public Object sendIospMessage(Object message) {
    if (message == NetcdfFile.IOSP_MESSAGE_RANDOM_ACCESS_FILE) {
      return raf;
    }
    return null;
  }

  @Override
  public boolean syncExtend() throws IOException {
    return false;
  }

  /**
   * Returns the time that the underlying file(s) were last modified. If they've changed since they were stored in the
   * cache, they will be closed and reopened with {@link ucar.nc2.util.cache.FileFactory}.
   *
   * @return  a {@code long} value representing the time the file(s) were last modified or {@code 0L} if the
   *          last-modified time couldn't be determined for any reason.
   */
  public long getLastModified() {
    if (location != null) {
      File file = new File(location);
      return file.lastModified();
    } else {
      return 0;
    }
  }

  @Override
  public String toStringDebug(Object o) {
    return "";
  }

  @Override
  public String getDetailInfo() {
    if (raf == null) return "";
    try {
      Formatter fout = new Formatter();
      double size = raf.length() / (1000.0 * 1000.0);
      fout.format(" raf = %s%n", raf.getLocation());
      fout.format(" size= %d (%s Mb)%n%n", raf.length(), Format.dfrac(size, 3));
      return fout.toString();

    } catch (IOException e) {
      return e.getMessage();
    }
  }

  @Override
  public String getFileTypeVersion() {
    return "N/A";
  }

}


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

import java.io.IOException;
import java.io.OutputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import ucar.ma2.Array;
import ucar.ma2.DataType;
import ucar.ma2.IndexIterator;
import ucar.nc2.Attribute;
import ucar.nc2.constants.AxisType;
import ucar.nc2.ft2.coverage.CoordSysSet;
import ucar.nc2.ft2.coverage.Coverage;
import ucar.nc2.ft2.coverage.CoverageCoordAxis;
import ucar.nc2.ft2.coverage.CoverageCoordSys;
import ucar.nc2.ft2.coverage.CoverageCollection;
import ucar.nc2.ft2.coverage.CoverageTransform;
import ucar.nc2.ncml.NcMLWriter;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.util.Misc;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.geoloc.ProjectionRect;

/**
 * Helper class to create a DatasetCapabilities XML document for CoverageDataset
 *
 * @author caron
 * @since 5/7/2015
 */
public class CoverageDatasetCapabilities {
  private CoverageCollection gcd;
  private String path;

  private final NcMLWriter ncmlWriter = new NcMLWriter();

  public CoverageDatasetCapabilities(CoverageCollection gds, String path) {
    this.gcd = gds;
    this.path = path;
  }

  /**
   * Write the information as an XML document
   *
   * @param doc write XML for this Document
   * @return String output
   */
  public String writeXML(Document doc) {
    XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
    return fmt.outputString(doc);
  }

  /**
   * Write the information as an XML document
   *
   * @param doc write XML for this Document
   * @param os  write to this output stream
   * @throws java.io.IOException on write error
   */
  public void writeXML(Document doc, OutputStream os) throws IOException {
    XMLOutputter fmt = new XMLOutputter(Format.getPrettyFormat());
    fmt.output(doc, os);
  }

  /**
   * Create the "Dataset Description" XML document from this GridDataset
   *
   * @return a JDOM Document
   */
  public Document makeDatasetDescription() throws IOException {
    Element rootElem = new Element("gridDataset");
    Document doc = new Document(rootElem);
    rootElem.setAttribute("location", gcd.getName());
    if (null != path)
      rootElem.setAttribute("path", path);

    // coordinate axes
    for (CoverageCoordAxis axis : gcd.getCoordAxes()) {
      rootElem.addContent(writeAxis(axis));
    }

    // gridSets
    for (CoordSysSet gridset : gcd.getCoverageSets()) {
      rootElem.addContent(writeCoverageSet(gridset.getCoordSys(), gridset.getCoverages(), gcd.getProjBoundingBox()));
    }

    // coordinate transforms
    for (CoverageTransform ct : gcd.getCoordTransforms()) {
      rootElem.addContent(writeCoordTransform(ct));
    }

 		/* global attributes
     Iterator atts = gds.getGlobalAttributes().iterator();
     while (atts.hasNext()) {
       ucar.nc2.Attribute att = (ucar.nc2.Attribute) atts.next();
       rootElem.addContent(ucar.nc2.ncml.NcMLWriter.writeAttribute(att, "attribute", null));
     } */

    // add lat/lon bounding box
    LatLonRect bb = gcd.getLatlonBoundingBox();
    if (bb != null)
      rootElem.addContent(writeBoundingBox(bb));

    // add date range
    CalendarDateRange calDateRange = gcd.getCalendarDateRange();
    if (calDateRange != null) {
      Element dateRange = new Element("TimeSpan");
      dateRange.addContent(new Element("begin").addContent(calDateRange.getStart().toString()));
      dateRange.addContent(new Element("end").addContent(calDateRange.getEnd().toString()));
      rootElem.addContent(dateRange);
    }

    return doc;
  }

  private Element writeAxis(CoverageCoordAxis axis) throws IOException {
    Element varElem = new Element("axis");
    varElem.setAttribute("name", axis.getName());
    varElem.setAttribute("shape", Misc.showInts(axis.getShape()));

    DataType dt = axis.getDataType();
    varElem.setAttribute("type", dt.toString());

    AxisType axisType = axis.getAxisType();
    if (null != axisType)
      varElem.setAttribute("axisType", axisType.toString());

    if (axis.getDependsOn() != null && axis.getDependsOn().trim().length() > 0)
      varElem.setAttribute("dependsOn", axis.getDependsOn().trim());

    // attributes
    for (Attribute att : axis.getAttributes()) {
      varElem.addContent(ncmlWriter.makeAttributeElement(att));
    }

		/*
        f.format("%s  npts: %d [%f,%f] spacing=%s", indent, ncoords, startValue, endValue, spacing);
    if (getResolution() != 0.0)
      f.format(" resolution=%f", resolution);
    f.format(" %s :", getDependenceType());
    for (String s : dependsOn)
		 */

    Element values = new Element("values");
    if (!axis.isRegular()) {
      Array array = axis.getCoordsAsArray();
      boolean isRealType = (array.getDataType() == DataType.DOUBLE) || (array.getDataType() == DataType.FLOAT);
      IndexIterator iter = array.getIndexIterator();

      StringBuilder buff = new StringBuilder();
      buff.append(isRealType ? iter.getDoubleNext() : iter.getIntNext());

      while (iter.hasNext()) {
        buff.append(" ");
        buff.append(isRealType ? iter.getDoubleNext() : iter.getIntNext());
      }

      values.setText(buff.toString());
    }

    values.setAttribute("spacing", axis.getSpacing().toString());
    values.setAttribute("npts", Long.toString(axis.getNcoords()));
    values.setAttribute("start", Double.toString(axis.getStartValue()));
    values.setAttribute("end", Double.toString(axis.getEndValue()));
    if (axis.getResolution() != 0.0)
      values.setAttribute("resolution", Double.toString(axis.getResolution()));

    varElem.addContent(values);
    return varElem;
  }

  // display name plus the dimensions
  private String getShapeString(int[] shape) {
    StringBuilder buf = new StringBuilder();
    for (int i = 0; i < shape.length; i++) {
      if (i != 0) buf.append(" ");
      buf.append(shape[i]);
    }
    return buf.toString();
  }

  private Element writeBoundingBox(LatLonRect bb) {

    Element bbElem = new Element("LatLonBox");
    //LatLonPoint llpt = bb.getLowerLeftPoint();
    //LatLonPoint urpt = bb.getUpperRightPoint();

    //bbElem.addContent(new Element("west").addContent(ucar.unidata.util.Format.dfrac(llpt.getLongitude(), 4)));
    bbElem.addContent(new Element("west").addContent(ucar.unidata.util.Format.dfrac(bb.getLonMin(), 4)));
    //bbElem.addContent(new Element("east").addContent(ucar.unidata.util.Format.dfrac(urpt.getLongitude(), 4)));
    bbElem.addContent(new Element("east").addContent(ucar.unidata.util.Format.dfrac(bb.getLonMax(), 4)));
    //bbElem.addContent(new Element("south").addContent(ucar.unidata.util.Format.dfrac(llpt.getLatitude(), 4)));
    bbElem.addContent(new Element("south").addContent(ucar.unidata.util.Format.dfrac(bb.getLatMin(), 4)));
    //bbElem.addContent(new Element("north").addContent(ucar.unidata.util.Format.dfrac(urpt.getLatitude(), 4)));
    bbElem.addContent(new Element("north").addContent(ucar.unidata.util.Format.dfrac(bb.getLatMax(), 4)));

    return bbElem;

  }

  private Element writeCoverageSet(CoverageCoordSys cs, List<Coverage> grids, ProjectionRect rect) {
    Element csElem = new Element("gridSet");
    csElem.setAttribute("name", cs.getName());

    if (rect != null) {
      Element projBBOX = new Element("projectionBox");
      Element minx = new Element("minx");
      minx.addContent(Double.valueOf(rect.getMinX()).toString());
      projBBOX.addContent(minx);
      Element maxx = new Element("maxx");
      maxx.addContent(Double.valueOf(rect.getMaxX()).toString());
      projBBOX.addContent(maxx);
      Element miny = new Element("miny");
      miny.addContent(Double.valueOf(rect.getMinY()).toString());
      projBBOX.addContent(miny);
      Element maxy = new Element("maxy");
      maxy.addContent(Double.valueOf(rect.getMaxY()).toString());
      projBBOX.addContent(maxy);

      csElem.addContent(projBBOX);
    }

    for (String axisName : cs.getAxisNames()) {
      Element axisElem = new Element("axisRef");
      axisElem.setAttribute("name", axisName);
      csElem.addContent(axisElem);
    }

    for (String ctName : cs.getTransformNames()) {
      Element elem = new Element("coordTransRef");
      elem.setAttribute("name", ctName);
      csElem.addContent(elem);
    }

    Collections.sort(grids, new GridCoverageComparator());
    for (Coverage grid : grids) {
      csElem.addContent(writeGrid(grid));
    }

    return csElem;
  }

 	/* private Element writeCoordSys(GridCoordSystem cs) {
     Element csElem = new Element("coordSys");
     csElem.setAttribute("name", cs.getName());
     List axes = cs.getCoordinateAxes();
     for (int i = 0; i < axes.size(); i++) {
       CoordinateAxis axis = (CoordinateAxis) axes.get(i);
       Element axisElem = new Element("axisRef");
       axisElem.setAttribute("name", axis.getName());
       csElem.addContent(axisElem);
     }
     List cts = cs.getCoordinateTransforms();
     for (int j = 0; j < cts.size(); j++) {
       CoordinateTransform ct = (CoordinateTransform) cts.get(j);
       Element elem = new Element("coordTransRef");
       elem.setAttribute("name", ct.getName());
       csElem.addContent(elem);
     }
     return csElem;
   } */

  private Element writeCoordTransform(CoverageTransform ct) {
    Element ctElem = new Element("coordTransform");
    ctElem.setAttribute("name", ct.getName());
    ctElem.setAttribute("transformType", ct.isHoriz() ? "Projection" : "Vertical");
    for (Attribute param : ct.getAttributes()) {
      Element pElem = ncmlWriter.makeAttributeElement(param);
      pElem.setName("parameter");
      ctElem.addContent(pElem);
    }
    return ctElem;
  }

  private Element writeGrid(Coverage grid) {
    Element varElem = new Element("grid");
    varElem.setAttribute("name", grid.getName());

    String desc = grid.getDescription() != null ? grid.getDescription() : "No description";
    varElem.setAttribute("desc", desc);

 		/* StringBuilder buff = new StringBuilder();
 		List dims = grid.getDimensions();
 		for (int i = 0; i < dims.size(); i++) {
 			Dimension dim = (Dimension) dims.get(i);
 			if (i > 0) buff.append(" ");
 			if (dim.isShared())
 				buff.append(dim.getShortName());
 			else
 				buff.append(dim.getLength());
 		}
 		if (buff.length() > 0)
 			varElem.setAttribute("shape", buff.toString()); */

    DataType dt = grid.getDataType();
    if (dt != null)
      varElem.setAttribute("type", dt.toString());

    //GridCoordSystem cs = grid.getCoordinateSystem();
    //varElem.setAttribute("coordSys", cs.getName());

    // attributes
    for (ucar.nc2.Attribute att : grid.getAttributes()) {
      varElem.addContent(ncmlWriter.makeAttributeElement(att));
    }

    return varElem;
  }

  // sort by domain size, then name
  private static class GridCoverageComparator implements Comparator<Coverage> {
    public int compare(Coverage grid1, Coverage grid2) {
      return grid1.getName().compareTo(grid2.getName());
    }
  }
}


/* Copyright */
package ucar.nc2;

import ucar.nc2.util.Indent;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.List;

/**
 * manages Collections of Attributes.
 *
 * @author caron
 * @since 5/5/2015
 */
public class AttributeContainerHelper implements AttributeContainer {
  final String name;
  List<Attribute> atts;

  public AttributeContainerHelper(String name) {
    this.name = name;
    this.atts = new ArrayList<>();
  }

  public AttributeContainerHelper(String name, List<Attribute> from) {
    this(name);
    addAll(from);
  }

  @Override
  public String getName() {
    return name;
  }

  public void setImmutable() {
    this.atts = Collections.unmodifiableList(atts);
  }

  @Override
  public List<Attribute> getAttributes() {
    return atts;
  }

  @Override
  public Attribute addAttribute(Attribute att) {
    if (att == null) return null;
    for (int i = 0; i < atts.size(); i++) {
      Attribute a = atts.get(i);
      if (att.getShortName().equals(a.getShortName())) {
        atts.set(i, att); // replace
        return att;
      }
    }
    atts.add(att);
    return att;
  }

  /** Add all; replace old if has same name */
  @Override
  public void addAll(Iterable<Attribute> atts) {
    for (Attribute att : atts) addAttribute(att);
  }

  @Override
  public String findAttValueIgnoreCase(String attName, String defaultValue) {
    String attValue = null;
    Attribute att = findAttributeIgnoreCase(attName);

    if ((att != null) && att.isString())
      attValue = att.getStringValue();

    if (null == attValue)                     // not found, use default
      attValue = defaultValue;

    return attValue;
  }

  @Override
  public Attribute findAttribute(String name) {
    for (Attribute a : atts) {
      if (name.equals(a.getShortName()))
        return a;
    }
    return null;
  }

  @Override
  public Attribute findAttributeIgnoreCase(String name) {
    for (Attribute a : atts) {
      if (name.equalsIgnoreCase(a.getShortName()))
        return a;
    }
    return null;
  }

  /**
   * Remove an Attribute : uses the attribute hashCode to find it.
   *
   * @param a remove this attribute
   * @return true if was found and removed
   */
  @Override
  public boolean remove(Attribute a) {
    return a != null && atts.remove(a);
  }

  /**
   * Remove an Attribute by name.
   *
   * @param attName if exists, remove this attribute
   * @return true if was found and removed
   */
  @Override
  public boolean removeAttribute(String attName) {
    Attribute att = findAttribute(attName);
    return att != null && atts.remove(att);
  }

  /**
   * Remove an Attribute by name, ignoring case
   *
   * @param attName if exists, remove this attribute
   * @return true if was found and removed
   */
  @Override
  public boolean removeAttributeIgnoreCase(String attName) {
    Attribute att = findAttributeIgnoreCase(attName);
    return att != null && atts.remove(att);
  }

  static public AttributeContainer filter(AttributeContainer atts, String... remove) {
    List<Attribute> result = new ArrayList<>();
    for (Attribute att : atts.getAttributes()) {
      boolean ok = true;
      for (String s : remove)
        if (att.getShortName().startsWith(s)) ok = false;
      if (ok) result.add(att);
    }
    return new AttributeContainerHelper(atts.getName(), result);
  }

  static public void show(AttributeContainer atts, Indent indent, Formatter f) {
    for (Attribute att : atts.getAttributes()) {
      f.format("%s%s%n", indent, att);
    }
  }

}


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

import ucar.nc2.grib.grib1.Grib1Record;
import ucar.nc2.grib.grib2.Grib2Record;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarPeriod;
import ucar.nc2.util.Counters;
import ucar.nc2.util.Indent;

import javax.annotation.concurrent.Immutable;
import java.util.*;

/**
 * Grib runtime coordinate
 * Effectively Immutable
 * @author caron
 * @since 11/24/13
 */
@Immutable
public class CoordinateRuntime implements Coordinate {
  private final long[] runtimes;
  final CalendarDate firstDate;
  final CalendarPeriod timeUnit;
  final String periodName;
  private String name = "reftime";   // yeah yeah, not final, bugger off

  public CoordinateRuntime(List<Long> runtimeSorted, CalendarPeriod timeUnit) {
    this.runtimes = new long[runtimeSorted.size()];
    int idx = 0;
    for (long val : runtimeSorted)
      this.runtimes[idx++] = val;

    this.firstDate = CalendarDate.of(runtimeSorted.get(0));
    this.timeUnit = timeUnit == null ? CalendarPeriod.Hour : timeUnit;

    CalendarPeriod.Field cf = this.timeUnit.getField();
    if (cf == CalendarPeriod.Field.Month || cf == CalendarPeriod.Field.Year)
      this.periodName = "calendar "+ cf.toString();
    else
      this.periodName = cf.toString();
  }

  public CalendarPeriod getTimeUnits() {
    return timeUnit;
  }

  public CalendarDate getRuntimeDate(int idx) {
    return CalendarDate.of(runtimes[idx]);
  }

  public long getRuntime(int idx) {
    return runtimes[idx];
  }

  public String getPeriodName() {
    return periodName;
  }

  /**
   * Get offsets from firstDate, in units of timeUnit
   * @return for each runtime, a list of values from firstdate
   */
  public List<Double> getOffsetsInTimeUnits() {
    double start = firstDate.getMillis();

    List<Double> result = new ArrayList<>(runtimes.length);
    for (int idx=0; idx<runtimes.length; idx++) {
      double runtime = (double) getRuntime(idx);
      double msecs = (runtime - start);
      result.add(msecs / timeUnit.getValueInMillisecs());
    }
    return result;
  }

  public double getOffsetInTimeUnits(CalendarDate start) {
    return timeUnit.getOffset(start, getFirstDate());
  }

  @Override
  public int getSize() {
    return runtimes.length;
  }

  @Override
  public int getNCoords() {
    return getSize();
  }

  @Override
  public Type getType() {
    return Type.runtime;
  }

  @Override
  public int estMemorySize() {
    return 616 + getSize() * (48);
  }

  @Override
  public String getUnit() {
    return periodName+" since "+firstDate.toString();
  }

  @Override
  public String getName() {
    return name;
  }

  public void setName(String name) {  // LOOK need to get this in the constructor to make the name final
    if (!this.name.equals("reftime")) throw new IllegalStateException("Cant modify");
    this.name = name;
  }

  @Override
  public int getCode() { return 0; }

  public CalendarDate getFirstDate() {
    return firstDate;
  }

  public CalendarDate getLastDate() {
    return getRuntimeDate(getSize() - 1);
  }

  @Override
  public List<?> getValues() {
    List<Long> result = new ArrayList<>(runtimes.length);
    for (long val : runtimes) result.add(val);
    return result;
  }

  @Override
  public int getIndex(Object val) {
    long want;

    if (val instanceof CalendarDate)
      want = ((CalendarDate) val).getMillis();
    else if (val instanceof Number)
      want = ((Number)val).longValue();
    else
      throw new IllegalArgumentException(val.getClass().getName());

    return Arrays.binarySearch(runtimes, want);
  }

  @Override
  public Object getValue(int idx) {
    return runtimes[idx];
  }

  @Override
  public void showInfo(Formatter info, Indent indent) {
    info.format("%s%s:", indent, getType());
    for (int idx=0; idx<getSize(); idx++)
      info.format(" %s,", getRuntimeDate(idx));
    info.format(" (%d) %n", runtimes.length);
  }

  @Override
  public void showCoords(Formatter info) {
    info.format("Run Times: %s (%s)%n", getName(), getUnit());
    List<Double> udunits = getOffsetsInTimeUnits();
    int count = 0;
    for (int idx=0; idx<getSize(); idx++)
      info.format("   %s (%f)%n", getRuntimeDate(idx), udunits.get(count++));
  }

  @Override
  public Counters calcDistributions() {
    ucar.nc2.util.Counters counters = new Counters();
    counters.add("resol");

    List<Double> offsets = getOffsetsInTimeUnits();
    for (int i = 0; i < offsets.size() - 1; i++) {
      Double diff = offsets.get(i + 1) - offsets.get(i);
      counters.count("resol", diff);
    }

    return counters;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    CoordinateRuntime that = (CoordinateRuntime) o;

    if (!periodName.equals(that.periodName)) return false;
    if (!Arrays.equals(runtimes, that.runtimes)) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = Arrays.hashCode(runtimes);
    result = 31 * result + periodName.hashCode();
    return result;
  }

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

  public static class Builder2 extends CoordinateBuilderImpl<Grib2Record> {

    CalendarPeriod timeUnit;

    public Builder2(CalendarPeriod timeUnit) {
      this.timeUnit = timeUnit;
    }

    @Override
    public Object extract(Grib2Record gr) {
      return gr.getReferenceDate().getMillis();
    }

    @Override
    public Coordinate makeCoordinate(List<Object> values) {
      List<Long> runtimeSorted = new ArrayList<>(values.size());
      for (Object val : values)
        runtimeSorted.add((Long) val);
      Collections.sort(runtimeSorted);
      return new CoordinateRuntime(runtimeSorted, timeUnit);
    }
  }

  public static class Builder1 extends CoordinateBuilderImpl<Grib1Record> {
    CalendarPeriod timeUnit;

    public Builder1(CalendarPeriod timeUnit) {
      this.timeUnit = timeUnit;
    }

    @Override
    public Object extract(Grib1Record gr) {
      return gr.getReferenceDate().getMillis();
    }

    @Override
    public Coordinate makeCoordinate(List<Object> values) {
      List<Long> runtimeSorted = new ArrayList<>(values.size());
      for (Object val : values) runtimeSorted.add((Long) val);
      Collections.sort(runtimeSorted);
      return new CoordinateRuntime(runtimeSorted, timeUnit);
    }
  }

}


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

import ucar.nc2.util.ListenerManager;

/** An ActionCoordinator helps manage the set of objects that send and receive
 *  an ActionValueEvent. It is assumed that each event generator is also
 *  interested in recieving the event if its from someone else; these objects are
 *  of type ActionSourceListener.
 *
 * For each kind of event, an ActionCoordinator
 *  object is created. When it gets an event, it sends it to all others who have
 *  registered except not to the event source.
 *
 * @see ActionValueEvent
 * @see ActionSourceListener
 *
 * @author John Caron
 */

// LOOK could replace with guava EventBus probably
public class ActionCoordinator implements ActionValueListener {
  private ListenerManager lm;
  private String eventType;

  public ActionCoordinator(String eventType) {
    this.eventType = eventType;

    // manage Action Listeners
    lm = new ListenerManager(
        "ucar.nc2.ui.event.ActionValueListener",
        "ucar.nc2.ui.event.ActionValueEvent",
        "actionPerformed");
  }

  public void actionPerformed( ActionValueEvent e) {
    lm.sendEventExcludeSource(e);
  }

        /** add an ActionSource listener */
  public void addActionSourceListener( ActionSourceListener l) {
    if (!eventType.equals(l.getEventTypeName()))
      throw new IllegalArgumentException("ActionCoordinator: tried to add ActionSourceListener for wrong kind of Action "+
         eventType+ " != "+ l.getEventTypeName());

    lm.addListener(l);
    l.addActionValueListener(this);
  }
      /** remove an ActionSource listener */
  public void removeActionSourceListener( ActionSourceListener l) {
    lm.removeListener(l);
    l.removeActionValueListener(this);
  }

    /** add an ActionValue listener
  public void addActionValueListener( ActionValueListener l) {
    lm.addListener(l);
  }
    /** remove an ActionValue listener
  public void removeActionValueListener( ActionValueListener l) {
    lm.removeListener(l);
  } */

  static public void main( String[] argv) {
    ActionCoordinator ac = new ActionCoordinator("test");
/*    System.out.println("failure test------------");
    try {
      ac.addActionSourceListener(new ActionSourceListener("that") {
        public void actionPerformed( java.awt.event.ActionEvent e) {
          System.out.println(" event ok ");
        }
      });
      System.out.println("good dog!");
    } catch (IllegalArgumentException e) {
      System.out.println("bad dog! = "+e);
    }

    System.out.println("next test------------");  */
    ActionSourceListener as1 = new ActionSourceListener("test") {
      public void actionPerformed( ActionValueEvent e) {
        System.out.println(" first listener got event "+e.getValue());
      }
    };
    ac.addActionSourceListener(as1);

    ActionSourceListener as2 = new ActionSourceListener("test") {
      public void actionPerformed( ActionValueEvent e) {
        System.out.println(" second listener got event "+e.getValue());
      }
    };
    ac.addActionSourceListener(as2);

    ActionSourceListener as3 = new ActionSourceListener("test") {
      public void actionPerformed( ActionValueEvent e) {
        System.out.println(" third listener got event "+e.getValue());
      }
    };
    ac.addActionSourceListener(as3);

    as1.fireActionValueEvent("testing", "newValue 1");
    as2.fireActionValueEvent("testing", "newValue 2");
  }
}


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

import opendap.servlet.GuardedDataset;

import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;

public class OpendapSessionAttributeListener implements HttpSessionAttributeListener {
  //static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(OpendapSessionAttributeListener.class);

   // HttpSessionAttributeListener
    public void attributeRemoved(HttpSessionBindingEvent e) {

      if (e.getValue() instanceof GuardedDataset) {
        GuardedDataset gdataset = (GuardedDataset) e.getValue();
        gdataset.close();
        //System.out.printf(" close gdataset %s in session %s %n", gdataset, e.getSession().getId());
        //if (log.isDebugEnabled()) log.debug(" close gdataset " + gdataset + " in session " + e.getSession().getId());
      }
    }

    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) { }
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) { }
}


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

import ucar.nc2.ui.gis.GisFeatureRendererMulti;
import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.*;

import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;

/**
 * Provides a convenient interface to ESRI shapefiles by creating lists of
 * ucar.unidata.gis.AbstractGisFeature.  Java2D Shape or VisAD SampledSet
 * objects can be created from these.
 *
 * @author Russ Rew
 * @author John Caron
 */
public class EsriShapefileRenderer extends GisFeatureRendererMulti {
  private static java.util.Map<String, EsriShapefileRenderer> sfileHash;
  private static double defaultCoarseness = 0.0; // expose later?

  /**
   * Use factory to obtain a EsriShapefileRenderer.  This caches the EsriShapefile for reuse.
   * <p/>
   * Implementation note: should switch to weak references.
   */
  static public EsriShapefileRenderer factory(String filename) {
    if (sfileHash == null)
      sfileHash = new HashMap<String, EsriShapefileRenderer>();

    if (sfileHash.containsKey(filename))
      return sfileHash.get(filename);

    try {
      EsriShapefileRenderer sfile = new EsriShapefileRenderer(filename);
      sfileHash.put(filename, sfile);
      return sfile;
    } catch (Exception ex) {
      //System.err.println("EsriShapefileRenderer failed on " + filename + "\n" + ex);
      //ex.printStackTrace();
      return null;
    }
  }

  static public EsriShapefileRenderer factory(String key, InputStream stream) {
    if (sfileHash == null)
      sfileHash = new HashMap<String, EsriShapefileRenderer>();

    if (sfileHash.containsKey(key))
      return sfileHash.get(key);

    try {
      EsriShapefileRenderer sfile = new EsriShapefileRenderer(stream);
      sfileHash.put(key, sfile);
      return sfile;
    } catch (Exception ex) {
      // System.err.println("EsriShapefileRenderer failed on " + stream + "\n" + ex);
      return null;
    }
  }

  ////////////////////////////////////////
  private EsriShapefile esri = null;
  private ProjectionImpl dataProject = new LatLonProjection("Cylindrical Equidistant");

  private EsriShapefileRenderer(InputStream stream) throws IOException {
    esri = new EsriShapefile(stream, null, defaultCoarseness);

    double avgD = getStats(esri.getFeatures().iterator());
    createFeatureSet(avgD);
    createFeatureSet(2 * avgD);
    createFeatureSet(3 * avgD);
    createFeatureSet(5 * avgD);
    createFeatureSet(8 * avgD);
  }

  private EsriShapefileRenderer(String filename) throws IOException {
    this(filename, defaultCoarseness);
  }

  private EsriShapefileRenderer(String filename, double coarseness) throws IOException {
    esri = new EsriShapefile(filename, coarseness);

    double avgD = getStats(esri.getFeatures().iterator());
    createFeatureSet(2 * avgD);
    createFeatureSet(4 * avgD);
    createFeatureSet(8 * avgD);
    createFeatureSet(16 * avgD);
  }

  public LatLonRect getPreferredArea() {
    Rectangle2D bb = esri.getBoundingBox();
    return new LatLonRect(new LatLonPointImpl(bb.getMinY(), bb.getMinX()), bb.getHeight(), bb.getWidth());
  }

  protected java.util.List getFeatures() {
    return esri.getFeatures();
  }

  protected ProjectionImpl getDataProjection() {
    return dataProject;
  }

}


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

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

package thredds.server.opendap;

import opendap.servers.*;

import java.io.IOException;
import java.io.DataOutputStream;

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

/**
 * Wraps a netcdf scalar short variable.
 *
 * @author jcaron
 */
public class NcSDInt16 extends SDInt16 implements HasNetcdfVariable {
  private Variable ncVar;

  /**
   * Constructor
   *
   * @param v : the netcdf Variable
   */
  NcSDInt16(Variable v) {
      super(Variable.getDAPName(v));
    this.ncVar = v;
  }

  public Variable getVariable() { return ncVar; }

  /**
   * Read the value (parameters are ignored).
   */
  public boolean read(String datasetName, Object specialO) throws IOException {
    setData(ncVar.read());
    return (false);
  }

  public void setData(Array data) {
    setValue(data.getShort(data.getIndex()));
    setRead(true);
  }

  public void serialize(DataOutputStream sink, StructureData sdata, StructureMembers.Member m) throws IOException {
    setValue( sdata.getScalarShort(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.*;

/**
 * Holds a OPeNDAP String value.
 *
 * @author jehamby
 * @version $Revision: 21071 $
 * @see BaseType
 */
public class DString extends DPrimitive implements ClientIO {

    // Track if a value has been set
    private boolean hasValue = false;

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

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

    /**
     * The value of this DString.
     */
    private String val;

    /**
     * Get the current value as a String.
     *
     * @return the current value.
     */
    public final String getValue() {
        return (hasValue?val:null);
    }

    /**
     * Set the current value.
     *
     * @param newVal the new value.
     */
    public final void setValue(String newVal) {
        val = newVal;
	hasValue = true;
    }

    /**
     * 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 "String";
    }

    /**
     * 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.
     * @see BaseType#printVal(PrintWriter, String, boolean)
     */
    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
        if (print_decl_p) {
            printDecl(os, space, false);
            os.println(" = \"" + Util.escattr(val) + "\";");
        } else
            os.print("\"" + Util.escattr(val) + "\"");
    }

    /**
     * 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.
     * @param statusUI the StatusUI object to use for GUI updates
     *                 and user cancellation notification (may be null).
     * @throws EOFException      if EOF is found before the variable is completely
     *                           deserialized.
     * @throws IOException       thrown on any other InputStream exception.
     * @throws DataReadException if a negative string length was read.
     * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    public synchronized void deserialize(DataInputStream source,
                                         ServerVersion sv,
                                         StatusUI statusUI)
            throws IOException, EOFException, DataReadException {
        int dap_len = source.readInt();

        //LogStream.out.println("DString deserialize string dap_length: "+ dap_len);

        if (dap_len < 0)
            throw new DataReadException("Negative string length (dap_length: "+ dap_len +") read.");
        if (dap_len > Short.MAX_VALUE)
            throw new DataReadException("DString deserialize string length (dap_length: "+ dap_len +") too large.");

        int modFour = dap_len % 4;
        // number of bytes to pad
        int pad = (modFour != 0) ? (4 - modFour) : 0;

        byte byteArray[] = new byte[dap_len];

        // With blackdown JDK1.1.8v3 (comes with matlab 6) read() didn't always
        // finish reading a string.  readFully() insures that it gets all <dap_len>
        // characters it requested.  rph 08/20/01.

        //source.read(byteArray, 0, dap_len);
        source.readFully(byteArray, 0, dap_len);

        // pad out to a multiple of four bytes
        byte unused;
        for (int i = 0; i < pad; i++)
            unused = source.readByte();

        if (statusUI != null)
            statusUI.incrementByteCount(4 + dap_len + pad);

        // convert bytes to a new String using ISO8859_1 (Latin 1) encoding.
        // This was chosen because it converts each byte to its Unicode value
        // with no translation (the first 256 glyphs in Unicode are ISO8859_1)
        try {
            val = new String(byteArray, 0, dap_len, "ISO8859_1");
            hasValue = true;
        }
        catch (UnsupportedEncodingException e) {
            // this should never happen
            throw new UnsupportedEncodingException("ISO8859_1 encoding not supported by this VM!");
        }
    }

    /**
     * Writes data to a DataOutputStream. This method is used
     * primarily by GUI clients which need to download OPeNDAP data, manipulate
     * it, and then re-save 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 {
        // convert String to a byte array using ISO8859_1 (Latin 1) encoding.
        // This was chosen because it converts each byte to its Unicode value
        // with no translation (the first 256 glyphs in Unicode are ISO8859_1)

        try {
            byte byteArray[] = val.getBytes("ISO8859_1");
            sink.writeInt(byteArray.length);
            int modFour = byteArray.length % 4;
            // number of bytes to pad
            int pad = (modFour != 0) ? (4 - modFour) : 0;
            sink.write(byteArray, 0, byteArray.length);
            for (int i = 0; i < pad; i++) {
                sink.writeByte(0);
            }
        }
        catch (UnsupportedEncodingException e) {
            // this should never happen
            throw new UnsupportedEncodingException("ISO8859_1 encoding not supported by this VM!");
        }
    }

    public void printConstraint(PrintWriter os)
    {
	if(hasValue) {
            String s = String.format("%s",val);
            s = ('"' +  Util.escattr(s) + '"');
	    os.print(s);
	} else
	    super.printConstraint(os);
    }

}


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

import ucar.nc2.util.Misc;
import ucar.unidata.util.Format;

import java.util.Formatter;

/**
 * Implementation of ProjectionPoint
 *
 * @author John Caron
 * @see ProjectionPoint
 */
public class ProjectionPointImpl implements ProjectionPoint, java.io.Serializable {
  private double x, y;

  /**
   * Default constructor, initialized to 0,0
   */
  public ProjectionPointImpl() {
    this(0.0, 0.0);
  }

  /**
   * Constructor that copies ProjectionPoint values into this.
   *
   * @param pt point to copy
   */
  public ProjectionPointImpl(ProjectionPoint pt) {
    this.x = pt.getX();
    this.y = pt.getY();
  }

  /**
   * Constructor, initialized to x, y
   *
   * @param x x coordinate
   * @param y y coordinate
   */
  public ProjectionPointImpl(double x, double y) {
    this.x = x;
    this.y = y;
  }

  public double getX() {
    return x;
  }

  public double getY() {
    return y;
  }

  public void setX(double x) {
    this.x = x;
  }

  public void setY(double y) {
    this.y = y;
  }

  @Override
  public boolean nearlyEquals(ProjectionPoint other, double maxRelDiff) {
    return Misc.nearlyEquals(x, other.getX(), maxRelDiff) && Misc.nearlyEquals(y, other.getY(), maxRelDiff);
  }

  // Exact comparison is needed in order to be consistent with hashCode().
  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    ProjectionPointImpl that = (ProjectionPointImpl) o;
    if (Double.compare(that.x, x) != 0) {
      return false;
    }
    return Double.compare(that.y, y) == 0;
  }

  @Override
  public int hashCode() {
    int result;
    long temp;
    temp = Double.doubleToLongBits(x);
    result = (int) (temp ^ (temp >>> 32));
    temp = Double.doubleToLongBits(y);
    result = 31 * result + (int) (temp ^ (temp >>> 32));
    return result;
  }

  /**
   * nicely format this point
   *
   * @return nicely formatted point
   */
  public String toString() {
    return Format.d(getX(), 4) + " " + Format.d(getY(), 4);
  }

  public void toString(Formatter f) {
    f.format("x=%f y=%f ", getX(), getY());
  }

  // convenience setting routines

  /**
   * set x,y location from  pt
   *
   * @param pt point to use for values
   */
  public void setLocation(ProjectionPoint pt) {
    setLocation(pt.getX(), pt.getY());
  }

  public void setLocation(double x, double y) {
       this.x = x;
       this.y = y;
   }

  /**
   * See if either coordinate is +/- infinite. This happens sometimes
   * in projective geometry.
   *
   * @return true if either coordinate is +/- infinite.
   */
  public boolean isInfinite() {
    return (x == java.lang.Double.POSITIVE_INFINITY)
        || (x == java.lang.Double.NEGATIVE_INFINITY)
        || (y == java.lang.Double.POSITIVE_INFINITY)
        || (y == java.lang.Double.NEGATIVE_INFINITY);
  }

  /**
   * See if either coordinate in pt is +/- infinite.
   * This happens sometimes in projective geometry.
   *
   * @param pt point to check
   * @return true if either coordinate is +/- infinite.
   */
  static public boolean isInfinite(ProjectionPoint pt) {
    return (pt.getX() == java.lang.Double.POSITIVE_INFINITY)
        || (pt.getX() == java.lang.Double.NEGATIVE_INFINITY)
        || (pt.getY() == java.lang.Double.POSITIVE_INFINITY)
        || (pt.getY() == java.lang.Double.NEGATIVE_INFINITY);
  }
}