// $Id: NetcdfDataset.java,v 1.21 2004/12/10 17:04:16 caron Exp $
/*
 * Copyright 1997-2000 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * support@unidata.ucar.edu.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package ucar.nc2.dataset;

import ucar.ma2.*;
import ucar.nc2.*;
import ucar.nc2.ncml.NcMLReader;
import ucar.nc2.ncml.NcMLWriter;
import ucar.nc2.util.CancelTask;

// factories for remote access
import ucar.nc2.adde.AddeDatasetFactory;
import ucar.nc2.dods.DODSNetcdfFile;
import ucar.nc2.thredds.ThreddsDataFactory;

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

/**
 * NetcdfDataset extends the netCDF API, adding standard attribute parsing such as
 *  scale and offset, and explicit support for Coordinate Systems.
 * A NetcdfDataset either wraps a NetcdfFile, or is defined by an NcML document.
 *
 * @see ucar.nc2.NetcdfFile
 * @author caron
 * @version $Revision: 1.21 $ $Date: 2004/12/10 17:04:16 $
 */

public class NetcdfDataset extends ucar.nc2.NetcdfFile {
  static protected boolean useNaNs = true;
  static protected boolean fillValueIsMissing = true, invalidDataIsMissing = true, missingDataIsMissing = true;

  // modes
  /** Set whether to use NaNs for missing values, for efficiency */
  static public void setUseNaNs(boolean b) { useNaNs = b; }
  static public boolean getUseNaNs() { return useNaNs; }
  /** Set if _FillValue attribute is considered isMissing() */
  static public void setFillValueIsMissing( boolean b) { fillValueIsMissing = b; }
  static public boolean getFillValueIsMissing() { return fillValueIsMissing; }
  /** Set if valid_range attribute is considered isMissing() */
  static public void setInvalidDataIsMissing( boolean b) { invalidDataIsMissing = b; }
  static public boolean getInvalidDataIsMissing() { return invalidDataIsMissing; }
  /** Set if missing_data attribute is considered isMissing() */
  static public void setMissingDataIsMissing( boolean b) { missingDataIsMissing = b; }
  static public boolean getMissingDataIsMissing() { return missingDataIsMissing; }

  /**
   * Factory method for opening a dataset through the netCDF API, and identifying
   * its coordinate variables.
   *
   * @param location location of file. This may be a
   * <ol>
   *  <li>local filename (with a file: prefix or no prefix) for netCDF (version 3) or hdf5 files.
   *  <li>OpenDAP dataset URL (with a dods: or http: prefix).
   *  <li>NcML URL if the location ends with ".xml".
   * </ol>
   * @return NetcdfDataset object, with coordinate variables added.
   * @throws IOException
   *
   * @see ucar.nc2.util.CancelTask
   */
  public static NetcdfDataset openDataset(String location) throws IOException {
    return openDataset( location, true, null);
  }

  /**
   * Factory method for opening a dataset through the netCDF API, with option to add Coordinate Systems.
   *
   * <p> The standard attribute conventions are used to turn the original Variables into
   * VariableEnhanced. Coordinates system information is optionally added if addCoordinates is true,
   * by using convention parsing classes.
   *
   * @param location location of file. This may be a
   * <ol>
   *  <li>local filename (with a file: prefix or no prefix) for netCDF (version 3) or hdf5 files.
   *  <li>OpenDAP dataset URL (with a dods: or http: prefix).
   *  <li>NcML URL if the location ends with ".xml".
   * </ol>
   * @param addCoordinates if true, parse and add coordinate variables.
   * @param cancelTask use to allow task to be cancelled; may be null.
   * @return NetcdfFile object
   * @throws IOException
   *
   * @see ucar.nc2.util.CancelTask
   */
  public static NetcdfDataset openDataset(String location, boolean addCoordinates, ucar.nc2.util.CancelTask cancelTask) throws IOException {

    NetcdfFile ncfile = openFile( location, cancelTask);
    NetcdfDataset ds = (ncfile instanceof NetcdfDataset) ? (NetcdfDataset) ncfile : new NetcdfDataset( ncfile);
    if (addCoordinates) {
      ucar.nc2.dataset.CoordSysBuilder.addCoordinateSystems( ds, cancelTask);

      ds.finish(); // recalc the global lists
      // ds.enhance(); // recalc any enhancement info NOT NEEDED ?
    }

    return ds;
  }

