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

package thredds.logs;

import ucar.unidata.util.StringUtil2;

import java.io.Serializable;
import java.util.TreeMap;
import java.util.Iterator;
import java.util.SortedMap;
import java.util.Comparator;

/**
 * Duplicate thredds.servlet.PathMatcher
 *
 * @author caron
 * @since Mar 24, 2009
 */
public class PathMatcher {

  private final TreeMap<String, Match> treeMap;

  public static class Match {
    public String root;

    Match(String root) {
      this.root = root;
    }
  }

  public PathMatcher() {
    treeMap = new TreeMap<>(new PathComparator());
  }

  /**
   * Add an object to the collection to be searched by a String key.
   * @param root sort key
   */
  public void put(String root) {
    treeMap.put( root, new Match(root));
  }

  /**
   * See if this object already exists in the collection, using equals().
   * @param key find object that has this key
   * @return existing object, else null.
   */
  public Match get(String  key) {
    return treeMap.get( key);
  }

  /**
   * Get an iterator over the values, in sorted order.
   * @return iterator
   */
  public Iterator iterator() {
    return treeMap.values().iterator();
  }

  /**
   * Find the longest match.
   * @param path find object with longest match where path.startsWith( key)
   * @return the value whose key is the longest that matches path, or null if none
   */
  public Match match( String path) {
    SortedMap<String, Match> tail = treeMap.tailMap( path);
    if (tail.isEmpty()) return null;
    String after = tail.firstKey();
    //System.out.println("  "+path+"; after="+afterPath);
    if (path.startsWith( after)) // common case
      return treeMap.get( after);

    // have to check more, until no common starting chars
    for (String key : tail.keySet()) {
      if (path.startsWith(key))
        return treeMap.get(key);
      // terminate when there's no match at all.
      if (StringUtil2.match(path, key) == 0)
        break;
    }

    return null;
  }

  private static class PathComparator implements Comparator<String>, Serializable {
    public int compare(String o1, String o2) {
      int compare = -1 * o1.compareTo(o2); // reverse sort
      if (debug) System.out.println(" compare "+o1+" to "+o2+" = "+compare);
      return compare;
    }
  }

  // testing
  private void doit( String s) {
    System.out.println(s+" == "+match(s));
  }

  static private boolean debug = false;
  static public void main( String[] args) {
    PathMatcher m = new PathMatcher();
    m.put("/thredds/dods/test/longer");
    m.put("/thredds/dods/test");
    m.put("/thredds/dods/tester");
    m.put("/thredds/dods/short");
    m.put("/actionable");
    m.put("myworld");
    m.put("mynot");
    m.put("ncmodels");
    m.put("ncmodels/bzipped");

    m.doit("nope");
    m.doit("/thredds/dods/test");
    m.doit("/thredds/dods/test/lo");
    m.doit("/thredds/dods/test/longer/donger");
    m.doit("myworldly");
    m.doit("/my");
    m.doit("mysnot");

    debug = true;
    m.doit("ncmodels/canonical");
  }
}


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

package thredds.catalog;

/**
 * Type-safe enumeration of THREDDS Metadata types.
 *
 * @author john caron
 */

public final class MetadataType {
  private static java.util.List<MetadataType> members = new java.util.ArrayList<MetadataType>(20);

  public final static MetadataType NONE = new MetadataType("");

  public final static MetadataType THREDDS = new MetadataType("THREDDS");
  public final static MetadataType ADN = new MetadataType("ADN");
  public final static MetadataType AGGREGATION = new MetadataType("Aggregation");
  public final static MetadataType CATALOG_GEN_CONFIG = new MetadataType("CatalogGenConfig");
  public final static MetadataType DUBLIN_CORE = new MetadataType("DublinCore");
  public final static MetadataType DIF = new MetadataType("DIF");
  public final static MetadataType FGDC = new MetadataType("FGDC");
  public final static MetadataType LAS = new MetadataType("LAS");
  public final static MetadataType ESG = new MetadataType("ESG");

  // not 1.0 : is anyone using?
  public final static MetadataType NETCDF = new MetadataType("NetCDF");
  public final static MetadataType NcML = new MetadataType("NcML");
  public final static MetadataType THREDDS_DLEntry = new MetadataType("THREDDS_DLEntry");
  public final static MetadataType THREDDS_DLCollection = new MetadataType("THREDDS_DLCollection");

  private String name;

  private MetadataType(String s) {
    this.name = s;
    members.add(this);
  }

  private MetadataType(String name, boolean fake) {
    this.name = name;
  }

  /**
   * Get all MetadataType objects
   * @return all MetadataType objects
   */
  public static java.util.Collection<MetadataType> getAllTypes() {
    return members;
  }

  /**
   * Return the known MetadataType that matches the given name (ignoring case)
   * or null if the name is unknown.
   *
   * @param name the name of the desired DataFormatType.
   * @return MetadataType or null if no match.
   */
  public static MetadataType findType(String name) {
    if (name == null) return null;
    for (MetadataType m : members) {
      if (m.name.equalsIgnoreCase(name))
        return m;
    }
    return null;
  }

  /**
   * Return a MetadataType that matches the given name by either matching
   * a known type (ignoring case) or creating an unknown type.
   *
   * @param name name of the desired MetadataType
   * @return the named MetadataType or null if given name is null.
   */
  public static MetadataType getType(String name) {
    if (name == null) return null;
    MetadataType type = findType( name );
    return type != null ? type : new MetadataType( name, false );
  }

  /**
   * Return the MetadataType name.
   */
  public String toString() {
    return name;
  }

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

  /**
   * MetaDataType with same name are equal.
   */
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof MetadataType)) return false;
    return o.hashCode() == this.hashCode();
  }
}


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

import ucar.nc2.constants.DataFormatType;
import ucar.ma2.*;

import ucar.nc2.constants.CDM;
import ucar.nc2.iosp.netcdf3.N3iosp;
import ucar.nc2.time.CalendarDate;
import ucar.unidata.io.RandomAccessFile;
import ucar.nc2.iosp.*;
import ucar.nc2.iosp.hdf4.HdfEos;
import ucar.nc2.iosp.hdf4.H4header;
import ucar.nc2.*;

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

/**
 * HDF5 I/O
 *
 * @author caron
 */

public class H5iosp extends AbstractIOServiceProvider {
  static public final String IOSP_MESSAGE_INCLUDE_ORIGINAL_ATTRIBUTES = "IncludeOrgAttributes";

  static public final int VLEN_T_SIZE = 16; // Appears to be no way to compute on the fly.

  static boolean debug = false;
  static boolean debugPos = false;
  static boolean debugHeap = false;
  static boolean debugHeapStrings = false;
  static boolean debugFilter = false;
  static boolean debugRead = false;
  static boolean debugFilterIndexer = false;
  static boolean debugChunkIndexer = false;
  static boolean debugVlen = false;
  static boolean debugStructure = false;
  static boolean useHdfEos = true;

  static org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(H5iosp.class);

  static public void setDebugFlags(ucar.nc2.util.DebugFlags debugFlag) {
    debug = debugFlag.isSet("H5iosp/read");
    debugPos = debugFlag.isSet("H5iosp/filePos");
    debugHeap = debugFlag.isSet("H5iosp/Heap");
    debugFilter = debugFlag.isSet("H5iosp/filter");
    debugFilterIndexer = debugFlag.isSet("H5iosp/filterIndexer");
    debugChunkIndexer = debugFlag.isSet("H5iosp/chunkIndexer");
    debugVlen = debugFlag.isSet("H5iosp/vlen");

    H5header.setDebugFlags(debugFlag);
    H4header.setDebugFlags(debugFlag);
    if(debugFilter)
      H5tiledLayoutBB.debugFilter = debugFilter;

  }

  public boolean isValidFile(ucar.unidata.io.RandomAccessFile raf) throws IOException {
    return H5header.isValidFile(raf);
  }

  public String getFileTypeId() {
    if (isEos) return "HDF5-EOS";
    if (headerParser.isNetcdf4()) return DataFormatType.NETCDF4.getDescription();
    return DataFormatType.HDF5.getDescription();
  }

  public String getFileTypeDescription() {
    return "Hierarchical Data Format, version 5";
  }

  public void getEosInfo(Formatter f) throws IOException {
    NetcdfFile ncfile = headerParser.ncfile;
    Group eosInfo = ncfile.getRootGroup().findGroup(HdfEos.HDF5_GROUP);
    if (eosInfo != null) {
      HdfEos.getEosInfo(ncfile, eosInfo, f);
    } else {
      f.format("Cant find GROUP '%s'", HdfEos.HDF5_GROUP);
    }
  }

  public static void useHdfEos(boolean val) {
    useHdfEos = val;
  }

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

  //private RandomAccessFile raf;
  private H5header headerParser;
  private boolean isEos;
  boolean includeOriginalAttributes = false;

  /////////////////////////////////////////////////////////////////////////////
  // reading

  public void open(RandomAccessFile raf, ucar.nc2.NetcdfFile ncfile, ucar.nc2.util.CancelTask cancelTask) throws IOException {
    super.open(raf, ncfile, cancelTask);
    headerParser = new H5header(this.raf, ncfile, this);
    headerParser.read(null);

    // check if its an HDF5-EOS file
    Group eosInfo = ncfile.getRootGroup().findGroup(HdfEos.HDF5_GROUP);
    if (eosInfo != null && useHdfEos) {
      isEos = HdfEos.amendFromODL(ncfile, eosInfo);
    }

    ncfile.finish();
  }

  public Array readData(ucar.nc2.Variable v2, Section section) throws IOException, InvalidRangeException {
    H5header.Vinfo vinfo = (H5header.Vinfo) v2.getSPobject();
    if (debugRead) System.out.printf("%s read %s%n", v2.getFullName(), section);
    return readData(v2, vinfo.dataPos, section);
  }

  // all the work is here, so can be called recursively
  private Array readData(ucar.nc2.Variable v2, long dataPos, Section wantSection) throws IOException, InvalidRangeException {
    H5header.Vinfo vinfo = (H5header.Vinfo) v2.getSPobject();
    DataType dataType = v2.getDataType();
    Object data;
    Layout layout;

    if (vinfo.useFillValue) { // fill value only
      Object pa = IospHelper.makePrimitiveArray((int) wantSection.computeSize(), dataType, vinfo.getFillValue());
      if (dataType == DataType.CHAR)
        pa = IospHelper.convertByteToChar((byte[]) pa);
      return Array.factory(dataType, wantSection.getShape(), pa);
    }

    if (vinfo.mfp != null) { // filtered
      if (debugFilter) System.out.println("read variable filtered " + v2.getFullName() + " vinfo = " + vinfo);
      assert vinfo.isChunked;
      ByteOrder bo = (vinfo.typeInfo.endian == 0) ? ByteOrder.BIG_ENDIAN : ByteOrder.LITTLE_ENDIAN;
      layout = new H5tiledLayoutBB(v2, wantSection, raf, vinfo.mfp.getFilters(), bo);
      if (vinfo.typeInfo.isVString) {
        data = readFilteredStringData((LayoutBB) layout);
      } else{
        data = IospHelper.readDataFill((LayoutBB) layout, v2.getDataType(), vinfo.getFillValue());
      }

    } else { // normal case
      if (debug) System.out.println("read variable " + v2.getFullName() + " vinfo = " + vinfo);

      DataType readDtype = v2.getDataType();
      int elemSize = v2.getElementSize();
      Object fillValue = vinfo.getFillValue();
      int endian = vinfo.typeInfo.endian;

      // fill in the wantSection
      wantSection = Section.fill(wantSection, v2.getShape());

      if (vinfo.typeInfo.hdfType == 2) { // time
        readDtype = vinfo.mdt.timeType;
        elemSize = readDtype.getSize();
        fillValue = N3iosp.getFillValueDefault(readDtype);

      } else if (vinfo.typeInfo.hdfType == 8) { // enum
        H5header.TypeInfo baseInfo = vinfo.typeInfo.base;
        readDtype = baseInfo.dataType;
        elemSize = readDtype.getSize();
        fillValue = N3iosp.getFillValueDefault(readDtype);
        endian = baseInfo.endian;

      } else if (vinfo.typeInfo.hdfType == 9) { // vlen
        elemSize = vinfo.typeInfo.byteSize;
        endian = vinfo.typeInfo.endian;
        //wantSection = wantSection.removeVlen(); // remove vlen dimension
      }

      if (vinfo.isChunked) {
        layout = new H5tiledLayout((H5header.Vinfo) v2.getSPobject(), readDtype, wantSection);
      } else {
        layout = new LayoutRegular(dataPos, elemSize, v2.getShape(), wantSection);
      }
      data = readData(vinfo, v2, layout, readDtype, wantSection.getShape(), fillValue, endian);
    }

    if (data instanceof Array)
      return (Array) data;
    else if (dataType == DataType.STRUCTURE)
      return convertStructure((Structure) v2, layout, wantSection.getShape(), (byte[]) data);  // LOOK
    else
      return Array.factory(dataType, wantSection.getShape(), data);
  }

