// $Id: NetcdfFile.java,v 1.38 2004/12/10 17:04:16 caron Exp $
/*
 * Copyright 1997-2004 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;

import ucar.ma2.*;

import java.util.*;
import java.net.URL;
import java.io.PrintStream;
import java.io.IOException;
import java.io.ByteArrayOutputStream;

/**
 * Read-only scientific datasets that are accessible through the netCDF API.
 *
 * <h2>Naming</h2>
 * Each object has a name (aka "full name") that is unique within the entire netcdf file, and
 * a "short name" that is unique within the parent group.
 * These coincide for objects in the root group, and so is backwards compatible with version 3 files.
 * <ol>
 * <li>Variable: group1/group2/varname
 * <li>Structure member Variable: group1/group2/varname.s1.s2
 * <li>Group Attribute: group1/group2@attName
 * <li>Variable Attribute: group1/group2/varName@attName
 * </ol>
 *
 * @author caron
 * @version $Revision: 1.38 $ $Date: 2004/12/10 17:04:16 $
 */

public class NetcdfFile {
  static private boolean useRecordStructure = false;
  static private ArrayList registeredProviders = new ArrayList();
  static private boolean debugSPI = false;

  // this is so that we can run without specialized IOServiceProviders, but they will
  // still get automatically loaded if they are present.
  static {
    try {
      registerIOProvider( "ucar.nc2.iosp.grib.Grib1ServiceProvider");
    } catch (Exception e) {
      System.err.println("Cant load class ucar.nc2.iosp.grib.Grib1ServiceProvider "+e);
    }
    try {
      registerIOProvider( "ucar.nc2.iosp.grib.Grib2ServiceProvider");
    } catch (Exception e) {
      System.err.println("Cant load class ucar.nc2.iosp.grib.Grib2ServiceProvider "+e);
    }
    try {
      registerIOProvider( "ucar.nc2.iosp.dmsp.DMSPiosp");
    } catch (Exception e) {
      System.err.println("Cant load class ucar.nc2.iosp.dmsp.DMSPiosp "+e);
    }
    try {
      registerIOProvider( "ucar.nc2.iosp.gini.Giniiosp");
    } catch (Exception e) {
      System.err.println("Cant load class ucar.nc2.iosp.gini.Giniiosp "+e);
    }
    try {
      registerIOProvider( "ucar.nc2.iosp.nexrad2.Nexrad2IOServiceProvider");
    } catch (Exception e) {
      System.err.println("Cant load class ucar.nc2.iosp.nexrad2.Nexrad2IOServiceProvider "+e);
    }
    try {
      registerIOProvider( "ucar.nc2.iosp.nids.Nidsiosp");
    } catch (Exception e) {
      System.err.println("Cant load class ucar.nc2.iosp.nids.Nidsiosp "+e);
    }
  }

    /**
   * Register an IOServiceProvider, using its class string name.
   * @param className Class that implements IOServiceProvider.
   * @throws IllegalAccessException if class is not accessible.
   * @throws InstantiationException if class doesnt have a no-arg constructor.
   * @throws ClassNotFoundException if class not found.
   */
  static public void registerIOProvider( String className) throws IllegalAccessException, InstantiationException, ClassNotFoundException {
    Class ioClass = NetcdfFile.class.getClassLoader().loadClass(className);
    registerIOProvider( ioClass);
  }

  /**
   * Register an IOServiceProvider. A new instance will be created when one of its files is opened.
   * @param iospClass Class that implements IOServiceProvider.
   * @throws IllegalAccessException if class is not accessible.
   * @throws InstantiationException if class doesnt have a no-arg constructor.
   * @throws ClassCastException if class doesnt implement IOServiceProvider interface.
   */
  static public void registerIOProvider( Class iospClass) throws IllegalAccessException, InstantiationException {
    IOServiceProvider spi;
    spi = (IOServiceProvider) iospClass.newInstance(); // fail fast
    registeredProviders.add( spi);
  }

  /** Set whether to make records (variables with unlimited dimensions) into Structures.
   * Used only for netcdf-3 files.
   * This becomes the default for subsequent files that are opened, until changed by user.
   * Default value is false.
   **/
  static public void setUseRecordStructure( boolean b) { useRecordStructure = b; }