  public static NetcdfDataset openDataset(String location, boolean addCoordinates,
       ucar.nc2.util.CancelTask cancelTask, boolean nc3UseRecords) throws IOException {

    boolean saveMode = NetcdfFile.getUseRecordStructure();
    NetcdfFile.setUseRecordStructure( nc3UseRecords);
    NetcdfDataset ds = openDataset( location, addCoordinates, cancelTask);
    NetcdfFile.setUseRecordStructure( saveMode);
    return ds;
  }

  /**
   * Factory method for opening a NetcdfFile through the netCDF API. May be any kind of file that
   *  can be read through the netCDF API.
   *
   * <p> This does not necessarily turn it into a NetcdfDataset (it may), use NetcdfDataset.open()
   * method for that. It definitely does not add coordinate systems
   *
   * @param location location of file. This may be a
   * <ol>
   *  <li>local filename (with a file: prefix or no prefix) for netCDF (version 3), hdf5 files, or any file type
   *   registered with NetcdfFile.register().
   *  <li>OpenDAP dataset URL (with a dods: or http: prefix).
   *  <li>ADDE dataset URL (with a adde: prefix).
   *  <li>Thredds dataset URL (with a thredds: prefix).
   *  <li>NcML URL if the location ends with ".xml".
   * </ol>
   * @param cancelTask use to allow task to be cancelled; may be null.
   * @return NetcdfFile object
   * @throws IOException
   *
   * @see ucar.nc2.util.CancelTask
   */
  public static NetcdfFile openFile(String location, ucar.nc2.util.CancelTask cancelTask) throws IOException {

    if (location == null)
      throw new IOException( "NetcdfDataset.openFile: location is null");

    NetcdfFile ncfile = null;
    location = location.trim();

    if (location.startsWith("thredds:")) {
      // open a thredds dataset in a catalog, returns a NetcdfDataset
      ThreddsDataFactory factory = new ThreddsDataFactory();
      ncfile = factory.openDataset( location, cancelTask);
      if (ncfile == null) {
        throw new IOException(factory.getErrorMessages());
      }

    } else if (location.startsWith("dods:")) {
      // open through DODS
      ncfile = tryDODS( location, cancelTask);

     } else if (location.startsWith("adde:")) {
      // open athrough ADDE
      ncfile = AddeDatasetFactory.openDataset( location, cancelTask);

    } else if (location.endsWith(".xml") || location.endsWith(".ncml")) { //open as a NetcdfDataset through NcML
      if (!location.startsWith("http:") && !location.startsWith("file:"))
        location = "file:" + location;

      NcMLReader reader = new NcMLReader();
      return reader.readNcML (location, cancelTask);

    } else if (location.startsWith("http:")) {
      try {
        ncfile = tryDODS( location, cancelTask); // try as a dods file

      } catch(FileNotFoundException ioe) {
        FileNotFoundException dodsException = ioe;
        try {
          ncfile = NetcdfFile.open(location, cancelTask); // try as an http netcdf3 file
        } catch (IOException e) {
          throw dodsException;
        }
      }

    } else {

      // try it as a NetcdfFile; this handles various local file formats
      ncfile = NetcdfFile.open(location, cancelTask);
    }

    return ncfile;
  }

  // // try to open as a dods file
  static private NetcdfFile tryDODS(String location, ucar.nc2.util.CancelTask cancelTask) throws IOException {
    String dodsUri = null;
    try {
      dodsUri = DODSNetcdfFile.canonicalURL(location);
      return new DODSNetcdfFile(dodsUri, cancelTask);
    } catch (IOException e) {
      throw new FileNotFoundException( "Cant open "+ location+" or as DODS "+dodsUri+"\n"+e.getMessage());
    } catch (dods.dap.DODSException e) {
      throw new FileNotFoundException( "Cant open "+ location+" or as DODS "+dodsUri+"\n"+e.getMessage());
    }
  }