  public String[] readFilteredStringData(LayoutBB layout) throws java.io.IOException {
    int size = (int) layout.getTotalNelems();
    String[] sa = new String[size];
    while (layout.hasNext()) {
      LayoutBB.Chunk chunk = layout.next();
      ByteBuffer bb = chunk.getByteBuffer();
      //bb.position(chunk.getSrcElem());
      if (debugHeapStrings) System.out.printf("readFilteredStringData chunk=%s%n", chunk);
      int destPos = (int) chunk.getDestElem();
      for (int i = 0; i < chunk.getNelems(); i++) { // 16 byte "heap ids"
        sa[destPos++] = headerParser.readHeapString(bb, (chunk.getSrcElem() + i) * 16); // LOOK does this handle section correctly ??
      }
    }
    return sa;
  }

  /*
   * Read data subset from file for a variable, return Array or java primitive array.
   *
   * @param v         the variable to read.
   * @param layout     handles skipping around in the file.
   * @param dataType  dataType of the data to read
   * @param shape     the shape of the output
   * @param fillValue fill value as a wrapped primitive
   * @return primitive array or Array with data read in
   * @throws java.io.IOException            if read error
   * @throws ucar.ma2.InvalidRangeException if invalid section
   */
  private Object readData(H5header.Vinfo vinfo, Variable v, Layout layout, DataType dataType, int[] shape,
                          Object fillValue, int endian) throws java.io.IOException, InvalidRangeException {

    H5header.TypeInfo typeInfo = vinfo.typeInfo;

    // special processing
    if (typeInfo.hdfType == 2) { // time
      Object data = IospHelper.readDataFill(raf, layout, dataType, fillValue, endian, true);
      Array timeArray = Array.factory(dataType, shape, data);

      // now transform into an ISO Date String
      String[] stringData = new String[(int) timeArray.getSize()];
      int count = 0;
      while (timeArray.hasNext()) {
        long time = timeArray.nextLong();
        stringData[count++] = CalendarDate.of(time).toString();
      }
      return Array.factory(DataType.STRING, shape, stringData);
    }

    if (typeInfo.hdfType == 8) { // enum
      Object data = IospHelper.readDataFill(raf, layout, dataType, fillValue, endian);
      return Array.factory(dataType, shape, data);
    }

    if (typeInfo.isVlen) { // vlen (not string)
      DataType readType = dataType;
      if (typeInfo.base.hdfType == 7) // reference
        readType = DataType.LONG;

      // general case is to read an array of vlen objects
      // each vlen generates an Array - so return ArrayObject of Array
      // boolean scalar = false; // layout.getTotalNelems() == 1; // if scalar, return just the len Array // remove 12/25/10 jcaron
      Array[] data = new Array[(int) layout.getTotalNelems()];
      int count = 0;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        if (chunk == null) continue;
        for (int i = 0; i < chunk.getNelems(); i++) {
          long address = chunk.getSrcPos() + layout.getElemSize() * i;
          Array vlenArray = headerParser.getHeapDataArray(address, readType, endian);
          data[count++] = (typeInfo.base.hdfType == 7) ? convertReference(vlenArray) : vlenArray;
        }
      }
      int prefixrank = 0;
      for (int i=0;i<shape.length;i++) { // find leftmost vlen
        if (shape[i] < 0) {
          prefixrank = i;
          break;
        }
      }
      Array result;
      if(prefixrank == 0) // if scalar, return just the singleton vlen array
        result = data[0];
       else {
        int[] newshape = new int[prefixrank];
        System.arraycopy(shape, 0, newshape, 0, prefixrank);
        // result = Array.makeObjectArray(readType, data[0].getClass(), newshape, data);
        result = Array.makeVlenArray(newshape, data);
      }

      /*
      else if (prefixrank == 1) // LOOK cant these two cases be combines - just differ in shape ??
        result = Array.makeObjectArray(readType, data[0].getClass(), new int[]{count}, data);
     else {  // LOOK cant these two cases be combines - just differ in shape ??
          // Otherwise create and fill in an n-dimensional Array Of Arrays
          int[] newshape = new int[prefixrank];
          System.arraycopy(shape, 0, newshape, 0, prefixrank);
          Array ndimarray = Array.makeObjectArray(readType, Array.class, newshape, null);
          // Transfer the elements of data into the n-dim arrays
          IndexIterator iter = ndimarray.getIndexIterator();
          for(int i = 0;iter.hasNext();i++) {
              iter.setObjectNext(data[i]);
          }
          result = ndimarray;
      } */
      //return (scalar) ? data[0] : new ArrayObject(data[0].getClass(), shape, data);
      //return new ArrayObject(data[0].getClass(), shape, data);
      return result;
    }

    if (dataType == DataType.STRUCTURE) {  // LOOK what about subset ?
      int recsize = layout.getElemSize();
      long size = recsize * layout.getTotalNelems();
      byte[] byteArray = new byte[(int)size];
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        if (chunk == null) continue;
        if (debugStructure)
          System.out.println(" readStructure " + v.getFullName() + " chunk= " + chunk + " index.getElemSize= " + layout.getElemSize());
        // copy bytes directly into the underlying byte[] LOOK : assumes contiguous layout ??
        raf.seek(chunk.getSrcPos());
        raf.readFully(byteArray, (int) chunk.getDestElem() * recsize, chunk.getNelems() * recsize);
      }