  /** Get whether to make records into Structures. */
  static public boolean getUseRecordStructure( ) { return useRecordStructure; }

  /** debugging */
  static public void setDebugFlags( ucar.nc2.util.DebugFlags debugFlag) {
    H5header.setDebugFlags( debugFlag);
    H5iosp.setDebugFlags( debugFlag);
  }

  /** debugging */
  static public void setDebugOutputStream( PrintStream printStream) {
    H5header.setDebugOutputStream( printStream);
  }

  /** Open an existing netcdf file (read only).
   *  @param location location of file.
   */
  public static NetcdfFile open(String location) throws IOException {
    return open( location, null);
  }

  /**
   * Open an existing file (read only), with option of cancelling.
   *
   * @param location location of file. This may be a
   * <ol>
   *  <li>local netcdf-3 filename (with a file: prefix or no prefix)
   *  <li>remote netcdf-3 filename (with an http: prefix)
   *  <li>local netcdf-4 filename (with a file: prefix or no prefix)
   *  <li>local hdf-5 filename (with a file: prefix or no prefix)
   * </ol>
   *
   * @param cancelTask allow task to be cancelled; may be null.
   * @return NetcdfFile object, or null if cant find IOServiceProver
   * @throws IOException
   *
   * @see ucar.nc2.util.CancelTask
   */
  static public NetcdfFile open(String location, ucar.nc2.util.CancelTask cancelTask) throws IOException {

    // get rid of file prefix, if any
    String uriString = location.trim();
    if (uriString.startsWith("file://"))
      uriString = uriString.substring(7);
    else if (uriString.startsWith("file:"))
      uriString = uriString.substring(5);

    ucar.unidata.io.RandomAccessFile raf;
    if (uriString.startsWith("http:")) { // open through URL
      URL url = new URL( location);
      //raf = new ucar.unidata.io.http.HTTPRandomAccessFile2(url); LOOK
      raf = new ucar.unidata.io.http.HTTPRandomAccessFile3(uriString);
    } else {
      raf = new ucar.unidata.io.RandomAccessFile(uriString, "r");
    }

    return open( raf, location, cancelTask);
  }

  /** Open an in-memory netcdf file.
   * @param location location of file, used as the name.
   * @param data in-memory netcdf file
   */
  public static NetcdfFile openInMemory(String location, byte[] data) throws IOException {
    ucar.unidata.io.InMemoryRandomAccessFile raf = new ucar.unidata.io.InMemoryRandomAccessFile(location, data);
    return open( raf, location, null);
  }

  private static NetcdfFile open(ucar.unidata.io.RandomAccessFile raf, String location, ucar.nc2.util.CancelTask cancelTask) throws IOException {

    IOServiceProvider spi = null;
    if (debugSPI) System.out.println("NetcdfFile try to open = "+location);

     // avoid opening file more than once, so pass around the raf.
    if (N3header.isValidFile( raf)) {
      spi = SPFactory.getServiceProvider();
      spi.setProperty("useRecordStructure", useRecordStructure ? "true" : "false"); // LOOK: generalize to iosp property ??

    } else if (H5header.isValidFile( raf)) {
      spi = new ucar.nc2.H5iosp();

    } else {
      // look for registered providers
      for (int i = 0; i < registeredProviders.size(); i++) {
        IOServiceProvider registeredSpi = (IOServiceProvider) registeredProviders.get(i);
        if (debugSPI) System.out.println(" try iosp = "+registeredSpi.getClass().getName());

        if (registeredSpi.isValidFile( raf)) {
          // need a new instance for thread safety
          Class c = registeredSpi.getClass();
          try {
            spi = (IOServiceProvider) c.newInstance();
          } catch (InstantiationException e) {
            throw new IOException("IOServiceProvider "+c.getName()+"must have no-arg constructor."); // shouldnt happen
          } catch (IllegalAccessException e) {
            throw new IOException("IOServiceProvider "+c.getName()+" IllegalAccessException: "+e.getMessage()); // shouldnt happen
          }
          break;
        }
      }
    }

    if (spi == null) {
      raf.close();
      throw new IOException("Cant read "+location+": not a valid NetCDF file.");
    }

    return new NetcdfFile(spi, raf, location, cancelTask);
  }

