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

package ucar.nc2.iosp.misc;

import ucar.ma2.*;

import ucar.nc2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.util.CancelTask;

import ucar.unidata.io.RandomAccessFile;

import java.io.IOException;

import java.nio.ByteBuffer;

/**
 * National Lightning Detection Network (NLDN)
 *
 * @author caron
 * @see "http://www.unidata.ucar.edu/data/lightning.html"
 * @since Nov 19, 2008
 */
public class Nldn extends AbstractLightningIOSP {

  /*
    Field               Example
    -------------------+---------------------
    date/time (msec)    09/22/93 10:22:33.334
    latitude                            47.33
    longitude                         -87.116
    polarity/signal strength           -188.7
    multiplicity                            6
    ellipse angle                         174
    semi-major axis                       6.0
    eccentricity                          2.0
    chi-square                            1.0

The specifics for the binary NLDN data record contained in the IDD is:

    Size     Name      Description
    --------+---------+----------------------------------------------------
    char[4]  NLDN      'NLDN' marks the start of record
    int[4]   tsec      time in seconds since 1970
    int[4]   nsec      nanoseconds since tsec (seems to be thousandths)
    int[4]   lat       latitude [deg] * 1000
    int[4]   lon       longitude [deg] * 1000
    short[2] fill      padding
    short[2] sgnl      signal strength * 10 [150 NLDN measures ~= 30 kAmps]
    short[2] fill      padding
    short[2] mult      multiplicity [#strokes per flash]
    char[1]  fill      padding
    char[1]  semimaj   semi-major axis
    char[1]  eccent    eccentricity
    char[1]  angle     ellipse angle
    char[1]  chisqr    chi-square

  */

  /**
   * The magic mushroom
   */
  private static final String MAGIC = "NLDN";

  /**
   * The structure members
   */
  private StructureMembers sm;

  /**
   * The time in seconds variable name
   */
  private static final String TSEC = "tsec";

  /**
   * The time in nanoseconds from TSEC variable name
   */
  private static final String NSEC = "nsec";

  /**
   * The chi squared variable name
   */
  private static final String CHISQR = "chisqr";

  /**
   * The fill variable name
   */
  private static final String FILL = "fill";

  /**
   * header size
   */
  private static final int recHeader = 84;

  /**
   * record size
   */
  private static final int recSize = 28;

  /**
   * Check if this is a valid file for this IOServiceProvider.
   * You must make this method thread safe, ie dont keep any state.
   *
   * @param raf RandomAccessFile
   * @return true if valid.
   * @throws IOException if read error
   */
  public boolean isValidFile(RandomAccessFile raf) throws IOException {
    raf.seek(0);
    String test = raf.readString(MAGIC.length());
    return test.equals(MAGIC);
  }

  /**
   * Get a unique id for this file type.
   *
   * @return registered id of the file type
   * @see "http://www.unidata.ucar.edu/software/netcdf-java/formats/FileTypes.html"
   */
  public String getFileTypeId() {
    return "NLDN";
  }

  /**
   * Get a human-readable description for this file type.
   *
   * @return description of the file type
   * @see "http://www.unidata.ucar.edu/software/netcdf-java/formats/FileTypes.html"
   */
  public String getFileTypeDescription() {
    return "National Lightning Detection Network";
  }

  /**
   * Open existing file, and populate ncfile with it. This method is only
   * called by the NetcdfFile constructor on itself. The provided NetcdfFile
   * object will be empty except for the location String and the
   * IOServiceProvider associated with this NetcdfFile object.
   *
   * @param raf        the file to work on, it has already passed the
   *                   isValidFile() test.
   * @param ncfile     add objects to this empty NetcdfFile
   * @param cancelTask used to monitor user cancellation; may be null.
   * @throws IOException if read error
   */
  public void open(RandomAccessFile raf, NetcdfFile ncfile, CancelTask cancelTask) throws IOException {
    super.open(raf, ncfile, cancelTask);

    Structure seq = new Sequence(ncfile, null, null, RECORD);
    ncfile.addVariable(null, seq);

    /*
    makeLightningVariable(NetcdfFile ncfile, Group group,
                          Structure seq, String name,
                          DataType dataType, String dims,
                          String longName, String cfName,
                          String units, AxisType type) {
    */
    Variable v = makeLightningVariable(ncfile, null, seq, TSEC, DataType.INT,
            "", "time of stroke", null,
            secondsSince1970,
            AxisType.Time);
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, "nsec", DataType.INT,
            "", "nanoseconds since tsec", null,
            "1.0e-9 s", null);
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, LAT, DataType.INT, "",
            "latitude", "latitude", CDM.LAT_UNITS,
            AxisType.Lat);
    v.addAttribute(new Attribute(CDM.SCALE_FACTOR, new Float(1.0e-3)));
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, LON, DataType.INT, "",
            "longitude", "longitude", CDM.LON_UNITS,
            AxisType.Lon);
    v.addAttribute(new Attribute(CDM.SCALE_FACTOR, new Float(1.0e-3)));
    seq.addMemberVariable(v);

    v = makeLightningVariable(
            ncfile, null, seq, SIGNAL, DataType.SHORT, "",
            "signal strength/polarity [150 NLDN measures ~= 30 kAmps]", null, "",
            null);
    v.addAttribute(new Attribute(CDM.SCALE_FACTOR, new Float(1.0e-1)));
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, MULTIPLICITY,
            DataType.BYTE, "",
            "multiplicity [#strokes per flash]", null,
            "", null);
    seq.addMemberVariable(v);

    v = new Variable(ncfile, null, seq, FILL);
    v.setDataType(DataType.BYTE);
    v.setDimensions("");
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, MAJOR_AXIS,
            DataType.BYTE, "",
            "error ellipse semi-major axis", null, "",
            null);
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, ECCENTRICITY,
            DataType.BYTE, "",
            "error ellipse eccentricity ", null, "",
            null);
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, ELLIPSE_ANGLE,
            DataType.BYTE, "",
            "error ellipse axis angle of orientation ",
            null, "degrees", null);
    seq.addMemberVariable(v);

    v = makeLightningVariable(ncfile, null, seq, CHISQR, DataType.BYTE,
            "", "chi-squared", null, "", null);
    seq.addMemberVariable(v);

    addLightningGlobalAttributes(ncfile);

    ncfile.finish();

    sm = seq.makeStructureMembers();
    sm.findMember(TSEC).setDataParam(0);
    sm.findMember(NSEC).setDataParam(4);
    sm.findMember(LAT).setDataParam(8);
    sm.findMember(LON).setDataParam(12);
    sm.findMember(SIGNAL).setDataParam(18);
    sm.findMember(MULTIPLICITY).setDataParam(22);
    sm.findMember(FILL).setDataParam(23);
    sm.findMember(MAJOR_AXIS).setDataParam(24);
    sm.findMember(ECCENTRICITY).setDataParam(25);
    sm.findMember(ELLIPSE_ANGLE).setDataParam(26);
    sm.findMember(CHISQR).setDataParam(27);
    sm.setStructureSize(recSize);
  }

  /**
   * Add the global attributes.
   *
   * @param ncfile the file to add to
   */
  protected void addLightningGlobalAttributes(NetcdfFile ncfile) {
    super.addLightningGlobalAttributes(ncfile);
    ncfile.addAttribute(null,
            new Attribute("title", "NLDN Lightning Data"));

    ncfile.addAttribute(null, new Attribute(CDM.CONVENTIONS, "NLDN-CDM"));
  }

  /* The specifics for the binary NLDN data record contained in the IDD is:

    Size     Name      Description
    --------+---------+----------------------------------------------------
    char[4]  NLDN      'NLDN' marks the start of record
    int[4]   tsec      time in seconds since 1970
    int[4]   nsec      nanoseconds since tsec (seems to be thousandths)
    int[4]   lat       latitude [deg] * 1000
    int[4]   lon       longitude [deg] * 1000
    short[2] fill      padding
    short[2] sgnl      signal strength * 10 [150 NLDN measures ~= 30 kAmps]
    short[2] fill      padding
    short[2] mult      multiplicity [#strokes per flash]
    char[1]  fill      padding
    char[1]  semimaj   semi-major axis
    char[1]  eccent    eccentricity
    char[1]  angle     ellipse angle
    char[1]  chisqr    chi-square
  */

  /* public Array readData(Variable v2, Section section) throws IOException, InvalidRangeException {
  Range r = section.getRange(0);
  int nrecs = r.length();
  byte[] bb = new byte[nrecs * recSize];

  int pos = 0;
  Range.Iterator iter = r.getIterator();
  while (iter.hasNext()) {
    int index = iter.next();
    raf.seek(recHeader + index * recSize);
    raf.read(bb, pos, recSize);
    pos += recSize;
  }

  return new ArrayStructureBB(sm, new int[]{nrecs}, ByteBuffer.wrap(bb), 0);
}  */

  /**
   * Read data from a top level Variable and return a memory resident Array.
   * This Array has the same element type as the Variable, and the requested shape.
   *
   * @param v2      a top-level Variable
   * @param section the section of data to read.
   *                There must be a Range for each Dimension in the variable, in order.
   *                Note: no nulls allowed. IOSP may not modify.
   * @return the requested data in a memory-resident Array
   * @throws IOException           if read error
   * @throws InvalidRangeException if invalid section
   * @see ucar.ma2.Range
   */
  public Array readData(Variable v2, Section section)
          throws IOException, InvalidRangeException {
    return new ArraySequence(sm, new SeqIter(), nelems);
  }

  private int nelems = -1;

  /**
   * Get the structure iterator
   *
   * @param s          the Structure
   * @param bufferSize the buffersize
   * @return the data iterator
   * @throws java.io.IOException if problem reading data
   */
  public StructureDataIterator getStructureIterator(Structure s,
                                                    int bufferSize)
          throws java.io.IOException {
    return new SeqIter();
  }

  /**
   * Sequence Iterator
   *
   * @author Unidata Development Team
   */
  private class SeqIter implements StructureDataIterator {

    /**
     * done?
     */
    private int done = 0;

    /**
     * number bytes already read
     */
    private int alreadyRead = 0;

    /**
     * next index
     */
    private int nextIndex = 0;

    /**
     * the wrapped asbb
     */
    private ArrayStructureBB asbb = null;

    /**
     * total bytes
     */
    private long totalBytes;

    /**
     * bytes read
     */
    private long bytesRead;

    /**
     * Create a new one
     *
     * @throws IOException problem reading the file
     */
    SeqIter() throws IOException {
      totalBytes = (int) raf.length();
      raf.seek(0);
    }

    @Override
    public StructureDataIterator reset() {
      done = 0;
      alreadyRead = 0;
      bytesRead = 0;
      nextIndex = 0;

      try {
        raf.seek(0);
      } catch (IOException e) {
        throw new RuntimeException(e);
      }
      return this;
    }

    @Override
    public boolean hasNext() throws IOException {
      return done < alreadyRead || readHeader();
    }

    @Override
    public StructureData next() throws IOException {
      done++;
      return asbb.getStructureData(nextIndex++);
    }

    /**
     * Read the header
     *
     * @return true if okay
     * @throws IOException problem reading file
     */
    private boolean readHeader() throws IOException {
      if ((bytesRead + recHeader) > totalBytes) {
        nelems = done; // record the number of elements for next time
        return false;
      }

      byte[] b = new byte[recHeader];
      raf.readFully(b);
      bytesRead += recHeader;

      ByteBuffer bb = ByteBuffer.wrap(b);
      int count = bb.getInt(8);
      if (count == 0) {
        return readHeader();
      }

      if ((bytesRead + count * recSize) > totalBytes) {
        return false;
      }
      byte[] data = new byte[count * recSize];
      raf.readFully(data);
      bytesRead += count * recSize;
      alreadyRead += count;
      nextIndex = 0;

      ByteBuffer bbdata = ByteBuffer.wrap(data);
      asbb = new ArrayStructureBB(sm, new int[]{count}, bbdata, 0);
      return true;
    }

    @Override
    public int getCurrentRecno() {
      return done - 1;
    }

  }

  // this is thye start of a buffererd iterator
  /* private class MySDIter implements StructureDataIterator {
   private int done = 0;
   private int readStart = 0;
   private int recsAlreadyRead = 0;
   private int readAtaTime;
   private ArrayStructureBB asbb = null;

   private int recsLeft;
   private int recsDone;

   MySDIter(int bufferSize) throws IOException {
     setBufferSize( bufferSize);
     recsLeft = (int) raf.length() / recSize;
     recsDone = 0;
   }

   public boolean hasNext() {
     if (done < recsAlreadyRead) return true;
     return (recsLeft > 0);
   }

   public StructureDataIterator reset() {
     done = 0;
     readStart = 0;
     readRead = 0;
     return this;
   }

   public StructureData next() throws IOException {
     if (done >= readStart) {
       readNextBuffer();
     }
     done++;
     return asbb.getStructureData( readRead++);
   }

   private void readNextBuffer() throws IOException {
     bytesLeft = (int)(raf.length() - raf.getFilePointer());
     int recsLeft = bytesLeft / recSize;
     int recsToRead = Math.min(recsLeft, readAtaTime);

     byte[] bb = new byte[recsToRead * recSize];
     ByteBuffer.wrap(bb);
     asbb = new ArrayStructureBB(sm, new int[]{nrecs}, bb, 0);

     recsAlreadyRead += recsToRead;
     readRead = 0;
   }

   public void setBufferSize(int bytes) {
     if (done > 0) return; // too late
     if (bytes <= 0)
       bytes = defaultBufferSize;
     readAtaTime = Math.max( 10, bytes / recSize);
   }
 } */

}


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