      // place data into an ArrayStructureBB
      return convertStructure((Structure) v, layout, shape, byteArray); // LOOK
    }

    // normal case
    return readDataPrimitive(layout, dataType, shape, fillValue, endian, true);
  }

  Array convertReference(Array refArray) throws java.io.IOException {
    int nelems = (int) refArray.getSize();
    Index ima = refArray.getIndex();
    String[] result = new String[nelems];
    for (int i = 0; i < nelems; i++) {
      long reference = refArray.getLong(ima.set(i));
      String name = headerParser.getDataObjectName(reference);
      result[i] = name != null ? name : Long.toString(reference);
      if (debugVlen) System.out.printf(" convertReference 0x%x to %s %n", reference, result[i]);
    }
    return Array.factory(DataType.STRING, new int[]{nelems}, result);
  }

  private ArrayStructure convertStructure(Structure s, Layout layout, int[] shape, byte[] byteArray) throws IOException, InvalidRangeException {
    // create StructureMembers - must set offsets
    StructureMembers sm = s.makeStructureMembers();
    int calcSize = ArrayStructureBB.setOffsets(sm); // standard
    //ArrayStructureBB.showOffsets(sm, new Indent(2), new Formatter(System.out));

    // special offset setting
    boolean hasHeap = convertStructure(s, sm);
    //ArrayStructureBB.showOffsets(sm, new Indent(2), new Formatter(System.out));

    int recSize = layout.getElemSize();
    if (recSize < calcSize) {
      log.error("calcSize = %d actualSize = %d%n", calcSize, recSize);
      throw new IOException("H5iosp illegal structure size " + s.getFullName());
    }
    sm.setStructureSize(recSize);

    // place data into an ArrayStructureBB
    ByteBuffer bb = ByteBuffer.wrap(byteArray);
    ArrayStructureBB asbb = new ArrayStructureBB(sm, shape, bb, 0);

    // strings and vlens are stored on the heap, and must be read separately
    if (hasHeap) {
      int destPos = 0;
      for (int i = 0; i < layout.getTotalNelems(); i++) { // loop over each structure
        convertHeap(asbb, destPos, sm);
        destPos += layout.getElemSize();
      }
    }
    return asbb;
  }

  // recursive
  private boolean convertStructure(Structure s, StructureMembers sm ) {
    boolean hasHeap = false;
    for (StructureMembers.Member m : sm.getMembers()) {
      Variable v2 = s.findVariable(m.getName());
      assert v2 != null;
      H5header.Vinfo vm = (H5header.Vinfo) v2.getSPobject();

      // apparently each member may have seperate byte order (!!!??)
      if (vm.typeInfo.endian >= 0)
        m.setDataObject(vm.typeInfo.endian == RandomAccessFile.LITTLE_ENDIAN ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);

      // vm.dataPos : offset since start of Structure
      m.setDataParam((int) vm.dataPos);

      // track if there is a heap
      if (v2.getDataType() == DataType.STRING || v2.isVariableLength())
        hasHeap = true;

      // recurse
      if (v2 instanceof Structure) {
        Structure nested = (Structure) v2;
        StructureMembers nestSm = nested.makeStructureMembers();
        m.setStructureMembers(nestSm);
        hasHeap |= convertStructure(nested, nestSm);
      }
    }
    return hasHeap;
  }

  /*
    public static int setOffsets(StructureMembers members) {
    int offset = 0;
    for (StructureMembers.Member m : members.getMembers()) {
      m.setDataParam(offset);
      offset += m.getSizeBytes();

      // set inner offsets (starts again at 0)
      if (m.getStructureMembers() != null)
        setOffsets(m.getStructureMembers());
    }
    members.setStructureSize(offset);
    return offset;
  }
   */

  void convertHeap(ArrayStructureBB asbb, int pos, StructureMembers sm) throws java.io.IOException, InvalidRangeException {
    ByteBuffer bb = asbb.getByteBuffer();
    for (StructureMembers.Member m : sm.getMembers()) {
      if (m.getDataType() == DataType.STRING) {
        m.setDataObject(ByteOrder.nativeOrder()); // the index is always written in "native order"
        int size = m.getSize();
        int destPos = pos + m.getDataParam();
        String[] result = new String[size];
        for (int i = 0; i < size; i++)
          result[i] = headerParser.readHeapString(bb, destPos + i * 16); // 16 byte "heap ids" are in the ByteBuffer

        int index = asbb.addObjectToHeap(result);
        bb.order(ByteOrder.nativeOrder()); // the string index is always written in "native order"
        bb.putInt(destPos, index); // overwrite with the index into the StringHeap

      } else if (m.isVariableLength()) {
        int startPos = pos + m.getDataParam();
        bb.order(ByteOrder.LITTLE_ENDIAN);

        ByteOrder bo = (ByteOrder) m.getDataObject();
        int endian = bo.equals(ByteOrder.LITTLE_ENDIAN) ? RandomAccessFile.LITTLE_ENDIAN : RandomAccessFile.BIG_ENDIAN;
        // Compute rank and size upto the first (and ideally last) VLEN
        int[] fieldshape = m.getShape();
        int prefixrank = 0;
        int size = 1;
        for(;prefixrank < fieldshape.length;prefixrank++) {
            if(fieldshape[prefixrank] < 0) break;
            size *= fieldshape[prefixrank];
        }
        assert size == m.getSize() : "Internal error: field size mismatch";
        Array[] fieldarray = new Array[size]; // hold all the vlen instance data
        // destPos will point to each vlen instance in turn
        // assuming we have 'size' such instances in a row.
        int destPos = startPos;
        for(int i = 0;i < size;i++) {
          // vlenarray extracts the i'th vlen contents (struct not supported).
          Array vlenArray = headerParser.readHeapVlen(bb, destPos, m.getDataType(), endian);
          fieldarray[i] = vlenArray;
          destPos += VLEN_T_SIZE;  // Apparentlly no way to compute VLEN_T_SIZE on the fly
        }
        Array result;
        if(prefixrank == 0) // if scalar, return just the singleton vlen array
          result = fieldarray[0];
        else {
          int[] newshape = new int[prefixrank];
          System.arraycopy(fieldshape, 0, newshape, 0, prefixrank);
          // result = Array.makeObjectArray(m.getDataType(), fieldarray[0].getClass(), newshape, fieldarray);
          result = Array.makeVlenArray(newshape, fieldarray);
        }

        /* if (prefixrank == 1)
          result = Array.makeObjectArray(m.getDataType(), fieldarray[0].getClass(), new int[]{size}, fieldarray);
        else {
          // Otherwise create and fill in an n-dimensional Array Of Arrays
          int[] newshape = new int[prefixrank];
          System.arraycopy(fieldshape, 0, newshape, 0, prefixrank);
          Array ndimarray = Array.makeObjectArray(m.getDataType(), Array.class, newshape, null);
          // Transfer the elements of data into the n-dim arrays
          IndexIterator iter = ndimarray.getIndexIterator();
          for(int i = 0;iter.hasNext();i++) {
              iter.setObjectNext(fieldarray[i]);
          }
          result = ndimarray;
        } */

        //Array vlenArray = headerParser.readHeapVlen(bb, destPos, m.getDataType(), endian);

        int index = asbb.addObjectToHeap(result);
        bb.order(ByteOrder.nativeOrder());
        bb.putInt(startPos, index); // overwrite with the index into the Heap
      }
    }
  }

  /*
   * Read data subset from file for a variable, create primitive array.
   *
   * @param layout     handles skipping around in the file.
   * @param dataType  dataType of the variable
   * @param shape     the shape of the output
   * @param fillValue fill value as a wrapped primitive
   * @param endian byte order
   * @return primitive array with data read in
   * @throws java.io.IOException            if read error
   * @throws ucar.ma2.InvalidRangeException if invalid section
   */
  Object readDataPrimitive(Layout layout, DataType dataType, int[] shape, Object fillValue, int endian, boolean convertChar) throws java.io.IOException, InvalidRangeException {

    if (dataType == DataType.STRING) {
      int size = (int) layout.getTotalNelems();
      String[] sa = new String[size];
      int count = 0;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        if (chunk == null) continue;
        for (int i = 0; i < chunk.getNelems(); i++) { // 16 byte "heap ids"
          sa[count++] = headerParser.readHeapString(chunk.getSrcPos() + layout.getElemSize() * i);
        }
      }
      return sa;
    }

    if (dataType == DataType.OPAQUE) {
      Array opArray = Array.factory(DataType.OPAQUE, shape);
      assert (new Section(shape).computeSize() == layout.getTotalNelems());

      int count = 0;
      while (layout.hasNext()) {
        Layout.Chunk chunk = layout.next();
        if (chunk == null) continue;
        int recsize = layout.getElemSize();
        for (int i = 0; i < chunk.getNelems(); i++) {
          byte[] pa = new byte[recsize];
          raf.seek(chunk.getSrcPos() + i * recsize);
          raf.readFully(pa, 0, recsize);
          opArray.setObject(count++, ByteBuffer.wrap(pa));
        }
      }
      return opArray;
    }

    // normal case
    return IospHelper.readDataFill(raf, layout, dataType, fillValue, endian, convertChar);
  }

  // old way
  private StructureData readStructure(Structure s, ArrayStructureW asw, long dataPos) throws IOException, InvalidRangeException {
    StructureDataW sdata = new StructureDataW(asw.getStructureMembers());
    if (debug) System.out.println(" readStructure " + s.getFullName() + " dataPos = " + dataPos);

    for (Variable v2 : s.getVariables()) {
      H5header.Vinfo vinfo = (H5header.Vinfo) v2.getSPobject();
      if (debug) System.out.println(" readStructureMember " + v2.getFullName() + " vinfo = " + vinfo);
      Array dataArray = readData(v2, dataPos + vinfo.dataPos, v2.getShapeAsSection());
      sdata.setMemberData(v2.getShortName(), dataArray);
    }

    return sdata;
  }

  //////////////////////////////////////////////////////////////////////////
  // override base class

  @Override
  public void close() throws IOException {
    super.close();
    headerParser.close();
  }

  @Override
  public void reacquire() throws IOException {
    super.reacquire();
    headerParser.raf = this.raf;
  }

  @Override
  public String toStringDebug(Object o) {
    if (o instanceof Variable) {
      Variable v = (Variable) o;
      H5header.Vinfo vinfo = (H5header.Vinfo) v.getSPobject();
      return vinfo.toString();
    }
    return null;
  }

  @Override
  public String getDetailInfo() {
    Formatter f = new Formatter();
    ByteArrayOutputStream os = new ByteArrayOutputStream(100 * 1000);
    PrintWriter pw = new PrintWriter( new OutputStreamWriter(os, CDM.utf8Charset));

    try {
      NetcdfFile ncfile = new NetcdfFileSubclass();
      H5header detailParser = new H5header(raf, ncfile, this);
      detailParser.read(pw);
      f.format("%s", super.getDetailInfo());
      f.format("%s", os.toString(CDM.UTF8));

    } catch (IOException e) {
      e.printStackTrace();
    }

    return f.toString();
  }

  @Override
  public Object sendIospMessage(Object message) {
    if (message.toString().equals(IOSP_MESSAGE_INCLUDE_ORIGINAL_ATTRIBUTES)) {
      includeOriginalAttributes = true;
      return null;
    }

    if (message.toString().equals("header"))
      return headerParser;

    if (message.toString().equals("headerEmpty")) {
      NetcdfFile ncfile = new NetcdfFileSubclass();
      return new H5header(raf, ncfile, this);
    }

    return super.sendIospMessage(message);
  }

  // debug
  NetcdfFile getNetcdfFile() {
    return headerParser.ncfile;
  }

}


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

package ucar.nc2.dataset.conv;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants.CF;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.constants.AxisType;
import ucar.nc2.units.SimpleUnit;
import ucar.nc2.util.CancelTask;
import ucar.nc2.dataset.*;
import ucar.nc2.dataset.transform.WRFEtaTransformBuilder;

import ucar.unidata.geoloc.*;
import ucar.unidata.geoloc.projection.*;
//import ucar.units.ConversionException;

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

/**
 * ADAS netcdf files.
 *
 * @author caron
 */

public class ADASConvention extends CoordSysBuilder {

  public ADASConvention() {
    this.conventionName = "ARPS/ADAS";
  }

  // private double originX = 0.0, originY = 0.0;
  private ProjectionCT projCT = null;
  private static final boolean debugProj = false;