  ////////////////////////////////////////////////
  protected String location, id, title;
  protected Group rootGroup = new Group( this, null, "");
  protected boolean isClosed = false;
  protected IOServiceProvider spi;

  // "global view" is derived from the group information.
  protected ArrayList variables;
  protected ArrayList dimensions;
  protected ArrayList gattributes;

    /** is the dataset already closed? */
  public synchronized boolean isClosed() { return isClosed; }

    /** close the dataset. */
  public synchronized void close() throws java.io.IOException {
    spi.close();
    isClosed = true;
  }

   /** Get the dataset location. This is a URL, or a file pathname. */
  public String getLocation() { return location; }

   /** Get the globally unique dataset identifier */
  public String getId() { return id; }

   /** Get the human-readable title. */
  public String getTitle() { return title; }

  /** Get the root group. */
  public Group getRootGroup() { return rootGroup; }

  /** Get all of the variables in the file, in all groups.
   * This is part of "version 3 compatibility" interface.
   * Alternatively, use groups.
   * @return List of type Variable.
   */
  public java.util.List getVariables() {
    return new ArrayList(variables);
  }

  /**
   * Retrieve the Variable with the specified (full) name, which is not a member of a Structure.
   * @param name full name, starting from root group.
   * @return the Variable, or null if not found
   */
  public Variable findTopVariable(String name) {
    if (name == null) return null;

    for (int i=0; i<variables.size(); i++) {
      Variable v = (Variable) variables.get(i);
      if (name.equals(v.getName()))
        return v;
    }
    return null;
  }

  /**
   * Find a variable, with the specified (full) name.
   * It may possibly be nested in multiple groups and/or structures.
   * @param fullName eg "group/subgroup/name1.name2.name".
   * @return Variable or null if not found.
   */
  public Variable findVariable( String fullName) {
    StringTokenizer stoke = new StringTokenizer(fullName, ".");
    String selector = stoke.nextToken();
    if (selector == null) return null;

    Variable v = findTopVariable( selector);
    if (v == null) return null;

    while (stoke.hasMoreTokens()) {
      if (!(v instanceof Structure)) return null;
      String name = stoke.nextToken();
      v = ((Structure) v).findVariable( name);
      if (v == null) return null;
   }
    return v;
  }


  /* public Variable findNestedVariable(String[] names) {
    return findNestedVariable( Arrays.asList( names).iterator());
  }

  public Variable findNestedVariable(Iterator names) {
    if (!names.hasNext()) return null;
    String name = (String) names.next();
    Variable nested = findVariable( name);
    if (nested == null) return null;
    if (!names.hasNext()) return nested;
    if (nested.getDataType() != DataType.STRUCTURE) return null;
    Structure s = (Structure) nested;
    return s.findNestedVariable( names);
  } */

  /**
   * Get the shared Dimensions used in this file.
   * This is part of "version 3 compatibility" interface.
   * <p> If the dimensions are in a group, the dimension name will have the
   *  group name, in order to disambiguate the dimensions. This means that
   *  a Variable's dimensions will not match Dimensions in this list.
   *  Therefore it is generally better to get the shared Dimensions from the
   *  Groups.
   * @return List of type Dimension.
   * @deprecated use Group.getDimensions()
   */
  public List getDimensions() { return new ArrayList( dimensions); }

  /**
   * Retrieve a dimension by fullName.
   * @param name dimension full name, (using parent group names if not in the root group)
   * @return the dimension, or null if not found
   * @deprecated use Group.findDimension()
   */
  public Dimension findDimension(String name) {
    for (int i=0; i<dimensions.size(); i++) {
      Dimension d = (Dimension) dimensions.get(i);
      if (name.equals(d.getName()))
        return d;
    }
    return null;
  }

  /**
   * Returns the set of global attributes associated with this file,
   * @return Iterator over type Attribute
   *
  public Iterator getGlobalAttributeIterator() {
   return gattributes.iterator();
  } */

  /**
   * Returns the set of global attributes associated with this file.
   * This is part of "version 3 compatibility" interface.
   * Alternatively, use groups.
   * @return List of type Attribute
   */
  public java.util.List getGlobalAttributes() { return new ArrayList(gattributes); }