package opendap.servlet;

import java.io.*;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.Deflater;

/**
 * The Servlet to exercise what's available.
 *
 * @author Nathan David Potter
 */

public class DummySink extends DeflaterOutputStream {

    int count = 0;

    /**
     * Creates a new output stream with the specified compressor and
     * buffer size.
     *
     * @param out the output stream
     * @param def the compressor ("deflater")
     * @throws IllegalArgumentException if size is <= 0
     */
    public DummySink(OutputStream out, Deflater def, int size) {
        super(out);
        count = 0;
    }

    /**
     * Creates a new output stream with the specified compressor and
     * a default buffer size.
     *
     * @param out the output stream
     * @param def the compressor ("deflater")
     */
    public DummySink(OutputStream out, Deflater def) {
        this(out, def, 512);
    }

    /**
     * Creates a new output stream with a defaul compressor and buffer size.
     */
    public DummySink(OutputStream out) {
        this(out, new Deflater());
    }

    //Closes this output stream and releases any system resources associated with this stream.
    public void close() {
    }

    public void flush() {
    }

    public void write(int b) throws IOException {
        count++;
        super.write(b);
    }

    /**
     * Writes an array of bytes to the compressed output stream. This
     * method will block until all the bytes are written.
     *
     * @param off the start offset of the data
     * @param len the length of the data
     * @throws IOException if an I/O error has occurred
     */
    public void write(byte[] b, int off, int len) throws IOException {

        count += len;
        super.write(b, off, len);

    }

    public int getCount() {
        return count;
    }

    public void setCount(int c) {
        count = c;
    }

    public void resetCount() {
        count = 0;
    }

}


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

package opendap.servers;

import opendap.dap.*;

import java.io.*;

import opendap.servers.parsers.ExprParserConstants;

/**
 * Holds a OPeNDAP Server Unsigned Int32 value.
 *
 * @author ndp
 * @version $Revision: 15901 $
 * @see BaseType
 */
public abstract class SDUInt32 extends DUInt32 implements ServerMethods, RelOps, ExprParserConstants {
    private boolean Synthesized;
    private boolean ReadMe;

    /**
     * Constructs a new SDUInt32.
     */
    public SDUInt32() {
        super();
        Synthesized = false;
        ReadMe = false;
    }

    /**
     * Constructs a new SDUInt32 with name n.
     *
     * @param n the name of the variable.
     */
    public SDUInt32(String n) {
        super(n);
        Synthesized = false;
        ReadMe = false;
    }

    /**
     * Write the variable's declaration in a C-style syntax. This
     * function is used to create textual representation of the Data
     * Descriptor Structure (DDS).  See The OPeNDAP User Manual for
     * information about this structure.
     *
     * @param os          The PrintWriter on which to print the
     *                    declaration.
     * @param space       Each line of the declaration will begin with the
     *                    characters in this string.  Usually used for leading spaces.
     * @param print_semi  a boolean value indicating whether to print a
     *                    semicolon at the end of the declaration.
     * @param constrained a boolean value indicating whether to print
     *                    the declartion dependent on the projection information. This
     *                    is only used by Server side code.
     * @see DDS
     */
    public void printDecl(PrintWriter os, String space,
                          boolean print_semi, boolean constrained) {
        if (constrained && !isProject())
            return;

        // BEWARE! Since printDecl()is (multiple) overloaded in BaseType
        // and all of the different signatures of printDecl() in BaseType
        // lead to one signature, we must be careful to override that
        // SAME signature here. That way all calls to printDecl() for
        // this object lead to this implementation.

        // Also, since printDecl()is (multiple) overloaded in BaseType
        // and all of the different signatures of printDecl() in BaseType
        // lead to the signature we are overriding here, we MUST call
        // the printDecl with the SAME signature THROUGH the super class
        // reference (assuming we want the super class functionality). If
        // we do otherwise, we will create an infinte call loop. OOPS!

        super.printDecl(os, space, print_semi, constrained);
    }

    /**
     * Prints the value of the variable, with its declaration.  This
     * function is primarily intended for debugging OPeNDAP applications and
     * text-based clients such as geturl.
     *

     *
 Important Note

     * This method overrides the BaseType method of the same name and
     * type signature and it significantly changes the behavior for all versions
     * of printVal() for this type:
     *  All the various versions of printVal() will only
     * print a value, or a value with declaration, if the variable is
     * in the projection.
     *

     *
In other words, if a call to
     * isProject() for a particular variable returns
     * true then printVal() will print a value
     * (or a declaration and a value).
     *

     *
If isProject() for a particular variable returns
     * false then printVal() is basically a No-Op.
     *

     *

     *
     * @param os           the PrintWriter on which to print the value.
     * @param space        this value is passed to the printDecl method,
     *                     and controls the leading spaces of the output.
     * @param print_decl_p a boolean value controlling whether the
     *                     variable declaration is printed as well as the value.
     * @see BaseType#printVal(PrintWriter, String, boolean)
     * @see ServerMethods#isProject()
     */
    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
        if (!isProject())
            return;
        super.printVal(os, space, print_decl_p);
    }

