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

import ucar.nc2.constants.CDM;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.io.PositioningDataInputStream;
import ucar.ma2.*;
import ucar.nc2.ParsedSectionSpec;
import ucar.nc2.Variable;
import ucar.nc2.Structure;
import ucar.nc2.stream.NcStream;

import java.io.OutputStream;
import java.nio.*;
import java.nio.channels.WritableByteChannel;
import java.nio.channels.Channels;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import java.util.ArrayList;

/**
 * Helper methods for IOSP's
 *
 * @author caron
 * @since Jan 3, 2008
 */
public class IospHelper {
  static private boolean showLayoutTypes = false;

  /**
   * Read data subset from RandomAccessFile, create primitive array of size Layout.getTotalNelems.
   * Reading is controlled by the Layout object.
   *
   * @param raf       read from here.
   * @param index     handles skipping around in the file.
   * @param dataType  dataType of the variable
   * @param fillValue must be Number if dataType.isNumeric(), or String for STRING, byte[] for Structure, or null for none
   * @param byteOrder if equal to RandomAccessFile.ORDER_XXXX, set the byte order just before reading
   * @return primitive array with data read in
   * @throws java.io.IOException on read error
   */
  static public Object readDataFill(RandomAccessFile raf, Layout index, DataType dataType, Object fillValue,
                                    int byteOrder) throws java.io.IOException {
    Object arr = (fillValue == null) ? makePrimitiveArray((int) index.getTotalNelems(), dataType) :
            makePrimitiveArray((int) index.getTotalNelems(), dataType, fillValue);
    return readData(raf, index, dataType, arr, byteOrder, true);
  }

  static public Object readDataFill(RandomAccessFile raf, Layout index, DataType dataType, Object fillValue,
                                    int byteOrder, boolean convertChar) throws java.io.IOException {
    Object arr = (fillValue == null) ? makePrimitiveArray((int) index.getTotalNelems(), dataType) :
            makePrimitiveArray((int) index.getTotalNelems(), dataType, fillValue);
    return readData(raf, index, dataType, arr, byteOrder, convertChar);
  }

