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

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.ShortBuffer;

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

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

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

  ///////////////////////////////////////////////////////////////////////////////
  protected short[] storage;

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

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

  /**
   * create new Array with given indexImpl and same backing store
   */
  protected Array createView(Index index) {
    return ArrayShort.factory(index, isUnsigned(), storage);
  }
  /* Get underlying primitive array storage. CAUTION! You may invalidate your warrentee! */
  public Object getStorage() {
    return storage;
  }

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

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

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

    public ByteBuffer getDataAsByteBuffer(ByteOrder order)
    {
      ByteBuffer bb = super.getDataAsByteBuffer((int) (2 * getSize()), order);
      ShortBuffer ib = bb.asShortBuffer();
      ib.put((short[]) get1DJavaArray(getDataType())); // make sure its in canonical order
    return bb;
  }

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

  /**
   * Get the value at the specified index.
   * @param i the index
   * @return the value at the specified index.
   */
  public short get(Index i) {
    return storage[i.currentElement()];
  }

  /**
   * Set the value at the specified index.
   * @param i the index
   * @param value set to this value
   */
  public void set(Index i, short value) {
    storage[i.currentElement()] = value;
  }

  public double getDouble(Index i) {
    short val = storage[i.currentElement()];
    return (double) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public float getFloat(Index i) {
    short val = storage[i.currentElement()];
    return (float) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public long getLong(Index i) {
    short val = storage[i.currentElement()];
    return (long) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public int getInt(Index i) {
    short val = storage[i.currentElement()];
    return (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public short getShort(Index i) {
    return storage[i.currentElement()];
  }

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

  public byte getByte(Index i) {
    return (byte) storage[i.currentElement()];
  }

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

  public char getChar(Index i) {
    short val = storage[i.currentElement()];
    return (char) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

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

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

  public Object getObject(Index i) {
    return storage[i.currentElement()]; // Short
  }

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

  // package private : mostly for iterators
  public double getDouble(int index) {
    short val = storage[index];
    return (double) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

  public void setDouble(int index, double value) {
    storage[index] = (short) value;
  }

  public float getFloat(int index) {
    short val = storage[index];
    return (float) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public long getLong(int index) {
    short val = storage[index];
    return (long) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public int getInt(int index) {
    short val = storage[index];
    return (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public short getShort(int index) {
    return storage[index];
  }

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

  public byte getByte(int index) {
    return (byte) storage[index];
  }

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

  public char getChar(int index) {
    short val = storage[index];
    return (char) (isUnsigned() ? DataType.unsignedShortToInt(val) : val);
  }

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

  public boolean getBoolean(int index) {
    throw new ForbiddenConversionException();
  }

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

  public Object getObject(int index) {
    return getShort(index);
  }

  public void setObject(int index, Object value) {
    storage[index] = ((Number) value).shortValue();
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 0.
   */
  public static class D0 extends ArrayShort {
    private Index0D ix;

    public D0(boolean isUnsigned) {
      super(new int[]{}, isUnsigned);
      ix = (Index0D) indexCalc;
    }

    private D0(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index0D) indexCalc;
    }

    public short get() {
      return storage[ix.currentElement()];
    }

    public void set(short value) {
      storage[ix.currentElement()] = value;
    }
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 1.
   */
  public static class D1 extends ArrayShort {
    private Index1D ix;

    public D1(int len0, boolean isUnsigned) {
      super(new int[]{len0}, isUnsigned);
      ix = (Index1D) indexCalc;
    }

    private D1(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index1D) indexCalc;
    }

    public short get(int i) {
      return storage[ix.setDirect(i)];
    }

    public void set(int i, short value) {
      storage[ix.setDirect(i)] = value;
    }
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 2.
   */
  public static class D2 extends ArrayShort {
    private Index2D ix;

    public D2(int len0, int len1, boolean isUnsigned) {
      super(new int[]{len0, len1}, isUnsigned);
      ix = (Index2D) indexCalc;
    }

    private D2(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index2D) indexCalc;
    }

    public short get(int i, int j) {
      return storage[ix.setDirect(i, j)];
    }

    public void set(int i, int j, short value) {
      storage[ix.setDirect(i, j)] = value;
    }
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 3.
   */
  public static class D3 extends ArrayShort {
    private Index3D ix;

    public D3(int len0, int len1, int len2, boolean isUnsigned) {
      super(new int[]{len0, len1, len2}, isUnsigned);
      ix = (Index3D) indexCalc;
    }

    private D3(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index3D) indexCalc;
    }

    public short get(int i, int j, int k) {
      return storage[ix.setDirect(i, j, k)];
    }

    public void set(int i, int j, int k, short value) {
      storage[ix.setDirect(i, j, k)] = value;
    }
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 4.
   */
  public static class D4 extends ArrayShort {
    private Index4D ix;

    public D4(int len0, int len1, int len2, int len3, boolean isUnsigned) {
      super(new int[]{len0, len1, len2, len3}, isUnsigned);
      ix = (Index4D) indexCalc;
    }

    private D4(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index4D) indexCalc;
    }

    public short get(int i, int j, int k, int l) {
      return storage[ix.setDirect(i, j, k, l)];
    }

    public void set(int i, int j, int k, int l, short value) {
      storage[ix.setDirect(i, j, k, l)] = value;
    }
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 5.
   */
  public static class D5 extends ArrayShort {
    private Index5D ix;

    public D5(int len0, int len1, int len2, int len3, int len4, boolean isUnsigned) {
      super(new int[]{len0, len1, len2, len3, len4}, isUnsigned);
      ix = (Index5D) indexCalc;
    }

    private D5(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index5D) indexCalc;
    }

    public short get(int i, int j, int k, int l, int m) {
      return storage[ix.setDirect(i, j, k, l, m)];
    }

    public void set(int i, int j, int k, int l, int m, short value) {
      storage[ix.setDirect(i, j, k, l, m)] = value;
    }
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 6.
   */
  public static class D6 extends ArrayShort {
    private Index6D ix;

    public D6(int len0, int len1, int len2, int len3, int len4, int len5, boolean isUnsigned) {
      super(new int[]{len0, len1, len2, len3, len4, len5}, isUnsigned);
      ix = (Index6D) indexCalc;
    }

    private D6(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index6D) indexCalc;
    }

    public short get(int i, int j, int k, int l, int m, int n) {
      return storage[ix.setDirect(i, j, k, l, m, n)];
    }

    public void set(int i, int j, int k, int l, int m, int n, short value) {
      storage[ix.setDirect(i, j, k, l, m, n)] = value;
    }
  }

  /**
   * Concrete implementation of Array specialized for shorts, rank 7.
   */
  public static class D7 extends ArrayShort {
    private Index7D ix;

    public D7(int len0, int len1, int len2, int len3, int len4, int len5, int len6, boolean isUnsigned) {
      super(new int[]{len0, len1, len2, len3, len4, len5, len6}, isUnsigned);
      ix = (Index7D) indexCalc;
    }

    private D7(Index i, boolean isUnsigned, short[] store) {
      super(i, isUnsigned, store);
      ix = (Index7D) indexCalc;
    }

    public short get(int i, int j, int k, int l, int m, int n, int o) {
      return storage[ix.setDirect(i, j, k, l, m, n, o)];
    }

    public void set(int i, int j, int k, int l, int m, int n, int o, short value) {
      storage[ix.setDirect(i, j, k, l, m, n, o)] = value;
    }
  }

}


/////////////////////////////////////////////////////////////////////////////
// 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.*;
import java.util.zip.DeflaterOutputStream;
import javax.servlet.*;
import javax.servlet.http.*;

import opendap.dap.*;
import opendap.servers.*;
import opendap.dap.parsers.ParseException;
import opendap.util.Debug;
import ucar.nc2.constants.CDM;
import ucar.nc2.util.EscapeStrings;

/**
 * AbstractServlet is the base servlet class for an OPeNDAP
 * servers.
 * Default handlers for all of the acceptable OPeNDAP client
 * requests are here.
 *

 * Each of the request handlers appears as an adjunct method to
 * the doGet() method of the base servlet class. In order to
 * reduce the bulk of this file, many of these methods have been
 * in wrapper classes in this package (opendap.servlet).
 *

 * This is an abstract class because it is left to the individual
 * server development efforts to write the getDDS() and
 * getServerVersion() methods. The getDDS() method is intended to
 * be where the server specific OPeNDAP server data types are
 * used via their associated class factory.
 *

 * This code relies on the javax.servlet.ServletConfig
 * interface (in particular the getInitParameter() method)
 * to record detailed configuration information used by
 * the servlet and it's children.
 *

 * The servlet should be started in the servlet engine with the following
 * initParameters for the tomcat servlet engine:
 *

 *    <servlet>
 *        <servlet-name>
 *            dts
 *        </servlet-name>
 *        <servlet-class>
 *            opendap.servers.test.dts
 *        </servlet-class>
 *        <init-param>
 *            <param-name>INFOcache</param-name>
 *            <param-value>/home/Datasets/info</param-value>
 *        </init-param>
 *        <init-param>
 *            <param-name>DDScache</param-name>
 *            <param-value>/home/Datasets/dds</param-value>
 *        </init-param>
 *        <init-param>
 *            <param-name>DAScache</param-name>
 *            <param-value>/home/Datasets/das</param-value>
 *        </init-param>
 *        <init-param>
 *            <param-name>DDXcache</param-name>
 *            <param-value>/home/Datasets/ddx</param-value>
 *        </init-param>
 *    </servlet>
 *
 *

 *

 * Obviously the actual values of these parameters will depend on your particular
 * file system.
 *
See the file SERVLETS in the top level directory of the
 * software distribution for more detailed information about servlet
 * configuration.

 * Also, the method processDodsURL() could be overloaded
 * if some kind of special processing of the incoming request is needed
 * to ascertain the OPeNDAP URL information.
 *
 * @author Nathan David Potter
 * @author jcaron 2/7/07 merge changes
 * @see opendap.servlet.GetAsciiHandler
 * @see opendap.servlet.GetDirHandler
 * @see opendap.servlet.GetHTMLInterfaceHandler
 * @see opendap.servlet.GetInfoHandler
 * @see opendap.servlet.ReqState
 * @see opendap.servlet.ParsedRequest
 * @see opendap.servlet.GuardedDataset
 */

public abstract class AbstractServlet extends javax.servlet.http.HttpServlet {

  //////////////////////////////////////////////////
  // Statics
  static final boolean debug = false;

  // Define an overall logger for everyone to use
  // Start with a default logger, but allow an application to change it later
  static public org.slf4j.Logger log
          = org.slf4j.LoggerFactory.getLogger(AbstractServlet.class);

  static public void setLog(Class cl) {
    log = org.slf4j.LoggerFactory.getLogger(cl);
  }

  static public void printDODSException(opendap.dap.DAP2Exception de) {
    de.printStackTrace();
  }

  static public void printThrowable(Throwable t) {
    AbstractServlet.log.error(t.getMessage());
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);
    t.printStackTrace(pw);
    pw.close();
    String trace = null;
    try {sw.close(); trace = sw.toString(); } catch (IOException ioe) {trace = "unknown";}
    log.error(trace);
  }

  /**
   * ************************************************************************
   * Compression
   */

  protected boolean allowDeflate = true;

  /**
   * ************************************************************************
   * Debugging
   */
  private boolean track = false;

  /**
   * ************************************************************************
   * Used for thread syncronization.
   *
   * @serial
   */
  private Object syncLock = new Object();

  /*
   * ************************************************************************
   * Count "hits" on the server...
   *
   * @serial
   */
  private int HitCounter = 0;

  /**
   * Keep a copy of the servlet config
   */
  //private ServletConfig servletConfig = null;

  /**
   * Keep a copy of the servlet context
   */
  //private ServletContext servletContext = null;

  /**
   * path to the root of the servlet in tomcat webapps directory
   */
  protected String rootpath = "";

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

  public AbstractServlet() {
  }

  protected void setRootpath(String rootPath) {
    this.rootpath = rootPath;
  }

  //////////////////////////////////////////////////////////
  // Get/Set

  /**
   * Getter function for rootpath
   *
   * @return rootpath
   *
  public String getRootPath() {
    return rootpath;
  } */

  /**
   * This function must be implemented locally for each OPeNDAP server. It should
   * return a String containing the OPeNDAP Server Version...
   *
   * @return The Server Version String
   */
  public abstract String getServerVersion();

  /**
   * ************************************************************************
   * This method must be implemented locally for each OPeNDAP server. The
   * local implementation of this method is the key piece for connecting
   * any localized data types that are derived from the opendap.server types
   * back into the running servlet.
   *

   * This method should do the following:
   *

   *
 Make a new ServerFactory (aka BaseTypeFactory) for the dataset requested.
   *
 Instantiate a ServerDDS using the ServerFactory and populate it (this
   * could be accomplished by just opening a (cached?) DDS in a file and parsing it)
   *
 Return this freshly minted ServerDDS object (to the servlet code where it is used.)
   *

   *
   * @param rs The ReqState object for this particular client request.
   * @return The ServerDDS object all parsed and ready to roll.
   * @throws DAP2Exception
   * @throws IOException
   * @throws ParseException
   * @see opendap.servers.ServerDDS
   */
  protected abstract opendap.servlet.GuardedDataset getDataset(opendap.servlet.ReqState rs)
          throws Exception;

  /**
   * ************************************************************************
   * Intitializes the servlet. Init (at this time) basically sets up
   * the object opendap.util.util.Debug from the debuggery flags in the
   * servlet InitParameters. The Debug object can be referenced (with
   * impunity) from anywhere in the VM.
   */

  /* public void init() throws ServletException {
    super.init();

    // debuggering
    String debugOn = getInitParameter("DebugOn");
    if (debugOn != null) {
      StringTokenizer toker = new StringTokenizer(debugOn);
      while (toker.hasMoreTokens())
        Debug.set(toker.nextToken(), true);
    }

    servletConfig = this.getServletConfig();
    servletContext = servletConfig.getServletContext();
    rootpath = servletContext.getRealPath("/");

  } */

  /**
   * ************************************************************************
   * Turns a ParseException into a OPeNDAP DAP2 error and sends it to the client.
   *
   * @param pe       The ParseException that caused the problem.
   * @param response The HttpServletResponse for the client.
   */
  public void parseExceptionHandler(ParseException pe, HttpServletResponse response) {
    log.error("DODSServlet.parseExceptionHandler", pe);

    if (Debug.isSet("showException")) {
      log.debug(pe.toString());
      printThrowable(pe);
    }

    //LogStream.out.println(pe);
    //pe.printStackTrace(LogStream.out);

    try {
      BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream());
      response.setHeader("Content-Description", "dods-error");

      // This should probably be set to "plain" but this works, the
      // C++ slients don't barf as they would if I sent "plain" AND
      // the C++ don't expect compressed data if I do this...
      response.setHeader("Content-Encoding", "");
      // response.setContentType("text/plain");  LOOK do we needLogStream.out

      // Strip any double quotes out of the parser error message.
      // These get stuck in auto-magically by the javacc generated parser
      // code and they break our error parser (bummer!)
      String msg = pe.getMessage().replace('\"', '\'');

      DAP2Exception de2 = new DAP2Exception(opendap.dap.DAP2Exception.CANNOT_READ_FILE, msg);
      de2.print(eOut);
    } catch (IOException ioe) {
      log.error("Cannot respond to client! IO Error: " + ioe.getMessage());
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Sends a OPeNDAP DAP2 error to the client.
   *
   * @param de       The OPeNDAP DAP2 exception that caused the problem.
   * @param response The HttpServletResponse for the client.
   */
  public void dap2ExceptionHandler(DAP2Exception de, HttpServletResponse response) {
    log.info("DODSServlet.dodsExceptionHandler (" + de.getErrorCode() + ") " + de.getErrorMessage());

    if (Debug.isSet("showException")) {
      de.print(System.err);
      de.printStackTrace(System.err);
      printDODSException(de);
    }

    try {
      BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream());

      response.setHeader("Content-Description", "dods-error");

      // This should probably be set to "plain" but this works, the
      // C++ slients don't barf as they would if I sent "plain" AND
      // the C++ don't expect compressed data if I do this...
      response.setHeader("Content-Encoding", "");
      de.print(eOut);

    } catch (IOException ioe) {
      log.error("Cannot respond to client! IO Error: " + ioe.getMessage());
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Sends an error to the client.
   * LOOK: The problem is that if the message is already committed when the IOException occurs, the headers dont get set.
   *
   * @param e  The exception that caused the problem.
   * @param rs The ReqState for the client.
   */
  public void IOExceptionHandler(IOException e, ReqState rs) {
    HttpServletResponse response = rs.getResponse();
    try {
      BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream());
      response.setHeader("Content-Description", "dods-error");

      // This should probably be set to "plain" but this works, the
      // C++ slients don't barf as they would if I sent "plain" AND
      // the C++ don't expect compressed data if I do this...
      response.setHeader("Content-Encoding", "");

      // Strip any double quotes out of the parser error message.
      // These get stuck in auto-magically by the javacc generated parser
      // code and they break our error parser (bummer!)
      String msg = e.getMessage();
      if (msg != null)
        msg = msg.replace('\"', '\'');

      DAP2Exception de2 = new DAP2Exception(opendap.dap.DAP2Exception.CANNOT_READ_FILE, msg);
      de2.print(eOut);

      if (Debug.isSet("showException")) {
        // Error message
        log.error("DODServlet ERROR (IOExceptionHandler): " + e);
        log.error(rs.toString());
        if (track) {
          RequestDebug reqD = (RequestDebug) rs.getUserObject();
          log.error("  request number: " + reqD.reqno + " thread: " + reqD.threadDesc);
        }
        printThrowable(e);
      }

    } catch (IOException ioe) {
      log.error("Cannot respond to client! IO Error: " + ioe.getMessage());
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Sends an error to the client.
   *
   * @param e  The exception that caused the problem.
   * @param rs The ReqState for the client.
   */
  public void anyExceptionHandler(Throwable e, ReqState rs) {
    log.error("DODServlet ERROR (anyExceptionHandler): " + e);
    printThrowable(e);
    try {
        if(rs == null)
          throw new DAP2Exception("anyExceptionHandler: no request state provided");
        log.error(rs.toString());
        HttpServletResponse response = rs.getResponse();
        log.error(rs.toString());
        if (track) {
          RequestDebug reqD = (RequestDebug) rs.getUserObject();
          log.error("  request number: " + reqD.reqno + " thread: " + reqD.threadDesc);
        }
        response.setHeader("Content-Description", "dods-error");

        // This should probably be set to "plain" but this works, the
        // C++ slients don't barf as they would if I sent "plain" AND
        // the C++ don't expect compressed data if I do this...
        response.setHeader("Content-Encoding", "");

        // Strip any double quotes out of the parser error message.
        // These get stuck in auto-magically by the javacc generated parser
        // code and they break our error parser (bummer!)
        String msg = e.getMessage();
        if (msg != null)
          msg = msg.replace('\"', '\'');
        DAP2Exception de2 = new DAP2Exception(opendap.dap.DAP2Exception.UNDEFINED_ERROR, msg);
        BufferedOutputStream eOut = new BufferedOutputStream(response.getOutputStream());
        de2.print(eOut);
    } catch (Exception ioe) {
      log.error("Cannot respond to client! IO Error: " + ioe.getMessage());
    }
  }

  /***************************************************************************/

  /**
   * ************************************************************************
   * Sends a OPeNDAP DAP2 error (type UNKNOWN ERROR) to the client and displays a
   * message on the server console.
   *
   * @param request   The client's  HttpServletRequest request object.
   * @param response  The server's  HttpServletResponse response object.
   * @param clientMsg Error message String to send to the client.
   * @param serverMsg Error message String to display on the server console.
   */
  public void sendDODSError(HttpServletRequest request,
                            HttpServletResponse response,
                            String clientMsg,
                            String serverMsg)
          throws IOException, ServletException {

    response.setContentType("text/plain");
    response.setHeader("XDODS-Server", getServerVersion());
    response.setHeader("Content-Description", "dods-error");
    // Commented because of a bug in the OPeNDAP C++ stuff...
    //response.setHeader("Content-Encoding", "none");

    ServletOutputStream Out = response.getOutputStream();

    DAP2Exception de = new DAP2Exception(opendap.dap.DAP2Exception.UNKNOWN_ERROR, clientMsg);

    de.print(Out);

    response.setStatus(HttpServletResponse.SC_OK);

    log.error(serverMsg);
  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's DAS request. Operates on the assumption
   * that the DAS information is cached on a disk local to the server. If you
   * don't like that, then you better override it in your server :)
   *

   *
Once the DAS has been parsed it is sent to the requesting client.
   *
   * @param rs The ReqState of this client request. Contains all kinds of
   *           important stuff.
   * @see ReqState
   */
  public void doGetDAS(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse")) {
      log.debug("doGetDAS for dataset: " + rs.getDataSet());
    }

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (ds == null) return;

      rs.getResponse().setContentType("text/plain");
      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setHeader("Content-Description", "dods-das");
      // Commented because of a bug in the OPeNDAP C++ stuff...
      //rs.getResponse().setHeader("Content-Encoding", "plain");

      OutputStream Out = new BufferedOutputStream(rs.getResponse().getOutputStream());

      DAS myDAS = ds.getDAS();
      myDAS.print(Out);
      rs.getResponse().setStatus(HttpServletResponse.SC_OK);
      if (Debug.isSet("showResponse")) {
        //log.debug("DAS=\n");
        //myDAS.print(LogStream.out);
      }
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (Throwable t) {
      anyExceptionHandler(t, rs);
    } finally { // release lock if needed
      if (ds != null) ds.release();
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's DDS request. Requires the getDDS() method
   * implemented by each server localization effort.
   *

   *
Once the DDS has been parsed and constrained it is sent to the
   * requesting client.
   *
   * @param rs The ReqState of this client request. Contains all kinds of
   *           important stuff.
   * @see ReqState
   */
  public void doGetDDS(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse"))
      log.debug("doGetDDS for dataset: " + rs.getDataSet());

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      rs.getResponse().setContentType("text/plain");
      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setHeader("Content-Description", "dods-dds");
      // Commented because of a bug in the OPeNDAP C++ stuff...
      //rs.getResponse().setHeader("Content-Encoding", "plain");

      OutputStream Out = new BufferedOutputStream(rs.getResponse().getOutputStream());

      // Utilize the getDDS() method to get a parsed and populated DDS
      // for this server.
      ServerDDS myDDS = ds.getDDS();

      if (rs.getConstraintExpression().equals("")) { // No Constraint Expression?
        // Send the whole DDS
        myDDS.print(Out);
        Out.flush();
      } else { // Otherwise, send the constrained DDS

        // Instantiate the CEEvaluator and parse the constraint expression
        CEEvaluator ce = new CEEvaluator(myDDS);
        ce.parseConstraint(rs);

        // Send the constrained DDS back to the client
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(Out, Util.UTF8));
        myDDS.printConstrained(pw);
        pw.flush();
      }

      rs.getResponse().setStatus(HttpServletResponse.SC_OK);
      if (Debug.isSet("showResponse")) {
        if (rs.getConstraintExpression().equals("")) { // No Constraint Expression?
          log.debug("Unconstrained DDS=\n");
          //myDDS.print();
        } else {
          log.debug("Constrained DDS=\n");
          //myDDS.printConstrained();
        }
      }

    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (IOException pe) {
      IOExceptionHandler(pe, rs);
    } catch (Throwable t) {
      anyExceptionHandler(t, rs);
    } finally { // release lock if needed
      if (ds != null) ds.release();
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's DDS request. Requires the getDDS() method
   * implemented by each server localization effort.
   *

   *
Once the DDS has been parsed and constrained it is sent to the
   * requesting client.
   *
   * @param rs The ReqState of this client request. Contains all kinds of
   *           important stuff.
   * @see ReqState
   */
  public void doGetDDX(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse"))
      log.debug("doGetDDX for dataset: " + rs.getDataSet());

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      rs.getResponse().setContentType("text/plain");
      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setHeader("Content-Description", "dods-ddx");
      // Commented because of a bug in the OPeNDAP C++ stuff...
      // rs.getResponse().setHeader("Content-Encoding", "plain");

      OutputStream Out = new BufferedOutputStream(rs.getResponse().getOutputStream());

      // Utilize the getDDS() method to get a parsed and populated DDS
      // for this server.
      ServerDDS myDDS = ds.getDDS();

      if (rs.getConstraintExpression().equals("")) { // No Constraint Expression?
        // Send the whole DDS
        myDDS.printXML(Out);
        Out.flush();
      } else { // Otherwise, send the constrained DDS

        // Instantiate the CEEvaluator and parse the constraint expression
        CEEvaluator ce = new CEEvaluator(myDDS);
        ce.parseConstraint(rs);

        // Send the constrained DDS back to the client
        PrintWriter pw = new PrintWriter(new OutputStreamWriter(Out, Util.UTF8));
        myDDS.printConstrainedXML(pw);
        pw.flush();
      }

      rs.getResponse().setStatus(HttpServletResponse.SC_OK);
      if (Debug.isSet("showResponse")) {
        if (rs.getConstraintExpression().equals("")) { // No Constraint Expression?
          log.debug("Unconstrained DDX=\n");
          myDDS.printXML(System.out);
        } else {
          log.debug("Constrained DDX=\n");
          myDDS.printConstrainedXML(System.out);
        }
      }
    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (IOException pe) {
      IOExceptionHandler(pe, rs);
    } catch (Throwable t) {
      anyExceptionHandler(t, rs);
    } finally { // release lock if needed
      if (ds != null) ds.release();
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's data request. Requires the getDDS()
   * method implemented by each server localization effort.
   *

   *
Once the DDS has been parsed, the data is read (using the class in the
   * localized server factory etc.), compared to the constraint expression,
   * and then sent to the client.
   *
   * @param rs The ReqState of this client request. Contains all kinds of
   *           important stuff.
   * @opendap.ddx.experimental
   * @see ReqState
   */
  public void doGetBLOB(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse")) {
      log.debug("doGetBLOB For: " + rs.getDataSet());
    }
    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      rs.getResponse().setContentType("application/octet-stream");
      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setHeader("Content-Description", "dods-blob");

      ServletOutputStream sOut = rs.getResponse().getOutputStream();
      OutputStream bOut;
      DeflaterOutputStream dOut = null;
      if (rs.getAcceptsCompressed() && allowDeflate) {
        rs.getResponse().setHeader("Content-Encoding", "deflate");
        dOut = new DeflaterOutputStream(sOut);
        bOut = new BufferedOutputStream(dOut);
      } else {
        // Commented out because of a bug in the OPeNDAP C++ stuff...
        //rs.getResponse().setHeader("Content-Encoding", "plain");
        bOut = new BufferedOutputStream(sOut);
      }

      // Utilize the getDDS() method to get	a parsed and populated DDS
      // for this server.
      ServerDDS myDDS = ds.getDDS();

      // Instantiate the CEEvaluator and parse the constraint expression
      CEEvaluator ce = new CEEvaluator(myDDS);
      ce.parseConstraint(rs.getConstraintExpression(), rs.getRequestURL().toString());

      // Send the binary data back to the client
      DataOutputStream sink = new DataOutputStream(bOut);
      ce.send(myDDS.getEncodedName(), sink, ds);
      sink.flush();

      // Finish up sending the compressed stuff, but don't
      // close the stream (who knows what the Servlet may expect!)
      if (null != dOut)
        dOut.finish();
      bOut.flush();

      rs.getResponse().setStatus(HttpServletResponse.SC_OK);

    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (IOException ioe) {
      IOExceptionHandler(ioe, rs);
    } finally {  // release lock if needed
      if (ds != null) ds.release();
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's data request. Requires the getDDS()
   * method implemented by each server localization effort.
   *

   *
Once the DDS has been parsed, the data is read (using the class in the
   * localized server factory etc.), compared to the constraint expression,
   * and then sent to the client.
   *
   * @param rs The ReqState of this client request. Contains all kinds of
   *           important stuff.
   * @throws IOException
   * @throws ServletException
   * @see ReqState
   */
  public void doGetDAP2Data(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse")) {
      log.debug("doGetDAP2Data For: " + rs.getDataSet());
    }
    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      rs.getResponse().setContentType("application/octet-stream");
      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setHeader("Content-Description", "dods-data");

      ServletOutputStream sOut = rs.getResponse().getOutputStream();
      OutputStream bOut;
      DeflaterOutputStream dOut = null;
      if (rs.getAcceptsCompressed() && allowDeflate) {
        rs.getResponse().setHeader("Content-Encoding", "deflate");
        dOut = new DeflaterOutputStream(sOut);
        bOut = new BufferedOutputStream(dOut);

      } else {
        // Commented out because of a bug in the OPeNDAP C++ stuff...
        //rs.getResponse().setHeader("Content-Encoding", "plain");
        bOut = new BufferedOutputStream(sOut);
      }

      // Utilize the getDDS() method to get	a parsed and populated DDS
      // for this server.
      ServerDDS myDDS = ds.getDDS();

      // Instantiate the CEEvaluator and parse the constraint expression
      CEEvaluator ce = new CEEvaluator(myDDS);
      ce.parseConstraint(rs);

      // debug
      //log.debug("CE DDS = ");
      // myDDS.printConstrained(LogStream.out);

      // Send the constrained DDS back to the client
      PrintWriter pw = new PrintWriter(new OutputStreamWriter(bOut, Util.UTF8));
      myDDS.printConstrained(pw);

      // Send the Data delimiter back to the client
      //pw.println("Data:"); // JCARON CHANGED
      pw.flush();
      bOut.write("\nData:\n".getBytes(CDM.utf8Charset)); // JCARON CHANGED
      bOut.flush();

      // Send the binary data back to the client
      DataOutputStream sink = new DataOutputStream(bOut);
      ce.send(myDDS.getEncodedName(), sink, ds);
      sink.flush();

      // Finish up tsending the compressed stuff, but don't
      // close the stream (who knows what the Servlet may expect!)
      if (null != dOut)
        dOut.finish();
      bOut.flush();

      rs.getResponse().setStatus(HttpServletResponse.SC_OK);

    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (IOException ioe) {
      IOExceptionHandler(ioe, rs);
    } finally {  // release lock if needed
      if (ds != null) ds.release();
    }

  }

  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's directory request.
   *

   * Returns an html document to the client showing (a possibly pseudo)
   * listing of the datasets available on the server in a directory listing
   * format.
   *

   * The bulk of this code resides in the class opendap.servlet.GetDirHandler and
   * documentation may be found there.
   *
   * @param rs The client's  ReqState
   * @see opendap.servlet.GetDirHandler
   */
  public void doGetDIR(ReqState rs)
          throws Exception {

    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setContentType("text/html");
    rs.getResponse().setHeader("Content-Description", "dods-directory");

    try {
      GetDirHandler di = new GetDirHandler();
      di.sendDIR(rs);
      rs.getResponse().setStatus(HttpServletResponse.SC_OK);
    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (Throwable t) {
      anyExceptionHandler(t, rs);
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's version request.
   *

   *
Returns a plain text document with server version and OPeNDAP core
   * version #'s
   *
   * @param rs The client's  ReqState
   */
  public void doGetVER(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse")) {
      log.debug("Sending Version Tag.");
    }
    rs.getResponse().setContentType("text/plain");
    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setHeader("Content-Description", "dods-version");
    // Commented because of a bug in the OPeNDAP C++ stuff...
    //rs.getResponse().setHeader("Content-Encoding", "plain");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));

    pw.println("Server Version: " + getServerVersion());
    pw.flush();

    rs.getResponse().setStatus(HttpServletResponse.SC_OK);
  }

  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for the client's help request.
   *

   *
 Returns an html page of help info for the server
   *
   * @param rs The client's  ReqState
   */
  public void doGetHELP(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse")) {
      log.debug("Sending Help Page.");
    }
    rs.getResponse().setContentType("text/html");
    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setHeader("Content-Description", "dods-help");
    // Commented because of a bug in the OPeNDAP C++ stuff...
    //rs.getResponse().setHeader("Content-Encoding", "plain");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));
    printHelpPage(pw);
    pw.flush();

    rs.getResponse().setStatus(HttpServletResponse.SC_OK);
  }

  /***************************************************************************/

  /**
   * ************************************************************************
   * Sends an html document to the client explaining that they have used a
   * poorly formed URL and then the help page...
   *
   * @param rs The client's  ReqState
   */
  public void badURL(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse")) {
      log.debug("Sending Bad URL Page.");
    }

    //log.info("DODSServlet.badURL " + rs.getRequest().getRequestURI());

    rs.getResponse().setContentType("text/html");
    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setHeader("Content-Description", "dods-error");
    // Commented because of a bug in the OPeNDAP C++ stuff...
    //rs.getResponse().setHeader("Content-Encoding", "plain");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));

    printBadURLPage(pw);
    printHelpPage(pw);
    pw.flush();

    rs.getResponse().setStatus(HttpServletResponse.SC_OK);

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for OPeNDAP ascii data requests. Returns the request data as
   * a comma delimited ascii file. Note that this means that the more complex
   * OPeNDAP structures such as Grids get flattened...
   *

   *

   * Modified 2/8/07 jcaron to not make a DConnect2 call to itself
   *
   * @param rs the decoded Request State
   */
  public void doGetASC(ReqState rs) throws Exception {
    if (Debug.isSet("showResponse")) {
      log.debug("doGetASC For: " + rs.getDataSet());
    }

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (ds == null) return;

      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setContentType("text/plain");
      rs.getResponse().setHeader("Content-Description", "dods-ascii");

      if (debug)
        log.debug("Sending OPeNDAP ASCII Data For: " + rs + "  CE: '" + rs.getConstraintExpression() + "'");

      ServerDDS dds = ds.getDDS();
      //dds = url.getData(ce, null, new asciiFactory());  previous way

      // Instantiate the CEEvaluator and parse the constraint expression
      CEEvaluator ce = new CEEvaluator(dds);

      // i think this makes the dds constrained
      ce.parseConstraint(rs);

      PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));
      dds.printConstrained(pw);
      pw.println("---------------------------------------------");

      AsciiWriter writer = new AsciiWriter(); // could be static
      writer.toASCII(pw, dds, ds);

      // the way that getDAP2Data works
      // DataOutputStream sink = new DataOutputStream(bOut);
      // ce.send(myDDS.getName(), sink, ds);

      pw.flush();

      rs.getResponse().setStatus(HttpServletResponse.SC_OK);

    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (Throwable t) {
      anyExceptionHandler(t, rs);
    } finally { // release lock if needed
      if (ds != null) ds.release();
    }

  }

  /**
   * ************************************************************************
   * Default handler for OPeNDAP info requests. Returns an HTML document
   * describing the contents of the servers datasets.
   *

   * The bulk of this code resides in the class opendap.servlet.GetInfoHandler and
   * documentation may be found there.
   *
   * @param rs The client's  ReqState
   * @see GetInfoHandler
   */
  public void doGetINFO(ReqState rs)
          throws Exception {

    if (Debug.isSet("showResponse")) {
      log.debug("doGetINFO For: " + rs.getDataSet());
    }

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (null == ds) return;

      PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));
      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setContentType("text/html");
      rs.getResponse().setHeader("Content-Description", "dods-description");

      GetInfoHandler di = new GetInfoHandler();
      di.sendINFO(pw, ds, rs);
      rs.getResponse().setStatus(HttpServletResponse.SC_OK);

    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (IOException pe) {
      IOExceptionHandler(pe, rs);
    } catch (Throwable t) {
      anyExceptionHandler(t, rs);
    } finally {  // release lock if needed
      if (ds != null) ds.release();
    }

  }
  /**************************************************************************/

  /**
   * ************************************************************************
   * Default handler for OPeNDAP .html requests. Returns the OPeNDAP Web
   * Interface (aka The Interface From Hell) to the client.
   *

   * The bulk of this code resides in the class
   * opendap.servlet.GetHTMLInterfaceHandler and
   * documentation may be found there.
   *
   * @param rs The client's  ReqState
   * @see GetHTMLInterfaceHandler
   */
  public void doGetHTML(ReqState rs)
          throws Exception {

    GuardedDataset ds = null;
    try {
      ds = getDataset(rs);
      if (ds == null) return;

      rs.getResponse().setHeader("XDODS-Server", getServerVersion());
      rs.getResponse().setContentType("text/html");
      rs.getResponse().setHeader("Content-Description", "dods-form");

      // Utilize the getDDS() method to get	a parsed and populated DDS
      // for this server.
      ServerDDS myDDS = ds.getDDS();
      DAS das = ds.getDAS();
      GetHTMLInterfaceHandler di = new GetHTMLInterfaceHandler();
      di.sendDataRequestForm(rs, rs.getDataSet(), myDDS, das);
      rs.getResponse().setStatus(HttpServletResponse.SC_OK);

    } catch (ParseException pe) {
      parseExceptionHandler(pe, rs.getResponse());
    } catch (DAP2Exception de) {
      dap2ExceptionHandler(de, rs.getResponse());
    } catch (IOException pe) {
      IOExceptionHandler(pe, rs);
    } catch (Throwable t) {
      anyExceptionHandler(t, rs);
    } finally {  // release lock if needed
      if (ds != null) ds.release();
    }

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for OPeNDAP catalog.xml requests.
   *
   * @param rs The client's  ReqState
   * @see GetHTMLInterfaceHandler
   */
  public void doGetCatalog(ReqState rs)
          throws Exception {

    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setContentType("text/xml");
    rs.getResponse().setHeader("Content-Description", "dods-catalog");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));
    printCatalog(rs, pw);
    pw.flush();
    rs.getResponse().setStatus(HttpServletResponse.SC_OK);

  }

  // to be overridden by servers that implement catalogs
  protected void printCatalog(ReqState rs, PrintWriter os) throws IOException {
    os.println("Catalog not available for this server");
    os.println("Server version = " + getServerVersion());
  }

  /***************************************************************************/

  /**
   * ************************************************************************
   * Default handler for debug requests;
   *
   * @param rs The client's  ReqState   object.
   */
  public void doDebug(ReqState rs) throws IOException {

    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setContentType("text/html");
    rs.getResponse().setHeader("Content-Description", "dods_debug");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));
    pw.println("<title>Debugging</title>");
    pw.println("
");

    StringTokenizer tz = new StringTokenizer(rs.getConstraintExpression(), "=;");
    while (tz.hasMoreTokens()) {
      String cmd = tz.nextToken();
      pw.println("Cmd= " + cmd);

      if (cmd.equals("help")) {
        pw.println(" help;log;logEnd;logShow");
        pw.println(" showFlags;showInitParameters;showRequest");
        pw.println(" on|off=(flagName)");
        doDebugCmd(cmd, tz, pw); // for subclasses
      } else if (cmd.equals("on"))
        Debug.set(tz.nextToken(), true);

      else if (cmd.equals("off"))
        Debug.set(tz.nextToken(), false);

      else if (cmd.equals("showFlags")) {
        Iterator iter = Debug.keySet().iterator();
        while (iter.hasNext()) {
          String key = (String) iter.next();
          pw.println("  " + key + " " + Debug.isSet(key));
        }
      } else if (cmd.equals("showInitParameters"))
        pw.println(rs.toString());

      else if (cmd.equals("showRequest"))
        probeRequest(pw, rs);

      else if (!doDebugCmd(cmd, tz, pw)) { // for subclasses
        pw.println("  unrecognized command");
      }
    }

    pw.println("--------------------------------------");
    pw.println("Logging is on");
    Iterator iter = Debug.keySet().iterator();
    while (iter.hasNext()) {
      String key = (String) iter.next();
      boolean val = Debug.isSet(key);
      if (val)
        pw.println("  " + key + " " + Debug.isSet(key));
    }

    pw.println("
");
    pw.flush();
    rs.getResponse().setStatus(HttpServletResponse.SC_OK);

  }

  protected boolean doDebugCmd(String cmd, StringTokenizer tz, PrintWriter pw) {
    return false;
  }

  /**
   * ************************************************************************
   * Default handler for OPeNDAP status requests; not publically available,
   * used only for debugging
   *
   * @param rs The client's  ReqState
   * @see GetHTMLInterfaceHandler
   */
  public void doGetSystemProps(ReqState rs)
          throws Exception {

    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setContentType("text/html");
    rs.getResponse().setHeader("Content-Description", "dods-status");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));
    pw.println("");
    pw.println("<title>System Properties</title>");
    pw.println("
");
    pw.println("
System Properties
");
    pw.println("
Date: " + new Date() + "
");

    Properties sysp = System.getProperties();
    Enumeration e = sysp.propertyNames();

    pw.println("
");
    while (e.hasMoreElements()) {
      String name = (String) e.nextElement();

      String value = System.getProperty(name);

      pw.println("
" + name + ": " + value + "
");
    }
    pw.println("
");

    pw.println("
Runtime Info:
");

    Runtime rt = Runtime.getRuntime();
    pw.println("JVM Max Memory:   " + (rt.maxMemory() / 1024) / 1000. + " MB (JVM Maximum Allowable Heap)
");
    pw.println("JVM Total Memory: " + (rt.totalMemory() / 1024) / 1000. + " MB (JVM Heap size)
");
    pw.println("JVM Free Memory:  " + (rt.freeMemory() / 1024) / 1000. + " MB (Unused part of heap)
");
    pw.println("JVM Used Memory:  " + ((rt.totalMemory() - rt.freeMemory()) / 1024) / 1000. + " MB (Currently active memory)
");

    pw.println("
");
    pw.println("");
    pw.println("");
    pw.flush();
    rs.getResponse().setStatus(HttpServletResponse.SC_OK);

  }

  /**
   * ************************************************************************
   * Default handler for OPeNDAP status requests; not publically available,
   * used only for debugging
   *
   * @param rs The client's  ReqState
   * @see GetHTMLInterfaceHandler
   */
  public void doGetStatus(ReqState rs)
          throws Exception {

    rs.getResponse().setHeader("XDODS-Server", getServerVersion());
    rs.getResponse().setContentType("text/html");
    rs.getResponse().setHeader("Content-Description", "dods-status");

    PrintWriter pw = new PrintWriter(new OutputStreamWriter(rs.getResponse().getOutputStream(), Util.UTF8));
    pw.println("<title>Server Status</title>");
    pw.println("
");
    printStatus(pw);
    pw.println("
");
    pw.flush();
    rs.getResponse().setStatus(HttpServletResponse.SC_OK);
  }

  // to be overridden by servers that implement status report
  protected void printStatus(PrintWriter os) throws IOException {
    os.println("
Server version = " + getServerVersion() + "
");
    os.println("
Number of Requests Received = " + HitCounter + "
");
    if (track) {
      int n = prArr.size();
      int pending = 0;
      StringBuilder preqs = new StringBuilder();
      for (int i = 0; i < n; i++) {
        ReqState rs = (ReqState) prArr.get(i);
        RequestDebug reqD = (RequestDebug) rs.getUserObject();
        if (!reqD.done) {
          preqs.append("
-----------------------\n");
          preqs.append("Request[");
          preqs.append(reqD.reqno);
          preqs.append("](");
          preqs.append(reqD.threadDesc);
          preqs.append(") is pending.\n");
          preqs.append(rs.toString());
          preqs.append("
");
          pending++;
        }
      }
      os.println("
" + pending + " Pending Request(s)
");
      os.println(preqs.toString());
    }
  }

  /***************************************************************************/

  /**
   * ************************************************************************
   * This is a bit of instrumentation that I kept around to let me look at the
   * state of the incoming HttpServletRequest from the client.
   * This method calls the get* methods of the request and prints
   * the results to standard out.
   *
   * @param ps The PrintStream to send output.
   * @param rs The ReqState object to probe.
   */

  public void probeRequest(PrintWriter ps, ReqState rs) {

    Enumeration e;
    int i;

    ps.println("####################### PROBE ##################################");
    ps.println("The HttpServletRequest object is actually a: " + rs.getRequest().getClass().getName());
    ps.println("");
    ps.println("HttpServletRequest Interface:");
    ps.println("    getAuthType:           " + rs.getRequest().getAuthType());
    ps.println("    getMethod:             " + rs.getRequest().getMethod());
    ps.println("    getPathInfo:           " + rs.getRequest().getPathInfo());
    ps.println("    getPathTranslated:     " + rs.getRequest().getPathTranslated());
    ps.println("    getRequestURL:         " + rs.getRequest().getRequestURL());
    ps.println("    getQueryString:        " + rs.getRequest().getQueryString());
    ps.println("    getRemoteUser:         " + rs.getRequest().getRemoteUser());
    ps.println("    getRequestedSessionId: " + rs.getRequest().getRequestedSessionId());
    ps.println("    getRequestURI:         " + rs.getRequest().getRequestURI());
    ps.println("    getServletPath:        " + rs.getRequest().getServletPath());
    ps.println("    isRequestedSessionIdFromCookie: " + rs.getRequest().isRequestedSessionIdFromCookie());
    ps.println("    isRequestedSessionIdValid:      " + rs.getRequest().isRequestedSessionIdValid());
    ps.println("    isRequestedSessionIdFromURL:    " + rs.getRequest().isRequestedSessionIdFromURL());

    ps.println("");
    i = 0;
    e = rs.getRequest().getHeaderNames();
    ps.println("    Header Names:");
    while (e.hasMoreElements()) {
      i++;
      String s = (String) e.nextElement();
      ps.print("        Header[" + i + "]: " + s);
      ps.println(": " + rs.getRequest().getHeader(s));
    }

    ps.println("");
    ps.println("ServletRequest Interface:");
    ps.println("    getCharacterEncoding:  " + rs.getRequest().getCharacterEncoding());
    ps.println("    getContentType:        " + rs.getRequest().getContentType());
    ps.println("    getContentLength:      " + rs.getRequest().getContentLength());
    ps.println("    getProtocol:           " + rs.getRequest().getProtocol());
    ps.println("    getScheme:             " + rs.getRequest().getScheme());
    ps.println("    getServerName:         " + rs.getRequest().getServerName());
    ps.println("    getServerPort:         " + rs.getRequest().getServerPort());
    ps.println("    getRemoteAddr:         " + rs.getRequest().getRemoteAddr());
    ps.println("    getRemoteHost:         " + rs.getRequest().getRemoteHost());
    //ps.println("    getRealPath:           "+rs.getRequest().getRealPath());

    ps.println(".............................");
    ps.println("");
    i = 0;
    e = rs.getRequest().getAttributeNames();
    ps.println("    Attribute Names:");
    while (e.hasMoreElements()) {
      i++;
      String s = (String) e.nextElement();
      ps.print("        Attribute[" + i + "]: " + s);
      ps.println(" Type: " + rs.getRequest().getAttribute(s));
    }

    ps.println(".............................");
    ps.println("");
    i = 0;
    e = rs.getRequest().getParameterNames();
    ps.println("    Parameter Names:");
    while (e.hasMoreElements()) {
      i++;
      String s = (String) e.nextElement();
      ps.print("        Parameter[" + i + "]: " + s);
      ps.println(" Value: " + rs.getRequest().getParameter(s));
    }

    ps.println("- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -");
    ps.println(" . . . . . . . . . Servlet Infomation API  . . . . . . . . . . . . . .");
    ps.println("");

    ps.println("Servlet Context:");
    ps.println("");

    /* i = 0;
    e = servletContext.getAttributeNames();
    ps.println("    Attribute Names:");
    while (e.hasMoreElements()) {
      i++;
      String s = (String) e.nextElement();
      ps.print("        Attribute[" + i + "]: " + s);
      ps.println(" Type: " + servletContext.getAttribute(s));
    }

    ps.println("    ServletContext.getRealPath(\".\"): " + servletContext.getRealPath("."));
    ps.println("    ServletContext.getMajorVersion(): " + servletContext.getMajorVersion());
//        ps.println("ServletContext.getMimeType():     " + sc.getMimeType());
    ps.println("    ServletContext.getMinorVersion(): " + servletContext.getMinorVersion());
//        ps.println("ServletContext.getRealPath(): " + sc.getRealPath()); */

    ps.println(".............................");
    ps.println("Servlet Config:");
    ps.println("");

    ServletConfig scnfg = getServletConfig();

    i = 0;
    e = scnfg.getInitParameterNames();
    ps.println("    InitParameters:");
    while (e.hasMoreElements()) {
      String p = (String) e.nextElement();
      ps.print("        InitParameter[" + i + "]: " + p);
      ps.println(" Value: " + scnfg.getInitParameter(p));
      i++;
    }
    ps.println("");
    ps.println("######################## END PROBE ###############################");
    ps.println("");

  }
  /***************************************************************************/

  /**
   * ************************************************************************
   *

   * In this (default) implementation of the getServerName() method we just get
   * the name of the servlet and pass it back. If something different is
   * required, override this method when implementing the getDDS() and
   * getServerVersion() methods.
   *

   * This is typically used by the getINFO() method to figure out if there is
   * information specific to this server residing in the info directory that
   * needs to be returned to the client as part of the .info rs.getResponse().
   *
   * @return A string containing the name of the servlet class that is running.
   */
  public String getServerName() {

    // Ascertain the name of this server.
    String servletName = this.getClass().getName();

    return (servletName);
  }

  /**
   * ***********************************************************************
   * Handles incoming requests from clients. Parses the request and determines
   * what kind of OPeNDAP response the client is requesting. If the request is
   * understood, then the appropriate handler method is called, otherwise
   * an error is returned to the client.
   *

   * This method is the entry point for AbstractServlet.
   *
   * @param request  The client's  HttpServletRequest request
   *                 object.
   * @param response The server's  HttpServletResponse response
   *                 object.
   * @see opendap.servlet.ReqState
   */
  public void doGet(HttpServletRequest request,
                    HttpServletResponse response) {

    // setHeader("Last-Modified", (new Date()).toString() );

    boolean isDebug = false;
    ReqState rs = null;
    RequestDebug reqD = null;
    try {

      rs = getRequestState(request, response);
      if (rs != null) {
        if (Debug.isSet("probeRequest")) {
          probeRequest(new PrintWriter(new OutputStreamWriter(System.out, Util.UTF8), true), rs);
        }

        String ds = rs.getDataSet();
        String suff = rs.getRequestSuffix();
        isDebug = ((ds != null) && ds.equals("debug") && (suff != null) && suff.equals(""));
      }

      synchronized (syncLock) {

        if (!isDebug) {
          long reqno = HitCounter++;
          if (track) {
            reqD = new RequestDebug(reqno, Thread.currentThread().toString());
            rs.setUserObject(reqD);
            if (prArr == null) prArr = new ArrayList(10000);
            prArr.add((int) reqno, rs);
          }

          if (Debug.isSet("showRequest")) {
            log.debug("-------------------------------------------");
            log.debug("Server: " + getServerName() + "   Request #" + reqno);
            log.debug("Client: " + rs.getRequest().getRemoteHost());
            log.debug(rs.toString());
            log.debug("Request dataset: '" + rs.getDataSet() + "' suffix: '" + rs.getRequestSuffix() +
                    "' CE: '" + rs.getConstraintExpression() + "'");
          }
        }
      } // synch

      if (rs != null) {
        String dataSet = rs.getDataSet();
        String requestSuffix = rs.getRequestSuffix();

        if (dataSet == null) {
          doGetDIR(rs);
        } else if (dataSet.equals("/")) {
          doGetDIR(rs);
        } else if (dataSet.equals("")) {
          doGetDIR(rs);
        } else if (dataSet.equalsIgnoreCase("/version") || dataSet.equalsIgnoreCase("/version/")) {
          doGetVER(rs);
        } else if (dataSet.equalsIgnoreCase("/help") || dataSet.equalsIgnoreCase("/help/")) {
          doGetHELP(rs);
        } else if (dataSet.equalsIgnoreCase("/" + requestSuffix)) {
          doGetHELP(rs);
        } else if (requestSuffix.equalsIgnoreCase("dds")) {
          doGetDDS(rs);
        } else if (requestSuffix.equalsIgnoreCase("das")) {
          doGetDAS(rs);
        } else if (requestSuffix.equalsIgnoreCase("ddx")) {
          doGetDDX(rs);
        } else if (requestSuffix.equalsIgnoreCase("blob")) {
          doGetBLOB(rs);
        } else if (requestSuffix.equalsIgnoreCase("dods")) {
          doGetDAP2Data(rs);
        } else if (requestSuffix.equalsIgnoreCase("asc") ||
                requestSuffix.equalsIgnoreCase("ascii")) {
          doGetASC(rs);
        } else if (requestSuffix.equalsIgnoreCase("info")) {
          doGetINFO(rs);
        } else if (requestSuffix.equalsIgnoreCase("html") || requestSuffix.equalsIgnoreCase("htm")) {
          doGetHTML(rs);
        } else if (requestSuffix.equalsIgnoreCase("ver") || requestSuffix.equalsIgnoreCase("version")) {
          doGetVER(rs);
        } else if (requestSuffix.equalsIgnoreCase("help")) {
          doGetHELP(rs);

        /* JC added
        } else if (dataSet.equalsIgnoreCase("catalog") && requestSuffix.equalsIgnoreCase("xml")) {
          doGetCatalog(rs);
        } else if (dataSet.equalsIgnoreCase("status")) {
          doGetStatus(rs);
        } else if (dataSet.equalsIgnoreCase("systemproperties")) {
          doGetSystemProps(rs);
        } else if (isDebug) {
          doDebug(rs);  */
        } else if (requestSuffix.equals("")) {
          badURL(rs);
        } else {
          badURL(rs);
        }
      } else {
        badURL(rs);
      }

      if (reqD != null) reqD.done = true;
    } catch (Throwable e) {
      anyExceptionHandler(e, rs);
    }

  }

  /**
   * @param request
   * @return the request state
   */
  protected ReqState
  getRequestState(HttpServletRequest request, HttpServletResponse response)
    throws DAP2Exception
  {
    ReqState rs = null;
    // The url and query strings will come to us in encoded form
    // (see HTTPmethod.newMethod())
    String baseurl = request.getRequestURL().toString();
    baseurl = EscapeStrings.unescapeURL(baseurl);

    String query = request.getQueryString();
    query = EscapeStrings.unescapeURLQuery(query);

    rs = new ReqState(this, request, response, rootpath, baseurl, query);
    return rs;
  }
  //**************************************************************************

  /**
   * ************************************************************************
   * Prints the OPeNDAP Server help page to the passed PrintWriter
   *
   * @param pw PrintWriter stream to which to dump the help page.
   */
  private void printHelpPage(PrintWriter pw) {

    pw.println("
OPeNDAP Server Help
");
    pw.println("To access most of the features of this OPeNDAP server, append");
    pw.println("one of the following a eight suffixes to a URL: .das, .dds, .dods, .ddx, .blob, .info,");
    pw.println(".ver or .help. Using these suffixes, you can ask this server for:");
    pw.println("
");
    pw.println("
 das

 Dataset Attribute Structure (DAS)
");
    pw.println("
 dds

 Dataset Descriptor Structure (DDS)
");
    pw.println("
 dods

 DataDDS object (A constrained DDS populated with data)
");
    pw.println("
 ddx

 XML version of the DDS/DAS
");
    pw.println("
 blob

 Serialized binary data content for requested data set, " +
            "with the constraint expression applied.
");
    pw.println("
 info

 info object (attributes, types and other information)
");
    pw.println("
 html

 html form for this dataset
");
    pw.println("
 ver

 return the version number of the server
");
    pw.println("
 help

 help information (this text)
");
    pw.println("
");
    pw.println("For example, to request the DAS object from the FNOC1 dataset at URI/GSO (a");
    pw.println("test dataset) you would appand `.das' to the URL:");
    pw.println("http://opendap.gso.url.edu/cgi-bin/nph-nc/data/fnoc1.nc.das.");

    pw.println("
Note: Many OPeNDAP clients supply these extensions for you so you don't");
    pw.println("need to append them (for example when using interfaces supplied by us or");
    pw.println("software re-linked with a OPeNDAP client-library). Generally, you only need to");
    pw.println("add these if you are typing a URL directly into a WWW browser.");
    pw.println("
Note: If you would like version information for this server but");
    pw.println("don't know a specific data file or data set name, use `/version' for the");
    pw.println("filename. For example: http://opendap.gso.url.edu/cgi-bin/nph-nc/version will");
    pw.println("return the version number for the netCDF server used in the first example. ");

    pw.println("
Suggestion: If you're typing this URL into a WWW browser and");
    pw.println("would like information about the dataset, use the `.info' extension.");

    pw.println("
If you'd like to see a data values, use the `.html' extension and submit a");
    pw.println("query using the customized form.");

  }
  //**************************************************************************

  /**
   * ************************************************************************
   * Prints the Bad URL Page page to the passed PrintWriter
   *
   * @param pw PrintWriter stream to which to dump the bad URL page.
   */
  private void printBadURLPage(PrintWriter pw) {

    pw.println("
Error in URL
");
    pw.println("The URL extension did not match any that are known by this");
    pw.println("server. Below is a list of the five extensions that are be recognized by");
    pw.println("all OPeNDAP servers. If you think that the server is broken (that the URL you");
    pw.println("submitted should have worked), then please contact the");
    pw.println("OPeNDAP user support coordinator at: ");
    pw.println("support@unidata.ucar.edu
");

  }
  //**************************************************************************

  // debug
  private ArrayList prArr = null;

  static private class RequestDebug {
    long reqno;
    String threadDesc;
    boolean done = false;

    RequestDebug(long reqno, String threadDesc) {
      this.reqno = reqno;
      this.threadDesc = threadDesc;
    }
  }
}


/////////////////////////////////////////////////////////////////////////////
// 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 String value.
 *
 * @author ndp
 * @version $Revision: 15901 $
 * @see BaseType
 */
public abstract class SDString extends DString implements
        ServerMethods,
        RelOps,
        ExprParserConstants {

    private static final boolean _Debug = false;
    private boolean Synthesized;
    private boolean ReadMe;

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

    }

    /**
     * Constructs a new SDString with name n.
     *
     * @param n the name of the variable.
     */
    public SDString(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) {

        // 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!

        // System.out.println("SDString.printXML(pw,pad,"+constrained+")  Project: "+Project);

        if (constrained && !isProject())
            return;

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

}


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

package ucar.nc2.iosp.grads;

/**
 * Utility class for GrADS stuff
 *
 * @author Don Murray CU/CIRES
 */
public class GradsUtil {

    /** GAUSR15 identifier */
    public static final String GAUSR15 = "GAUSR15";

    /** GAUSR20 identifier */
    public static final String GAUSR20 = "GAUSR20";

    /** GAUSR30 identifier */
    public static final String GAUSR30 = "GAUSR30";

    /** GAUSR40 identifier */
    public static final String GAUSR40 = "GAUSR40";

    /** GAUST62 identifier */
    public static final String GAUST62 = "GAUST62";

    //J-

    /** GAUSR15 latitudes */
    private static double glts15[] = {
           -86.60,-82.19,-77.76,-73.32,-68.88,-64.43,-59.99,
           -55.55,-51.11,-46.66,-42.22,-37.77,-33.33,-28.89,
           -24.44,-20.00,-15.55,-11.11, -6.67, -2.22,  2.22,
             6.67, 11.11, 15.55, 20.00, 24.44, 28.89, 33.33,
            37.77, 42.22, 46.66, 51.11, 55.55, 59.99, 64.43,
            68.88, 73.32, 77.76, 82.19, 86.60};

    /** GAUSR20 latitudes */
    private static double glts20[] = {
           -87.38,-83.98,-80.56,-77.13,-73.71,-70.28,-66.85,
           -63.42,-59.99,-56.57,-53.14,-49.71,-46.28,-42.85,
           -39.43,-36.00,-32.57,-29.14,-25.71,-22.28,-18.86,
           -15.43,-12.00, -8.57, -5.14, -1.71,  1.71,  5.14,
             8.57, 12.00, 15.43, 18.86, 22.28, 25.71, 29.14,
            32.57, 36.00, 39.43, 42.85, 46.28, 49.71, 53.14,
            56.57, 59.99, 63.42, 66.85, 70.28, 73.71, 77.13,
            80.56, 83.98, 87.38};

    /** GAUSR30 latitudes */
    private static double glts30[] = {
           -88.29, -86.07, -83.84, -81.61, -79.37, -77.14, -74.90,
           -72.67, -70.43, -68.20, -65.96, -63.72, -61.49, -59.25,
           -57.02, -54.78, -52.55, -50.31, -48.07, -45.84, -43.60,
           -41.37, -39.13, -36.89, -34.66, -32.42, -30.19, -27.95,
           -25.71, -23.48, -21.24, -19.01, -16.77, -14.53, -12.30,
           -10.06,  -7.83,  -5.59,  -3.35,  -1.12,   1.12,   3.35,
             5.59,   7.83,  10.06,  12.30,  14.53,  16.77,  19.01,
            21.24,  23.48,  25.71,  27.95,  30.19,  32.42,  34.66,
            36.89,  39.13,  41.37,  43.60,  45.84,  48.07,  50.31,
            52.55,  54.78,  57.02,  59.25,  61.49,  63.72,  65.96,
            68.20,  70.43,  72.67,  74.90,  77.14,  79.37,  81.61,
            83.84,  86.07,  88.29};

    /** GAUSR40 latitudes */
    private static double glats[] = {
         -88.66,-86.91,-85.16,-83.41,-81.65,-79.90,-78.14,-76.39,-74.63,
         -72.88,-71.12,-69.36,-67.61,-65.85,-64.10,-62.34,-60.58,-58.83,
         -57.07,-55.32,-53.56,-51.80,-50.05,-48.29,-46.54,-44.78,-43.02,
         -41.27,-39.51,-37.76,-36.00,-34.24,-32.49,-30.73,-28.98,-27.22,
         -25.46,-23.71,-21.95,-20.19,-18.44,-16.68,-14.93,-13.17,-11.41,
          -9.66, -7.90, -6.15, -4.39, -2.63, -0.88,  0.88,  2.63,  4.39,
           6.15,  7.90,  9.66, 11.41, 13.17, 14.93, 16.68, 18.44, 20.19,
          21.95, 23.71, 25.46, 27.22, 28.98, 30.73, 32.49, 34.24, 36.00,
          37.76, 39.51, 41.27, 43.02, 44.78, 46.54, 48.29, 50.05, 51.80,
          53.56, 55.32, 57.07, 58.83, 60.58, 62.34, 64.10, 65.85, 67.61,
          69.36, 71.12, 72.88, 74.63, 76.39, 78.14, 79.90, 81.65, 83.41,
          85.16, 86.91, 88.66 };

    /** GAUSt62 latitudes */
    private static double gltst62[] = {
            -88.542, -86.6531, -84.7532, -82.8508, -80.9473, -79.0435,
            -77.1394, -75.2351, -73.3307, -71.4262, -69.5217, -67.6171,
            -65.7125, -63.8079, -61.9033, -59.9986, -58.0939, -56.1893,
            -54.2846, -52.3799, -50.4752, -48.5705, -46.6658, -44.7611,
            -42.8564, -40.9517, -39.047, -37.1422, -35.2375, -33.3328,
            -31.4281, -29.5234, -27.6186, -25.7139, -23.8092, -21.9044,
            -19.9997, -18.095, -16.1902, -14.2855, -12.3808, -10.47604,
            -8.57131, -6.66657, -4.76184, -2.8571, -0.952368, 0.952368,
            2.8571, 4.76184, 6.66657, 8.57131, 10.47604, 12.3808, 14.2855,
            16.1902, 18.095, 19.9997, 21.9044, 23.8092, 25.7139, 27.6186,
            29.5234, 31.4281, 33.3328, 35.2375, 37.1422, 39.047, 40.9517,
            42.8564, 44.7611, 46.6658, 48.5705, 50.4752, 52.3799, 54.2846,
            56.1893, 58.0939, 59.9986, 61.9033, 63.8079, 65.7125, 67.6171,
            69.5217, 71.4262, 73.3307, 75.2351, 77.1394, 79.0435, 80.9473,
            82.8508, 84.7532, 86.6531, 88.542 };
//J+

    /**
     * Get the latitude values for the given type.
     *
     * @param type gaussian type
     * @param start  starting index (1 based)
     * @param num  number of values
     *
     * @return the values
     *
     * @throws IllegalArgumentException  invalid or unsupported type
     */
    public static double[] getGaussianLatitudes(String type, int start,
            int num)
            throws IllegalArgumentException {
        double[] baseArray = null;
        start--;  // it's one based
        if (type.equalsIgnoreCase(GAUST62)) {
            baseArray = gltst62;
        } else if (type.equalsIgnoreCase(GAUSR15)) {
            baseArray = glts15;
        } else if (type.equalsIgnoreCase(GAUSR20)) {
            baseArray = glts20;
        } else if (type.equalsIgnoreCase(GAUSR30)) {
            baseArray = glts30;
        } else if (type.equalsIgnoreCase(GAUSR40)) {
            baseArray = glats;
        } else {
            throw new IllegalArgumentException("Unsupported type: " + type);
        }
        if (start + num > baseArray.length) {
            throw new IllegalArgumentException("Maximum " + baseArray.length
                    + " latitudes exceeded");
        }
        double[] retVals = new double[num];
        for (int i = 0; i < num; i++) {
            retVals[i] = baseArray[start + i];
        }
        return retVals;
    }

}