// --------------- RelOps Interface

    /**
     * The RelOps interface defines how each type responds to relational
     * operators. Most (all?) types will not have sensible responses to all of
     * the relational operators (e.g. DInt won't know how to match a regular
     * expression but DString will). For those operators that are nonsensical a
     * class should throw InvalidOperatorException.
     */

    public boolean equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        return (Operator.op(EQUAL, this, bt));
    }

    public boolean not_equal(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        return (Operator.op(NOT_EQUAL, this, bt));
    }

    public boolean greater(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        return (Operator.op(GREATER, this, bt));
    }

    public boolean greater_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        return (Operator.op(GREATER_EQL, this, bt));
    }

    public boolean less(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        return (Operator.op(LESS, this, bt));
    }

    public boolean less_eql(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        return (Operator.op(LESS_EQL, this, bt));
    }

    public boolean regexp(BaseType bt) throws InvalidOperatorException, RegExpException, SBHException {
        return (Operator.op(REGEXP, this, bt));
    }

// --------------- FileIO Interface

    /**
     * Set the Synthesized property.
     *
     * @param state If true then the variable is considered a
     *              synthetic variable and no part of OPeNDAP will ever try to read it from a
     *              file, otherwise if false the variable is considered a
     *              normal variable whose value should be read using the
     *              read() method. By default this property is false.
     * @see #isSynthesized()
     * @see #read(String, Object)
     */
    public void setSynthesized(boolean state) {
        Synthesized = state;
    }

    /**
     * Get the value of the Synthesized property.
     *
     * @return true if this is a synthetic variable,
     *         false otherwise.
     */
    public boolean isSynthesized() {
        return (Synthesized);
    }

    /**
     * Set the Read property. A normal variable is read using the
     * read() method. Once read the Read property is
     * true. Use this function to manually set the property
     * value. By default this property is false.
     *
     * @param state true if the variable has been read,
     *              false otherwise.
     * @see #isRead()
     * @see #read(String, Object)
     */
    public void setRead(boolean state) {
        ReadMe = state;
    }

    /**
     * Get the value of the Read property.
     *
     * @return true if the variable has been read,
     *         false otherwise.
     * @see #read(String, Object)
     * @see #setRead(boolean)
     */
    public boolean isRead() {
        return (ReadMe);
    }

    /**
     * Read a value from the named dataset for this variable.
     *
     * @param datasetName String identifying the file or other data store
     *                    from which to read a vaue for this variable.
     * @param specialO    This Object is a goody that is used by Server implementations
     *                    to deliver important, and as yet unknown, stuff to the read method. If you
     *                    don't need it, make it a null.
     * @return true if more data remains to be read, otherwise
     *         false. This is an abtsract method that must be implemented
     *         as part of the installation/localization of a OPeNDAP server.
     * @throws IOException
     * @throws EOFException
     */
    public abstract boolean read(String datasetName, Object specialO) throws NoSuchVariableException, IOException, EOFException;

    /**
     * Server-side serialization for OPeNDAP variables (sub-classes of
     * BaseType).
     * This does not send the entire class as the Java Serializable
     * interface does, rather it sends only the binary data values. Other software
     * is responsible for sending variable type information (see DDS).
     *

     * Writes data to a DataOutputStream. This method is used
     * on the server side of the OPeNDAP client/server connection, and possibly
     * by GUI clients which need to download OPeNDAP data, manipulate it, and
     * then re-save it as a binary file.
     *
     * @param sink a DataOutputStream to write to.
     * @throws IOException thrown on any OutputStream exception.
     * @see BaseType
     * @see DDS
     * @see ServerDDS
     */
    public void serialize(String dataset, DataOutputStream sink, CEEvaluator ce, Object specialO)
            throws NoSuchVariableException, DAP2ServerSideException, IOException {

        if (!isRead())
            read(dataset, specialO);

        if (ce.evalClauses(specialO))
            externalize(sink);

    }

    /**
     * Write the variable's declaration in XML. This
     * function is used to create the XML representation of the Data
     * Descriptor Structure (DDS).  See The OPeNDAP User Manual for
     * information about this structure.
     *
     * @param constrained a boolean value indicating whether to print
     *                    the declartion dependent on the projection information. This
     *                    is only used by Server side code.
     * @see DDS
     */
    public void printXML(PrintWriter pw, String pad, boolean constrained) {
        if (constrained && !isProject())
            return;

        // BEWARE! Since printXML()is (multiple) overloaded in BaseType
        // and all of the different signatures of printXML() in BaseType
        // lead to one signature, we must be careful to override that
        // SAME signature here. That way all calls to printDecl() for
        // this object lead to this implementation.

        // Also, since printXML()is (multiple) overloaded in BaseType
        // and all of the different signatures of printXML() in BaseType
        // lead to the signature we are overriding here, we MUST call
        // the printXML with the SAME signature THROUGH the super class
        // reference (assuming we want the super class functionality). If
        // we do otherwise, we will create an infinte call loop. OOPS!

        super.printXML(pw, pad, constrained);
    }

}


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

package opendap.dap;

import opendap.dap.parsers.DDSXMLParser;

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

/**
 * This abstract class defines the basic data type features for the OPeNDAP data
 * access protocol (DAP) data types. All of the DAP type classes
 * (DFloat64, DArray, etc.) subclass it or one of
 * its two abstract descendents, DVector or
 * DConstructor.
 *

 * These classes and their methods give a user the capacity to set up
 * sophisticated data types. They do not provide sophisticated ways to
 * access and use this data. On the server side, in many cases, the class
 * instances will have no data in them at all until the serialize
 * method is called to send data to the client. On the client side, most OPeNDAP
 * application programs will unpack the data promptly into whatever local
 * data structure the programmer deems the most useful.
 *

 * Descendents of this class should implement the ClientIO
 * interface.  That interface defines a deserialize method used
 * by a OPeNDAP client to retrieve the variable's declaration and value(s) from
 * a OPeNDAP server.
 *
 * @author jehamby
 * @version $Revision: 19676 $
 * @see DDS
 * @see ClientIO
 */
public abstract class BaseType extends DAPNode
{

    /**
     * The Attribute Table used to contain attributes specific to this
     * instance of a BaseType variable. This is the repository for
     * "Semantic Metadata"
     */
    private Attribute _attr;
    private AttributeTable _attrTbl;

    /**
     * Constructs a new BaseType with no name.
     */
    public BaseType() {
        this(null);
    }

    /**
     * Constructs a new BaseType with name n.
     * Name is always assumed to be dap encoded
     * @param clearname the name of the variable.
     */
    public BaseType(String clearname)
    {
	    super();
        _attrTbl = new AttributeTable(getClearName());
        _attr = new Attribute(getClearName(), _attrTbl);
        setClearName(clearname);
    }

    /**
     * Sets the unencoded name of the class instance.
     *
     * @param clearname the unencoded name of the class instance.
     */
    @Override
    public void setClearName(String clearname)
    {
	    super.setClearName(clearname);
        if(_attr != null) _attr.setClearName(clearname);
        if(_attrTbl !=  null) _attrTbl.setClearName(clearname);
    }

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

    /**
     * Returns the number of variables contained in this object. For simple and
     * vector type variables, it always returns 1. To count the number
     * of simple-type variable in the variable tree rooted at this variable, set
     * leaves to true.
     *
     * @param leaves If true, count all the simple types in the `tree' of
     *               variables rooted at this variable.
     * @return the number of contained variables.
     */
    public int elementCount(boolean leaves) {
        return 1;
    }

    /**
     * Returns the number of variables contained in this object. For simple and
     * vector type variables, it always returns 1.
     *
     * @return the number of contained variables.
     */
    public final int elementCount() {
        return elementCount(false);
    }

    /**
     * Write the variable's declaration in a C-style syntax. This
     * function is used to create textual representation of the Data
     * Descriptor Structure (DDS).  See The OPeNDAP User Manual for
     * information about this structure.
     *
     * @param os          The PrintWriter on which to print the
     *                    declaration.
     * @param space       Each line of the declaration will begin with the
     *                    characters in this string.  Usually used for leading spaces.
     * @param print_semi  a boolean value indicating whether to print a
     *                    semicolon at the end of the declaration.
     * @param constrained a boolean value indicating whether to print
     *                    the declartion dependent on the projection information. This
     *                    is only used by Server side code.
     * @see DDS
     */
    public void printDecl(PrintWriter os, String space,
                          boolean print_semi, boolean constrained) {

        //LogStream.out.println("BaseType.printDecl()...");
        os.print(space + getTypeName() + " " + getEncodedName());
        if (print_semi)
            os.println(";");
    }

    /**
     * Write the variable's declaration in a C-style syntax. This
     * function is used to create textual representation of the Data
     * Descriptor Structure (DDS).  See The OPeNDAP User Manual for
     * information about this structure.
     *
     * @param os         The PrintWriter on which to print the
     *                   declaration.
     * @param space      Each line of the declaration will begin with the
     *                   characters in this string.  Usually used for leading spaces.
     * @param print_semi a boolean value indicating whether to print a
     *                   semicolon at the end of the declaration.
     * @see DDS
     */
    public void printDecl(PrintWriter os, String space,
                          boolean print_semi) {

        printDecl(os, space, print_semi, false);

    }