  /**
   * Retrieve a "standard" Variable with the specified name, that handles scale, offset, etc.
   * This is for backwards compatibility with 2.1,
   * used in ucar.unidata.geoloc.vertical
   * @param name String which identifies the desired variable
   * @return the VariableStandardized, or null if not found
   */
  public Variable findStandardVariable(String name) {
    return findVariable(name); // all VariableDS handle scale/offset
  }

  ////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////
  private NetcdfFile orgFile = null;

  private ArrayList coordSys = new ArrayList(); // type CoordinateSystem
  private ArrayList coordAxes = new ArrayList(); // type CoordinateSystem
  private ArrayList coordTransforms = new ArrayList(); // type CoordinateTransform
  private boolean coordSysWereAdded = false;

  // NcML stuff
  /* private boolean useReferencedDataset = false;
  private NetcdfDataset referencedDataset = null; */
  private String referencedDatasetUri; // uri of underlying netCDF file, if any
  private ucar.nc2.ncml.Aggregation agg = null; // need to close underlying files

  // used only if this is part of an aggregation
  //private String aggDimName, aggDimValue;
  //private ArrayList nestedDatasets = new ArrayList();
  //public ArrayList getNestedDatasets() { return nestedDatasets; }
  public void setAggregation( ucar.nc2.ncml.Aggregation agg) {this.agg = agg; }


   /**
    * Create a NetcdfDataset using an NcML XML doc.
    * @param ncmlLocation location of the NcML document.
    * @throws IOException
    * @throws MalformedURLException
    *
  public NetcdfDataset(String ncmlLocation) throws IOException, java.net.MalformedURLException {
    this( ncmlLocation, null);
  }

   /**
    * Create a NetcdfDataset using an NcML XML doc.
    * @param ncmlLocation location of the NcML document.
    * @param cancelTask use to allow task to be cancelled; may be null.
    * @throws IOException
    * @throws MalformedURLException
    *
  public NetcdfDataset(String ncmlLocation, ucar.nc2.util.CancelTask cancelTask) throws IOException, java.net.MalformedURLException {
    this.location = ncmlLocation;
    isNcML = true;
    new NcMLReader().readXML( this, ncmlLocation, cancelTask);
  }

   /**
    * Create a NetcdfDataset using an NcML XML doc, substitute the underlying uriString. Experimental API
    *  for adding metadata from THREDDS catalogs.
    * @param location location of the NcML document.
    * @param referencedDatasetUri NcML refers to this underlying dataset
    * @throws IOException
    * @throws MalformedURLException
    *
  public NetcdfDataset(String location, String referencedDatasetUri) throws IOException, java.net.MalformedURLException {
    this.location = location;
    new NcMLReader().readXML( this, location, referencedDatasetUri);
  }

   /**
    * Create a NetcdfDataset by reading the NcML XML doc from the input stream.
    * @param location location of the NcML document.
    * @param ins read NcML from here
    * @throws IOException
    * @throws MalformedURLException
    *
  public NetcdfDataset(String location, InputStream ins) throws IOException {
    new NcMLReader().readXML( this, ins);
    this.location = location;
  } */

  /**
   * Get the list of all CoordinateSystem objects used by this dataset.
   * @return list of type CoordinateSystem; may be empty, not null.
   */
  public List getCoordinateSystems() { return coordSys; }

  /**
   * Get the list of all CoordinateTransform objects used by this dataset.
   * @return list of type CoordinateTransform; may be empty, not null.
   */
  public List getCoordinateTransforms() { return coordTransforms; }

  /**
   * Get the list of all CoordinateAxis objects used by this dataset.
   * @return list of type CoordinateAxis; may be empty, not null.
   */
  public List getCoordinateAxes() { return coordAxes; }