  /**
   * Look up global Attribute by (full) name.
   *
   * @param name the name of the attribute
   * @return the attribute, or null if not found
   */
  public Attribute findGlobalAttribute(String name) {
    for (int i=0; i<gattributes.size(); i++) {
      Attribute a = (Attribute) gattributes.get(i);
      if (name.equals(a.getName()))
        return a;
    }
    return null;
  }

  /**
   * Look up global Attribute by name, ignore case.
   *
   * @param name the name of the attribute
   * @return the attribute, or null if not found
   */
  public Attribute findGlobalAttributeIgnoreCase(String name) {
    for (int i=0; i<gattributes.size(); i++) {
      Attribute a = (Attribute) gattributes.get(i);
      if (name.equalsIgnoreCase(a.getName()))
        return a;
    }
    return null;
  }

  /**
   * Find a String-valued global or variable Attribute by
   * Attribute name (ignore case), return the Value of the Attribute.
   * If not found return defaultValue
   *
   * @param v the variable or null for global attribute
   * @param attName the (full) name of the attribute, case insensitive
   * @param defaultValue return this if attribute not found
   * @return the attribute value, or defaultValue if not found
   */
  public String findAttValueIgnoreCase( Variable v, String attName, String defaultValue) {
    String attValue = null;
    Attribute att;

    if (v == null)
      att = findGlobalAttributeIgnoreCase( attName);
    else
      att = v.findAttributeIgnoreCase(attName);

    if ((att != null) && att.isString())
      attValue = att.getStringValue();

    /* if (null == attValue) {                    // not found, look for global attribute
      att = findGlobalAttributeIgnoreCase(attName);
      if ((att != null) && att.isString())
        attValue = att.getStringValue();
    } */

    if (null == attValue)                     // not found, use default
      attValue = defaultValue;

    return attValue;
  }

  ////////////////////////////////////////////////////////////////////////////////
  // public I/O

  /**
   * Do a bulk read on a list of Variables and
   * return a corresponding list of Array that contains the results
   * of a full read on each Variable.
   * This is mostly here so DODSNetcdf can override it with one call to the server.
   *
   * @param variables List of type Variable
   * @return List of Array, one for each Variable in the input.
   * @throws IOException
   */
  public java.util.List readArrays (java.util.List variables) throws IOException {
    java.util.List result = new java.util.ArrayList();
    for (int i=0;i<variables.size(); i++) {
      result.add (((Variable)variables.get (i)).read ());
    }
    return result;
  }

 /**
  * Read a variable using the given section specification, equivilent to readAllStructures() if
  *   its a member of a Structure, or read() otherwise.
  * @param variableSection the constraint expression. This must start with a top variable.
  * @param flatten if true and its a member of a Structure, remove the surrounding StructureData.
  * @return Array data read.
  * @throws IOException
  * @throws InvalidRangeException
  *
  * @see NCdump#parseVariableSection for syntax of constraint expression
  */
  public Array read(String variableSection, boolean flatten) throws IOException, InvalidRangeException  {
    NCdump.CEresult cer = NCdump.parseVariableSection(this, variableSection);
    if (cer.hasInner)
      return cer.v.readAllStructures( cer.ranges, flatten);
    else
      return cer.v.read( cer.ranges);
  }

  //////////////////////////////////////////////////////////////////////////////////////
  /**
   * Write CDL representation to OutputStream.
   * @param os
   */
  public void writeCDL( java.io.OutputStream os, boolean strict) {
    PrintStream out = new PrintStream( os);
    toStringStart(out, strict);
    toStringEnd(out);
    out.flush();
  }

  /** nicely formatted string representation */
  public String toString() {
    ByteArrayOutputStream ba = new ByteArrayOutputStream(40000);
    PrintStream out = new PrintStream( ba);
    toStringStart(out, false);
    toStringEnd(out);
    out.flush();
    return ba.toString();
  }

  protected void toStringStart(PrintStream out, boolean strict) {
    out.print("netcdf "+getLocation()+" {\n");
    rootGroup.writeCDL( out, "", strict);
  }

  protected void toStringEnd(PrintStream out) {
    out.print("}\n");
  }