    /**
     * Print the variable's declaration.  Same as
     * printDecl(os, space, true).
     *
     * @param os    The PrintWriter on which to print the
     *              declaration.
     * @param space Each line of the declaration will begin with the
     *              characters in this string.  Usually used for leading spaces.
     * @see DDS#print(PrintWriter)
     */
    public final void printDecl(PrintWriter os, String space) {
        printDecl(os, space, true, false);
    }

    /**
     * Print the variable's declaration.  Same as
     * printDecl(os, "    ", true).
     *
     * @param os The PrintWriter on which to print the
     *           declaration.
     * @see DDS#print(PrintWriter)
     */
    public final void printDecl(PrintWriter os) {
        printDecl(os, "    ", true, false);
    }

    /**
     * Print the variable's declaration using OutputStream.
     *
     * @param os         The OutputStream on which to print the
     *                   declaration.
     * @param space      Each line of the declaration will begin with the
     *                   characters in this string.  Usually used for leading spaces.
     * @param print_semi a boolean value indicating whether to print a
     *                   semicolon at the end of the declaration.
     * @see DDS#print(PrintWriter)
     */
    public final void printDecl(OutputStream os, String space,
                                boolean print_semi, boolean constrained) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printDecl(pw, space, print_semi, constrained);
        pw.flush();
    }

    /**
     * Print the variable's declaration using OutputStream.
     *
     * @param os         The OutputStream on which to print the
     *                   declaration.
     * @param space      Each line of the declaration will begin with the
     *                   characters in this string.  Usually used for leading spaces.
     * @param print_semi a boolean value indicating whether to print a
     *                   semicolon at the end of the declaration.
     * @see DDS#print(PrintWriter)
     */
    public final void printDecl(OutputStream os, String space,
                                boolean print_semi) {
        printDecl(os, space, print_semi, false);
    }

    /**
     * Print the variable's declaration.  Same as
     * printDecl(os, space, true).
     *
     * @param os    The OutputStream on which to print the
     *              declaration.
     * @param space Each line of the declaration will begin with the
     *              characters in this string.  Usually used for leading spaces.
     * @see DDS#print(PrintWriter)
     */
    public final void printDecl(OutputStream os, String space) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printDecl(pw, space);
        pw.flush();
    }

    /**
     * Print the variable's declaration.  Same as
     * printDecl(os, "    ", true).
     *
     * @param os The OutputStream on which to print the
     *           declaration.
     * @see DDS#print(PrintWriter)
     */
    public  void printDecl(OutputStream os) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printDecl(pw);
        pw.flush();
    }

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

    /**
     * Print the variable's value.  Same as
     * printVal(os, space, true).
     *
     * @param os    the PrintWriter on which to print the value.
     * @param space this value is passed to the printDecl method,
     *              and controls the leading spaces of the output.
     * @see DataDDS#printVal(PrintWriter)
     */
    public  void printVal(PrintWriter os, String space) {
        printVal(os, space, true);
    }

    /**
     * Print the variable's value using OutputStream.
     *
     * @param os           the OutputStream on which to print the value.
     * @param space        this value is passed to the printDecl method,
     *                     and controls the leading spaces of the output.
     * @param print_decl_p a boolean value controlling whether the
     *                     variable declaration is printed as well as the value.
     * @see DataDDS#printVal(PrintWriter)
     */
    public  void printVal(OutputStream os, String space,
                               boolean print_decl_p) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printVal(pw, space, print_decl_p);
        pw.flush();
    }

    /**
     * Print the variable's value using OutputStream.
     *
     * @param os    the OutputStream on which to print the value.
     * @param space this value is passed to the printDecl method,
     *              and controls the leading spaces of the output.
     * @see DataDDS#printVal(PrintWriter)
     */
    public void printVal(OutputStream os, String space) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printVal(pw, space);
        pw.flush();
    }

    /**
     * Checks for internal consistency.  This is important to check for complex
     * constructor classes.
     * For example, an DInt32 instance would return false if it had
     * no name defined.  A DGrid instance might return false for
     * more complex reasons, such as having Map arrays of the wrong
     * size or shape.
     *

     * This method is used by the DDS class, and will rarely, if
     * ever, be explicitly called by a OPeNDAP application program.  A
     * variable must pass this test before it is sent, but there may be
     * many other stages in a retrieve operation where it would fail.
     *
     * @param all For complex constructor types (
     *            DGrid, DSequence, DStructure),
     *            this flag indicates whether to check the
     *            semantics of the member variables, too.
     * @throws BadSemanticsException if semantics are bad, explains why.
     * @see DDS#checkSemantics(boolean)
     */
    public void checkSemantics(boolean all)
            throws BadSemanticsException {
        if (_nameClear == null)
            throw new BadSemanticsException("BaseType.checkSemantics(): Every variable must have a name");
    }

    /**
     * Check semantics.  Same as checkSemantics(false).
     *
     * @throws BadSemanticsException if semantics are bad, explains why.
     * @see BaseType#checkSemantics(boolean)
     */
    public final void checkSemantics() throws BadSemanticsException {
        checkSemantics(false);
    }

    /**
     * Constructs a new PrimitiveVector object optimized for the
     * particular data type of this BaseType.  For example, a
     * DByte class would create a new
     * BytePrimitiveVector in this call.  This allows for a very
     * optimized, yet type-safe, implementation of DVector
     * functionality.  For non-primitive types, such as
     * DArray, DGrid, DSequence, and
     * DStructure, the default implementation returns a
     * BaseTypePrimitiveVector object which can
     * deserialize an array of complex types.
     *
     * @return a new PrimitiveVector object for the variable type.
     */
    public PrimitiveVector newPrimitiveVector() {
        return new BaseTypePrimitiveVector(this);
    }

    public String getLongName() {

        boolean done = false;

        BaseType parent = (BaseType)getParent();

        String longName = _nameEncoded;

        while (parent != null && !(parent instanceof DDS)) {
            longName = parent.getEncodedName() + "." + longName;
            parent = (BaseType)parent.getParent();
        }
        return (longName);
    }

    // **************************************************************
    //
    // Attribute Table Methods
    //
    // ..............................................................

    public boolean hasAttributes() {

        Enumeration e = _attrTbl.getNames();

        if (e.hasMoreElements())
            return (true);

        return (false);

    }

    public Attribute getAttribute() {
        return _attr;
    }

    public AttributeTable getAttributeTable() {
        return _attrTbl;
    }

    public void addAttributeAlias(String alias, String attributeName)
            throws DASException {
        _attrTbl.addAlias(alias, attributeName);
    }

    public void appendAttribute(String clearname, int type, String value, boolean check)
            throws DASException {
        _attrTbl.appendAttribute(clearname, type, value, check);
    }

    public void appendAttribute(String clearname, int type, String value)
            throws DASException {
        _attrTbl.appendAttribute(clearname, type, value);
    }

    public void addAttributeContainer(AttributeTable at)
            throws AttributeExistsException {
        _attrTbl.addContainer(at.getClearName(), at);
    }

    public AttributeTable appendAttributeContainer(String clearname) {
        return (_attrTbl.appendContainer(clearname));
    }

    public void delAttribute(String clearname) {
        _attrTbl.delAttribute(clearname);
    }

    public void delAttribute(String clearname, int i) throws DASException {
        _attrTbl.delAttribute(clearname, i);
    }

    public Attribute getAttribute(String clearname) {
        return (_attrTbl.getAttribute(clearname));
    }

    public Enumeration getAttributeNames() {
        return (_attrTbl.getNames());
    }

    public void printAttributes(OutputStream os) {
        _attrTbl.print(os);
    }

    public void printAttributes(OutputStream os, String pad) {
        _attrTbl.print(os, pad);
    }

    public void printAttributes(PrintWriter pw) {
        _attrTbl.print(pw);
    }

    public void printAttributes(PrintWriter pw, String pad) {
        _attrTbl.print(pw, pad);
    }

    /**
     *
     * @param os
     * @opendap.ddx.experimental
     */
    public void printXML(OutputStream os) {
        printXML(os, "");
    }

    /**
     *
     * @param os
     * @param pad
     * @opendap.ddx.experimental
     */
    public void printXML(OutputStream os, String pad) {
        PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(os,Util.UTF8)));
        printXML(pw, pad, false);
        pw.flush();
    }

    /**
     *
     * @param pw
     * @opendap.ddx.experimental
     */
    public void printXML(PrintWriter pw) {
        printXML(pw, "");
    }

    /**
     *
     * @param pw
     * @param pad
     * @opendap.ddx.experimental
     */
    public void printXML(PrintWriter pw, String pad) {
        printXML(pw, pad, false);

    }

    /**
     *
     * @param pw
     * @param pad
     * @param constrained
     * @opendap.ddx.experimental
     */
    public void printXML(PrintWriter pw, String pad, boolean constrained) {

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

        Enumeration e = getAttributeNames();
        if (e.hasMoreElements()) {
            pw.println(">");
            while (e.hasMoreElements()) {
                String aName = (String) e.nextElement();

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

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

    }

    public void printConstraint(PrintWriter os)
    {
        BaseType parent = (BaseType)getParent();
        BaseType array =  null;
        if(parent != null) {
            if(parent instanceof DArray) {
            array = parent;
            parent = (BaseType)parent.getParent();
            }
        }
        if(array != null)
            array.printConstraint(os);
            else  {
                if(parent != null && !(parent instanceof DDS)) {
                parent.printConstraint(os);
                os.print(".");
            }
                os.print(getEncodedName());
            }
    }

    /**
     * Returns a clone of this BaseType.
     * See DAPNode.cloneDAG.
     *
     * @param map The set of already cloned nodes.
     * @return a clone of this BaseType.
     */
    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
        BaseType bt = (BaseType)super.cloneDAG(map);
        if(this._attrTbl != null)
  	        bt._attrTbl = (AttributeTable) cloneDAG(map,this._attrTbl);
        if(this._attr != null)
	        bt._attr = new Attribute(getClearName(), bt._attrTbl);
        return bt;
    }

}