  /**
   * create a NetcdfDataset out of this NetcdfFile, adding coordinates etc.
   */
  public void augmentDataset(NetcdfDataset ds, CancelTask cancelTask) throws IOException {
    if (null != ds.findVariable("x")) return; // check if its already been done - aggregating enhanced datasets.

    // old way
    Attribute att = ds.findGlobalAttribute("MAPPROJ");
    int projType = att.getNumericValue().intValue();

    double lat1 = findAttributeDouble(ds, "TRUELAT1", Double.NaN);
    double lat2 = findAttributeDouble(ds, "TRUELAT2", Double.NaN);
    double lat_origin = lat1;
    double lon_origin = findAttributeDouble(ds, "TRUELON", Double.NaN);
    double false_easting = 0.0;
    double false_northing = 0.0;

    // new way
    String projName = ds.findAttValueIgnoreCase(null, CF.GRID_MAPPING_NAME, null);
    if (projName != null) {
      projName = projName.trim();
      lat_origin = findAttributeDouble(ds, "latitude_of_projection_origin", Double.NaN);
      lon_origin = findAttributeDouble(ds, "longitude_of_central_meridian", Double.NaN);
      false_easting = findAttributeDouble(ds, "false_easting", 0.0);
      false_northing = findAttributeDouble(ds, "false_northing", 0.0);

      Attribute att2 = ds.findGlobalAttributeIgnoreCase("standard_parallel");
      if (att2 != null) {
        lat1 = att2.getNumericValue().doubleValue();
        lat2 = (att2.getLength() > 1) ? att2.getNumericValue(1).doubleValue() : lat1;
      }
    } else {
      if (projType == 2) projName = "lambert_conformal_conic";
    }

    Variable coord_var = ds.findVariable("x_stag");
    if (!Double.isNaN(false_easting) || !Double.isNaN(false_northing)) {
      String units = ds.findAttValueIgnoreCase(coord_var, CDM.UNITS, null);
      double scalef = 1.0;
      try {
        scalef = SimpleUnit.getConversionFactor(units, "km");
      } catch (IllegalArgumentException e) {
        log.error(units + " not convertible to km");
      }
      false_easting *= scalef;
      false_northing *= scalef;
    }

    ProjectionImpl proj;
    if ((projName != null) && projName.equalsIgnoreCase("lambert_conformal_conic")) {
      proj = new LambertConformal(lat_origin, lon_origin, lat1, lat2, false_easting, false_northing);
      projCT = new ProjectionCT("Projection", "FGDC", proj);
      if (false_easting == 0.0) calcCenterPoints(ds, proj); // old way
    } else {
      parseInfo.format("ERROR: unknown projection type = %s%n", projName);
    }

    if (debugProj && (proj != null)) {
      System.out.println(" using LC " + proj.paramsToString());
      double lat_check = findAttributeDouble(ds, "CTRLAT", Double.NaN);
      double lon_check = findAttributeDouble(ds, "CTRLON", Double.NaN);

      LatLonPointImpl lpt0 = new LatLonPointImpl(lat_check, lon_check);
      ProjectionPoint ppt0 = proj.latLonToProj(lpt0, new ProjectionPointImpl());
      System.out.println("CTR lpt0= " + lpt0 + " ppt0=" + ppt0);

      Variable xstag = ds.findVariable("x_stag");
      ArrayFloat.D1 xstagData = (ArrayFloat.D1) xstag.read();
      float center_x = xstagData.get((int) xstag.getSize() - 1);
      Variable ystag = ds.findVariable("y_stag");
      ArrayFloat.D1 ystagData = (ArrayFloat.D1) ystag.read();
      float center_y = ystagData.get((int) ystag.getSize() - 1);
      System.out.println("CTR should be x,y= " + center_x / 2000 + ", " + center_y / 2000);

      lpt0 = new LatLonPointImpl(lat_origin, lon_origin);
      ppt0 = proj.latLonToProj(lpt0, new ProjectionPointImpl());
      System.out.println("ORIGIN lpt0= " + lpt0 + " ppt0=" + ppt0);

      lpt0 = new LatLonPointImpl(lat_origin, lon_origin);
      ppt0 = proj.latLonToProj(lpt0, new ProjectionPointImpl());
      System.out.println("TRUE ORIGIN lpt0= " + lpt0 + " ppt0=" + ppt0);
    }

    if (projCT != null) {
      VariableDS v = makeCoordinateTransformVariable(ds, projCT);
      v.addAttribute(new Attribute(_Coordinate.AxisTypes, "GeoX GeoY"));
      ds.addVariable(null, v);
    }

    if (ds.findVariable("x_stag") != null)
      ds.addCoordinateAxis(makeCoordAxis(ds, "x"));
    if (ds.findVariable("y_stag") != null)
      ds.addCoordinateAxis(makeCoordAxis(ds, "y"));
    if (ds.findVariable("z_stag") != null)
      ds.addCoordinateAxis(makeCoordAxis(ds, "z"));

    Variable zsoil = ds.findVariable("ZPSOIL");
    if (zsoil != null)
      zsoil.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.GeoZ.toString()));

    ds.finish();
  }

  // old
  private void calcCenterPoints(NetcdfDataset ds, Projection proj) throws IOException {
    double lat_check = findAttributeDouble(ds, "CTRLAT", Double.NaN);
    double lon_check = findAttributeDouble(ds, "CTRLON", Double.NaN);

    LatLonPointImpl lpt0 = new LatLonPointImpl(lat_check, lon_check);
    ProjectionPoint ppt0 = proj.latLonToProj(lpt0, new ProjectionPointImpl());
    System.out.println("CTR lpt0= " + lpt0 + " ppt0=" + ppt0);

    Variable xstag = ds.findVariable("x_stag");
    int nxpts = (int) xstag.getSize();
    ArrayFloat.D1 xstagData = (ArrayFloat.D1) xstag.read();
    float center_x = xstagData.get(nxpts - 1);
    double false_easting = center_x / 2000 - ppt0.getX() * 1000.0;
    System.out.println("false_easting= " + false_easting);

    Variable ystag = ds.findVariable("y_stag");
    int nypts = (int) ystag.getSize();
    ArrayFloat.D1 ystagData = (ArrayFloat.D1) ystag.read();
    float center_y = ystagData.get(nypts - 1);
    double false_northing = center_y / 2000 - ppt0.getY() * 1000.0;
    System.out.println("false_northing= " + false_northing);

    double dx = findAttributeDouble(ds, "DX", Double.NaN);
    double dy = findAttributeDouble(ds, "DY", Double.NaN);

    double w = dx * (nxpts - 1);
    double h = dy * (nypts - 1);
    double startx = ppt0.getX() * 1000.0 - w / 2;
    double starty = ppt0.getY() * 1000.0 - h / 2;

    xstag.setValues(nxpts, startx, dx);
    ystag.setValues(nypts, starty, dy);
  }

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

  protected void makeCoordinateTransforms(NetcdfDataset ds) {
    if (projCT != null) {
      VarProcess vp = findVarProcess(projCT.getName(), null);
      vp.isCoordinateTransform = true;
      vp.ct = projCT;
    }
    super.makeCoordinateTransforms(ds);
  }

  protected AxisType getAxisType(NetcdfDataset ds, VariableEnhanced ve) {
    Variable v = (Variable) ve;
    String vname = v.getShortName();

    if (vname.equalsIgnoreCase("x") || vname.equalsIgnoreCase("x_stag"))
      return AxisType.GeoX;

    if (vname.equalsIgnoreCase("lon"))
      return AxisType.Lon;

    if (vname.equalsIgnoreCase("y") || vname.equalsIgnoreCase("y_stag"))
      return AxisType.GeoY;

    if (vname.equalsIgnoreCase("lat"))
      return AxisType.Lat;

    if (vname.equalsIgnoreCase("z") || vname.equalsIgnoreCase("z_stag"))
      return AxisType.GeoZ;

    if (vname.equalsIgnoreCase("Z"))
      return AxisType.Height;

    if (vname.equalsIgnoreCase("time"))
      return AxisType.Time;

    String unit = ve.getUnitsString();
    if (unit != null) {
      if (SimpleUnit.isCompatible("millibar", unit))
        return AxisType.Pressure;

      if (SimpleUnit.isCompatible("m", unit))
        return AxisType.Height;
    }

    return null;
  }

  /**
   * Does increasing values of Z go vertical  up?
   *
   * @param v for this axis
   * @return "up" if this is a Vertical (z) coordinate axis which goes up as coords get bigger,
   *         else return "down"
   */
  public String getZisPositive(CoordinateAxis v) {
    return "down"; //eta coords decrease upward
  }

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

  private CoordinateAxis makeCoordAxis(NetcdfDataset ds, String axisName) throws IOException {
    Variable stagV = ds.findVariable(axisName + "_stag");
    Array data_stag = stagV.read();
    int n = (int) data_stag.getSize() - 1;
    DataType dt = DataType.getType(data_stag);
    Array data = Array.factory(dt, new int[]{n});
    Index stagIndex = data_stag.getIndex();
    Index dataIndex = data.getIndex();
    for (int i = 0; i < n; i++) {
      double val = data_stag.getDouble(stagIndex.set(i)) + data_stag.getDouble(stagIndex.set(i + 1));
      data.setDouble(dataIndex.set(i), 0.5 * val);
    }

    DataType dtype = DataType.getType(data);
    String units = ds.findAttValueIgnoreCase(stagV, CDM.UNITS, "m");
    CoordinateAxis v = new CoordinateAxis1D(ds, null, axisName, dtype, axisName, units, "synthesized non-staggered " + axisName + " coordinate");
    v.setCachedData(data, true);
    return v;
  }

  private double findAttributeDouble(NetcdfDataset ds, String attname, double defValue) {
    Attribute att = ds.findGlobalAttributeIgnoreCase(attname);
    if (att == null) return defValue;
    return att.getNumericValue().doubleValue();
  }

  /* private VerticalCT makeWRFEtaVerticalCoordinateTransform(NetcdfDataset ds, CoordinateSystem cs) {
    if ((null == ds.findVariable("PH")) || (null == ds.findVariable("PHB")) ||
        (null == ds.findVariable("P")) || (null == ds.findVariable("PB")))
      return null;

  	VerticalCT.Type type = VerticalCT.Type.WRFEta;
  	VerticalCT ct = new VerticalCT(type.toString(), conventionName, type);

  	ct.addParameter(new Parameter("height formula", "height(x,y,z) = (PH(x,y,z) + PHB(x,y,z)) / 9.81"));
  	ct.addParameter(new Parameter("perturbation geopotential variable name", "PH"));
  	ct.addParameter(new Parameter("base state geopotential variable name", "PHB"));
  	ct.addParameter(new Parameter("pressure formula", "pressure(x,y,z) = P(x,y,z) + PB(x,y,z)"));
  	ct.addParameter(new Parameter("perturbation pressure variable name", "P"));
  	ct.addParameter(new Parameter("base state pressure variable name", "PB"));
  	ct.addParameter(new Parameter("staggered x", ""+isStaggered(cs.getXaxis())));
  	ct.addParameter(new Parameter("staggered y", ""+isStaggered(cs.getYaxis())));
  	ct.addParameter(new Parameter("staggered z", ""+isStaggered(cs.getZaxis())));
  	ct.addParameter(new Parameter("eta", ""+cs.getZaxis().getName()));

    parseInfo.append(" added vertical coordinate transform = "+type+"\n");
  	return ct;
  }

  private boolean isStaggered(CoordinateAxis axis) {
  	if (axis == null) return false;
  	String name = axis.getName();
  	if (name == null) return false;
  	if (name.endsWith("stag")) return true;
  	return false;
  } */

  /**
   * Assign CoordinateTransform objects to Coordinate Systems.
   */
  protected void assignCoordinateTransforms(NetcdfDataset ncDataset) {
    super.assignCoordinateTransforms(ncDataset);

    // any cs whose got a vertical coordinate with no units
    List<CoordinateSystem> csys = ncDataset.getCoordinateSystems();
    for (CoordinateSystem cs : csys) {
      if (cs.getZaxis() != null) {
        String units = cs.getZaxis().getUnitsString();
        if ((units == null) || (units.trim().length() == 0)) {
          VerticalCT vct = makeWRFEtaVerticalCoordinateTransform(ncDataset, cs);
          if (vct != null) {
            cs.addCoordinateTransform(vct);
            parseInfo.format("***Added WRFEta verticalCoordinateTransform to %s%n", cs.getName());
          }
        }
      }
    }
  }

  private VerticalCT makeWRFEtaVerticalCoordinateTransform(NetcdfDataset ds, CoordinateSystem cs) {
    if ((null == ds.findVariable("PH")) || (null == ds.findVariable("PHB")) ||
        (null == ds.findVariable("P")) || (null == ds.findVariable("PB")))
      return null;

    WRFEtaTransformBuilder builder = new WRFEtaTransformBuilder(cs);
    return builder.makeCoordinateTransform(ds, null);
  }

}


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

package dap4.core.ce;

import dap4.core.dmr.DapVariable;
import dap4.core.util.Slice;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;

public class CEAST
{
    //////////////////////////////////////////////////
    // Type Decls

    static public class NodeList extends ArrayList<CEAST>
    {
    }

    static public class Path extends NodeList
    {
    }

    static public class StringList extends ArrayList<String>
    {
    }

    static public class SliceList extends ArrayList<Slice>
    {
    }

    static public enum Sort
    {
        CONSTRAINT,
        PROJECTION,
        SEGMENT,
        SELECTION,
        EXPR,
        CONSTANT,
        DEFINE,
    }

    static public enum Constant
    {
        STRING, LONG, DOUBLE, BOOLEAN;

        static public EnumSet<Constant> COMPARABLE = EnumSet.of(LONG, DOUBLE, STRING);
        static public EnumSet<Constant> NUMERIC = EnumSet.of(LONG, DOUBLE);

    }

    static public enum Operator
    {
        LT("<", true, Constant.BOOLEAN, Constant.COMPARABLE),
        LE("<=", true, Constant.BOOLEAN, Constant.COMPARABLE),
        GT(">", true, Constant.BOOLEAN, Constant.COMPARABLE),
        GE(">=", true, Constant.BOOLEAN, Constant.COMPARABLE),
        EQ("==", true, Constant.BOOLEAN, Constant.COMPARABLE),
        NEQ("!=", true, Constant.BOOLEAN, Constant.COMPARABLE),
        REQ("~=", true, Constant.BOOLEAN, EnumSet.of(Constant.STRING)),
        AND(",", true, Constant.BOOLEAN, EnumSet.of(Constant.BOOLEAN)),
        NOT("!", true, Constant.BOOLEAN, EnumSet.of(Constant.BOOLEAN));

        private final String printstring;
        private final boolean binary;
        private final Constant type;
        private final EnumSet<Constant> legalvalues;

        Operator(String printstring, boolean binary, Constant type, EnumSet<Constant> legal)
        {
            this.printstring = printstring;
            this.binary = binary;
            this.type = type;
            this.legalvalues = legal;
        }

        public String printstring()
        {
            return this.printstring;
        }

        public boolean isbinary()
        {
            return this.binary;
        }

        public EnumSet<Constant> legalvalues()
        {
            return this.legalvalues;
        }

        public Constant returntype()
        {
            return this.type;
        }
    }

    //////////////////////////////////////////////////
    // Instance Variables ; do not bother with accessors.

    public Sort sort = null;

    // case CONSTRAINT
    public NodeList clauses = null;
    public Map<String, Slice> dimdefs = null;

    // case PROJECTION
    public CEAST tree = null;

    // case SEGMENT; actually a node in a segment tree, so may have subnodes
    public String name = null;
    public boolean isleaf = true;
    public List<Slice> slices = null;
    public NodeList subnodes = null;

    // case SELECTION
    public CEAST projection = null;
    public CEAST filter = null;
    public DapVariable field = null; // used by compilefilter()

    // case EXPR
    public Operator op = null;
    public CEAST lhs = null;
    public CEAST rhs = null;

    // public case CONSTANT
    public CEAST.Constant kind = null;
    public Object value = null;

    // case DEFINE: also uses name
    public Slice slice = null;

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

    public CEAST(Sort sort)
    {
        this.sort = sort;
    }

    //////////////////////////////////////////////////
    // Sort specific

    public void
    addSegment(CEAST segment)
    {
        assert sort == Sort.SEGMENT;
        if(subnodes == null)
            subnodes = new NodeList();
        subnodes.add(segment);
    }

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