  /**
   * Write the NcML representation: dont show coodinate values, use getPathName() for the uri attribute.
   * @param os : write to this Output Stream.
   * @throws IOException
   */
  public void writeNcML( java.io.OutputStream os) throws IOException {
    NCdump.writeNcML( this, os, false, null);
  }

  //////////////////////////////////////////////////////////////////////////////////////
  // construction

  /**
   * Open an existing netcdf file (read only).
   * @param location location of file. This is a URL string, or a local pathname.
   * @throws IOException
   */
  protected NetcdfFile(IOServiceProvider spi, ucar.unidata.io.RandomAccessFile raf, String location, ucar.nc2.util.CancelTask cancelTask) throws IOException {

    this.spi = spi;
    this.location = location;

    if (debugSPI) System.out.println("NetcdfFile uses iosp = "+spi.getClass().getName());

    spi.open(raf, this, cancelTask);
    finish();

    if (id == null)
      setId( findAttValueIgnoreCase( null, "_Id", null));
    if (title == null)
      setId( findAttValueIgnoreCase( null, "_Title", null));
  }

  /** for subclass construction. call finish() when completed construction. */
  public NetcdfFile() { }

  /**
   * Copy constructor.
   * Groups get reparented, so dont use the underlying NetcdfFile after this call.
   * Variable dont get reparented (!)
   *
   * @param ncfile NetcdfFile to copy.
   */
  protected NetcdfFile( NetcdfFile ncfile) {
    this.location = ncfile.getLocation();
    this.id = ncfile.getId();
    this.title = ncfile.getTitle();
    this.spi = ncfile.spi;
    this.rootGroup = ncfile.getRootGroup();
    reparentGroup( ncfile.getRootGroup());
    finish();
  }

  private void reparentGroup( Group g) {
    g.ncfile = this;
    for (Iterator iter = g.getGroups().iterator(); iter.hasNext(); ) {
      Group item = (Group)iter.next();
      reparentGroup( item);
    }
  }

  /** Add a group attribute. If group is null, use root group */
  public void addAttribute(Group g, Attribute att) {
    if (g == null) g = rootGroup;
    g.addAttribute(att);
  }

  /** Add a group to the parent group. If parent is null, use root group */
  public void addGroup(Group parent, Group g) {
    if (parent == null) parent = rootGroup;
    parent.addGroup(g);
  }

  /** Add a shared Dimension to a Group. If group is null, use root group */
  public void addDimension(Group g, Dimension d) {
    if (g == null) g = rootGroup;
    g.addDimension(d);
  }

  /** Remove a shared Dimension from a Group by name. If group is null, use root group.
   * @return true if found and removed. */
  public boolean removeDimension(Group g, String dimName) {
    if (g == null) g = rootGroup;
    return g.removeDimension( dimName);
  }

  /** Add a Variable to the given group. If group is null, use root group */
  public void addVariable( Group g, Variable v) {
    if (g == null) g = rootGroup;
    if (v != null) g.addVariable( v);
  }

  /** Remove a Variable from the given group by name. If group is null, use root group.
   * @return true is variable found and removed */
  public boolean removeVariable( Group g, String varName) {
    if (g == null) g = rootGroup;
    return g.removeVariable( varName);
  }

  /** Add a variable attribute. */
  public void addVariableAttribute(Variable v, Attribute att) {
    v.addAttribute(att);
  }

  /** Add a Variable to the given structure. */
  public void addMemberVariable( Structure s, Variable v) {
    if (v != null) s.addMemberVariable( v);
  }

  /** 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) {
    v.replaceDimension(d);
  }

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

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

   /** Set the globally unique dataset identifier. */
   public void setId(String id) { this.id = id; }

   /** Set the dataset "human readable" title. */
   public void setTitle(String title) { this.title = title; }

   /** Set the location, a URL or local filename. */
   public void setLocation(String location) { this.location = location; }