package ucar.nc2.ogc.gml;

import net.opengis.gml.x32.TimeInstantType;
import ucar.nc2.ogc.MarshallingUtil;

/**
 * Created by cwardgar on 2014-04-27.
 */
public class NcTimeInstantType {
    // wml2:Collection/wml2:observationMember/om:OM_Observation/om:resultTime/gml:TimeInstant
    public static TimeInstantType initTimeInstant(TimeInstantType timeInstant) {
        // @gml:id
        String id = MarshallingUtil.createIdForType(TimeInstantType.class);
        timeInstant.setId(id);

        // gml:timePosition
        NcTimePositionType.initTimePosition(timeInstant.addNewTimePosition());

        return timeInstant;
    }

    private NcTimeInstantType() { }
}


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

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

import ucar.nc2.*;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateUnit;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants._Coordinate;
import ucar.ma2.DataType;
import ucar.unidata.geoloc.LatLonRect;
import ucar.unidata.geoloc.LatLonPointImpl;

import java.util.List;

/**
 * Helper routines for point feature datasets using Unidata Conventions.
 *
 * @author caron
 * @since Feb 29, 2008
 */
public class UnidataPointDatasetHelper {

  static public CalendarDate getStartDate(NetcdfDataset ds, CalendarDateUnit timeUnit) {
    return getDate(ds, timeUnit, "time_coverage_start");
  }

  static public CalendarDate getEndDate(NetcdfDataset ds, CalendarDateUnit timeUnit) {
    return getDate(ds, timeUnit, "time_coverage_end");
  }

  static private CalendarDate getDate(NetcdfDataset ds, CalendarDateUnit timeUnit, String attName) {
    Attribute att = ds.findGlobalAttributeIgnoreCase(attName);
    if (null == att)
      throw new IllegalArgumentException("Must have a global attribute named "+attName);

    CalendarDate result;
    if (att.getDataType() == DataType.STRING) {
      result = CalendarDate.parseUdunitsOrIso(null, att.getStringValue());
      if (result == null && timeUnit != null) {
        double val = Double.parseDouble(att.getStringValue());
        result = timeUnit.makeCalendarDate(val);
      }
    } else if (timeUnit != null) {
      double val = att.getNumericValue().doubleValue();
      result = timeUnit.makeCalendarDate(val);

    } else {
      throw new IllegalArgumentException(attName+" must be a ISO or udunit date string");
    }

    return result;
  }

  static public LatLonRect getBoundingBox(NetcdfDataset ds) {
    double lat_max = getAttAsDouble( ds, "geospatial_lat_max");
    double lat_min = getAttAsDouble( ds, "geospatial_lat_min");
    double lon_max = getAttAsDouble( ds, "geospatial_lon_max");
    double lon_min = getAttAsDouble( ds, "geospatial_lon_min");

    return new LatLonRect(new LatLonPointImpl(lat_min, lon_min), lat_max-lat_min, lon_max-lon_min);
  }

  static private double getAttAsDouble( NetcdfDataset ds, String attname) {
    Attribute att = ds.findGlobalAttributeIgnoreCase(attname);
    if (null == att)
      throw new IllegalArgumentException("Must have a "+attname+" global attribute");

    if (att.getDataType() == DataType.STRING) {
      return Double.parseDouble( att.getStringValue());
    } else {
      return att.getNumericValue().doubleValue();
    }
  }

  /**
   * Tries to find the coordinate variable of the specified type.
   * @param ds search in this dataset
   * @param a AxisType.LAT, LON, HEIGHT, or TIME
   * @return coordinate variable, or null if not found.
   */
  static public String getCoordinateName(NetcdfDataset ds, AxisType a) {
    List<Variable> varList = ds.getVariables();
    for (Variable v : varList) {
      if (v instanceof Structure) {
        List<Variable> vars = ((Structure) v).getVariables();
        for (Variable vs : vars) {
          String axisType = ds.findAttValueIgnoreCase(vs, _Coordinate.AxisType, null);
          if ((axisType != null) && axisType.equals(a.toString()))
            return vs.getShortName();
        }
      } else {
        String axisType = ds.findAttValueIgnoreCase(v, _Coordinate.AxisType, null);
        if ((axisType != null) && axisType.equals(a.toString()))
          return v.getShortName();
      }
    }

    if (a == AxisType.Lat)
      return findVariableName( ds, "latitude");

    if (a == AxisType.Lon)
      return findVariableName( ds, "longitude");

    if (a == AxisType.Time)
      return findVariableName( ds, "time");

    if (a == AxisType.Height) {
      Variable v = findVariable( ds, "altitude");
      if (null == v) v = findVariable( ds, "depth");
      if (v != null) return v.getShortName();
    }

    // I think the CF part is done by the CoordSysBuilder adding the _CoordinateAxisType attrinutes.
    return null;
  }

  /**
   * Tries to find the coordinate variable of the specified type, which has the specified dimension as its firsst dimension
   * @param ds search in this dataset
   * @param a AxisType.LAT, LON, HEIGHT, or TIME
   * @param dim must use this dimension
   * @return coordinate variable, or null if not found.
   */
  static public String getCoordinateName(NetcdfDataset ds, AxisType a, Dimension dim) {
    String name = getCoordinateName(ds, a);
    if (name == null) return null;

    Variable v = ds.findVariable(name);
    if (v == null) return null;

    if (v.isScalar()) return null;
    if (!v.getDimension(0).equals(dim)) return null;

    return name;
  }

  /**
   * Tries to find the coordinate variable of the specified type.
   * @param ds search in this dataset
   * @param a AxisType.LAT, LON, HEIGHT, or TIME
   * @return coordinate variable, or null if not found.
   */
  static public Variable getCoordinate(NetcdfDataset ds, AxisType a) {
    List<Variable> varList = ds.getVariables();
    for (Variable v : varList) {
      if (v instanceof Structure) {
        //System.out.println( "v is a Structure" );
        List<Variable> vars = ((Structure) v).getVariables();
        for (Variable vs : vars) {
          //System.out.println( "vs =" + vs.getShortName() );
          String axisType = ds.findAttValueIgnoreCase(vs, _Coordinate.AxisType, null);
          if ((axisType != null) && axisType.equals(a.toString()))
            return vs;
        }
      } else {
        String axisType = ds.findAttValueIgnoreCase(v, _Coordinate.AxisType, null);
        if ((axisType != null) && axisType.equals(a.toString()))
          return v;
      }
    }

    if (a == AxisType.Lat)
      return findVariable( ds, "latitude");

    if (a == AxisType.Lon)
      return findVariable( ds, "longitude");

    if (a == AxisType.Time)
      return findVariable( ds, "time");

    if (a == AxisType.Height) {
      Variable v = findVariable( ds, "altitude");
      if (null == v) v = findVariable( ds, "depth");
      if (v != null) return v;
    }

    // I think the CF part is done by the CoordSysBuilder adding the _CoordinateAxisType attrinutes.
    return null;
  }

  static public String findVariableName(NetcdfFile ds, String name) {
    Variable result = findVariable(ds, name);
    return result == null ? null : result.getShortName();
  }

  static public Variable findVariable(NetcdfFile ds, String name) {
    Variable result = ds.findVariable(name);
    if (result == null) {
      String aname = ds.findAttValueIgnoreCase(null, name+"_coordinate", null);
      if (aname != null)
        result = ds.findVariable(aname);
      else {
        aname = ds.findAttValueIgnoreCase(null, name+"_variable", null);
        if (aname != null)
          result = ds.findVariable(aname);
      }
    }
    return result;
  }

  static public Dimension findDimension(NetcdfFile ds, String name) {
    Dimension result = ds.findDimension(name); // LOOK use group
    if (result == null) {
      String aname = ds.findAttValueIgnoreCase(null, name+"Dimension", null);
      if (aname != null)
        result = ds.findDimension(aname); // LOOK use group
    }
    return result;
  }

  static public Dimension findObsDimension(NetcdfFile ds) {
    Dimension result = null;
    String aname = ds.findAttValueIgnoreCase(null, "observationDimension", null);
    if (aname != null)
      result = ds.findDimension(aname);   // LOOK use group
    if (result == null)
      result = ds.getUnlimitedDimension();    // LOOK use group
    return result;
  }
}


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

package dap4.core.dmr;

import dap4.core.util.DapException;
import dap4.core.util.Escape;

/**
 * The official format for an error response is defined here:
 * http://docs.opendap.org/index.php/DAP4_Web_Services_v3#DAP4_Error_Response.
 * Basically, the response looks something like this.
 * <Error httpcode="...">
 * <Message>...</Message>
 * <Context>...</Context>
 * <OtherInformation>...</OtherInformation>
 * </Error>
 */

public class ErrorResponse
{

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