    static public void toString(CEAST node, StringBuilder buf)
    {
        if(node == null) return;
        switch (node.sort) {
        case CONSTRAINT:
            boolean first = true;
            for(CEAST elem : node.clauses) {
                if(!first) buf.append(";");
                toString(elem, buf);
                first = false;
            }
            break;
        case PROJECTION:
            toString(node.tree, buf);
            break;
        case SELECTION:
            toString(node.projection, buf);
            buf.append("|");
            toString(node.filter, buf);
            break;
        case SEGMENT:
            buf.append(node.name);
            if(node.slice != null)
                buf.append(node.slice.toString());
            if(node.subnodes != null) {
                buf.append(".{");
                first = true;
                for(CEAST subnode : node.subnodes) {
                    if(!first) {
                        buf.append(",");
                    } else {
                        first = false;
                    }
                    buf.append(subnode.toString());
                }
                buf.append("}");
            }
            break;
        case EXPR:
            if(node.op.isbinary()) {
                buf.append(node.lhs.toString());
                buf.append(node.op.printstring());
                buf.append(node.rhs.toString());
            }
            break;
        case CONSTANT:
            buf.append(node.value.toString());
            break;
        case DEFINE:
            buf.append(node.name);
            if(node.slice != null) {
                buf.append("=");
                buf.append(node.slice.toString());
            }
            break;
        }
    }

    // Primarily for debug; dump in input form:
    public String toString()
    {
        StringBuilder buf = new StringBuilder();
        toString(this, buf);
        return buf.toString();
    }

}


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

import java.awt.*;
import java.beans.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;

import javax.swing.*;

import ucar.nc2.util.ListenerManager;
import ucar.util.prefs.PreferencesExt;

import ucar.unidata.geoloc.*;

/**
 * Manages a modal dialogue box that allows the user to define
 * projections using subclasses of ucar.unidata.gis.ProjectionImpl.
 * <pre>
 *    1) It uses bean introspection on ProjectionImpl subclasses to dynamically
 *       configure edit fields for the projection's parameters. The subclass should
 *       define getXXX() and setXXX() methods for each parameter.
 *    2) A user-defined list of ProjectionImpl is maintained through a PersistentStore object.
 *    3) when the user selects a projection, a NewProjectionEvent is sent to any listeners
 *    4) currently the list of possible projection classes is maintained as a
 *       hard-wired list of class names. This should be rethunk.
 * </pre>
 *
 * @author John Caron
 * @version revived 9/20/2012
 */
public class ProjectionManager {
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(ProjectionManager.class);
  static private final int min_sigfig = 6;
  static private final Object[] voidObjectArg = new Object[]{};

  // the current list of projection classes: be nice to do this dynamically
  // what about reading all classes in ucar.unidata.gis/projection ? (glenn doesnt like)
  static String[] types = {
          "ucar.unidata.geoloc.projection.LatLonProjection",
          "ucar.unidata.geoloc.projection.AlbersEqualArea",
          "ucar.unidata.geoloc.projection.FlatEarth",
          "ucar.unidata.geoloc.projection.LambertAzimuthalEqualArea",
          "ucar.unidata.geoloc.projection.LambertConformal",
          "ucar.unidata.geoloc.projection.Mercator",
          "ucar.unidata.geoloc.projection.Orthographic",
          "ucar.unidata.geoloc.projection.RotatedLatLon",
          "ucar.unidata.geoloc.projection.Stereographic",
          "ucar.unidata.geoloc.projection.TransverseMercator",
          "ucar.unidata.geoloc.projection.UtmProjection",
          "ucar.unidata.geoloc.projection.VerticalPerspectiveView",
          "ucar.unidata.geoloc.projection.sat.MSGnavigation",
          "ucar.unidata.geoloc.projection.proj4.AlbersEqualAreaEllipse",
          "ucar.unidata.geoloc.projection.proj4.CylindricalEqualAreaProjection",
          "ucar.unidata.geoloc.projection.proj4.EquidistantAzimuthalProjection",
          "ucar.unidata.geoloc.projection.proj4.LambertConformalConicEllipse",
          "ucar.unidata.geoloc.projection.proj4.PolyconicProjection",
          "ucar.unidata.geoloc.projection.proj4.StereographicAzimuthalProjection",
          "ucar.unidata.geoloc.projection.proj4.TransverseMercatorProjection",
  };

  private PreferencesExt store;
  private boolean eventsOK = false;

  // GUI stuff that needs class scope
  // private JTableProjection projTable;
  //private NPController npViewControl;
  //private NavigatedPanel mapViewPanel;
  //private PersistentDataDialog viewDialog;
  //private NewProjectionDialog newDialog = null;

  private Frame owner;
  private NewProjectionDialog dialog; // created in JFormDesigner
  private ListenerManager lm;

  // misc
  private boolean debug = false, debugBeans = false;

  /**
   * default constructor
   */
  public ProjectionManager(Frame owner, PreferencesExt store) {
    this.owner = owner;
    this.store = store;

    // manage NewProjectionListeners
    lm = new ListenerManager(
            "java.beans.PropertyChangeListener",
            "java.beans.PropertyChangeEvent",
            "propertyChange");

    /*
    // the actual list is a JTable subclass
    projTable = new JTableProjection(store);

    // empty table : populate it
    if (projTable.isEmpty()) {
      for (String aProjClassName : projClassName) {
        try {
          ProjectionClass pc = new ProjectionClass(aProjClassName);
          ProjectionImpl pi = pc.makeDefaultProjection();
          pi.setName("Default " + pi.getClassName());
          projTable.addProjection(pi);

        } catch (ClassNotFoundException ee) {
          System.err.println("ProjectionManager failed on " + aProjClassName + " " + ee);
        } catch (IntrospectionException ee) {
          System.err.println("ProjectionManager failed on " + aProjClassName + " " + ee);
        }
      }
    }

    // put it together in the viewDialog
    // viewDialog = new PersistentDataDialog(parent, false, "Projection", mapViewPanel, new JScrollPane(projTable), this);

    // default current and working projection
    if (null != (current = projTable.getSelected())) {
      setWorkingProjection(current);
      projTable.setCurrentProjection(current);
    }

    // listen for new working Projections from projTable
    projTable.addNewProjectionListener(new NewProjectionListener() {
      public void actionPerformed(NewProjectionEvent e) {
        if (e.getProjection() != null) {
          if (debug) System.out.println("NewProjectionListener from ProjTable " + e.getProjection());
          setWorkingProjection(e.getProjection());
        }
      }
    });  */

    eventsOK = true;
  }

  public void addPropertyChangeListener(java.beans.PropertyChangeListener l) {
    lm.addListener(l);
  }

  public void removePropertyChangeListener(java.beans.PropertyChangeListener l) {
    lm.removeListener(l);
  }

  public void setVisible() {
    if (null == dialog) init();
    dialog.setVisible(true);
  }

  public void storePersistentData() {
    // projTable.storePersistentData();
  }

  private void init() {
    dialog = new NewProjectionDialog(owner);
    ArrayList<Object> ps = new ArrayList<>();
    for (String p : types)
      try {
        ps.add(new ProjectionClass(p));
      } catch (ClassNotFoundException | IntrospectionException e) {
        e.printStackTrace();  //To change body of catch statement use File | Settings | File Templates.
      }
    dialog.setProjectionManager(this, ps);
  }

  /*
   * Get the currently selected object, of type getPersistentObjectClass()
   *
  public PersistentObject getSelection() {
    return (PersistentObject) current;
  }

  /
   * Set the currently selected thing: must be of type getPersistentObjectClass().
   * this does NOT fire a PropertyChangeEvent
   *
  public void setSelection(PersistentObject select) {
    setWorkingProjection((ProjectionImpl) select);
  }

  ////// PersistentDataManager methods
  public void accept() {
    ProjectionRect bb = mapViewPanel.getMapArea();
    current.setDefaultMapArea((ProjectionRect) bb.clone());
    lm.sendEvent(new java.beans.PropertyChangeEvent(this, "ProjectionImpl", null, current));
  }

  public boolean contains(String id) {
    return projTable.contains(id);
  }

  public void deleteSelected() {
    projTable.deleteSelected();
  }

  public void edit(boolean isNew) {
    if (null == newDialog)
      newDialog = new NewProjectionDialog(parent);

    if (isNew) {
      newDialog.setDefaultProjection();
    } else {
      newDialog.setProjection(current);
    }

    newDialog.setVisible(true);
  }

  public void saveProjection(ProjectionImpl proj) {
    //setVisible(true);  how to make this work? seperate Thread ?

    // force new name
    ProjectionImpl newProj = proj.constructCopy();
    newProj.setName("");
    setWorkingProjection(newProj);

    if (null == newDialog) // lazy instantiation
      newDialog = new NewProjectionDialog(parent);

    // start up edit Dialog
    newDialog.setProjection(current);
    newDialog.show();
  }

  private void setMap(ucar.nc2.ui.util.Renderer map) {
    if (null == newDialog) // lazy instantiation
      newDialog = new NewProjectionDialog(parent);

    if (map != null) {
      npViewControl.addRenderer(map);
      newDialog.setMap(map);
    }
    System.out.println("Projection Manager setMap " + map);
  }

  private void setWorkingProjection(ProjectionImpl proj) {
    if (proj == null)
      return;
    if (debug) System.out.println("ProjManager.setWorkingProjection " + proj);
    current = proj.constructCopy();
    if (debug) System.out.println("ProjManager.setWorkingProjection map area = " + current.getDefaultMapArea());

    npViewControl.setProjection(current);
    //viewDialog.setCurrent(current.getName());
  }

    public void setProjection(ProjectionImpl proj) {
      ProjectionClass pc = findProjectionClass(proj);
      if (pc == null) {
        System.out.println("Projection Manager: cant find Class for " + proj);
        return;
      }
      if (debug) System.out.println(" NPDialog set projection " + proj);

      //if (proj instanceof LambertConformal)
      //  proj.setDefaultMapArea(new ProjectionRect(-2013.441384640456,-34.18738379648357,71.21978635954429,2160.192796203516));

      setProjectionClass(pc, proj);
      npEditControl.setProjection(proj);
      startingName = proj.getName();
    }

    void setDefaultProjection() {
      // let the first projection class be the default
      ProjectionClass selectedClass = (ProjectionClass) projClassCB.getItemAt(0);
      if (selectedClass == null) {
        System.out.println("Projection Manager: no Default Projection available");
        return;
      }
      ProjectionImpl proj = selectedClass.makeDefaultProjection();
      setProjection(proj);
      startingName = "";
    }

    public void setMap(ucar.nc2.ui.util.Renderer map) {
      if (map != null)
        npEditControl.addRenderer(map);
    }

    // user has hit the "accept/save" button
    private void accept() {
      ProjectionClass projClass = findProjectionClass(editProjection);
      if (null == projClass) {
        System.out.println("Projection Manager accept: findProjectionClass failed" + editProjection);
        return;
      }
      setProjFromDialog(projClass, editProjection);

      editProjection.setDefaultMapArea(mapEditPanel.getMapArea());
      if (debug) System.out.println("Projection Manager accept bb =" + editProjection.getDefaultMapArea());
      ProjectionImpl newProj = editProjection.constructCopy(); // use a copy

     //if (!viewDialog.checkSaveOK(startingName, newProj.getName()))
      //  return;

      if (ProjectionManager.this.contains(newProj.getName()))
        projTable.replaceProjection(newProj);
      else
        projTable.addProjection(newProj);

      // set new projection to working projection and exit this Dialog
      setWorkingProjection(newProj);
      NewProjectionDialog.this.setVisible(false);
    }

    private ProjectionClass findProjectionClass(Projection proj) {
      Class want = proj.getClass();
      ComboBoxModel projClassList = projClassCB.getModel();
      for (int i = 0; i < projClassList.getSize(); i++) {
        ProjectionClass pc = (ProjectionClass) projClassList.getElementAt(i);
        if (want.equals(pc.projClass))
          return pc;
      }
      return null;
    }

    private void setProjectionClass(ProjectionClass pc, ProjectionImpl proj) {
      if ((null == proj) || (null == pc))
        return;

      if (debug) System.out.println("Projection setProjectionClass= " + proj);

      setFieldsWithClassParams(pc);
      editProjection = proj.constructCopy();
      putProjInDialog(pc, editProjection);

      if (debug) System.out.println("Projection setProjectionClass ok ");

      invalidate();    // force new layout
      validate();
    }

    // construct input fields based on Projection Class
    private void setFieldsWithClassParams(ProjectionClass projClass) {

      // set the projection in the JComboBox
      String want = projClass.toString();
      for (int i = 0; i < projClassCB.getItemCount(); i++) {
        ProjectionClass pc = (ProjectionClass) projClassCB.getItemAt(i);
        if (pc.toString().equals(want)) {
          projClassCB.setSelectedItem(pc);
          break;
        }
      }

      // set the parameter fields
      paramPanel.removeAll();
      paramPanel.setVisible(0 < projClass.paramList.size());

      for (int i = 0; i < projClass.paramList.size(); i++) {
        ProjectionParam pp = projClass.paramList.get(i);
        // construct the label
        JPanel thisPanel = new JPanel();
        thisPanel.add(new JLabel(pp.name + ": "));
        // text input field
        JTextField tf = new JTextField();
        pp.setTextField(tf);
        tf.setColumns(12);
        thisPanel.add(tf);
        paramPanel.add(thisPanel);
      }
    }

  } // end NewProjectionDialog   */