  /** Convert a name to a legal netcdf name.
   * From the user manual:
     "The names of dimensions, variables and attributes consist of arbitrary sequences of
     alphanumeric characters (as well as underscore '_' and hyphen '-'), beginning with a letter
     or underscore. (However names commencing with underscore are reserved for system use.)
     Case is significant in netCDF names."

     Algorithm:
     <ol>
      <li>leading character: if alpha or underscore, ok; if digit, prepend "N"; otherwise discard
      <li>other characters: if space, change to underscore; other illegal char change to "-".
     </ol>
  */
  public static String createValidNetcdfObjectName( String name) {
    StringBuffer sb = new StringBuffer( name);

    //LOOK: could escape characters, as in DODS (%xx) ??

    while (true && (sb.length() > 0)) {
      char c = sb.charAt(0);
      if (Character.isLetter(c) || (c == '_')) break;
      if (Character.isDigit(c)) {
        sb.insert(0, 'N');
        break;
      }
      sb.deleteCharAt(0);
    }

   for (int i = 1; i < sb.length(); i++) {
      char c = sb.charAt(i);
      if (c == ' ') sb.setCharAt(i, '_');
      else {
        boolean ok = (c == '-') || (c == '_') || Character.isLetterOrDigit(c);
        if (!ok) {
          sb.deleteCharAt(i);   // setCharAt(i, '-'); // or remove?
          i--;
        }
      }
    }

    return sb.toString();
  }

  protected String makeFullNameWithString( Group parent, String name) {
    StringBuffer sbuff = new StringBuffer();
    appendGroupName(sbuff, parent);
    sbuff.append( name);
    return sbuff.toString();
  }

  static protected String makeFullName( Group parent, Variable v) {
    StringBuffer sbuff = new StringBuffer();
    appendGroupName(sbuff, parent);
    appendStructureName( sbuff, v);
    return sbuff.toString();
  }

  static private void appendGroupName(StringBuffer sbuff, Group g) {
    boolean isRoot = g.getParentGroup() == null;
    if (isRoot) return;

    if (g.getParentGroup() != null)
      appendGroupName(sbuff, g.getParentGroup());
    sbuff.append( g.getShortName());
    sbuff.append( "/");
  }

  static private void appendStructureName(StringBuffer sbuff, Variable v) {
    if (v.isMemberOfStructure()) {
      appendStructureName(sbuff, v.getParentStructure());
      sbuff.append( ".");
    }
    sbuff.append( v.getShortName());
  }

  /** Finish constructing the object model.
   * This construsts the "global" variables, attributes and dimensions.
   * It also looks for coordinate variables.
   */
  public void finish() {
    variables = new ArrayList();
    gattributes = new ArrayList();
    dimensions = new ArrayList();
    finishGroup(rootGroup);
  }

  private void finishGroup( Group g) {

    variables.addAll( g.variables);
    for (int i=0; i<g.variables.size(); i++) {
      Variable v = (Variable) g.variables.get(i);
      v.calcIsCoordinateVariable();
    }

    for (int i=0; i<g.attributes.size(); i++) {
      Attribute oldAtt = (Attribute) g.attributes.get(i);
      String newName = makeFullNameWithString( g, oldAtt.getName());
      //System.out.println("  add att="+newName);
      gattributes.add( new Attribute( newName, oldAtt));
    }

    // LOOK this wont match the variables' dimensions if there are groups
    for (int i=0; i<g.dimensions.size(); i++) {
      Dimension oldDim = (Dimension) g.dimensions.get(i);
      if (oldDim.isShared()) {
        if (g == rootGroup) {
          dimensions.add( oldDim);
        } else {
          String newName = makeFullNameWithString(g, oldDim.getName());
          dimensions.add(new Dimension(newName, oldDim));
        }
      }
    }

    List groups = g.getGroups();
    for (int i=0; i<groups.size(); i++) {
      Group nested = (Group) groups.get(i);
      finishGroup( nested);
    }
  }

  //////////////////////////////////////////////////////////////////////////////////////
  // Service Provider calls
  // ALL IO eventually goes through these calls.
  // LOOK: these should not be public !!! not hitting variable cache
  // used in NetcdfDataset - try to refactor

  // this is for reading non-member variables
  // section is null for full read
  /** do not call these directly, use Variable.read() !! */
  public Array readData(ucar.nc2.Variable v, List section) throws IOException, InvalidRangeException  {
    return spi.readData( v, section);
  }

  // this is for reading variables that are members of structures
  /** do not call these directly, use Variable.readSection() !! */
  public Array readMemberData(ucar.nc2.Variable v, List section, boolean flatten) throws IOException, InvalidRangeException  {
    return spi.readNestedData( v, section, flatten);
  }