    int code = 0;
    String message = null;
    String context = null;
    String otherinfo = null;

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

    public ErrorResponse() {}

    public ErrorResponse(int httpcode, String message,
                         String context, String other)
    {
	setCode(code);
	setMessage(message);
	setContext(context);
	setOtherInfo(other);
    }

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

    public int getCode() {return code;}

    public String getRawMessage() {return message;}

    public String getMessage() // Return XML escaped version
    {
	return (message == null ? null : Escape.entityEscape(message,null));
    }

    public String getRawContext() {return context;}

    public String getContext() // Return XML escaped version
    {
	return (context == null ? null : Escape.entityEscape(context,null));
    }

    public String getRawOtherInfo() {return otherinfo;}

    public String getOtherInfo() // Return XML escaped version
    {
	return (otherinfo == null ? null : Escape.entityEscape(otherinfo,null));
    }

    public void setCode(int code) {this.code = code;}
    public void setMessage(String message) {this.message = message;}
    public void setContext(String context) {this.context = context;}
    public void setOtherInfo(String other) {this.otherinfo = other;}

    //////////////////////////////////////////////////
    // Converters

    /**
     * Convert an ErrorResponse to the equivalent XML
     * @return  XML representation of the ErrorResponse
     */
    public String buildXML()
    {
        StringBuilder response = new StringBuilder();
        response.append("<Error");
        if(code > 0)
            response.append(String.format(" httpcode=\"%d\"", code));
        response.append(">\n");
        if(message != null)
            response.append("<Message>" + getMessage() + "</Message>\n");
        if(context != null)
            response.append("<Context>" + getContext() + "</Context>\n");
        if(otherinfo != null)
            response.append("<OtherInformation>" + getOtherInfo() + "</OtherInformation>\n");
        return response.toString();
    }

    /**
     * Convert an ErrorResponse to the equivalent DapException.
     * @return  DapException representation of the ErrorResponse
     */
    public DapException buildException()
    {
        String XML = buildXML();
        DapException dapex = new DapException(XML).setCode(code);
        return dapex;
    }

}


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

package ucar.nc2.dataset.conv;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;
import ucar.ma2.Array;
import ucar.ma2.ArrayChar;
import ucar.ma2.DataType;
import ucar.ma2.IndexIterator;
import ucar.ma2.InvalidRangeException;
import ucar.ma2.MAMath;
import ucar.ma2.Section;
import ucar.nc2.Attribute;
import ucar.nc2.Dimension;
import ucar.nc2.NetcdfFile;
import ucar.nc2.Variable;
import ucar.nc2.constants.AxisType;
import ucar.nc2.constants.CDM;
import ucar.nc2.constants._Coordinate;
import ucar.nc2.dataset.CoordSysBuilder;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.CoordinateAxis1D;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.dataset.ProjectionCT;
import ucar.nc2.dataset.VariableDS;
import ucar.nc2.dataset.VariableEnhanced;
import ucar.nc2.iosp.netcdf3.N3iosp;
import ucar.nc2.units.SimpleUnit;
import ucar.nc2.util.CancelTask;
import ucar.unidata.geoloc.LatLonPointImpl;
import ucar.unidata.geoloc.ProjectionPointImpl;
import ucar.unidata.geoloc.projection.LambertConformal;
import ucar.unidata.geoloc.projection.Stereographic;
import ucar.unidata.util.StringUtil2;

/**
 * AWIPS netcdf output.
 *
 * @author caron
 */

public class AWIPSConvention extends CoordSysBuilder {

  /**
   * @param ncfile the NetcdfFile to test
   * @return true if we think this is a AWIPS file.
   */
  public static boolean isMine(NetcdfFile ncfile) {
    return (null != ncfile.findGlobalAttribute("projName")) &&
            (null != ncfile.findDimension("charsPerLevel")) &&
            (null != ncfile.findDimension("x")) &&
            (null != ncfile.findDimension("y"));
  }

  private static final boolean debugProj = false;
  private static final boolean debugBreakup = false;

  // private List<Variable> mungedList = new ArrayList<>();
  private ProjectionCT projCT = null;
  private double startx, starty;

  public AWIPSConvention() {
    this.conventionName = "AWIPS";
  }

  public void augmentDataset(NetcdfDataset ds, CancelTask cancelTask) {
    if (null != ds.findVariable("x")) return; // check if its already been done - aggregating enhanced datasets.

    Dimension dimx = ds.findDimension("x");
    int nx = dimx.getLength();

    Dimension dimy = ds.findDimension("y");
    int ny = dimy.getLength();

    String projName = ds.findAttValueIgnoreCase(null, "projName", "none");
    if (projName.equalsIgnoreCase("LATLON")) {
      ds.addCoordinateAxis(makeLonCoordAxis(ds, nx, "x"));
      ds.addCoordinateAxis(makeLatCoordAxis(ds, ny, "y"));
    } else if (projName.equalsIgnoreCase("LAMBERT_CONFORMAL")) {
      projCT = makeLCProjection(ds, projName);
      ds.addCoordinateAxis(makeXCoordAxis(ds, nx, "x"));
      ds.addCoordinateAxis(makeYCoordAxis(ds, ny, "y"));
    } else if (projName.equalsIgnoreCase("STEREOGRAPHIC")) {
      projCT = makeStereoProjection(ds, projName);
      ds.addCoordinateAxis(makeXCoordAxis(ds, nx, "x"));
      ds.addCoordinateAxis(makeYCoordAxis(ds, ny, "y"));
    }

    CoordinateAxis timeCoord = makeTimeCoordAxis(ds);
    if (timeCoord != null) {
      ds.addCoordinateAxis(timeCoord);
      Dimension d = timeCoord.getDimension(0);
      if (!d.getShortName().equals(timeCoord.getShortName()))
        timeCoord.addAttribute(new Attribute(_Coordinate.AliasForDimension, d.getShortName()));
    }

    // AWIPS cleverly combines multiple z levels into a single variable (!!)
    for (Variable ncvar : ds.getVariables()) {
      String levelName = ncvar.getShortName() + "Levels";
      Variable levelVar = ds.findVariable(levelName);
      if (levelVar == null) continue;
      if (levelVar.getRank() != 2) continue;
      if (levelVar.getDataType() != DataType.CHAR) continue;

      try {
        List<Dimension> levels = breakupLevels(ds, levelVar);
        createNewVariables(ds, ncvar, levels, levelVar.getDimension(0));
      } catch (InvalidRangeException ex) {
        parseInfo.format("createNewVariables InvalidRangeException%n");
      } catch (IOException ioe) {
        parseInfo.format("createNewVariables IOException%n");
      }
      // mungedList.add(ncvar);
    }

    if (projCT != null) {
      VariableDS v = makeCoordinateTransformVariable(ds, projCT);
      v.addAttribute(new Attribute(_Coordinate.Axes, "x y"));
      ds.addVariable(null, v);
    }

    ds.finish();

    // kludge in fixing the units
    List<Variable> vlist = ds.getVariables();
    for (Variable v : vlist) {
      Attribute att = v.findAttributeIgnoreCase(CDM.UNITS);
      if (att != null) {
        String units = att.getStringValue();
        v.addAttribute(new Attribute(CDM.UNITS, normalize(units))); // removes the old
      }
    }

  }

  // pretty much WRF specific

  private String normalize(String units) {
    if (units.equals("/second")) units = "1/sec";
    if (units.equals("degrees K")) units = "K";
    else {
      units = StringUtil2.substitute(units, "**", "^");
      units = StringUtil2.remove(units, ')');
      units = StringUtil2.remove(units, '(');
    }
    return units;
  }

  // LOOK not dealing with "FHAG 0 10 ", "FHAG 0 30 "
  // take a combined level variable and create multiple levels out of it
  // return the list of Dimensions that were created

  private List<Dimension> breakupLevels(NetcdfDataset ds, Variable levelVar) throws IOException {
    if (debugBreakup) parseInfo.format("breakupLevels = %s%n", levelVar.getShortName());
    List<Dimension> dimList = new ArrayList<>();

    ArrayChar levelVarData;
    try {
      levelVarData = (ArrayChar) levelVar.read();
    } catch (IOException ioe) {
      return dimList;
    }

    List<String> values = null;
    String currentUnits = null;
    ArrayChar.StringIterator iter = levelVarData.getStringIterator();
    while (iter.hasNext()) {
      String s = iter.next();
      if (debugBreakup) parseInfo.format("   %s%n", s);
      StringTokenizer stoke = new StringTokenizer(s);

/* problem with blank string:
   char pvvLevels(levels_35=35, charsPerLevel=10);
"MB 1000   ", "MB 975    ", "MB 950    ", "MB 925    ", "MB 900    ", "MB 875    ", "MB 850    ", "MB 825    ", "MB 800    ", "MB 775    ", "MB 750    ",
"MB 725    ", "MB 700    ", "MB 675    ", "MB 650    ", "MB 625    ", "MB 600    ", "MB 575    ", "MB 550    ", "MB 525    ", "MB 500    ", "MB 450    ",
"MB 400    ", "MB 350    ", "MB 300    ", "MB 250    ", "MB 200    ", "MB 150    ", "MB 100    ", "BL 0 30   ", "BL 60 90  ", "BL 90 120 ", "BL 120 150",
"BL 150 180", ""
*/
      if (!stoke.hasMoreTokens())
        continue; // skip it

      // first token is the unit
      String units = stoke.nextToken().trim();
      if (!units.equals(currentUnits)) {
        if (values != null)
          dimList.add(makeZCoordAxis(ds, values, currentUnits));
        values = new ArrayList<>();
        currentUnits = units;
      }

      // next token is the value
      if (stoke.hasMoreTokens())
        values.add(stoke.nextToken());
      else
        values.add("0");
    }
    if (values != null)
      dimList.add(makeZCoordAxis(ds, values, currentUnits));

    if (debugBreakup) parseInfo.format("  done breakup%n");

    return dimList;
  }