  // inner class ProjectionClass: parsed projection classes
  class ProjectionClass {
    Class projClass;
    ProjectionImpl projInstance;
    String name;
    java.util.List<ProjectionParam> paramList = new ArrayList<>();

    public String toString() {
      return name;
    }

    ProjectionClass(String className) throws ClassNotFoundException, IntrospectionException {
      projClass = Class.forName(className);

      // eliminate common properties with "stop class" for getBeanInfo()
      Class stopClass;
      try {
        stopClass = Class.forName("ucar.unidata.geoloc.ProjectionImpl");
      } catch (Exception ee) {
        System.err.println("constructParamInput failed ");
        stopClass = null;
      }

      // analyze current projection class as a bean; may throw IntrospectionException
      BeanInfo info = java.beans.Introspector.getBeanInfo(projClass, stopClass);

      // find read/write methods
      PropertyDescriptor[] props = info.getPropertyDescriptors();
      if (debugBeans) {
        System.out.print("Bean Properties for class " + projClass);
        if (props == null || props.length == 0) {
          System.out.println("none");
          return;
        }
        System.out.println("");
      }
      for (PropertyDescriptor pd : props) {
        Method reader = pd.getReadMethod();
        Method writer = pd.getWriteMethod();
        // only interested in read/write properties
        if (reader == null || writer == null)
          continue;
        ProjectionParam p = new ProjectionParam(pd.getName(), reader, writer, pd.getPropertyType());
        paramList.add(p);

        if (debugBeans) System.out.println("  -->" + p);
      }

      // get an instance of this class so we can call toClassName()
      if (null == makeDefaultProjection()) {
        name = "none";
        return;
      }

      Class[] voidClassArg = new Class[]{};
      Object[] voidObjectArg = new Object[]{};

      // invoke the toClassName method
      try {
        Method m = projClass.getMethod("getClassName", voidClassArg);
        name = (String) m.invoke(projInstance, voidObjectArg);

      } catch (NoSuchMethodException ee) {
        System.err.println("ProjectionManager: class " + projClass + " does not have method getClassName()");
        throw new ClassNotFoundException();
      } catch (SecurityException ee) {
        System.err.println("ProjectionManager: class " + projClass + " got SecurityException on getClassName()" + ee);
        throw new ClassNotFoundException();
      } catch (Exception ee) {
        System.err.println("ProjectionManager: class " + projClass + " Exception when invoking getClassName()" + ee);
        throw new ClassNotFoundException();
      }
    }

    ProjectionImpl makeDefaultProjection() {
      Class[] voidClassArg = new Class[]{};
      Object[] voidObjectArg = new Object[]{};

      // the default constructor
      try {
        Constructor c = projClass.getConstructor(voidClassArg);
        projInstance = (ProjectionImpl) c.newInstance(voidObjectArg);
        return projInstance;

      } catch (Exception ee) {
        log.error("ProjectionManager makeDefaultProjection failed to construct class " + projClass, ee);
        return null;
      }
    }

    void putParamIntoDialog(Projection projInstance) {
      for (ProjectionParam pp : paramList)
        pp.putParamIntoDialog(this, projInstance);
    }

    void setProjFromDialog(Projection projInstance) {
      for (ProjectionParam pp : paramList)
        pp.setProjFromDialog(this, projInstance);
    }
  }  // end ProjectionClass inner class

  // inner class ProjectionParam: parameters for Projection Class
  class ProjectionParam {
    Method reader;
    Method writer;
    String name;
    Class paramType;
    JTextField tf;      // edit field component

    ProjectionParam(String name, Method reader, Method writer, Class paramType) {
      this.name = name;
      this.reader = reader;
      this.writer = writer;
      this.paramType = paramType;
    }

    public String toString() {
      return paramType.getName() + " " + name + " " + ((reader == null) ? "-" : "R") + ((writer == null) ? "-" : "W");
    }

    void setTextField(JTextField tf) {
      this.tf = tf;
    }

    private void putParamIntoDialog(ProjectionClass projClass, Projection proj) {
      Double value;
      try {
        if (debugBeans) System.out.println("Projection putParamIntoDialog invoke reader on " + name);
        value = (Double) reader.invoke(proj, voidObjectArg);
        if (debugBeans) System.out.println("Projection putParamIntoDialog value " + value);

      } catch (IllegalAccessException | InvocationTargetException ee) {
        log.error("putParamIntoDialog failed  invoking read {} class {}", name, projClass);
        return;
      }
      String valstr = ucar.unidata.util.Format.d(value, min_sigfig);
      tf.setText(valstr);
    }

    // set values from the Dialog fields into the projection
    private void setProjFromDialog(ProjectionClass projClass, Projection proj) {
      try {
        String valstr = tf.getText();
        Double valdub = new Double(valstr);
        Object[] args = {valdub};
        if (debugBeans) System.out.println("Projection setProjFromDialog invoke writer on " + name);
        writer.invoke(proj, args);

      } catch (IllegalAccessException | InvocationTargetException ee) {
        log.error("ProjectionManager: setProjParams failed  invoking write {} class {}", name, projClass);
      }
    }

  }

  // testing 1-2-3

  public static void main(String[] args) {
    ProjectionManager d = new ProjectionManager(null, null);
    d.setVisible();
  }

}


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

import javax.annotation.concurrent.Immutable;

/**
 * Knuth-Morris-Pratt Algorithm for Pattern Matching.
 * Immutable
 *
 * @author caron
 * @see http://www.fmi.uni-sofia.bg/fmi/logic/vboutchkova/sources/KMPMatch_java.html
 * @since May 9, 2008
 */
@Immutable
public class KMPMatch {

  private final byte[] match;
  private final int[] failure;

  /**
   * Constructor
   * @param match search for this byte pattern
   */
  public KMPMatch(byte[] match) {
    this.match = match;
    failure = computeFailure(match);
  }

  public int getMatchLength() { return match.length; }

  /**
   * Finds the first occurrence of match in data.
   * @param data search in this byte block
   * @param start start at data[start]
   * @param max end at data[start+max]
   * @return index into data[] of first match, else -1 if not found.
   */
  public int indexOf(byte[] data, int start, int max) {
    int j = 0;
    if (data.length == 0) return -1;
    if (start + max > data.length)
      System.out.println("HEY KMPMatch");

    for (int i = start; i < start + max; i++) {
      while (j > 0 && match[j] != data[i])
        j = failure[j - 1];

      if (match[j] == data[i])
        j++;

      if (j == match.length)
        return i - match.length + 1;

    }
    return -1;
  }

  /*
   * Finds the first occurrence of match in data.
   * @param data search in this byte block
   * @param start start at data[start]
   * @param max end at data[start+max]
   * @return index into block of first match, else -1 if not found.
   *
  public int scan(InputStream is, int start, int max) {
    int j = 0;
    if (data.length == 0) return -1;

    for (int i = start; i < start + max; i++) {
      while (j > 0 && match[j] != data[i])
        j = failure[j - 1];

      if (match[j] == data[i])
        j++;

      if (j == match.length)
        return i - match.length + 1;

    }
    return -1;
  } // */

  private int[] computeFailure(byte[] match) {
    int[] result = new int[match.length];

    int j = 0;
    for (int i = 1; i < match.length; i++) {
      while (j > 0 && match[j] != match[i])
        j = result[j - 1];

      if (match[j] == match[i])
        j++;

      result[i] = j;
    }

    return result;
  }
}


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

import ucar.nc2.dataset.*;
import ucar.nc2.constants.*;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.dt.*;
import ucar.nc2.ft.FeatureDataset;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.units.DateUnit;
import ucar.nc2.VariableSimpleIF;
import ucar.nc2.Variable;
import ucar.ma2.*;

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

/**
 * Make a Nids NetcdfDataset into a RadialDataset.
 *
 * @author yuan
 */

public class NidsRadialAdapter extends AbstractRadialAdapter {
  private NetcdfDataset ds;

  /////////////////////////////////////////////////
  public Object isMine( FeatureType wantFeatureType, NetcdfDataset ncd, Formatter errlog) throws IOException {
    String convention = ncd.findAttValueIgnoreCase(null, "Conventions", null);
    if ((null != convention) && convention.equals(_Coordinate.Convention)) {
      String format = ncd.findAttValueIgnoreCase(null, "Format", null);
      if (format != null && format.equals("Level3/NIDS"))
        return this;
    }
    return null;
  }

  public FeatureDataset open( FeatureType ftype, NetcdfDataset ncd, Object analysis, ucar.nc2.util.CancelTask task, Formatter errlog) throws IOException {
    return new NidsRadialAdapter(ncd);
  }

  public FeatureType getScientificDataType() {
    return FeatureType.RADIAL;
  }

  // needed for FeatureDatasetFactory
  public NidsRadialAdapter() {
  }

  /**
   * Constructor.
   *
   * @param ds must be from nids IOSP
   */
  public NidsRadialAdapter(NetcdfDataset ds) {
    super(ds);
    this.ds = ds;
    desc = "Nids 2 radar dataset";

    try {
      if (ds.findGlobalAttribute("isRadial").getNumericValue().intValue() == 0) {
        parseInfo.append("*** Dataset is not a radial data\n");
        throw new IOException("Dataset is not a radial data\n");
      }

      setEarthLocation();
      setTimeUnits();
      setStartDate();
      setEndDate();
      setBoundingBox();
    } catch (Throwable e) {
      System.err.println("CDM radial dataset failed to open this dataset " + e);
    }
  }

  public ucar.unidata.geoloc.EarthLocation getCommonOrigin() {
    return origin;
  }

  public String getRadarID() {
    ucar.nc2.Attribute att = ds.findGlobalAttribute("ProductStation");
    if (att == null)
      att = ds.findGlobalAttribute("Product_station");
    return att.getStringValue();
  }

  public boolean isStationary() {
    return true;
  }

  public String getRadarName() {
    return ds.findGlobalAttribute("ProductStationName").getStringValue();
  }

  public String getDataFormat() {
    return "Level III";
  }

  public boolean isVolume() {
    return false;
  }

  protected void setEarthLocation() {
    double lat = 0.0;
    double lon = 0.0;
    double elev = 0.0;
    ucar.nc2.Attribute attLat = ds.findGlobalAttribute("RadarLatitude");
    ucar.nc2.Attribute attLon = ds.findGlobalAttribute("RadarLongitude");
    ucar.nc2.Attribute attElev = ds.findGlobalAttribute("RadarAltitude");
    try {
      if (attLat != null)
        lat = attLat.getNumericValue().doubleValue();
      else
        throw new IOException("Unable to init radar location!\n");

      if (attLon != null)
        lon = attLon.getNumericValue().doubleValue();
      else
        throw new IOException("Unable to init radar location!\n");

      if (attElev != null)
        elev = attElev.getNumericValue().intValue();
      else
        throw new IOException("Unable to init radar location!\n");

    } catch (Throwable e) {
      System.err.println("CDM radial dataset failed to open this dataset " + e);

    }
    origin = new ucar.unidata.geoloc.EarthLocationImpl(lat, lon, elev);
  }