  /** Has Coordinate System metadata been added. */
  public boolean getCoordSysWereAdded() {
    return coordSysWereAdded;
  }

  /** Set whether Coordinate System metadata has been added. */
  public void setCoordSysWereAdded(boolean coordSysWereAdded) {
    this.coordSysWereAdded = coordSysWereAdded;
  }

  /**
   * Retrieve the CoordinateAxis with the specified name.
   * @param fullName full name of the coordinate axis
   * @return the CoordinateAxis, or null if not found
   */
  public CoordinateAxis findCoordinateAxis(String fullName) {
    if (fullName == null) return null;
    for (int i=0; i<coordAxes.size(); i++) {
      CoordinateAxis v = (CoordinateAxis) coordAxes.get(i);
      if (fullName.equals(v.getName()))
        return v;
    }
    return null;
  }

  /**
   * Retrieve the CoordinateSystem with the specified name.
   * @param name String which identifies the desired CoordinateSystem
   * @return the CoordinateSystem, or null if not found
   */
  public CoordinateSystem findCoordinateSystem(String name) {
    if (name == null) return null;
    for (int i=0; i<coordSys.size(); i++) {
      CoordinateSystem v = (CoordinateSystem) coordSys.get(i);
      if (name.equals(v.getName()))
        return v;
    }
    return null;
  }

  /**
   * Retrieve the CoordinateTransform with the specified name.
   * @param name String which identifies the desired CoordinateSystem
   * @return the CoordinateSystem, or null if not found
   */
  public CoordinateTransform findCoordinateTransform(String name) {
    if (name == null) return null;
    for (int i=0; i<coordTransforms.size(); i++) {
      CoordinateTransform v = (CoordinateTransform) coordTransforms.get(i);
      if (name.equals(v.getName()))
        return v;
    }
    return null;
  }

  /** Close all resources (files, sockets, etc) associated with this dataset. */
  public synchronized void close() throws java.io.IOException {
    if (orgFile != null) orgFile.close();
    orgFile = null;

    if (agg != null)
      agg.close();

    isClosed = true;
  }

  /**
   * Write the NcML representation.
   * @param os write to this Output Stream.
   * @param uri use this for the uri attribute; if null use getLocation().
   * @throws IOException
   */
  public void writeNcML( java.io.OutputStream os, String uri) throws IOException {
    new NcMLWriter().writeXML( this, os, uri);
  }

  //////////////////////////////////////////////////////////////////////////////////////////////////
  /**
   * Transform a NetcdfFile into a NetcdfDataset.
   * You must not use the underlying NetcdfFile after this call.
   * @param ncfile NetcdfFile to transform.
   */
  public NetcdfDataset( NetcdfFile ncfile) {
    super( ncfile);
    this.orgFile = ncfile; // track it so we can close it
    convertVariables( getRootGroup());
    finish(); // rebuild global lists
  }

  private void convertVariables( Group g) {
    Variable newVar;
    ArrayList newList = new ArrayList();
    for (Iterator iter = g.getVariables().iterator(); iter.hasNext(); ) {
      Variable v = (Variable) iter.next();
      if (v instanceof Structure) {
        newVar = new StructureDS( (Structure) v, false);
        convertStructure( (Structure) newVar);
      } else
        newVar = new VariableDS( v);

      newList.add( newVar);
    }
    replaceGroupVariables(g, newList);

    // nested groups
    for (Iterator iter = g.getGroups().iterator(); iter.hasNext(); ) {
      Group item = (Group)iter.next();
      convertVariables( item);
    }
  }

  private void convertStructure( Structure newStructure) {
    Variable newVar;
    ArrayList newList = new ArrayList();
    for (Iterator iter = newStructure.getVariables().iterator(); iter.hasNext(); ) {
      Variable v = (Variable) iter.next();

      if (v instanceof Structure) {
        newVar = new StructureDS((Structure) v, false);
        convertStructure( (Structure) newVar);
     }
      else
        newVar = new VariableDS( v);

      newList.add( newVar);
    }

    replaceStructureMembers(newStructure, newList);
  }