  // make a new variable out of the list in "values"

  private Dimension makeZCoordAxis(NetcdfDataset ds, List<String> values, String units) throws IOException {
    int len = values.size();
    String name = makeZCoordName(units);
    if (len > 1)
      name = name + Integer.toString(len);
    else
      name = name + values.get(0);
    StringUtil2.replace(name, ' ', "-");

    Dimension dim;
    if (null != (dim = ds.getRootGroup().findDimension(name))) {
      if (dim.getLength() == len) {
        // check against actual values
        Variable coord = ds.getRootGroup().findVariable(name);
        Array coordData = coord.read();
        Array newData = Array.makeArray(coord.getDataType(), values);
        if (MAMath.nearlyEquals(coordData, newData)) {
          if (debugBreakup) parseInfo.format("  use existing coord %s%n", dim);
          return dim;
        }
      }
    }

    String orgName = name;
    int count = 1;
    while (ds.getRootGroup().findDimension(name) != null) {
      name = orgName + "-" + count;
      count++;
    }

    // create new one
    dim = new Dimension(name, len);
    ds.addDimension(null, dim);
    if (debugBreakup) parseInfo.format("  make Dimension = %s length = %d%n", name, len);

    // if (len < 2) return dim; // skip 1D

    if (debugBreakup) {
      parseInfo.format("  make ZCoordAxis = = %s length = %d%n", name, len);
    }

    CoordinateAxis v = new CoordinateAxis1D(ds, null, name, DataType.DOUBLE, name,
            makeUnitsName(units), makeLongName(name));
    String positive = getZisPositive(ds, v);
    if (null != positive)
      v.addAttribute(new Attribute(_Coordinate.ZisPositive, positive));

    v.setValues(values);
    ds.addCoordinateAxis(v);

    parseInfo.format("Created Z Coordinate Axis = ");
    v.getNameAndDimensions(parseInfo, true, false);
    parseInfo.format("%n");

    return dim;
  }

  private String makeZCoordName(String units) {
    if (units.equalsIgnoreCase("MB")) return "PressureLevels";
    if (units.equalsIgnoreCase("K")) return "PotTempLevels";
    if (units.equalsIgnoreCase("BL")) return "BoundaryLayers";

    if (units.equalsIgnoreCase("FHAG")) return "FixedHeightAboveGround";
    if (units.equalsIgnoreCase("FH")) return "FixedHeight";
    if (units.equalsIgnoreCase("SFC")) return "Surface";
    if (units.equalsIgnoreCase("MSL")) return "MeanSeaLevel";
    if (units.equalsIgnoreCase("FRZ")) return "FreezingLevel";
    if (units.equalsIgnoreCase("TROP")) return "Tropopause";
    if (units.equalsIgnoreCase("MAXW")) return "MaxWindLevel";
    return units;
  }

  private String makeUnitsName(String units) {
    if (units.equalsIgnoreCase("MB")) return "hPa";
    if (units.equalsIgnoreCase("BL")) return "hPa";
    if (units.equalsIgnoreCase("FHAG")) return "m";
    if (units.equalsIgnoreCase("FH")) return "m";
    return "";
  }

  private String makeLongName(String name) {
    if (name.equalsIgnoreCase("PotTempLevels")) return "Potential Temperature Level";
    if (name.equalsIgnoreCase("BoundaryLayers")) return "BoundaryLayer hectoPascals above ground";
    else return name;
  }

  // create new variables as sections of ncVar

  private void createNewVariables(NetcdfDataset ds, Variable ncVar, List<Dimension> newDims,
                                  Dimension levelDim) throws InvalidRangeException {

    List<Dimension> dims = ncVar.getDimensions();
    int newDimIndex = dims.indexOf(levelDim);
    //String shapeS = ncVar.getShapeS();

    int[] origin = new int[ncVar.getRank()];
    int[] shape = ncVar.getShape();
    int count = 0;
    for (Dimension dim : newDims) {
      String name = ncVar.getShortName() + "-" + dim.getShortName();

      origin[newDimIndex] = count;
      shape[newDimIndex] = dim.getLength();

      Variable varNew = ncVar.section(new Section(origin, shape));
      varNew.setName(name);
      varNew.setDimension(newDimIndex, dim);

      // synthesize long name
      String long_name = ds.findAttValueIgnoreCase(ncVar, CDM.LONG_NAME, ncVar.getShortName());
      long_name = long_name + "-" + dim.getShortName();
      ds.addVariableAttribute(varNew, new Attribute(CDM.LONG_NAME, long_name));

      ds.addVariable(null, varNew);

      parseInfo.format("Created New Variable as section = ");
      varNew.getNameAndDimensions(parseInfo, true, false);
      parseInfo.format("%n");

      count += dim.getLength();
    }
  }

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

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

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

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

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

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