  /** Debug info for this object. */
  protected String toStringDebug(Object o) {
    return (spi == null) ? "" : spi.toStringDebug(o);
  }

  /** debug */
  public static void main( String[] arg) throws Exception {
    //NetcdfFile.registerIOProvider( ucar.nc2.grib.GribServiceProvider.class);

    try {
      String filename = "C:/data/grib2/eta2.wmo";
      //String filename = "C:/dev/grib/data/ndfd.wmo";
      //String filename = "c:/data/radar/level2/6500KHGX20000610_000110.raw";
      NetcdfFile ncfile = NetcdfFile.open(filename);

      System.out.println();
      System.out.println( ncfile.toString());

      Variable v = ncfile.findVariable("Temperature");
      v.read();
      
      //System.out.println( file.toStringV3());
      //file.writeNcML( System.out);
      ncfile.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

}

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

   Revision 1.37  2004/12/08 21:14:26  caron
   no message

   Revision 1.36  2004/12/08 18:08:32  caron
   implement _CoordinateAliasForDimension

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

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

   Revision 1.33  2004/11/21 01:16:48  caron
   ncml pass 1

   Revision 1.32  2004/11/16 23:35:39  caron
   no message

   Revision 1.31  2004/11/15 03:25:20  caron
   no message

   Revision 1.30  2004/11/10 17:00:29  caron
   no message

   Revision 1.29  2004/11/04 20:16:44  caron
   no message

   Revision 1.28  2004/10/29 00:14:13  caron
   no message

   Revision 1.27  2004/10/22 00:59:49  caron
   get selection correct when sorting

   Revision 1.26  2004/10/22 00:58:32  caron
   another round

   Revision 1.25  2004/10/20 23:23:15  caron
   add nexrad2 iosp

   Revision 1.24  2004/10/19 19:45:04  caron
   misc

   Revision 1.23  2004/10/14 19:34:49  rkambic
   changed exceptions
   remove gini iosp until isValidFile() works

   Revision 1.22  2004/10/14 17:14:32  caron
   add gini reader
   add imageioreader for PNG

   Revision 1.21  2004/10/13 19:45:13  caron
   add strict NCDump

   Revision 1.20  2004/10/06 21:39:05  edavis
   Add registration of ucar.nc2.iosp.dmsp.DMSPiosp.

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

   Revision 1.18  2004/10/02 21:01:07  caron
   *** empty log message ***

   Revision 1.17  2004/09/28 21:36:36  caron
   add removeVariable

   Revision 1.16  2004/09/25 00:09:44  caron
   add images, thredds tab

   Revision 1.15  2004/09/24 02:32:02  caron
   grib2 reading

   Revision 1.14  2004/09/22 18:44:32  caron
   move common to ucar.unidata

   Revision 1.13  2004/09/22 13:46:36  caron
   *** empty log message ***

   Revision 1.12  2004/09/13 22:52:51  caron
   *** empty log message ***

   Revision 1.11  2004/09/09 22:47:39  caron
   station updates

   Revision 1.10  2004/08/26 17:52:44  caron
   no message

   Revision 1.9  2004/08/19 21:38:13  caron
   no message

   Revision 1.8  2004/08/19 19:13:45  edavis
   Make methods for building a NetcdfFile public.

   Revision 1.7  2004/08/18 20:01:07  caron
   2.2 alpha (2)

   Revision 1.6  2004/08/18 19:56:43  caron
   2.2 alpha (2)

   Revision 1.5  2004/08/17 19:20:04  caron
   2.2 alpha (2)

   Revision 1.4  2004/08/16 20:53:46  caron
   2.2 alpha (2)

   Revision 1.3  2004/07/12 23:40:17  caron
   2.2 alpha 1.0 checkin

   Revision 1.2  2004/07/06 19:28:10  caron
   pre-alpha checkin

   Revision 1.1.1.1  2003/12/04 21:05:27  caron
   checkin 2.2

   Revision 1.2  2003/07/14 22:59:28  caron
   ucar/nc2/nio/Variable.java

   Revision 1.1.1.1  2003/06/06 20:13:19  caron
   import nio
 */