  /** CDL-formatted string representation
  public String toString() {
    ByteArrayOutputStream ba = new ByteArrayOutputStream(1000);
    PrintStream out = new PrintStream( ba);
    toStringStart(out);

    out.print(" data:\n");

    // loop over variables
    Iterator iterVar = getVariables().iterator();
    while (iterVar.hasNext()) {
      Variable v = (Variable) iterVar.next();
      if (!(v instanceof Variable)) continue;
      Variable vds = (Variable) v;
      if (!vds.isMetadata()) continue;
      // only the ones with metadata

      out.print("    "+v.getName()+" = ");
      if (vds.isRegular()) {
        out.print(vds.getStart()+" start  "+vds.getIncrement()+" increment  "+vds.getSize()+" npts");
      } else {
        Array a = null;
        try {
          a = v.read();
        } catch (IOException ioe) {
          continue;
        }

        if (a instanceof ArrayChar) {
           //strings
          ArrayChar dataC = (ArrayChar) a;
          out.print( "\""+dataC.getString(0)+"\"");
          for (int i=1; i<dataC.getShape()[0]; i++) {
            out.print(", ");
            out.print( "\""+dataC.getString(i)+"\"");
          }
        } else {
           // numbers
          boolean isRealType = (v.getElementType() == double.class) || (v.getElementType() == float.class);
          IndexIterator iter = a.getIndexIterator();
          out.print(isRealType ? iter.getDoubleNext() : iter.getIntNext());
          while (iter.hasNext()) {
            out.print(", ");
            out.print(isRealType ? iter.getDoubleNext() : iter.getIntNext());
          }
        }
      }
      out.print(";\n");
    }

    toStringEnd(out);
    out.flush();
    return ba.toString();
  } */

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

  /* protected void writeNcMLVariable( Variable v, PrintStream out, IndentLevel indent, boolean showCoords) throws IOException {
    String elemName = (v instanceof CoordinateAxis) ? "coordinateAxis" : "variable";

    out.print(indent);
    out.print("<"+elemName+" name='"+quote(v.getShortName())+"' type='"+ v.getDataType()+"'");

    // any dimensions (scalers must skip this attribute) ?
    if (v.getRank() > 0) {
      writeNcMLDimension( v, out);
    }

    // any coordinate systems ?
    java.util.List coordSys = v.getCoordinateSystems();
      if (coordSys.size() > 0) {
        out.print("' coordSys='");

        for (int i=0; i<coordSys.size(); i++) {
          CoordinateAxis axis = (CoordinateAxis) coordSys.get(i);
          if (i > 0) out.print(" ");
          out.print(axis.getName());
        }
        out.print("'");
      }

      indent.incr();

      boolean closed = false;

      // any attributes ?
      java.util.List atts = v.getAttributes();
      if (atts.size() > 0) {
        if (!closed) {
           out.print(" >\n");
           closed = true;
        }
        Iterator iter = atts.iterator();
        while (iter.hasNext()) {
          Attribute att = (Attribute) iter.next();
          writeNcMLAtt(att, out, indent);
        }
      }

      // print data ?
      if ((showCoords && v.isCoordinateVariable())) {
        if (!closed) {
           out.print(" >\n");
           closed = true;
        }
        writeNcMLValues(v, out, indent);
      }

      indent.decr();

      // close variable element
      if (!closed)
        out.print(" />\n");
      else {
        out.print(indent);
        out.print("</"+elemName+">\n");
      }

  } */


  /** Sort Variables, CoordAxes by name. */
  public void sort() {
    Collections.sort(variables, new VariableComparator());
    Collections.sort(coordAxes, new VariableComparator());
  }