  protected void setTimeUnits() throws Exception {
    CoordinateAxis axis = ds.findCoordinateAxis(AxisType.Time);
    if (axis == null) {
      parseInfo.append("*** Time Units not Found\n");

    } else {
      String units = axis.getUnitsString();
      dateUnits = new DateUnit(units);
      calDateUnits = CalendarDateUnit.of(null, units);
    }

  }

  protected void setStartDate() {

    String start_datetime = ds.findAttValueIgnoreCase(null, "time_coverage_start", null);
    if (start_datetime != null) {
      startDate = DateUnit.getStandardOrISO(start_datetime);
      return;
    } else {
      CoordinateAxis axis = ds.findCoordinateAxis(AxisType.Time);
      if (axis != null) {
        double val = axis.getMinValue();
        startDate = dateUnits.makeDate(val);
        return;
      }
    }
    parseInfo.append("*** start_datetime not Found\n");
  }

  protected void setEndDate() {

    String end_datetime = ds.findAttValueIgnoreCase(null, "time_coverage_end", null);
    if (end_datetime != null) {
      endDate = DateUnit.getStandardOrISO(end_datetime);
    } else {
      CoordinateAxis axis = ds.findCoordinateAxis(AxisType.Time);
      if (axis != null) {
        double val = axis.getMaxValue();
        endDate = dateUnits.makeDate(val);
        return;
      }
    }

    parseInfo.append("*** end_datetime not Found\n");
  }

  protected void addRadialVariable(NetcdfDataset nds, Variable var) {
    RadialVariable rsvar = null;
    String vName = var.getShortName();
    int rnk = var.getRank();

    if (!var.getShortName().endsWith("RAW") && rnk == 2) {
      VariableSimpleIF v = new MyRadialVariableAdapter(vName, var.getAttributes());
      rsvar = new Nids2Variable(nds, v, var);
    }

    if (rsvar != null)
      dataVariables.add(rsvar);
  }

  public void clearDatasetMemory() {
    List rvars = getDataVariables();
    Iterator iter = rvars.iterator();
    while (iter.hasNext()) {
      RadialVariable radVar = (RadialVariable) iter.next();
      radVar.clearVariableMemory();
    }
  }

  protected RadialVariable makeRadialVariable(NetcdfDataset nds, VariableSimpleIF v, Variable v0) {
    // this function is null in level 2
    return null;
  }

  public String getInfo() {
    StringBuilder sbuff = new StringBuilder();
    sbuff.append("Nids2Dataset\n");
    sbuff.append(super.getDetailInfo());
    sbuff.append("\n\n");
    sbuff.append(parseInfo.toString());
    return sbuff.toString();
  }

  private class Nids2Variable extends MyRadialVariableAdapter implements RadialDatasetSweep.RadialVariable {
    ArrayList sweeps;
    String name;

    private Nids2Variable(NetcdfDataset nds, VariableSimpleIF v, Variable v0) {
      super(v.getShortName(), v0.getAttributes());
      sweeps = new ArrayList();
      name = v.getShortName();

      int[] shape = v0.getShape();
      int count = v0.getRank() - 1;

      int ngates = shape[count];
      count--;
      int nrays = shape[count];
      count--;

      sweeps.add(new Nids2Sweep(nds, v0, 0, nrays, ngates));
      //sweep = new Sweep[nsweeps];
    }

    public String toString() {
      return name;
    }

    public float[] readAllData() throws IOException {
      Array allData;
      Sweep spn = (Sweep) sweeps.get(0);
      Variable v = spn.getsweepVar();
      try {
        allData = v.read();
      } catch (IOException e) {
        throw new IOException(e.getMessage());
      }
      return (float[]) allData.get1DJavaArray(float.class);
    }

    public int getNumSweeps() {
      return 1;
    }

    public Sweep getSweep(int sn) {
      if (sn != 0) return null;
      return (Sweep) sweeps.get(sn);
    }

    public void clearVariableMemory() {
      // doing nothing
    }

    //////////////////////////////////////////////////////////////////////
    private class Nids2Sweep implements RadialDatasetSweep.Sweep {
      double meanElevation = Double.NaN;
      double meanAzimuth = Double.NaN;
      double gateSize = Double.NaN;
      int nrays, ngates;
      Variable sweepVar;
      NetcdfDataset ds;

      Nids2Sweep(NetcdfDataset nds, Variable v, int sweepno, int rays, int gates) {
        this.sweepVar = v;
        this.nrays = rays;
        this.ngates = gates;
        this.ds = nds;
        //setMeanElevation(nds);
        //setMeanAzimuth(nds);
      }

      public Variable getsweepVar() {
        return sweepVar;
      }

      private void setMeanElevation() {
        if (Double.isNaN(meanElevation)) {
          try {
            Variable sp = ds.findVariable("elevation");
            Array spData = sp.read();
            sp.setCachedData(spData, false);
            meanElevation = MAMath.sumDouble(spData) / spData.getSize();
          } catch (IOException e) {
            e.printStackTrace();
            meanElevation = 0.0;
          }
        }
      }

      public float getMeanElevation() {
        if (Double.isNaN(meanElevation))
          setMeanElevation();
        return (float) meanElevation;
      }

      private void setMeanAzimuth() {
        if (getType() != null) {
          Array spData = null;
          try {
            Variable sp = ds.findVariable("azimuth");
            spData = sp.read();
            sp.setCachedData(spData, false);

          } catch (IOException e) {
            e.printStackTrace();
            meanAzimuth = 0.0;
          }
          meanAzimuth = MAMath.sumDouble(spData) / spData.getSize();

        } else meanAzimuth = 0.0;

      }

      public float getMeanAzimuth() {
        if (Double.isNaN(meanAzimuth))
          setMeanAzimuth();
        return (float) meanAzimuth;
      }

      public int getNumRadials() {
        return nrays;
      }

      public int getNumGates() {
        return ngates;
      }

      /* a 2D array nradials * ngates   */
      public float[] readData() throws IOException {
        Array allData = null;
        int[] shape = sweepVar.getShape();
        int[] origind = new int[sweepVar.getRank()];
        try {
          allData = sweepVar.read(origind, shape);
        } catch (InvalidRangeException e) {
          throw new IOException(e.getMessage());
        }
        return (float[]) allData.get1DJavaArray(float.class);
      }

      /* read 1d data ngates */
      public float[] readData(int ray) throws java.io.IOException {
        Array rayData;
        int[] shape = sweepVar.getShape();
        int[] origind = new int[sweepVar.getRank()];
        shape[0] = 1;
        origind[0] = ray;
        try {
          rayData = sweepVar.read(origind, shape);
        } catch (ucar.ma2.InvalidRangeException e) {
          throw new IOException(e.getMessage());
        }
        return (float[]) rayData.get1DJavaArray(float.class);
      }

      public RadialDatasetSweep.Type getType() {
        return null;
      }

      public boolean isConic() {
        return true;
      }

      public float getElevation(int ray) throws IOException {
        // setMeanElevation();
        return (float) meanElevation;
      }

      public float[] getElevation() throws IOException {
        float[] spArray = null;
        try {
          Variable sp = ds.findVariable("elevation");
          Array spData = sp.read();
          sp.setCachedData(spData, false);
          spArray = (float[]) spData.get1DJavaArray(float.class);

        } catch (IOException e) {
          e.printStackTrace();
        }

        return spArray;
      }

      public float getAzimuth(int ray) throws IOException {
        Variable sp = ds.findVariable("azimuth");
        Array spData = sp.read();
        sp.setCachedData(spData, false);
        Index index = spData.getIndex();
        return spData.getFloat(index.set(ray));
      }

      public float[] getAzimuth() throws IOException {
        Variable sp = ds.findVariable("azimuth");
        Array spData = sp.read();
        sp.setCachedData(spData, false);
        return (float[]) spData.get1DJavaArray(float.class);
      }

      public float getRadialDistance(int gate) throws IOException {
        Variable sp = ds.findVariable("gate");
        Array spData = sp.read();
        sp.setCachedData(spData, false);
        Index index = spData.getIndex();
        return spData.getFloat(index.set(gate));
      }

      public float getTime(int ray) throws IOException {
        Variable sp = ds.findVariable("rays_time");
        Array timeData = sp.read();
        sp.setCachedData(timeData, false);
        Index index = timeData.getIndex();
        return timeData.getFloat(index.set(ray));
      }

      public float getBeamWidth() {
        return 0.95f; // degrees, info from Chris Burkhart
      }

      public float getNyquistFrequency() {
        return 0; // LOOK this may be radial specific
      }

      public float getRangeToFirstGate() {
        return 0.0f;
      }

      public float getGateSize() {
        try {
            if (Double.isNaN(gateSize))
               gateSize = getRadialDistance(1) - getRadialDistance(0);
            return (float) gateSize;

        } catch (IOException e) {
          e.printStackTrace();
          return 0.0f;
        }
      }

      public Date getStartingTime() {
        return startDate;
      }

      public Date getEndingTime() {
        return endDate;
      }

      public boolean isGateSizeConstant() {
        return true;
      }

      public int getGateNumber() {
        return ngates;
      }

      public int getRadialNumber() {
        return nrays;
      }

      public ucar.unidata.geoloc.EarthLocation getOrigin(int ray) {
        return origin;
      }

      public int getSweepIndex() {
        return 0;
      }

      public void clearSweepMemory() {
        // doing nothing for nids adapter
      }
    } // Nids2Sweep class

  } // Nids2Variable

} // Nids2Dataset


/* Copyright (C) 2002  Richard Eigenmann.
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or any later version. This program is distributed
in the hope that it will be useful, but WITHOUT ANY WARRANTY;
without even the implied warranty of MERCHANTABILITY or FITNESS
FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
more details. You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
The license is in gpl.txt.
See http://www.gnu.org/copyleft/gpl.html for the details.
*/
package ucar.nc2.ui.image;

import java.util.*;
import java.net.*;

/**
 *  class that manages the cache of pictures
 *
 **/
public class PictureCache  {

	/**
	 *  hashtable to facilitate the caching of images. It is static
	 *  so that there is just one for the application
	 */
	private static Hashtable pictureCache = new Hashtable();

	/**
	 *  This Vector keeps track of which pictures the PictureCache has been
	 *  requested to load in the background. They may have to be stopped in
	 *  a hurry.
	 */
	public static Vector cacheLoadsInProgress = new Vector();

	/**
	 *  Vector to keep track of what we should remove first in the cache
	 */
	private static Vector removalQueue = new Vector();

  private static int maxCache = 10; // was Settings

	/**
	 *  this method removes the least popular picture(s) in the cache.
	 *  It first removes those pictures which have been suggested for
	 *  removal. And then it picks any it can find
	 *  As many pictures are removed as nescessary untill there are less pictures in the
	 *  cache than the Settings.maxCache specifies. (If maxCache is 0 then the
	 *  Enumeration finds no elements and we don't get an endless loop.
	 */
	public static synchronized void removeLeastPopular() {
		Tools.log("PictureCache.removeLeastPopular:");
		//reportCache();

		Enumeration e = removalQueue.elements();
		while ( ( e.hasMoreElements() )
		    &&  ( pictureCache.size() >= maxCache ) ) {
		     	String removeElement = (String) e.nextElement();
		     	Tools.log ("PictureCache.remove: " + removeElement );
			pictureCache.remove( removeElement );
			removalQueue.remove( removeElement );
		}

		e = pictureCache.keys();
		while ( ( pictureCache.size() >= maxCache )
		     && ( e.hasMoreElements() ) ) {
		     	String removeElement = (String) e.nextElement();
		     	Tools.log ("PictureCache.remove: " + removeElement );
			pictureCache.remove( removeElement );
		}
	}