    if (vname.equalsIgnoreCase("record"))
      return AxisType.Time;
    Dimension dim = v.getDimension(0);
    if ((dim != null) && dim.getShortName().equalsIgnoreCase("record"))
      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 AxisType.GeoZ;
  }

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

  private String getZisPositive(NetcdfDataset ds, CoordinateAxis v) {

    String attValue = ds.findAttValueIgnoreCase(v, "positive", null);
    if (null != attValue)
      return attValue.equalsIgnoreCase("up") ? "up" : "down";

    String unit = v.getUnitsString();
    if ((unit != null) && SimpleUnit.isCompatible("millibar", unit))
      return "down";
    if ((unit != null) && SimpleUnit.isCompatible("m", unit))
      return "up";

    // dunno
    return null;
  }

  private ProjectionCT makeLCProjection(NetcdfDataset ds, String name) throws NoSuchElementException {
    double centralLat = findAttributeDouble(ds, "centralLat");
    double centralLon = findAttributeDouble(ds, "centralLon");
    double rotation = findAttributeDouble(ds, "rotation");

    // we have to project in order to find the origin
    LambertConformal lc = new LambertConformal(rotation, centralLon, centralLat, centralLat);
    double lat0 = findAttributeDouble(ds, "lat00");
    double lon0 = findAttributeDouble(ds, "lon00");
    ProjectionPointImpl start = (ProjectionPointImpl) lc.latLonToProj(new LatLonPointImpl(lat0, lon0));
    if (debugProj) parseInfo.format("getLCProjection start at proj coord %s%n", start);
    startx = start.getX();
    starty = start.getY();

    return new ProjectionCT(name, "FGDC", lc);
  }

  private ProjectionCT makeStereoProjection(NetcdfDataset ds, String name) throws NoSuchElementException {
    double centralLat = findAttributeDouble(ds, "centralLat");
    double centralLon = findAttributeDouble(ds, "centralLon");

    // scale factor at lat = k = 2*k0/(1+sin(lat))  [Snyder,Working Manual p157]
    // then to make scale = 1 at lat, k0 = (1+sin(lat))/2
    double latDxDy = findAttributeDouble(ds, "latDxDy");
    double latR = Math.toRadians(latDxDy);
    double scale = (1.0 + Math.abs(Math.sin(latR))) / 2;  // thanks to R Schmunk

    // Stereographic(double latt, double lont, double scale)

    Stereographic proj = new Stereographic(centralLat, centralLon, scale);
    // we have to project in order to find the origin
    double lat0 = findAttributeDouble(ds, "lat00");
    double lon0 = findAttributeDouble(ds, "lon00");
    ProjectionPointImpl start = (ProjectionPointImpl) proj.latLonToProj(new LatLonPointImpl(lat0, lon0));
    startx = start.getX();
    starty = start.getY();

    // projection info
    parseInfo.format("---makeStereoProjection start at proj coord %s%n", start);

    double latN = findAttributeDouble(ds, "latNxNy");
    double lonN = findAttributeDouble(ds, "lonNxNy");
    ProjectionPointImpl pt = (ProjectionPointImpl) proj.latLonToProj(new LatLonPointImpl(latN, lonN));
    parseInfo.format("                        end at proj coord %s%n", pt);
    parseInfo.format("                        scale= %f%n", scale);

    return new ProjectionCT(name, "FGDC", proj);
  }

  private CoordinateAxis makeXCoordAxis(NetcdfDataset ds, int nx, String xname) {
    double dx = findAttributeDouble(ds, "dxKm");
    CoordinateAxis v = new CoordinateAxis1D(ds, null, xname, DataType.DOUBLE, xname, "km", "x on projection");
    v.setValues(nx, startx, dx);

    parseInfo.format("Created X Coordinate Axis = ");
    v.getNameAndDimensions(parseInfo, true, false);
    parseInfo.format("%n");

    return v;
  }

  private CoordinateAxis makeYCoordAxis(NetcdfDataset ds, int ny, String yname) {
    double dy = findAttributeDouble(ds, "dyKm");
    CoordinateAxis v = new CoordinateAxis1D(ds, null, yname, DataType.DOUBLE, yname, "km", "y on projection");
    v.setValues(ny, starty, dy);

    parseInfo.format("Created Y Coordinate Axis = ");
    v.getNameAndDimensions(parseInfo, true, false);
    parseInfo.format("%n");

    return v;
  }

  private CoordinateAxis makeLonCoordAxis(NetcdfDataset ds, int n, String xname) {
    double min = findAttributeDouble(ds, "xMin");
    double max = findAttributeDouble(ds, "xMax");
    double d = findAttributeDouble(ds, "dx");
    if (Double.isNaN(min) || Double.isNaN(max) || Double.isNaN(d)) return null;

    CoordinateAxis v = new CoordinateAxis1D(ds, null, xname, DataType.DOUBLE, xname, CDM.LON_UNITS, "longitude");
    v.setValues(n, min, d);
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lon.toString()));

    double maxCalc = min + d * n;
    parseInfo.format("Created Lon Coordinate Axis (max calc= %f shoule be = %f)%n", maxCalc, max);
    v.getNameAndDimensions(parseInfo, true, false);
    parseInfo.format("%n");

    return v;
  }

  private CoordinateAxis makeLatCoordAxis(NetcdfDataset ds, int n, String xname) {
    double min = findAttributeDouble(ds, "yMin");
    double max = findAttributeDouble(ds, "yMax");
    double d = findAttributeDouble(ds, "dy");
    if (Double.isNaN(min) || Double.isNaN(max) || Double.isNaN(d)) return null;

    CoordinateAxis v = new CoordinateAxis1D(ds, null, xname, DataType.DOUBLE, xname, CDM.LAT_UNITS, "latitude");
    v.setValues(n, min, d);
    v.addAttribute(new Attribute(_Coordinate.AxisType, AxisType.Lat.toString()));

    double maxCalc = min + d * n;
    parseInfo.format("Created Lat Coordinate Axis (max calc= %f should be = %f)%n", maxCalc, max);
    v.getNameAndDimensions(parseInfo, true, false);
    parseInfo.format("%n");

    return v;
  }

  private CoordinateAxis makeTimeCoordAxis(NetcdfDataset ds) {
    Variable timeVar = ds.findVariable("valtimeMINUSreftime");
    Dimension recordDim = ds.findDimension("record");
    Array vals;

    try {
      vals = timeVar.read();
    } catch (IOException ioe) {
      return null;
    }

    // it seems that the record dimension does not always match valtimeMINUSreftime dimension!!
    // HAHAHAHAHAHAHAHA !
    int recLen = recordDim.getLength();
    int valLen = (int) vals.getSize();
    if (recLen != valLen) {
      try {
        vals = vals.sectionNoReduce(new int[]{0}, new int[]{recordDim.getLength()}, null);
        parseInfo.format(" corrected the TimeCoordAxis length%n");
      } catch (InvalidRangeException e) {
        parseInfo.format("makeTimeCoordAxis InvalidRangeException%n");
      }
    }

    // create the units out of the filename if possible
    String units = makeTimeUnitFromFilename(ds.getLocation());
    if (units == null) // ok that didnt work, try something else
      return makeTimeCoordAxisFromReference(ds, timeVar, vals);

    // create the coord axis
    String desc = "synthesized time coordinate from valtimeMINUSreftime and filename YYYYMMDD_HHMM";
    CoordinateAxis1D timeCoord = new CoordinateAxis1D(ds, null, "timeCoord", DataType.INT, "record", units, desc);

    timeCoord.setCachedData(vals, true);

    parseInfo.format("Created Time Coordinate Axis = ");
    timeCoord.getNameAndDimensions(parseInfo, true, false);
    parseInfo.format("%n");

    return timeCoord;
  }

  private String makeTimeUnitFromFilename(String dsName) {
    dsName = dsName.replace('\\', '/');

    // posFirst: last '/' if it exists
    int posFirst = dsName.lastIndexOf('/');
    if (posFirst < 0) posFirst = 0;

    // posLast: next '.' if it exists
    int posLast = dsName.indexOf('.', posFirst);
    if (posLast < 0)
      dsName = dsName.substring(posFirst + 1);
    else
      dsName = dsName.substring(posFirst + 1, posLast);

    // gotta be YYYYMMDD_HHMM
    if (dsName.length() != 13)
      return null;

    String year = dsName.substring(0, 4);
    String mon = dsName.substring(4, 6);
    String day = dsName.substring(6, 8);
    String hour = dsName.substring(9, 11);
    String min = dsName.substring(11, 13);

    return "seconds since " + year + "-" + mon + "-" + day + " " + hour + ":" + min + ":0";
  }

  // construct time coordinate from reftime variable

  private CoordinateAxis makeTimeCoordAxisFromReference(NetcdfDataset ds, Variable timeVar, Array vals) {
    Variable refVar = ds.findVariable("reftime");
    if (refVar == null) return null;
    double refValue;
    try {
      Array refArray = refVar.read();
      refValue = refArray.getDouble(refArray.getIndex()); // get the first value
    } catch (IOException ioe) {
      return null;
    }
    if (refValue == N3iosp.NC_FILL_DOUBLE)
      return null;

    // construct the values array - make it a double to be safe
    Array dvals = Array.factory(DataType.DOUBLE, vals.getShape());
    IndexIterator diter = dvals.getIndexIterator();
    IndexIterator iiter = vals.getIndexIterator();
    while (iiter.hasNext())
      diter.setDoubleNext(iiter.getDoubleNext() + refValue); // add reftime to each of the values

    String units = ds.findAttValueIgnoreCase(refVar, CDM.UNITS, "seconds since 1970-1-1 00:00:00");
    units = normalize(units);
    String desc = "synthesized time coordinate from reftime, valtimeMINUSreftime";
    CoordinateAxis1D timeCoord = new CoordinateAxis1D(ds, null, "timeCoord", DataType.DOUBLE, "record", units, desc);

    timeCoord.setCachedData(dvals, true);

    parseInfo.format("Created Time Coordinate Axis From Reference = ");
    timeCoord.getNameAndDimensions(parseInfo, true, false);
    parseInfo.format("%n");

    return timeCoord;
  }

  private double findAttributeDouble(NetcdfDataset ds, String attname) {
    Attribute att = ds.findGlobalAttributeIgnoreCase(attname);
    if (att == null) {
      parseInfo.format("ERROR cant find attribute= %s%n", attname);
      return Double.NaN;
    }
    return att.getNumericValue().doubleValue();
  }

}


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

package ucar.nc2.grib.writer;

import java.io.Closeable;
import java.io.IOException;

import ucar.nc2.NetcdfFileWriter;
import ucar.nc2.grib.grib2.Grib2Record;
import ucar.nc2.grib.grib2.Grib2RecordScanner;
import ucar.nc2.write.Nc4Chunking;
import ucar.nc2.write.Nc4ChunkingStrategy;
import ucar.unidata.io.RandomAccessFile;

/**
 * Experimental code to write GRIB to netcdf4.
 *
 * @author caron
 * @since 8/4/2014
 */
public class GribToNetcdfWriter implements Closeable {
  String fileIn;
  NetcdfFileWriter writer;

  public GribToNetcdfWriter(String fileIn, String fileOut) throws IOException {
    this.fileIn = fileIn;

    Nc4Chunking chunker = Nc4ChunkingStrategy.factory(Nc4Chunking.Strategy.grib, 9, true);
    writer = NetcdfFileWriter.createNew(NetcdfFileWriter.Version.netcdf4, fileOut, chunker);
  }

  public void write() throws IOException {

    try (RandomAccessFile raf = RandomAccessFile.acquire(fileIn)) {

      Grib2RecordScanner scanner = new Grib2RecordScanner(raf);
      while (scanner.hasNext()) {
        Grib2Record gr = scanner.next();
        float[] data = gr.readData(raf);
        for (int i=0; i<data.length; i++) {
          data[i] = bitShave(data[i], mask11);
        }
      }
    }
  }

  public static int getBitMask(int bitN) {
    if (bitN >= 23) return allOnes;
    return allOnes << 23-bitN;
  }

  private static final int allOnes = 0xffffffff;
  private static final int mask23 = 0xffffffff;
  private static final int mask19 = 0xfffffff0;
  private static final int mask15 = 0xffffff00;
  private static final int mask11 = 0xfffff000;
  private static final int mask07 = 0xffff0000;
  private static final int mask03 = 0xfff00000;
  private static final int mask00 = 0xff800000;

  // set all the bits=0 in bitMask to 0,

  /**
   * Shave n bits off the float
   * @param value    original floating point
   * @param bitMask  bitMask from getBitMask()
   * @return modified float
   */
  public static float bitShave(float value, int bitMask) {
    if (Float.isNaN(value)) return value;   // ??

    int bits = Float.floatToRawIntBits(value);
    int shave = bits & bitMask;
    return Float.intBitsToFloat(shave);
  }

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

  // Write Grib file to a netcdf4 file. Experimental.
  public static void main(String[] args) {
    String fileIn = (args.length > 0) ? args[0] : "Q:/cdmUnitTest/formats/grib2/LMPEF_CLM_050518_1200.grb";
    String fileOut = (args.length > 1) ? args[1] : "C:/tmp/ds.mint.bi";

    try (GribToNetcdfWriter writer = new GribToNetcdfWriter(fileIn, fileOut)) {
      writer.write();

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

  }

}