  // sort by coord sys, then name
  private class VariableComparator implements java.util.Comparator {
    public int compare(Object o1, Object o2) {
      VariableEnhanced v1 = (VariableEnhanced) o1;
      VariableEnhanced v2 = (VariableEnhanced) o2;

      List list1 = v1.getCoordinateSystems();
      String cs1 = (list1.size() > 0) ? ((CoordinateSystem) list1.get(0)).getName() : "";
      List list2 = v2.getCoordinateSystems();
      String cs2 = (list2.size() > 0) ? ((CoordinateSystem) list2.get(0)).getName() : "";

      if (cs2.equals( cs1))
        return v1.getName().compareToIgnoreCase( v2.getName());
      else
        return cs1.compareToIgnoreCase( cs2);
    }
    public boolean equals(Object obj) {
      return (this == obj);
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  // used by NcMLReader

  public NetcdfDataset() {}

  public String getReferencedDatasetUri( ) { return referencedDatasetUri; }
  public void setReferencedDatasetUri( String referencedDatasetUri) {
    this.referencedDatasetUri = referencedDatasetUri;
  }
  /* void setReferencedDataset( NetcdfDataset refds) {
    this.referencedDataset = refds;
    this.useReferencedDataset = true;
  } */

  //String getAggDimensionName( ) { return aggDimName; }
  //void setAggDimensionName( String aggDimName) { this.aggDimName = aggDimName; }
  //String getAggDimensionValue( ) { return aggDimValue; }
  //void setAggDimensionValue( String aggDimValue) { this.aggDimValue = aggDimValue; }

  /* NetcdfDataset openReferencedDataset(CancelTask cancelTask) throws IOException, java.net.MalformedURLException {
    if (referencedDataset == null)
      referencedDataset = NetcdfDataset.openDataset( referencedDatasetUri, false, cancelTask);
    return referencedDataset;
  }

  Variable findReferencedVariable(VariableDS vds) {
    if (vds.referencedVariable == null)
      vds.referencedVariable = referencedDataset.findVariable( vds.getName());
    if (vds.referencedVariable == null)
      throw new IllegalStateException("NcML referenced Variable is Missing="+vds.getName());
    return vds.referencedVariable;
  }

  // if NcML, send I/O to referencedDataset, else to superclass
  public Array readData(ucar.nc2.Variable v, List section) throws IOException, InvalidRangeException  {
    if (useReferencedDataset) {
      openReferencedDataset(null);
      Variable referV = findReferencedVariable((VariableDS) v);
      return referencedDataset.readData( referV, section);
    } else {
      return orgFile.readData(v, section);
    }
  }

    public Array readMemberData(ucar.nc2.Variable v, List section, boolean flatten) throws IOException, InvalidRangeException  {
    if (useReferencedDataset) {
      openReferencedDataset(null);
      Variable referV = findReferencedVariable((VariableDS) v);
      return referencedDataset.readMemberData(referV, section, flatten);
    } else {
      return orgFile.readMemberData(v, section, flatten);
    }
  } */

  public Array readMemberData(ucar.nc2.Variable v, List section, boolean flatten) throws IOException, InvalidRangeException  {
    return orgFile.readMemberData(v, section, flatten);
  }

    // if NcML, send I/O to referencedDataset, else to superclass
  public Array readData(ucar.nc2.Variable v, List section) throws IOException, InvalidRangeException  {
    return orgFile.readData(v, section);
  }


  protected String toStringDebug(Object o) {
   return "";
  }

  ///////////////////////////////////////////////////////////////////////////////////
  // constructor methods

  /** Add a CoordinateSystem to the dataset. */
  public void addCoordinateSystem( CoordinateSystem cs){
    coordSys.add(cs);
  }

  /** Add a CoordinateTransform to the dataset. */
  public void addCoordinateTransform( CoordinateTransform ct){
    coordTransforms.add(ct);
  }

   /** Add a CoordinateSystem to a variable. */
  public void addCoordinateSystem( VariableEnhanced v, CoordinateSystem cs){
    v.addCoordinateSystem( cs);
  }

  /** Add a CoordinateAxis. Also adds it as a variable. Remove any previous with the same name. */
  public CoordinateAxis addCoordinateAxis( VariableDS v) {
    if (v == null) return null;
    CoordinateAxis oldVar = findCoordinateAxis( v.getName());
    coordAxes.remove( oldVar);
    removeVariable( v.getParentGroup(), v.getShortName()); // remove by hashCode if it exists

    CoordinateAxis ca = (v instanceof CoordinateAxis) ? (CoordinateAxis) v : CoordinateAxis.factory(this, v);
    addVariable( ca.getParentGroup(), ca);
    coordAxes.add( ca);
    return ca;
  }

  /** Replace a Dimension in a Variable.
   * @param v replace in this Variable.
   * @param d replace existing dimension of the same name.
   */
  protected void replaceDimension(Variable v, Dimension d) {
    super.replaceDimension(v, d);
  }

  /** Replace the group's list of variables. For copy construction. */
  protected void replaceGroupVariables(Group g, ArrayList vlist) {
    super.replaceGroupVariables(g, vlist);
  }

  /** Replace the group's list of variables. For copy construction. */
  protected void replaceStructureMembers(Structure s, ArrayList vlist) {
    super.replaceStructureMembers(s, vlist);
  }

  // recalc any enhancement info NOT NEEDED ?
  protected void enhance() {
    List varList = getVariables();
    for (int i=0; i<varList.size(); i++) {
      VariableDS v = (VariableDS) varList.get(i);
      v.enhance();
    }
  }

  ///////////////////////////////////////////////////////////////////////
  // setting variable data values

  /**
   * Generate the list of values from a starting value and an increment.
   * @param npts number of values
   * @param start starting value
   * @param incr increment
   */
  public void setValues( Variable v, int npts, double start, double incr) {
    v.setCachedData( makeArray( v.getDataType(), npts, start, incr), true);
  }

  /**
   * Set the data values from a list of Strings.
   * @param values list of Strings
   * @throws NumberFormatException
   */
  public void setValues( Variable v, ArrayList values) throws NumberFormatException {
    Array data = makeArray( v.getDataType(), values);

    if (v.getRank() != 1)
      data = data.reshape( v.getShape());

    v.setCachedData( data, true);
  }

  /**
   * Make an 1D array from a list of strings.
   * @param dtype data type of the array.
   * @param stringValues list of strings.
   * @return resulting 1D array.
   */
  static public Array makeArray( DataType dtype, ArrayList stringValues) {
    Array result = Array.factory( dtype.getPrimitiveClassType(), new int[] {stringValues.size()} );
    IndexIterator dataI = result.getIndexIterator();
    Iterator iter = stringValues.iterator();

    while (iter.hasNext()) {
      if (dtype == DataType.STRING) {
        dataI.setObjectNext(iter.next());
      } else {
        double val = Double.parseDouble( (String) iter.next());
        dataI.setDoubleNext(val);
      }
    }
    return result;
  }

  /**
   * Make a 1D array from a start and inccr.
   * @param dtype data type of result. must be convertible to double.
   * @param npts number of points
   * @param start starting values
   * @param incr increment
   * @return 1D array
   */
  static public Array makeArray( DataType dtype, int npts, double start, double incr) {
    Array result = Array.factory( dtype.getPrimitiveClassType(), new int[] {npts});
    IndexIterator dataI = result.getIndexIterator();
    for (int i=0; i<npts; i++) {
      double val = start + i * incr;
      dataI.setDoubleNext( val);
    }
    return result;
  }

  ////////////////////////////////////////////////////////////////////
  // debugging

  private String parseInfo = null;
  /** Debugging: get the information from parsing */
  public String getParseInfo() { return parseInfo; }
  /** Debugging set the information from parsing */
  public void setParseInfo( String parseInfo) { this.parseInfo = parseInfo; }

  void dumpClasses(Group g, PrintStream out) {

    out.println("Dimensions:");
    Iterator s = g.getDimensions().iterator();
    while (s.hasNext()) {
      Dimension ds = (Dimension) s.next();
      out.println("  "+ds.getName()+" "+ds.getClass().getName());
    }

    out.println("Atributes:");
    Iterator atts = g.getAttributes().iterator();
    while (atts.hasNext()) {
      Attribute ds = (Attribute) atts.next();
      out.println("  "+ds.getName()+" "+ds.getClass().getName());
    }

    out.println("Variables:");
    Iterator vs = g.getVariables().iterator();
    while (vs.hasNext()) {
      Variable v = (Variable) vs.next();
      out.print("  "+v.getName()+" "+v.getClass().getName()); // +" "+Integer.toHexString(v.hashCode()));
      if (v instanceof CoordinateAxis) {
         out.println("  "+((CoordinateAxis)v).getAxisType());
      } else
        out.println();
    }

    out.println("Groups:");
    Iterator groups = g.getGroups().iterator();
    while (groups.hasNext()) {
      Group nested = (Group) groups.next();
      out.println("  "+nested.getName()+" "+nested.getClass().getName());
      dumpClasses( nested, out);
    }

  }

  /** debugging */
  public static void debugDump(PrintStream out, NetcdfDataset ncd) {
    out.println("\nNetcdfDataset dump = "+ncd.getLocation()+" uri= "+ncd.getReferencedDatasetUri()+"\n");
    ncd.dumpClasses( ncd.getRootGroup(), out);
  }

  public static void main( String arg[]) {
    //String urls = "file:///C:/data/buoy/cwindRM.xml";
    //String urls = "C:/data/conventions/wrf/wrf_masscore.nc";
    //String urls = "http://motherlode.ucar.edu/cgi-bin/dods/DODS-3.2.1/nph-dods/dods/model/2004050712_eta_211.nc";
    String urls = "C:/data/grib2/eta2.wmo";

    try {
      NetcdfDataset df = NetcdfDataset.openDataset(urls);
      System.out.println("NetcdfDataset = "+urls+"\n"+df);
      debugDump(System.out, df);
    } catch (Exception ioe) {
      System.out.println("error = "+urls);
      ioe.printStackTrace();
    }
  }

}

/* Change History:
   $Log: NetcdfDataset.java,v $
   Revision 1.21  2004/12/10 17:04:16  caron
   *** empty log message ***

   Revision 1.20  2004/12/07 01:29:30  caron
   redo convention parsing, use _Coordinate encoding.

   Revision 1.19  2004/12/03 04:46:26  caron
   no message

   Revision 1.18  2004/12/01 05:53:41  caron
   ncml pass 2, new convention parsing

   Revision 1.17  2004/11/21 01:16:46  caron
   ncml pass 1

   Revision 1.16  2004/11/10 17:00:28  caron
   no message

   Revision 1.15  2004/11/10 16:47:14  caron
   no message

   Revision 1.14  2004/11/07 03:00:49  caron
   *** empty log message ***

   Revision 1.13  2004/11/07 02:55:11  caron
   no message

   Revision 1.12  2004/10/29 00:14:10  caron
   no message

   Revision 1.11  2004/10/19 19:45:02  caron
   misc

   Revision 1.10  2004/10/06 19:03:41  caron
   clean up javadoc
   change useV3 -> useRecordsAsStructure
   remove id, title, from NetcdfFile constructors
   add "in memory" NetcdfFile

   Revision 1.9  2004/10/02 20:57:01  caron
   coord vars and groups

   Revision 1.8  2004/09/28 21:29:21  caron
   addCoordAxis remove old if exists

   Revision 1.7  2004/09/22 18:43:02  caron
   move common to ucar.unidata; projections use Parameter, no tAttribute

   Revision 1.6  2004/09/22 13:46:37  caron
   *** empty log message ***

   Revision 1.5  2004/09/09 22:47:40  caron
   station updates

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

   Revision 1.3  2004/08/19 21:38:11  caron
   no message

   Revision 1.2  2004/08/17 19:20:05  caron
   2.2 alpha (2)

   Revision 1.1  2004/08/16 20:53:48  caron
   2.2 alpha (2)

 */