	/**
	 *   Method that can be called when a picture is no longer needed.
	 *   These pictures will be removed first from the cache when
	 *   we need more space.
	 */
/*	public static void suggestCacheRemoval( SortableDefaultMutableTreeNode node ) {
		if (node == null ) return;
		Object userObject = node.getUserObject();
		if ( userObject instanceof PictureInfo ) {
			try {
				removalQueue.add( ((PictureInfo) userObject ).getHighresURL().toString() );
			} catch ( MalformedURLException x ) {
				// ignore
			}
		}
	}*/

	/**
	 *  returns whether an image is in the cache.

	 */
	public static synchronized boolean isInCache ( URL url ) {
		return isInCache( url.toString() );
	}

	/**
	 *  returns whether an image is in the cache.

	 */
	public static synchronized boolean isInCache ( String urlString ) {
		return pictureCache.containsKey( urlString );
	}

	/**
	 *  store an image in the cache
	 *  @param url	The URL of the picture
	 *  @param sp	The picture to be stored
	 */
	public static synchronized void add( URL url, SourcePicture sp ) {
		Tools.log("PictureCache.add: " + url.toString() );
		if ( sp.getSourceBufferedImage() == null ) {
			Tools.log ("PictureCache.add: invoked with a null picture! Not cached!");
			return;
		}

		if ( ( maxCache < 1 ) ) {
			Tools.log("PictureCache.add: cache is diabled. Not adding picture.");
			return;
		}

		if ( isInCache( url ) ) {
			Tools.log( "Picture " + url.toString() + " is already in the cache. Not adding again.");
			return;
		}

		if ( pictureCache.size() >= maxCache )
			removeLeastPopular();

		if ( pictureCache.size() < maxCache )
			pictureCache.put( url.toString(), sp );

		//reportCache();
	}

	/**
	 *  remove a picture from the cache
	 */
	public static synchronized void remove( String urlString ) {
		if ( isInCache( urlString ) ) {
			pictureCache.remove( urlString );
		}
	}

	/**
	 *  returns a picture from the cache. Returns null if image is not there
	 *  @param url 	The URL of the picture to be retrieved
	 */
	public static synchronized SourcePicture getSourcePicture( URL url ) {
		return (SourcePicture) pictureCache.get( url.toString() );
	}

	/**
	 *  clears out all images in the cache. Important after OutOfMemoryErrors
	 */
	public static synchronized void clear() {
		Tools.log("PictureCache.clear: Zapping entire cache");
		pictureCache.clear();
	}

	/**
	 *  method to inspect the cache
	 */
	public static synchronized void reportCache() {
		Tools.log("   PictureCache.reportCache: cache contains: "
			+ Integer.toString( pictureCache.size() )
			+ " max: "
			+ Integer.toString( maxCache ) );
		//Tools.freeMem();
		Enumeration e = pictureCache.keys();
		while ( e.hasMoreElements() ) {
			Tools.log("   Cache contains: " + ((String) e.nextElement()) );
		}
		Tools.log("  End of cache contents");
	}

	/**
	 * method to stop all background loading
	 */
	public static void stopBackgroundLoading() {
		Enumeration e = cacheLoadsInProgress.elements();
		while ( e.hasMoreElements() ) {
			((SourcePicture) e.nextElement()).stopLoading();
		}
	}

	/**
	 *  method to stop all background loading except the indicated file. Returns whether the
	 *  image is already being loaded. True = loading in progress, False = not in progress.
	 */
	public static boolean stopBackgroundLoadingExcept( URL exemptionURL ) {
		SourcePicture sp;
		String exemptionURLString = exemptionURL.toString();
		Enumeration e = cacheLoadsInProgress.elements();
		boolean inProgress = false;
		while ( e.hasMoreElements() ) {
			sp = ((SourcePicture) e.nextElement());
			if ( ! sp.getUrlString().equals( exemptionURLString ) )
				sp.stopLoading();
			else {
				Tools.log( "PictureCache.stopBackgroundLoading: picture was already loading");
				inProgress = true;
			}
		}
		return inProgress;
	}

}


package ucar.nc2.ui.table;

import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.*;
import java.util.Vector;
import java.util.Enumeration;

/**
 * HidableTableColumnModel extends the DefaultTableColumnModel .
 * It provides a comfortable way to hide/show columns.
 * Columns keep their positions when hidden and shown again.
 *

 * In order to work with JTable it cannot add any events to TableColumnModelListener.
 * Therefore hiding a column will result in columnRemoved event and showing it
 * again will notify listeners of a columnAdded, and possibly a columnMoved event.
 * For the same reason the following methods still deal with visible columns only:
 * getColumnCount(), getColumns(), getColumnIndex(), getColumn()
 * There are overloaded versions of these methods that take a parameter onlyVisible which let's
 * you specify whether you want invisible columns taken into account.
 *
 * @author Stephen Kelvin, mail@StephenKelvin.de
 * @version 0.9 04/03/01
 * @see DefaultTableColumnModel
 * @see original documentation
 */
public class HidableTableColumnModel extends DefaultTableColumnModel implements TableModelListener {
    /** The model we're currently listening to. */
    private TableModel model;

    /** Array of TableColumn objects in this model. Holds all column objects, regardless of their visibility. */
    protected Vector<TableColumn> allTableColumns = new Vector<>();

    public HidableTableColumnModel(TableModel model) {
        createColumnsFromModel(model);
    }

    /**
     * Sets the visibility of the specified TableColumn.
     * The call is ignored if the TableColumn is not found in this column model
     * or its visibility status did not change.
     *

     *
     * @param column the column to show/hide
     * @param visible its new visibility status
     */
    // listeners will receive columnAdded()/columnRemoved() event
    public void setColumnVisible(TableColumn column, boolean visible) {
        if (isColumnVisible(column) == visible) {
            return;  // Visibility status did not change.
        }

        if (!visible) {
            super.removeColumn(column);
        } else {
            // find the visible index of the column:
            // iterate through both collections of visible and all columns, counting
            // visible columns up to the one that's about to be shown again
            int noVisibleColumns = tableColumns.size();
            int noInvisibleColumns = allTableColumns.size();
            int visibleIndex = 0;

            for (int invisibleIndex = 0; invisibleIndex < noInvisibleColumns; ++invisibleIndex) {
                TableColumn visibleColumn =
                        (visibleIndex < noVisibleColumns ? tableColumns.get(visibleIndex) : null);
                TableColumn testColumn = allTableColumns.get(invisibleIndex);

                if (testColumn == column) {
                    if (visibleColumn != column) {
                        super.addColumn(column);
                        super.moveColumn(tableColumns.size() - 1, visibleIndex);
                    }
                    return; // ####################
                }
                if (testColumn == visibleColumn) {
                    ++visibleIndex;
                }
            }
        }
    }

    /**
     * Checks whether the specified column is currently visible.
     *
     * @param aColumn column to check
     * @return visibility of specified column (false if there is no such column at all. [It's not visible, right?])
     */
    public boolean isColumnVisible(TableColumn aColumn) {
        return (tableColumns.indexOf(aColumn) >= 0);
    }

    /**
     * Append column to the right of exisiting columns.
     * Posts columnAdded event.
     *
     * @param column The column to be added
     * @throws IllegalArgumentException if column is null
     * @see #removeColumn
     */
    @Override
    public void addColumn(TableColumn column) {
        allTableColumns.addElement(column);
        super.addColumn(column);
    }

    /**
     * Removes column from this column model.
     * Posts columnRemoved event.
     * Will do nothing if the column is not in this model.
     *
     * @param column the column to be added
     * @see #addColumn
     */
    @Override
    public void removeColumn(TableColumn column) {
        int allColumnsIndex = allTableColumns.indexOf(column);
        if (allColumnsIndex != -1) {
            allTableColumns.removeElementAt(allColumnsIndex);
        }
        super.removeColumn(column);
    }

    /**
     * Moves the column from oldIndex to newIndex.
     * Posts  columnMoved event.
     * Will not move any columns if oldIndex equals newIndex.
     *
     * @throws IllegalArgumentException if either oldIndex or
     *                                  newIndex
     *                                  are not in [0, getColumnCount() - 1]
     * @param    oldIndex            index of column to be moved
     * @param    newIndex            new index of the column
     */
    @Override
    public void moveColumn(int oldIndex, int newIndex) {
        if ((oldIndex < 0) || (oldIndex >= getColumnCount()) ||
                (newIndex < 0) || (newIndex >= getColumnCount()))
            throw new IllegalArgumentException("moveColumn() - Index out of range");

        TableColumn fromColumn = tableColumns.get(oldIndex);
        TableColumn toColumn = tableColumns.get(newIndex);

        int allColumnsOldIndex = allTableColumns.indexOf(fromColumn);
        int allColumnsNewIndex = allTableColumns.indexOf(toColumn);

        if (oldIndex != newIndex) {
            allTableColumns.removeElementAt(allColumnsOldIndex);
            allTableColumns.insertElementAt(fromColumn, allColumnsNewIndex);
        }

        super.moveColumn(oldIndex, newIndex);
    }

    /**
     * Returns the total number of columns in this model.
     *
     * @param onlyVisible if set only visible columns will be counted
     * @return the number of columns in the tableColumns array
     * @see    #getColumns
     */
    public int getColumnCount(boolean onlyVisible) {
        Vector columns = (onlyVisible ? tableColumns : allTableColumns);
        return columns.size();
    }

    /**
     * Returns an Enumeration of all the columns in the model.
     *
     * @param onlyVisible if set all invisible columns will be missing from the enumeration.
     * @return an Enumeration of the columns in the model
     */
    public Enumeration<TableColumn> getColumns(boolean onlyVisible) {
        Vector columns = (onlyVisible ? tableColumns : allTableColumns);

        return columns.elements();
    }

    /**
     * Returns the TableColumn object for the column
     * at columnIndex.
     *
     * @param    columnIndex    the index of the column desired
     * @param    onlyVisible    if set columnIndex is meant to be relative to all visible columns only
     * else it is the index in all columns
     * @return the TableColumn object for the column
     * at columnIndex
     */
    public TableColumn getColumn(int columnIndex, boolean onlyVisible) {
        if (onlyVisible) {
            return tableColumns.elementAt(columnIndex);
        } else {
            return allTableColumns.elementAt(columnIndex);
        }

    }

    @Override public void tableChanged(TableModelEvent e) {
        if (e.getFirstRow() == TableModelEvent.HEADER_ROW) {  // We only care about TableStructureChanged events.
            assert e.getSource() instanceof TableModel :
                    String.format("Why is %s firing a %s?", e.getSource(), e.getClass());
            createColumnsFromModel((TableModel) e.getSource());
        }
    }

    // Adapted from JTable.createDefaultColumnsFromModel().
    public void createColumnsFromModel(TableModel newModel) {
        if (newModel != model) {
            if (model != null) {
                model.removeTableModelListener(this);  // Stop listening to old model.
            }

            newModel.addTableModelListener(this);  // Start listening to new one.
            model = newModel;
        }

        // Removes all current columns including the hidden ones.
        // For visible columns that are removed, TableColumnModelEvents get fired.
        while (!allTableColumns.isEmpty()) {
            removeColumn(allTableColumns.elementAt(0));
        }

        // Create new columns from the data model info
        for (int modelColumnIndex = 0; modelColumnIndex < newModel.getColumnCount(); modelColumnIndex++) {
            TableColumn newColumn = new TableColumn(modelColumnIndex);
            String columnName = newModel.getColumnName(modelColumnIndex);
            newColumn.setHeaderValue(columnName);
            addColumn(newColumn);
        }
    }
}