  /**
   * Read data subset from RandomAccessFile, place in given primitive array.
   * Reading is controlled by the Layout object.
   *
   * @param raf         read from here.
   * @param layout      handles skipping around in the file.
   * @param dataType    dataType of the variable
   * @param arr         primitive array to read data into
   * @param byteOrder   if equal to RandomAccessFile.ORDER_XXXX, set the byte order just before reading
   * @param convertChar true if bytes should be converted to char for dataType CHAR
   * @return primitive array with data read in
   * @throws java.io.IOException on read error
   */
  static public Object readData(RandomAccessFile raf, Layout layout, DataType dataType, Object arr, int byteOrder, boolean convertChar) throws java.io.IOException {
    if (showLayoutTypes) System.out.println("***RAF LayoutType=" + layout.getClass().getName());

    if (dataType.getPrimitiveClassType() == byte.class || dataType == DataType.CHAR) {
      byte[] pa = (byte[]) arr;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        raf.order(byteOrder);
        raf.seek(chunk.getSrcPos());
        raf.readFully(pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      if (convertChar && dataType == DataType.CHAR) return convertByteToChar(pa);
      else return pa; // javac ternary compile error

    } else if (dataType.getPrimitiveClassType() == short.class) {
      short[] pa = (short[]) arr;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        raf.order(byteOrder);
        raf.seek(chunk.getSrcPos());
        raf.readShort(pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType.getPrimitiveClassType() == int.class) {
      int[] pa = (int[]) arr;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        raf.order(byteOrder);
        raf.seek(chunk.getSrcPos());
        raf.readInt(pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType == DataType.FLOAT) {
      float[] pa = (float[]) arr;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        raf.order(byteOrder);
        raf.seek(chunk.getSrcPos());
        raf.readFloat(pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType == DataType.DOUBLE) {
      double[] pa = (double[]) arr;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        raf.order(byteOrder);
        raf.seek(chunk.getSrcPos());
        raf.readDouble(pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType.getPrimitiveClassType() == long.class) {
      long[] pa = (long[]) arr;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        raf.order(byteOrder);
        raf.seek(chunk.getSrcPos());
        raf.readLong(pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType == DataType.STRUCTURE) {
      byte[] pa = (byte[]) arr;
      int recsize = layout.getElemSize();
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        raf.order(byteOrder);
        raf.seek(chunk.getSrcPos());
        raf.readFully(pa, (int) chunk.getDestElem() * recsize, chunk.getNelems() * recsize);
      }
      return pa;
    }

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

  /**
   * Read data subset from PositioningDataInputStream, create primitive array of size Layout.getTotalNelems.
   * Reading is controlled by the Layout object.
   *
   * @param is        read from here.
   * @param index     handles skipping around in the file.
   * @param dataType  dataType of the variable
   * @param fillValue must be Number if dataType.isNumeric(), or String for STRING, byte[] for Structure, or null for none
   * @return primitive array with data read in
   * @throws java.io.IOException on read error
   */
  static public Object readDataFill(PositioningDataInputStream is, Layout index, DataType dataType, Object fillValue) throws java.io.IOException {
    Object arr = (fillValue == null) ? makePrimitiveArray((int) index.getTotalNelems(), dataType) :
            makePrimitiveArray((int) index.getTotalNelems(), dataType, fillValue);
    return readData(is, index, dataType, arr);
  }

  /**
   * Read data subset from PositioningDataInputStream, place in given primitive array.
   * Reading is controlled by the Layout object.
   *
   * @param raf      read from here.
   * @param index    handles skipping around in the file.
   * @param dataType dataType of the variable
   * @param arr      primitive array to read data into
   * @return primitive array with data read in
   * @throws java.io.IOException on read error
   */
  static public Object readData(PositioningDataInputStream raf, Layout index, DataType dataType, Object arr) throws java.io.IOException {
    if (showLayoutTypes) System.out.println("***PositioningDataInputStream LayoutType=" + index.getClass().getName());

    if (dataType.getPrimitiveClassType() == byte.class || dataType == DataType.CHAR) {
      byte[] pa = (byte[]) arr;
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.read(chunk.getSrcPos(), pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      //return (dataType == DataType.CHAR) ? convertByteToChar(pa) : pa;
      if (dataType == DataType.CHAR) return convertByteToChar(pa);
      else return pa;

    } else if (dataType.getPrimitiveClassType() == short.class) {
      short[] pa = (short[]) arr;
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.readShort(chunk.getSrcPos(), pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType.getPrimitiveClassType() == int.class) {
      int[] pa = (int[]) arr;
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.readInt(chunk.getSrcPos(), pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType == DataType.FLOAT) {
      float[] pa = (float[]) arr;
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.readFloat(chunk.getSrcPos(), pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType == DataType.DOUBLE) {
      double[] pa = (double[]) arr;
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.readDouble(chunk.getSrcPos(), pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType.getPrimitiveClassType() == long.class) {
      long[] pa = (long[]) arr;
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.readLong(chunk.getSrcPos(), pa, (int) chunk.getDestElem(), chunk.getNelems());
      }
      return pa;

    } else if (dataType == DataType.STRUCTURE) {
      int recsize = index.getElemSize();
      byte[] pa = (byte[]) arr;
      while (index.hasNext()) {
        Layout.Chunk chunk = index.next();
        raf.read(chunk.getSrcPos(), pa, (int) chunk.getDestElem() * recsize, chunk.getNelems() * recsize);
      }
      return pa;
    }

    throw new IllegalStateException();
  } //

  /**
   * Read data subset from ByteBuffer, create primitive array of size Layout.getTotalNelems.
   * Reading is controlled by the Layout object.
   *
   * @param layout    handles skipping around in the file, provide ByteBuffer to read from
   * @param dataType  dataType of the variable
   * @param fillValue must be Number if dataType.isNumeric(), or String for STRING, byte[] for Structure, or null for none
   * @return primitive array with data read in
   * @throws java.io.IOException on read error
   */
  static public Object readDataFill(LayoutBB layout, DataType dataType, Object fillValue) throws java.io.IOException {
    long size = layout.getTotalNelems();
    if (dataType == DataType.STRUCTURE) size *= layout.getElemSize();
    Object arr = (fillValue == null) ? makePrimitiveArray((int) size, dataType) :
            makePrimitiveArray((int) size, dataType, fillValue);
    return readData(layout, dataType, arr);
  }

  /**
   * Read data subset from ByteBuffer, place in given primitive array.
   * Reading is controlled by the LayoutBB object.
   *
   * @param layout   handles skipping around in the file, privide ByteBuffer to read from
   * @param dataType dataType of the variable
   * @param arr      primitive array to read data into
   * @return the primitive array with data read in
   * @throws java.io.IOException on read error
   */
  static public Object readData(LayoutBB layout, DataType dataType, Object arr) throws java.io.IOException {
    if (showLayoutTypes) System.out.println("***BB LayoutType=" + layout.getClass().getName());

    if (dataType.getPrimitiveClassType() == byte.class || (dataType == DataType.CHAR)) {
      byte[] pa = (byte[]) arr;
      while (layout.hasNext()) {
        LayoutBB.Chunk chunk = layout.next();
        ByteBuffer bb = chunk.getByteBuffer();
        bb.position(chunk.getSrcElem());
        int pos = (int) chunk.getDestElem();
        for (int i = 0; i < chunk.getNelems(); i++)
          pa[pos++] = bb.get();
      }
      //return (dataType == DataType.CHAR) ? convertByteToChar(pa) : pa;
      if (dataType == DataType.CHAR) return convertByteToChar(pa);
      else return pa;

    } else if (dataType.getPrimitiveClassType() == short.class) {
      short[] pa = (short[]) arr;
      while (layout.hasNext()) {
        LayoutBB.Chunk chunk = layout.next();
        ShortBuffer buff = chunk.getShortBuffer();
        buff.position(chunk.getSrcElem());
        int pos = (int) chunk.getDestElem();
        for (int i = 0; i < chunk.getNelems(); i++)
          pa[pos++] = buff.get();
      }
      return pa;

    } else if (dataType.getPrimitiveClassType() == int.class) {
      int[] pa = (int[]) arr;
      while (layout.hasNext()) {
        LayoutBB.Chunk chunk = layout.next();
        IntBuffer buff = chunk.getIntBuffer();
        buff.position(chunk.getSrcElem());
        int pos = (int) chunk.getDestElem();
        for (int i = 0; i < chunk.getNelems(); i++)
          pa[pos++] = buff.get();
      }
      return pa;

    } else if (dataType == DataType.FLOAT) {
      float[] pa = (float[]) arr;
      while (layout.hasNext()) {
        LayoutBB.Chunk chunk = layout.next();
        FloatBuffer buff = chunk.getFloatBuffer();
        buff.position(chunk.getSrcElem());
        int pos = (int) chunk.getDestElem();
        for (int i = 0; i < chunk.getNelems(); i++)
          pa[pos++] = buff.get();
      }
      return pa;

    } else if (dataType == DataType.DOUBLE) {
      double[] pa = (double[]) arr;
      while (layout.hasNext()) {
        LayoutBB.Chunk chunk = layout.next();
        DoubleBuffer buff = chunk.getDoubleBuffer();
        buff.position(chunk.getSrcElem());
        int pos = (int) chunk.getDestElem();
        for (int i = 0; i < chunk.getNelems(); i++)
          pa[pos++] = buff.get();
      }
      return pa;

    } else if (dataType.getPrimitiveClassType() == long.class) {
      long[] pa = (long[]) arr;
      while (layout.hasNext()) {
        LayoutBB.Chunk chunk = layout.next();
        LongBuffer buff = chunk.getLongBuffer();
        buff.position(chunk.getSrcElem());
        int pos = (int) chunk.getDestElem();
        for (int i = 0; i < chunk.getNelems(); i++)
          pa[pos++] = buff.get();
      }
      return pa;

    } else if (dataType == DataType.STRUCTURE) {
      byte[] pa = (byte[]) arr;
      int recsize = layout.getElemSize();
      while (layout.hasNext()) {
        LayoutBB.Chunk chunk = layout.next();
        ByteBuffer bb = chunk.getByteBuffer();
        bb.position(chunk.getSrcElem() * recsize);
        int pos = (int) chunk.getDestElem() * recsize;
        for (int i = 0; i < chunk.getNelems() * recsize; i++)
          pa[pos++] = bb.get();
      }
      return pa;
    }

    throw new IllegalStateException();
  }

  /**
   * Copy data to a channel. Used by ncstream. Not doing Structures correctly yet.
   *
   * @param data    copy from here
   * @param channel copy to here
   * @return number of bytes copied
   * @throws java.io.IOException on write error
   */
  public static long copyToByteChannel(Array data, WritableByteChannel channel) throws java.io.IOException {
    Class classType = data.getElementType();

    /* if (data instanceof ArrayStructure) { // use NcStream encoding
      DataOutputStream os = new DataOutputStream(Channels.newOutputStream(channel));
      return NcStream.encodeArrayStructure((ArrayStructure) data, null, os);
    } */

    DataOutputStream outStream = new DataOutputStream(Channels.newOutputStream(channel));
    IndexIterator iterA = data.getIndexIterator();

    if (classType == double.class) {
      while (iterA.hasNext())
        outStream.writeDouble(iterA.getDoubleNext());

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

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

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

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

    } else if (classType == char.class) {  // LOOK why are we using chars anyway ?
      byte[] pa = convertCharToByte((char[]) data.get1DJavaArray(DataType.CHAR));
      outStream.write(pa, 0, pa.length);

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

    } else if (classType == boolean.class) {
      while (iterA.hasNext())
        outStream.writeBoolean(iterA.getBooleanNext());

    } else if (classType == String.class) {
      long size = 0;
      while (iterA.hasNext()) {
        String s = (String) iterA.getObjectNext();
        size += NcStream.writeVInt(outStream, s.length());
        byte[] b = s.getBytes(CDM.utf8Charset);
        outStream.write(b);
        size += b.length;
      }
      return size;

    } else if (classType == ByteBuffer.class) { // OPAQUE
      long size = 0;
      while (iterA.hasNext()) {
        ByteBuffer bb = (ByteBuffer) iterA.getObjectNext();
        size += NcStream.writeVInt(outStream, bb.limit());
        bb.rewind();
        channel.write(bb);
        size += bb.limit();
      }
      return size;

    } else if (data instanceof ArrayObject) { // vlen
      long size = 0;
      //size += NcStream.writeVInt(outStream, (int) data.getSize()); // nelems already written
      while (iterA.hasNext()) {
        Array row = (Array) iterA.getObjectNext();
        ByteBuffer bb = row.getDataAsByteBuffer();
        byte[] result = bb.array();
        size += NcStream.writeVInt(outStream, result.length); // size in bytes
        outStream.write(result);  // array
        size += result.length;
      }
      return size;

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

    return data.getSizeBytes();
  }

  /**
   * Copy data to a OutputStream. Used by ncstream. Not doing Structures correctly yet.
   *
   * @param data    copy from here
   * @param out     copy to here
   * @return number of bytes copied
   * @throws java.io.IOException on write error
   */
  public static long copyToOutputStream(Array data, OutputStream out) throws java.io.IOException {
    Class classType = data.getElementType();

    DataOutputStream dataOut;
    if (out instanceof DataOutputStream)
      dataOut = (DataOutputStream) out;
    else
      dataOut = new DataOutputStream(out);

    /* if (data instanceof ArrayStructure) { // use NcStream encoding
      return NcStream.encodeArrayStructure((ArrayStructure) data, null, dataOut);
    } */

    IndexIterator iterA = data.getIndexIterator();

    if (classType == double.class) {
      while (iterA.hasNext())
        dataOut.writeDouble(iterA.getDoubleNext());

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

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

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

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

    } else if (classType == char.class) {  // LOOK why are we using chars anyway ?
      byte[] pa = convertCharToByte((char[]) data.get1DJavaArray(DataType.CHAR));
      dataOut.write(pa, 0, pa.length);

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

    } else if (classType == boolean.class) {
      while (iterA.hasNext())
        dataOut.writeBoolean(iterA.getBooleanNext());

    } else if (classType == String.class) {
      long size = 0;
      while (iterA.hasNext()) {
        String s = (String) iterA.getObjectNext();
        size += NcStream.writeVInt(dataOut, s.length());
        byte[] b = s.getBytes(CDM.utf8Charset);
        dataOut.write(b);
        size += b.length;
      }
      return size;

    } else if (classType == ByteBuffer.class) { // OPAQUE
      long size = 0;
      while (iterA.hasNext()) {
        ByteBuffer bb = (ByteBuffer) iterA.getObjectNext();
        size += NcStream.writeVInt(dataOut, bb.limit());
        bb.rewind();
        dataOut.write(bb.array());
        size += bb.limit();
      }
      return size;

    } else if (data instanceof ArrayObject) { // vlen
      long size = 0;
      //size += NcStream.writeVInt(outStream, (int) data.getSize()); // nelems already written
      while (iterA.hasNext()) {
        Array row = (Array) iterA.getObjectNext();
        ByteBuffer bb = row.getDataAsByteBuffer();
        byte[] result = bb.array();
        size += NcStream.writeVInt(dataOut, result.length); // size in bytes
        dataOut.write(result);  // array
        size += result.length;
      }
      return size;

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

    return data.getSizeBytes();
  }

  /**
   * @deprecated use StructureDataDeep.copyToArrayBB
   */
  static public ArrayStructureBB makeArrayBB(ArrayStructure as) throws IOException {
    if (as.getClass().equals(ArrayStructureBB.class)) // no subclasses
      return (ArrayStructureBB) as;

    StructureMembers smo = as.getStructureMembers();
    StructureMembers sm = new StructureMembers(smo);
    ArrayStructureBB abb = new ArrayStructureBB(sm, as.getShape());
    ArrayStructureBB.setOffsets(sm);

    try (StructureDataIterator iter = as.getStructureDataIterator()) {
      while (iter.hasNext())
        StructureDataDeep.copyToArrayBB(iter.next(), abb);
    }
    return abb;
  }

  /**
   * @deprecated use StructureDataDeep.copyToArrayBB
   */
  static public ArrayStructureBB copyToArrayBB(StructureData sdata) {
    StructureMembers sm = new StructureMembers(sdata.getStructureMembers());
    int size = sm.getStructureSize();
    ByteBuffer bb = ByteBuffer.allocate(size); // default is big endian
    ArrayStructureBB abb = new ArrayStructureBB(sm, new int[]{1}, bb, 0);
    ArrayStructureBB.setOffsets(sm);
    StructureDataDeep.copyToArrayBB(sdata, abb);
    return abb;
  }

   /**
   * Create 1D primitive array of the given size and type
   *
   * @param size     the size of the array to create
   * @param dataType dataType of the variable
   * @return primitive array with data read in
   */
  static public Object makePrimitiveArray(int size, DataType dataType) {
    Object arr = null;

    if ((dataType.getPrimitiveClassType() == byte.class) || (dataType == DataType.CHAR) || (dataType == DataType.OPAQUE) || (dataType == DataType.STRUCTURE)) {
      arr = new byte[size];

    } else if (dataType.getPrimitiveClassType() == short.class) {
      arr = new short[size];

    } else if (dataType.getPrimitiveClassType() == int.class) {
      arr = new int[size];

    } else if (dataType.getPrimitiveClassType() == long.class) {
      arr = new long[size];

    } else if (dataType == DataType.FLOAT) {
      arr = new float[size];

    } else if (dataType == DataType.DOUBLE) {
      arr = new double[size];

   } else if (dataType == DataType.STRING) {
      arr = new String[size];
    }

    return arr;
  }

  /**
   * Create 1D primitive array of the given size and type, fill it with the given value
   *
   * @param size      the size of the array to create
   * @param dataType  dataType of the variable
   * @param fillValue must be Number if dataType.isNumeric(), or String for STRING, byte[] for Structure, or null for none
   * @return primitive array with data read in
   */
  static public Object makePrimitiveArray(int size, DataType dataType, Object fillValue) {

    if (dataType.getPrimitiveClassType() == byte.class || (dataType == DataType.CHAR)) {
      byte[] pa = new byte[size];
      byte val = ((Number) fillValue).byteValue();
      if (val != 0)
        for (int i = 0; i < size; i++) pa[i] = val;
      // if (dataType == DataType.CHAR) return convertByteToChar(pa);
      return pa;

    } else if (dataType == DataType.OPAQUE) {
      return new byte[size];

    } else if (dataType.getPrimitiveClassType() == short.class) {
      short[] pa = new short[size];
      short val = ((Number) fillValue).shortValue();
      if (val != 0)
        for (int i = 0; i < size; i++) pa[i] = val;
      return pa;

    } else if (dataType.getPrimitiveClassType() == int.class) {
      int[] pa = new int[size];
      int val = ((Number) fillValue).intValue();
      if (val != 0)
        for (int i = 0; i < size; i++) pa[i] = val;
      return pa;

    } else if (dataType.getPrimitiveClassType() == long.class) {
      long[] pa = new long[size];
      long val = ((Number) fillValue).longValue();
      if (val != 0)
        for (int i = 0; i < size; i++) pa[i] = val;
      return pa;

    } else if (dataType == DataType.FLOAT) {
      float[] pa = new float[size];
      float val = ((Number) fillValue).floatValue();
      if (val != 0.0)
        for (int i = 0; i < size; i++) pa[i] = val;
      return pa;

    } else if (dataType == DataType.DOUBLE) {
      double[] pa = new double[size];
      double val = ((Number) fillValue).doubleValue();
      if (val != 0.0)
        for (int i = 0; i < size; i++) pa[i] = val;
      return pa;

    } else if (dataType == DataType.STRING) {
      String[] pa = new String[size];
      for (int i = 0; i < size; i++) pa[i] = (String) fillValue;
      return pa;

    } else if (dataType == DataType.STRUCTURE) {
      byte[] pa = new byte[size];
      if (fillValue != null) {
        byte[] val = (byte[]) fillValue;
        int count = 0;
        while (count < size)
          for (byte aVal : val) pa[count++] = aVal;
      }
      return pa;
    }

    throw new IllegalStateException();
  }

    // convert byte array to char array, assuming UTF-8 encoding

  static public char[] convertByteToCharUTF(byte[] byteArray) {
    Charset c = CDM.utf8Charset;
    CharBuffer output = c.decode(ByteBuffer.wrap(byteArray));
    return output.array();
  }

  // convert char array to byte array, assuming UTF-8 encoding
  static public byte[] convertCharToByteUTF(char[] from) {
    Charset c = CDM.utf8Charset;
    ByteBuffer output = c.encode(CharBuffer.wrap(from));
    return output.array();
  }

  // convert byte array to char array

  static public char[] convertByteToChar(byte[] byteArray) {
    int size = byteArray.length;
    char[] cbuff = new char[size];
    for (int i = 0; i < size; i++)
      cbuff[i] = (char) DataType.unsignedByteToShort(byteArray[i]); // NOTE: not Unicode !
    return cbuff;
  }

  // convert char array to byte array

  static public byte[] convertCharToByte(char[] from) {
    byte[] to = null;
    if (from != null) {
      int size = from.length;
      to = new byte[size];
      for (int i = 0; i < size; i++)
        to[i] = (byte) from[i]; // LOOK wrong, convert back to unsigned byte ???
    }
    return to;
  }

  static public long transferData(Array result, WritableByteChannel channel)
          throws java.io.IOException, ucar.ma2.InvalidRangeException {

    // LOOK should we buffer ??
    DataOutputStream outStream = new DataOutputStream(Channels.newOutputStream(channel));

    IndexIterator iterA = result.getIndexIterator();
    Class classType = result.getElementType();

    if (classType == double.class) {
      while (iterA.hasNext())
        outStream.writeDouble(iterA.getDoubleNext());

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

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

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

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

    } else if (classType == char.class) {
      while (iterA.hasNext())
        outStream.writeChar(iterA.getCharNext());

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

    } else if (classType == boolean.class) {
      while (iterA.hasNext())
        outStream.writeBoolean(iterA.getBooleanNext());

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

    return 0;
  }

  // section reading for member data

  static public ucar.ma2.Array readSection(ParsedSectionSpec cer) throws IOException, InvalidRangeException {
    Variable inner = null;
    List<Range> totalRanges = new ArrayList<>();
    ParsedSectionSpec current = cer;
    while (current != null) {
      totalRanges.addAll(current.section.getRanges());
      inner = current.v;
      current = current.child;
    }
    assert inner != null;

    Section total = new Section(totalRanges);
    Array result = Array.factory(inner.getDataType(), total.getShape());

    // must be a Structure
    Structure outer = (Structure) cer.v;
    Structure outerSubset = outer.select(cer.child.v.getShortName()); // allows IOSPs to optimize for  this case
    ArrayStructure outerData = (ArrayStructure) outerSubset.read(cer.section);
    extractSection(cer.child, outerData, result.getIndexIterator());
    return result;
  }

  static private void extractSection(ParsedSectionSpec child, ArrayStructure outerData, IndexIterator to) throws IOException, InvalidRangeException {
    long wantNelems = child.section.computeSize();

    StructureMembers.Member m = outerData.findMember(child.v.getShortName());
    for (int recno = 0; recno < outerData.getSize(); recno++) {
      Array innerData = outerData.getArray(recno, m);

      if (child.child == null) {  // inner variable
        if (wantNelems != innerData.getSize())
          innerData = innerData.section(child.section.getRanges());
        MAMath.copy(child.v.getDataType(), innerData.getIndexIterator(), to);

      } else {                   // not an inner variable - must be an ArrayStructure

        if (innerData instanceof ArraySequence)
          extractSectionFromSequence(child.child, (ArraySequence) innerData, to);
        else {
          if (wantNelems != innerData.getSize())
            innerData = sectionArrayStructure(child, (ArrayStructure) innerData, m);
          extractSection(child.child, (ArrayStructure) innerData, to);
        }
      }
    }
  }

  static private void extractSectionFromSequence(ParsedSectionSpec child, ArraySequence outerData, IndexIterator to) throws IOException, InvalidRangeException {
    try (StructureDataIterator sdataIter = outerData.getStructureDataIterator()) {
      while (sdataIter.hasNext()) {
        StructureData sdata = sdataIter.next();
        StructureMembers.Member m = outerData.findMember(child.v.getShortName());
        Array innerData = sdata.getArray(child.v.getShortName());
        MAMath.copy(m.getDataType(), innerData.getIndexIterator(), to);
      }
    }
  }

  // LOOK could be used in createView ??

  static private ArrayStructure sectionArrayStructure(ParsedSectionSpec child, ArrayStructure innerData, StructureMembers.Member m) throws IOException, InvalidRangeException {
    StructureMembers membersw = new StructureMembers(m.getStructureMembers()); // no data arrays get propagated
    ArrayStructureW result = new ArrayStructureW(membersw, child.section.getShape());

    int count = 0;
    Section.Iterator iter = child.section.getIterator(child.v.getShape());
    while (iter.hasNext()) {
      int recno = iter.next(null);
      StructureData sd = innerData.getStructureData(recno);
      result.setStructureData(sd, count++);
    }

    return result;
  }

}


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

package dap4.cdm.nc2;

import dap4.cdm.CDMTypeFcns;
import dap4.cdm.CDMUtil;
import dap4.core.data.DSP;
import dap4.core.data.DataCursor;
import dap4.core.dmr.DapStructure;
import dap4.core.dmr.DapType;
import dap4.core.dmr.DapVariable;
import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import dap4.core.util.Slice;
import dap4.dap4lib.LibTypeFcns;
import ucar.ma2.*;
import ucar.nc2.Group;

import java.nio.ByteBuffer;
import java.util.List;

import static dap4.core.data.DataCursor.Scheme;

/**
 * Implementation of ArrayStructure that wraps
 * DAP4 databuffer
 * (Note: Needs serious optimization applied).
 *

 * Given
 * Structure S {f1,f2,...fm} [d1][d2]...[dn],
 * internally, this is stored as a 2-D array
 * CDMArray[][] instances;
 * The first dimension's length is d1*d2*...dn.
 * The second dimension has size |members| i.e. the number
 * of fields in the sequence.
 */

/*package*/ class
CDMArrayStructure extends ArrayStructure implements CDMArray
{
    //////////////////////////////////////////////////
    // Type decls

    /**
     * We need to keep a map of index X fieldno -> Array
     * representing the Array behind each field for each
     * struct instance in a matrix of struct instances.
     * To promote some clarity a eschew Array[|dimset|][|fields|]
     * in favor of FieldArrays[|dimset|].
     */
    static protected class FieldArrays
    {
         public Array[] fields; // Make externally accessible
        FieldArrays(int nfields)
        {
            fields = new Array[nfields];
        }

    }

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

    // CDMArry variables
    protected Group cdmroot = null;
    protected DSP dsp = null;
    protected DapVariable template = null;
    protected DapType basetype = null;
    protected long dimsize = 0;
    protected int nmembers = 0;

    protected DataCursor data = null;

    /**
     * Since we are using StructureDataA,
     * we store a list Field sets
     * So we have a map: index -> Field object.
     * Total number of objects is dimsize.
     * Accessed by calls from StructureDataA.
     * Note: We use the super.sdata field to store
     * the StructureData instances.
     */

    // Note: term records here means the elements of the array,
    // not record as in Sequence

    protected FieldArrays[] records = null; // list of Structure elements

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

    /**
     * Constructor
     *
     * @param cdmroot the parent CDMDataset
     * @param data    the structure data
     */
    CDMArrayStructure(Group cdmroot, DataCursor data)
    {
        super(computemembers((DapVariable) data.getTemplate()),
                CDMUtil.computeEffectiveShape(((DapVariable) data.getTemplate()).getDimensions()));
        this.template = (DapVariable) data.getTemplate();
        assert data.getScheme() == Scheme.STRUCTARRAY;
        this.dsp = data.getDSP();
        this.cdmroot = cdmroot;
        this.basetype = this.template.getBaseType();
        this.dimsize = DapUtil.dimProduct(template.getDimensions());
        this.nmembers = ((DapStructure) template.getBaseType()).getFields().size();

        this.data = data;

        // Fill in the structdata (in parent) and instance vectors
        super.sdata = new StructureDataA[(int) this.dimsize];
        records = new FieldArrays[(int) this.dimsize];
        for(int i = 0; i < dimsize; i++) {
            super.sdata[i] = new StructureDataA(this, i);
            records[i] = new FieldArrays(this.nmembers);
        }
    }

    /**
     *
     * @param recno   struct instance
     * @param fieldno  field of that struct
     * @param field   Array backing this field in this struct instance
     */
    /*package*/
    void
    add(long recno, int fieldno, Array field)
    {
        FieldArrays fs = records[(int) recno];
        if(fs == null)
            records[(int) recno] = (fs = new FieldArrays(this.nmembers));
        fs.fields[fieldno] = field;
    }
    //////////////////////////////////////////////////
    // CDMArray Interface

    @Override
    public DSP getDSP()
    {
        return this.dsp;
    }

    @Override
    public DapVariable getTemplate()
    {
        return this.template;
    }

    @Override
    public DapType getBaseType()
    {
        return this.basetype;
    }

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

    @Override
    public long getSize()
    {
        return this.dimsize;
    }

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

    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        DapVariable var = (DapVariable) this.template;
        DapStructure struct = (DapStructure) var.getBaseType();
        for(int i = 0; i < this.dimsize; i++) {
            List<DapVariable> fields = struct.getFields();
            if(i < (this.dimsize - 1))
                buf.append("\n");
            buf.append("Structure {\n");
            if(fields != null) {
                for(int j = 0; j < this.nmembers; j++) {
                    Array field = records[i].fields[j];
                    String sfield = (field == null ? "null" : fields.toString());
                    buf.append(sfield + "\n");
                }
            }
            buf.append(String.format("} [%d/%d]", i, dimsize));
        }
        return buf.toString();
    }

    //////////////////////////////////////////////////
    // ArrayStructure interface

    /**
     * Get the index'th StructureData(StructureDataA) object
     * We need instances of StructureData to give to the user.
     * We use StructureDataA so we can centralize everything
     * in this class. The total number of StructureData objects
     * is dimsize.
     *
     * @param index
     * @return
     */
    @Override
    public StructureData
    getStructureData(int index)
    {
        assert (super.sdata != null);
        if(index < 0 || index >= this.dimsize)
            throw new IllegalArgumentException(index + " >= " + super.sdata.length);
        assert (super.sdata[index] != null);
        return super.sdata[index];
    }

    public double getScalarDouble(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic data = getAtomicArray(index, m);
        return data.getDouble(0);
    }

    public float
    getScalarFloat(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic data = getAtomicArray(index, m);
        return data.getFloat(0);
    }

    public byte
    getScalarByte(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic data = getAtomicArray(index, m);
        return data.getByte(0);
    }

    public short
    getScalarShort(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic data = getAtomicArray(index, m);
        return data.getShort(0);
    }

    public int
    getScalarInt(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic data = getAtomicArray(index, m);
        return data.getInt(0);
    }

    public long
    getScalarLong(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic data = getAtomicArray(index, m);
        return data.getLong(0);
    }

    public char
    getScalarChar(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic data = getAtomicArray(index, m);
        return data.getChar(0);
    }

    /**
     * Get member databuffer of type String or char.
     *
     * @param recnum get databuffer from the recnum-th StructureData of the ArrayStructure. Must be less than getSize();
     * @param m      get databuffer from this StructureMembers.Member. Must be of type String or char.
     * @return scalar String value
     */
    public String getScalarString(int recnum, StructureMembers.Member m)
    {
        Array data = m.getDataArray();
        return (String) data.getObject(recnum).toString();
    }

    public double[]
    getJavaArrayDouble(int recnum, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(recnum, m);
        if(!array.getBaseType().isNumericType())
            throw new IllegalArgumentException("Cannot convert non-numeric type");
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (double[]) LibTypeFcns.convertVector(DapType.FLOAT64, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public float[]
    getJavaArrayFloat(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        if(!array.getBaseType().isNumericType())
            throw new IllegalArgumentException("Cannot convert non-numeric type");
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (float[]) LibTypeFcns.convertVector(DapType.FLOAT32, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public byte[]
    getJavaArrayByte(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        if(!array.getBaseType().isNumericType())
            throw new IllegalArgumentException("Cannot convert non-numeric type");
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (byte[]) LibTypeFcns.convertVector(DapType.INT8, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public short[]
    getJavaArrayShort(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        if(!array.getBaseType().isNumericType())
            throw new IllegalArgumentException("Cannot convert non-numeric type");
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (short[]) LibTypeFcns.convertVector(DapType.INT16, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public int[]
    getJavaArrayInt(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        if(!array.getBaseType().isNumericType())
            throw new IllegalArgumentException("Cannot convert non-numeric type");
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (int[]) LibTypeFcns.convertVector(DapType.INT32, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public long[]
    getJavaArrayLong(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        if(!array.getBaseType().isNumericType())
            throw new IllegalArgumentException("Cannot convert non-numeric type");
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (long[]) LibTypeFcns.convertVector(DapType.INT64, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public char[]
    getJavaArrayChar(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        if(!array.getBaseType().isNumericType())
            throw new IllegalArgumentException("Cannot convert non-numeric type");
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (char[]) LibTypeFcns.convertVector(DapType.CHAR, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public String[]
    getJavaArrayString(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (String[]) LibTypeFcns.convertVector(DapType.STRING, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    public ByteBuffer[]
    getJavaArrayOpaque(int index, StructureMembers.Member m)
    {
        CDMArrayAtomic array = getAtomicArray(index, m);
        DapType atomtype = array.getBaseType();
        try {
            List<Slice> slices = CDMUtil.shapeToSlices(m.getShape());
            Object vector = data.read(slices);
            return (ByteBuffer[]) LibTypeFcns.convertVector(DapType.OPAQUE, atomtype, vector);
        } catch (DapException de) {
            throw new UnsupportedOperationException(de);
        }
    }

    // Non-atomic cases

    public StructureData
    getScalarStructure(int index, StructureMembers.Member m)
    {
        if(m.getDataType() != DataType.STRUCTURE)
            throw new ForbiddenConversionException("Atomic field cannot be converted to Structure");
        Array ca = memberArray(index, memberIndex(m));
        if(ca.getDataType() != DataType.STRUCTURE && ca.getDataType() != DataType.SEQUENCE)
            throw new ForbiddenConversionException("Attempt to access non-structure member");
        CDMArrayStructure as = (CDMArrayStructure) ca;
        return as.getStructureData(0);
    }

    public ArrayStructure
    getArrayStructure(int index, StructureMembers.Member m)
    {
        if(m.getDataType() != DataType.STRUCTURE)
            throw new ForbiddenConversionException("Atomic field cannot be converted to Structure");
        Array dd = memberArray(index, memberIndex(m));
        if(dd.getDataType() != DataType.STRUCTURE && dd.getDataType() != DataType.SEQUENCE)
            throw new ForbiddenConversionException("Attempt to access non-structure member");
        return (CDMArrayStructure) dd;
    }

    public ArraySequence
    getArraySequence(StructureMembers.Member m)
    {
        throw new UnsupportedOperationException("CDMArraySequence");
    }

    @Override
    public Array copy()
    {
        return this; // temporary
    }

    /////////////////////////
    // Define API required by StructureDataA

    /**
     * Key interface method coming in from StructureDataA.
     *
     * @param recno The instance # of the array of Structure instances
     * @param m     The member of interest in the Structure instance
     * @return The ucar.ma2.Array instance corresponding to the instance.
     *

     * Hidden: friend of StructureDataA
     */
    @Override
    public ucar.ma2.Array
    getArray(int recno, StructureMembers.Member m)
    {
        return (ucar.ma2.Array) memberArray(recno, memberIndex(m));
    }

    //////////////////////////////////////////////////
    // Utilities

    @Override
    protected StructureData
    makeStructureData(ArrayStructure as, int index)
    {
        if(super.sdata[index] == null)
            super.sdata[index] = new StructureDataA(as, index);
        return super.sdata[index];
    }

    /**
     * Compute the StructureMembers object
     * from a DapStructure. May need to recurse
     * if a field is itself a Structure
     *
     * @param var The DapVariable to use to construct
     *           a StructureMembers object.
     * @return The StructureMembers object for the given DapStructure
     */
    static StructureMembers
    computemembers(DapVariable var)
    {
        DapStructure ds = (DapStructure)var.getBaseType();
        StructureMembers sm
                = new StructureMembers(ds.getShortName());
        List<DapVariable> fields = ds.getFields();
        for(int i = 0; i < fields.size(); i++) {
            DapVariable field = fields.get(i);
            DapType dt = field.getBaseType();
            DataType cdmtype = CDMTypeFcns.daptype2cdmtype(dt);
            StructureMembers.Member m =
                    sm.addMember(
                            field.getShortName(), "", null,
                            cdmtype,
                            CDMUtil.computeEffectiveShape(field.getDimensions()));
            m.setDataParam(i); // So we can index into various lists
            // recurse if this field is itself a structure
            if(dt.getTypeSort().isStructType()) {
                StructureMembers subsm = computemembers(field);
                m.setStructureMembers(subsm);
            }
        }
        return sm;
    }

    /**
     * @param recno The instance # of the array of Structure instances
     * @param memberindex    The member of interest in the Structure instance
     * @return The ucar.ma2.Array instance corresponding to the instance.
     */
    protected Array
    memberArray(int recno, int memberindex)
    {
        DapVariable var = (DapVariable) this.getTemplate();
        DapStructure struct = (DapStructure)var.getBaseType();
        DapVariable field = struct.getField(memberindex);
        DapType base = field.getBaseType();
        if(base == null)
            throw new IllegalStateException("Unknown field type: " + field);
        Object[] values = new Object[(int) field.getCount()];
        FieldArrays fs = records[recno];
        Array fa = fs.fields[memberindex];
        return fa;
    }

    static protected int
    memberIndex(StructureMembers.Member m)
    {
        return m.getDataParam();
    }

    protected CDMArrayAtomic
    getAtomicArray(int index, StructureMembers.Member m)
    {
        Array dd = memberArray(index, memberIndex(m));
        if(dd.getDataType() != DataType.STRUCTURE && dd.getDataType() != DataType.SEQUENCE)
            return (CDMArrayAtomic) dd;
        throw new ForbiddenConversionException("Cannot convert structure to AtomicArray");
    }

}


/////////////////////////////////////////////////////////////////////////////
// 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 UInt32 value.
 *
 * @author jehamby
 * @version $Revision: 15901 $
 * @see BaseType
 */
public class DUInt32 extends DInt32 {
    /**
     * Constructs a new DUInt32.
     */
    public DUInt32() {
        super();
    }

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

    /**
     * Constructs a new UInt32PrimitiveVector.
     *
     * @return a new UInt32PrimitiveVector.
     */
    public PrimitiveVector newPrimitiveVector() {
        return new UInt32PrimitiveVector(this);
    }

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

    /**
     * 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) {
        // to print properly, cast to long and convert unsigned to signed
        long tempVal = ((long) getValue()) & 0xFFFFFFFFL;
        if (print_decl_p) {
            printDecl(os, space, false);
            os.println(" = " + tempVal + ";");
        } else
            os.print(tempVal);
    }
}


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

package ucar.nc2.iosp.grid;

import ucar.nc2.*;
import java.util.*;

/**
 * Handles the Ensemble coordinate dimension.
 */
public abstract class GridEnsembleCoord {
  static private org.slf4j.Logger log =  org.slf4j.LoggerFactory.getLogger(GridEnsembleCoord.class);

  protected List<EnsCoord> ensCoords;
  protected int seq = 0;

  static protected class EnsCoord implements Comparable<EnsCoord> {
    public int number, type;

    public EnsCoord(int number, int type) {
      this.number = number;
      this.type = type;
    }

    @Override
    public int compareTo(EnsCoord o) {
      int r = number - o.number;
      return (r == 0) ? type - o.type : r;
    }

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

      EnsCoord ensCoord = (EnsCoord) o;

      if (number != ensCoord.number) return false;
      if (type != ensCoord.type) return false;

      return true;
    }

    @Override
    public int hashCode() {
      return 1000 * number + type;
    }

    @Override
    public String toString() {
      return "number=" + number +", type=" + type;
    }
  }

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

    GridEnsembleCoord that = (GridEnsembleCoord) o;
    return ensCoords.equals(that.ensCoords);
  }

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

  /**
   * Set the sequence number
   *
   * @param seq the sequence number
   */
  public void setSequence(int seq) {
    this.seq = seq;
  }

  /**
   * Get the name
   *
   * @return the name
   */
  public String getName() {
    return (seq == 0) ? "ens" : "ens" + seq;
  }

  /**
   * Add this as a dimension to a netCDF file
   *
   * @param ncfile the netCDF file
   * @param g      the group in the file
   */
  public void addDimensionsToNetcdfFile(NetcdfFile ncfile, Group g) {
    ncfile.addDimension(g, new Dimension(getName(), getNEnsembles(), true));
  }

  protected void addToNetcdfFile(NetcdfFile ncfile, Group g) {
    // noop
  }

  /**
   * Get the number of Ensembles
   *
   * @return the number of Ensembles
   */
  public int getNEnsembles() {
    return ensCoords.size();
  }

}


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

// $Id: FieldResizable.java,v 1.7 2005/08/22 01:12:29 caron Exp $

package ucar.util.prefs.ui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;

/**
 * Wraps a field to make it resizeable by the user. Should be package private.
 * To make rubberbanding work, we need it to know what component contains it.
 * @deprecated
 */

class FieldResizable extends Field {
  private Field delegate;
  private JComponent parent;
  private JPanel comp;
  private ResizeButton resizeButt = null;

    // resize dragging
  private ResizeButton dragButt = null;
  private JComponent glassPane = null;
  private RubberbandRectangle rb;
  private boolean resizeMode = false;
  private int startx, starty;

  private boolean debug = false;

  /**
   * Constructor.
   * @param d delegated field
   * @param p container
   */
  public FieldResizable(Field d, JComponent p) {
    super( d.getName(), d.getLabel(), d.getPersistenceManager());
    this.delegate = d;
    this.parent = p;

    // the button that gets dragged
    dragButt = new ResizeButton();
    dragButt.setColor(Color.blue);
    dragButt.addMouseMotionListener( new MouseMotionAdapter() {
      public void mouseDragged(MouseEvent de) {
        if (resizeMode) {
          Point pt = SwingUtilities.convertPoint(dragButt, de.getPoint(), glassPane);
          pt.setLocation( pt.getX(), starty);
          dragButt.setLocation( pt);
          rb.stretch( pt);
        }
      }
    });
    dragButt.addMouseListener( new MouseAdapter() {
      public void mouseReleased( MouseEvent e) {
        if (resizeMode) {
          Point pt = SwingUtilities.convertPoint(dragButt, e.getPoint(), glassPane);
          rb.stop(pt);

          resizeButt.setColor( Color.black);
          glassPane.setVisible(false);
          glassPane.remove(dragButt);
          resizeMode = false;

          //JComponent comp = delegate.getEditComponent();
          int newWidth = (int) (comp.getWidth() + pt.getX() - startx);
          if (debug) System.out.println( "***FieldResizable width= "+comp.getWidth()+"  new width= "+newWidth);

          Dimension d = comp.getSize();
          d.setSize( newWidth, (int) d.getHeight());
          comp.setPreferredSize( d);
          comp.revalidate();
        }
      }
    });

    // the resize button: must click twice!!!
    resizeButt = new ResizeButton();
    resizeButt.addMouseListener( new MouseAdapter() {
      public void mouseReleased( MouseEvent e) {
        if (glassPane == null) {
          // set up the glass panel for resizing
          JRootPane root = SwingUtilities.getRootPane( parent);
          glassPane = (JComponent) root.getGlassPane();
          glassPane.setLayout( null); // we use absolute positioning

          // the rubber band
          rb = new RubberbandRectangle(glassPane);
        }

        resizeButt.setColor( Color.blue);

        Rectangle r = resizeButt.getBounds();
        Point pt = SwingUtilities.convertPoint(comp, (int) r.getX(), (int) r.getY(), glassPane);
        r.setLocation( pt);
        startx = (int) pt.getX();
        starty = (int) pt.getY();
        dragButt.setBounds( r);

        pt.setLocation( pt.getX(), starty + r.getHeight());
        rb.start(pt);

        glassPane.add(dragButt);
        glassPane.setVisible(true);
        resizeMode = true;
      }
    }); // end addMouseListener

    /* comp = new MyJPanel();
    comp.setLayout(null);
    comp.add( delegate.getEditComponent());
    comp.add( resizeButt); */

    comp = new MyPanel();
    comp.setLayout( new BorderLayout());
    // comp.setLayout( new LayoutM(d.getName()));
    // comp.setLayout( null);
    comp.add( delegate.getEditComponent(), BorderLayout.CENTER);
    comp.add( resizeButt, BorderLayout.EAST);
    //comp.add( delegate.getEditComponent(), new LayoutM.Constraint(null,0,0)); // , BorderLayout.CENTER);
    //comp.add( resizeButt, new LayoutM.Constraint(delegate.getEditComponent(),1,0)); // , BorderLayout.EAST);
  }

  /** Return the wrapped field */
  public Field getDelegate() { return delegate; }

  /** Return the editing JComponent */
  public JComponent getEditComponent() { return comp; }

  // pass these calls on to delegates

   /** get current value from editComponent */
  public Object getEditValue() { return delegate.getEditValue(); }

  /** set value of editComponent */
  public void setEditValue(Object value) { delegate.setEditValue( value); }

  /** get value from Store, may return null */
  protected Object getStoreValue(Object defValue) { return delegate.getStoreValue( defValue); }

  /** put new value into Store */
  protected void setStoreValue(Object newValue) { delegate.setStoreValue(newValue); }

  protected boolean accept(StringBuffer buff){ return delegate.accept(buff); }
  protected boolean acceptIfDifferent(Object newValue) { return delegate.acceptIfDifferent( newValue); }
  protected void restoreValue( Object defValue) { delegate.restoreValue( defValue); }
  protected void sendEvent() { delegate.sendEvent(); }
  protected void setNewValueFromStore() { delegate.setNewValueFromStore(); }
  protected void next() { delegate.next(); }
  protected boolean validate( StringBuffer buff) { return delegate.validate(buff); }
  protected boolean _validate( StringBuffer buff) { return delegate._validate(buff); }

  static private class ResizeButton extends JButton {
    ResizeIcon icon = new ResizeIcon(Color.black);
    ResizeButton() {
      setIcon(icon);
      setBorder(BorderFactory.createEmptyBorder());
      setMargin(new Insets(0,0,0,0));
    }

    void setColor( Color c) { icon.setColor(c); }
  }

  static private class ResizeIcon implements javax.swing.Icon {
    private int h = 10, w = 5;
    private Color color;

    ResizeIcon( Color c) { color = c; }
    void setColor( Color c) { color = c; }

    public void paintIcon(Component c, Graphics g, int x, int y){
      JComponent component = (JComponent)c;
      h = component.getHeight();

      g.setColor( component.isEnabled() ? color : Color.gray);
      g.translate( x, y );
      g.drawLine( 1, -4, 1, h);
      g.drawLine( 3, -4, 3, h);
      g.translate( -x, -y );
    }

    public int getIconWidth() { return w; }
    public int getIconHeight()  { return h; }
  }

  static private class RubberbandRectangle {
    private boolean debugRB = false;
    private Point anchorPt    = new Point(0,0);
    private Point stretchedPt = new Point(0,0);
    private Point lastPt      = new Point(0,0);
    private Point endPt       = new Point(0,0);

    private Component component;
    private boolean   firstStretch = true;
    private boolean   active = false;

    public RubberbandRectangle(Component c) {
      component = c;
      component.addMouseMotionListener( new MouseMotionAdapter() {
         public void mouseDragged(MouseEvent event) {
            if (active) stretch(event.getPoint());
         }
      });
    }

    public void drawLast(Graphics graphics) {
      Rectangle rect = lastBounds();
      graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
      if (debugRB) System.out.println( "RB drawLast "+rect);
    }

    public void drawNext(Graphics graphics) {
      Rectangle rect = getBounds();
      graphics.drawRect(rect.x, rect.y, rect.width, rect.height);
      if (debugRB) System.out.println( "RB drawNext "+rect);
    }

    public void setActive(boolean b) { active = b; }
    public boolean isActive    () { return active;      }
    public Point   getAnchor   () { return anchorPt;    }
    public Point   getStretched() { return stretchedPt; }
    public Point   getLast     () { return lastPt;      }
    public Point   getEnd      () { return endPt;       }

    public void start(Point p) {
      firstStretch = true;
      anchorPt.x = p.x;
      anchorPt.y = p.y;

      stretchedPt.x = lastPt.x = anchorPt.x;
      stretchedPt.y = lastPt.y = anchorPt.y;
    }

    public void stretch(Point p) {
      lastPt.x      = stretchedPt.x;
      lastPt.y      = stretchedPt.y;
      stretchedPt.x = p.x;
      stretchedPt.y = p.y;
      if (debugRB) System.out.println( "RB stretch "+getBounds());

      Graphics g = component.getGraphics();
      if (g == null) return;

      try {
        g.setXORMode(component.getBackground());
        g.setColor( Color.blue);
        if (firstStretch)
          firstStretch = false;
        else
          drawLast(g);

        drawNext(g);
      } finally {
        g.dispose();
      }
    }

    public void stop(Point p) {
      lastPt.x = endPt.x = p.x;
      lastPt.y = endPt.y = p.y;

      Graphics g = component.getGraphics();
      if(g != null) {
       try {
         g.setXORMode(component.getBackground());
         g.setColor( Color.blue);
         drawLast(g);
       }
       finally {
         g.dispose();
       }
      }
    }

    public Rectangle getBounds() {
      return new Rectangle(stretchedPt.x < anchorPt.x ?
                           stretchedPt.x : anchorPt.x,
                           stretchedPt.y < anchorPt.y ?
                           stretchedPt.y : anchorPt.y,
                           Math.abs(stretchedPt.x - anchorPt.x),
                           Math.abs(stretchedPt.y - anchorPt.y));
    }

    public Rectangle lastBounds() {
      return new Rectangle(
                  lastPt.x < anchorPt.x ? lastPt.x : anchorPt.x,
                  lastPt.y < anchorPt.y ? lastPt.y : anchorPt.y,
                  Math.abs(lastPt.x - anchorPt.x),
                  Math.abs(lastPt.y - anchorPt.y));
    }
  }   // RubberbandRectangle

  // debug
  private static class MyPanel extends JPanel {

    public void setPreferredSize(Dimension preferredSize) {
      //System.out.println("MyPanel: setPreferredSize = "+preferredSize);
      super.setPreferredSize( preferredSize);
    }

    public Dimension getPreferredSize() {
      //System.out.println("MyPanel: getPreferredSize = "+super.getPreferredSize());
      return super.getPreferredSize();
    }

  }

  /** test */
  public static void main(String args[]) {

    JFrame frame = new JFrame("Test prefs Field");
    frame.addWindowListener(new WindowAdapter() {
      public void windowClosing(WindowEvent e) {System.exit(0);}
    });

    PrefPanel.Dialog d = new PrefPanel.Dialog(frame, false, "title", null, null);
    PrefPanel pp = d.getPrefPanel();
    final Field.Text tf = pp.addTextField("text", "text", "defValue");
    pp.setCursor(1, 0);
    pp.addTextField("text2", "text2", "text2");
    //final Field.Int intf = pp.addIntField("int", "int", 66);

    tf.setText("better value");
    d.finish();
    d.show();

    JPanel main = new JPanel(new FlowLayout());
    frame.getContentPane().add(main);
    main.setPreferredSize(new Dimension(200, 200));

    frame.pack();
    frame.setLocation(300, 300);
    frame.setVisible(true);

    pp.addActionListener(e -> {
        String text = tf.getText();
        tf.setText( text+"1");
    });

  }

}

/* Change History:
   $Log: FieldResizable.java,v $
   Revision 1.7  2005/08/22 01:12:29  caron
   DatasetEditor

   Revision 1.6  2005/08/17 18:36:27  caron
   no message

   Revision 1.5  2005/08/17 00:13:58  caron
   Dataset Editor

   Revision 1.4  2004/08/26 17:55:18  caron
   no message

   Revision 1.3  2003/05/29 23:33:28  john
   latest release

   Revision 1.2  2003/01/14 19:36:18  john
   get the delegate vs inheritence thing right

   Revision 1.1.1.1  2002/12/20 16:40:26  john
   start new cvs root: prefs

   Revision 1.2  2002/03/09 01:51:55  caron
   add BeanTable, fix FieldResizable

*/


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
// $Id: CrawlableDatasetFactory.java 63 2006-07-12 21:50:51Z edavis $
package thredds.crawlabledataset;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.io.IOException;

/**
 * A description
 *
 * @author edavis
 * @since Jun 23, 2005T10:29:26 PM
 */
public class CrawlableDatasetFactory
{

  private static String defaultClassName = "thredds.crawlabledataset.CrawlableDatasetFile";

  /**
   * Construct a CrawlableDataset for the given path using the
   * CrawlableDataset implementation indicated by the given class name.
   *
   * The class given by className is used to instantiate the requested
   * CrawlableDataset and so must be an implementation of
   * thredds.crawlabledataset.CrawlableDataset. The given class must also
   * supply a public constructor with a single String argument. The String
   * argument is the path for the CrawlableDataset being constructed.
   *
   * If className is null, thredds.crawlabledataset.CrawlableDatasetFile will
   * be used.
   *
   * If the path is an alias containing wildcard characters ("*"),
   * the class thredds.crawlabledataset.CrawlableDatasetAlias will be used to
   * expand the alias into a collection of CrawlableDatasets of the type given
   * by className.
   *
   * @param path the path of the CrawlableDataset.
   * @param className the class name of the CrawlableDataset implementation to instantiate.
   * @param configObj
   *
   * @return a CrawlableDataset for the given path and of the type given by the class name.
   *
   * @throws IOException if a CrawlableDataset cannot be created due to IO problems.
   * @throws ClassNotFoundException if the given CrawlableDataset implementation was not found.
   * @throws NoSuchMethodException if the given CrawlableDataset implementation does not have a constructor with a single String parameter which is required.
   * @throws IllegalAccessException if the constructor is inaccessible due to Java language access control.
   * @throws InvocationTargetException if the constructor throws an exception.
   * @throws InstantiationException if the given CrawlableDataset implementation is an abstract class.
   *
   * @throws NullPointerException if the given path is null.
   * @throws IllegalArgumentException if the given class name is not an implementation of CrawlableDataset.
   */
  public static CrawlableDataset createCrawlableDataset( String path, String className, Object configObj )
          throws IOException,
                 ClassNotFoundException, NoSuchMethodException,
                 IllegalAccessException, InvocationTargetException,
                 InstantiationException, IllegalArgumentException, NullPointerException
          // throws CrDsException, IllegalArgumentException
  {
    if ( path == null ) throw new NullPointerException( "Given path must not be null.");
    String tmpClassName = ( className == null
                            ? defaultClassName
                            : className );

    // @todo Remove alias until sure how to handle things like ".scour*" being a regular file.
//    if ( CrawlableDatasetAlias.isAlias( tmpPath) )
//        return new CrawlableDatasetAlias( tmpPath, tmpClassName, configObj );

    // Get the Class instance for desired CrawlableDataset implementation.
    Class crDsClass = Class.forName( tmpClassName );

    // Check that the Class is a CrawlableDataset.
    if ( ! CrawlableDataset.class.isAssignableFrom( crDsClass ) )
    {
      throw new IllegalArgumentException( "Requested class <" + className + "> not an implementation of thredds.crawlabledataset.CrawlableDataset.");
    }

    // Instantiate the desired CrawlableDataset.
    Class [] argTypes = { String.class, Object.class };
    Object [] args = { path, configObj };
    Constructor constructor = crDsClass.getDeclaredConstructor( argTypes );

    try
    {
      return (CrawlableDataset) constructor.newInstance( args );
    }
    catch ( InvocationTargetException e )
    {
      if ( IOException.class.isAssignableFrom( e.getCause().getClass()) )
        throw (IOException) e.getCause();
      else throw e;
    }
  }

  /**
   * Normalize the given path so that it can be used in the creation of a CrawlableDataset.
   * This method can be used on absolute or relative paths.
   *
   * Normal uses slashes ("/") as path seperator, not backslashes ("\"), and does
   * not use trailing slashes. This function allows users to specify Windows
   * pathnames and UNC pathnames in there normal manner.
   *
   * @param path the path to be normalized.
   * @return the normalized path.
   * @throws NullPointerException if path is null.
   */
  public static String normalizePath( String path )
  {
    // Replace any occurance of a backslash ("\") with a slash ("/").
    // NOTE: Both String and Pattern escape backslash, so need four backslashes to find one.
    // NOTE: No longer replace multiple backslashes with one slash, which allows for UNC pathnames (Windows LAN addresses).
    //       Was path.replaceAll( "\\\\+", "/");
    String newPath = path.replaceAll( "\\\\", "/");

    // Remove trailing slashes.
    while ( newPath.endsWith( "/") && ! newPath.equals("/") )
      newPath = newPath.substring( 0, newPath.length() - 1 );

    return newPath;
  }
}


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

import ucar.nc2.ui.gis.AbstractGisFeature;
import ucar.nc2.ui.gis.GisPart;

import java.util.*;      // for Iterator and ArrayList
import java.awt.geom.*;  // for Point2D.Double

/**
 * An AbstractGisFeature derived class for contour lines.
 * <p/>
 * Created:
 *
 * @author wier
 */

public class ContourFeature extends AbstractGisFeature {
  private int numparts, npts;
  private ArrayList lines; // arrayList of ContourLines
  private double contourValue;

  /**
   * constructor
   */
  public ContourFeature(ArrayList conLines) {

    // set how many GisParts there are
    numparts = conLines.size();

    // save the input ContourLines as member data
    lines = new ArrayList(conLines);

    // save the single contour value for all lines here as member data
    if (conLines.size() > 0)
      contourValue = ((ContourLine) (lines.get(0))).getContourLevel();
    else
      contourValue = 0.0;

    // using every line, add together how many points on all the lines
    int np, count = 0;
    for (int i = 0; i < numparts; i++) {
      GisPart cl = (GisPart) (conLines.get(i));
      np = cl.getNumPoints();
      count += np;

      // error if contour values not all the same
      if (((ContourLine) (lines.get(i))).getContourLevel() != contourValue)
        System.out.println("  Mismatch: all contour levels" +
                " in one ContourFeature should be the same.");
    }
    npts = count;
  }  // end cstr

  /**
   * Get the value of the contour level for all the contour lines
   * in this object.
   *
   * @return double value
   */
  public double getContourValue() {
    return contourValue;
  }

  ;

  // implement GisFeature methods:

  public java.awt.geom.Rectangle2D getBounds2D() {
    // Get the bounding box for this feature. from java.awt.geom.Rectangle2D
    double x0 = (((ContourLine) (lines.get(0))).getX())[0];
    double y0 = (((ContourLine) (lines.get(0))).getY())[0];
    double xMaxInd = x0, xmin = x0, yMaxInd = y0, ymin = y0;

    for (int i = 0; i < lines.size(); i++) {
      GisPart cline = (ContourLine) (lines.get(i));
      double[] xpts = cline.getX();
      double[] ypts = cline.getY();
      for (int j = 0; j < cline.getNumPoints(); j++) {
        if (xpts[j] < xmin)
          xmin = xpts[j];
        else if (xpts[j] > xMaxInd)
          xMaxInd = xpts[j];
        if (ypts[j] < ymin)
          ymin = ypts[j];
        else if (ypts[j] > yMaxInd)
          yMaxInd = ypts[j];
      }
    }

    // Rectangle2D.Double(double x, double y, double width, double height)
    Rectangle2D.Double rect =
            new Rectangle2D.Double(xmin, ymin, xMaxInd - xmin, yMaxInd - ymin);
    return rect;
  }

  public java.util.Iterator getGisParts() {
    return lines.iterator();
  }

  public int getNumParts() {
    return numparts;
  }

  public int getNumPoints() {
    return npts;
  }

} // ContourFeature