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

package ucar.nc2.iosp.grid;

import ucar.ma2.DataType;

import ucar.nc2.*;
import ucar.nc2.units.DateFormatter;
import ucar.unidata.io.RandomAccessFile;
import ucar.unidata.util.StringUtil2;

import java.util.ArrayList;
import java.util.Formatter;
import java.util.List;

/**
 * A Variable for a Grid dataset.
 *
 * @author caron
 */
public class GridVariable {

  /**
   * logger
   */
  static private org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(GridVariable.class);

  private final String filename;

  /**
   * disambiguated name == parameter name [ + suffix] [ + level]
   */
  private final String name;

  /**
   * variable name
   */
  public String vname;

  /**
   * first grid record
   */
  protected GridRecord firstRecord;

  /**
   * lookup table
   */
  protected GridTableLookup lookup;

  /**
   * horizontal coord system
   */
  private GridHorizCoordSys hcs;

  /**
   * time coord system
   */
  protected GridTimeCoord tcs = null;

  /**
   * ensemble coord system
   */
  protected GridEnsembleCoord ecs = null;

  /**
   * vertical coordinate
   */
  protected GridVertCoord vc = null;

  /**
   * list of records that make up this variable
   */
  private List<GridRecord> records = new ArrayList<>();  // GridRecord

  /**
   * number of levels
   */
  protected int nlevels;

  /**
   * number of Ensembles
   */
  private int nens;

  /**
   * number of times
   */
  protected int ntimes;

  /**
   * record tracker
   */
  protected GridRecord[] recordTracker;

  /**
   * flag for having a vertical coordinate
   */
  private boolean hasVert = false;

  /**
   * Create a new GridVariable
   *
   * @param name   name with level
   * @param hcs    horizontal coordinate system
   * @param lookup lookup table
   */
  protected GridVariable(String filename, String name, GridHorizCoordSys hcs, GridTableLookup lookup) {
    this.filename = filename;  //for debugging
    this.name = name;  // used to get unique grouping of products
    this.hcs = hcs;
    this.lookup = lookup;
  }

  /**
   * Add in a new product
   *
   * @param record grid  to add
   */
  void addProduct(GridRecord record) {
    records.add(record);
    if (firstRecord == null) {
      firstRecord = record;
    }
  }

  /**
   * Get the list of grids
   *
   * @return grid records
   */
  List<GridRecord> getRecords() {
    return records;
  }

  /**
   * get the first grid record
   *
   * @return the first in the list
   */
  GridRecord getFirstRecord() {
    return records.get(0);
  }

  /**
   * Get the horizontal coordinate system
   *
   * @return the horizontal coordinate system
   */
  GridHorizCoordSys getHorizCoordSys() {
    return hcs;
  }

  /**
   * Get the vertical coordinate
   *
   * @return the vertical coordinate
   */
  GridVertCoord getVertCoord() {
    return vc;
  }

  /**
   * Does this have a vertical dimension
   *
   * @return true if has a vertical dimension
   */
  boolean hasVert() {
    return hasVert;
  }

  /**
   * Set the variable name
   *
   * @param vname the variable name
   */
  void setVarName(String vname) {
    this.vname = vname;
  }

  /**
   * Set the vertical coordinate
   *
   * @param vc the vertical coordinate
   */
  void setVertCoord(GridVertCoord vc) {
    this.vc = vc;
  }

  /**
   * Set the time coordinate
   *
   * @param tcs the time coordinate
   */
  void setTimeCoord(GridTimeCoord tcs) {
    this.tcs = tcs;
  }

  /**
   * Set the Ensemble coordinate
   *
   * @param ecs the Ensemble coordinate
   */
  void setEnsembleCoord(GridEnsembleCoord ecs) {
    this.ecs = ecs;
  }

  /**
   * Get the number of Ensemble
   *
   * @return the number of Ensemble
   */
  public int getNEnsembles() {
    return (ecs == null) ? 1 : ecs.getNEnsembles();
  }

  /*
   * Get the product definition number of Ensemble
   *
   * @return the product definition number of Ensemble
   *
  public int getPDN() {
    return (ecs == null) ? -1 : ecs.getPDN();
  }

  /*
   * Get the types of Ensemble
   *
   * @return the types of Ensemble
   *
  public int[] getEnsTypes() {
    return (ecs == null) ? null : ecs.getEnsType();
  } */

  /*
   * Get the Index of Ensemble
   *
   * @param record GridRecord
   * @return the Index of Ensemble
   *
  int getEnsembleIndex(GridRecord record) {
    return (ecs == null) ? 1 : ecs.getIndex(record);
  } */

  /**
   * Does this have a Ensemble coordinate
   *
   * @return true if has a Ensemble coordinate
   */
  boolean hasEnsemble() {
    return (ecs != null);
  }

  protected boolean isEnsemble() {
    return false;
  }

  /**
   * Get the number of vertical levels
   *
   * @return the number of vertical levels
   */
  int getVertNlevels() {
    return vc.getNLevels();
  }

  /**
   * Get the name of the vertical dimension
   *
   * @return the name of the vertical dimension
   */
  String getVertName() {
    return vc.getVariableName();
  }

  /**
   * Get the name of the vertical level
   *
   * @return the name of the vertical level
   */
  String getVertLevelName() {
    return  vc.getLevelName();
  }

  /**
   * Is vertical used?
   *
   * @return true if vertical used
   */
  boolean getVertIsUsed() {
    return vc.isVertDimensionUsed();
  }

  /**
   * Get the index in the vertical for the particular grid
   *
   * @param p grid to check
   * @return the index
   */
  protected int getVertIndex(GridRecord p) {
    return vc.getIndex(p);
  }

  /**
   * Get the number of times
   *
   * @return the number of times
   */
  int getNTimes() {
    return (tcs == null) ? 1 : tcs.getNTimes();
  }

  /*
   * is this a time interval variable
   *
   * @return true if uses time intervals
   *
  boolean isInterval() {
    if (firstRecord instanceof GribGridRecord) {
      GribGridRecord ggr = (GribGridRecord) firstRecord;
      return ggr.isInterval();
    }
    return false;
  }

  String getIntervalTypeName() {
    if (firstRecord instanceof GribGridRecord) {
      GribGridRecord ggr = (GribGridRecord) firstRecord;
      return ggr.getStatisticalProcessTypeName();
    }
    return null;
  } */

  protected void addExtraAttributes(GridParameter param, Variable v) {
    int icf = hcs.getGds().getInt(GridDefRecord.VECTOR_COMPONENT_FLAG);
    String flag = GridCF.VectorComponentFlag.of( icf);
    v.addAttribute(new Attribute(GridDefRecord.VECTOR_COMPONENT_FLAG, flag));
  }

  protected boolean trackRecords(int time, int level, GridRecord p, RandomAccessFile raf, boolean messSent) {
    int recno = time * nlevels + level;

    if (recordTracker[recno] == null) {
      recordTracker[recno] = p;
      if (log.isDebugEnabled()) log.debug(" " + vc.getVariableName() + " (type=" + p.getLevelType1() + "," + p.getLevelType2() + ")  value="
              + p.getLevel1() + "," + p.getLevel2());
    }

    return false;
  }

  /**
   * Make the netcdf variable. If vname is not already set, use useName as name
   *
   * @param ncfile  netCDF file
   * @param g       group
   * @param useName use this as the variable name
   * @param raf read from here
   * @return the netcdf variable
   */
  Variable makeVariable(NetcdfFile ncfile, Group g, String useName, RandomAccessFile raf) {
    assert records.size() > 0 : "no records for this variable";

    this.nlevels = getVertNlevels();
    this.ntimes = tcs.getNTimes();
    if (vname == null) {
      useName = StringUtil2.replace(useName, ' ', "_");
      this.vname = useName;
    }

    Variable v = new Variable(ncfile, g, null, vname);
    v.setDataType(DataType.FLOAT);

    Formatter dims = new Formatter();

    if (hasEnsemble()) {
      dims.format("ens ");
    }

    dims.format("%s ", tcs.getName());

    if (getVertIsUsed()) {
      dims.format("%s ",  getVertName());
      hasVert = true;
    }

    if (hcs.isLatLon()) {
      dims.format("lat lon");
    } else {
      dims.format("y x");
    }

    v.setDimensions(dims.toString());

    // add attributes
    GridParameter param = lookup.getParameter(firstRecord);
    if (param == null) return null;

    String unit = param.getUnit();
    if (unit == null) unit = "";
    v.addAttribute(new Attribute("units", unit));

    v.addAttribute(new Attribute("long_name", makeLongName()));
    v.addAttribute(new Attribute("missing_value", lookup.getFirstMissingValue()));
    if (!hcs.isLatLon()) {
      if (GridServiceProvider.addLatLon)
        v.addAttribute(new Attribute("coordinates", "lat lon"));
      v.addAttribute(new Attribute("grid_mapping", hcs.getGridName()));
    }

    addExtraAttributes(param, v);

    v.setSPobject(this);

    int nrecs = ntimes * nlevels;
    if (hasEnsemble()) nrecs *= ecs.getNEnsembles();
    recordTracker = new GridRecord[nrecs];

    if (log.isDebugEnabled()) log.debug("Record Assignment for Variable " + getName());
    boolean oneSent = false;

    for (GridRecord p : records) {
      int level = getVertIndex(p);
      if (!getVertIsUsed() && (level > 0)) {
        log.warn("inconsistent level encoding=" + level);
        level = 0;  // inconsistent level encoding ??
      }

      int time = tcs.findIndex(p);
      // System.out.println("time="+time+" level="+level);
      if (level < 0) {
        log.warn("LEVEL NOT FOUND record; level=" + level + " time= "
                + time + " for " + getName() + " file="
                + ncfile.getLocation() + "\n" + "   "
                + getVertLevelName() + " (type=" + p.getLevelType1()
                + "," + p.getLevelType2() + ")  value="
                + p.getLevel1() + "," + p.getLevel2() + "\n");

        getVertIndex(p);  // allow breakpoint
        continue;
      }

      if (time < 0) {
        log.warn("TIME NOT FOUND record; level=" + level + " time= "
                + time + " for " + getName() + " file="
                + ncfile.getLocation() + "\n" + " validTime= "
                + p.getValidTime() + "\n");

        tcs.findIndex(p);  // allow breakpoint
        continue;
      }

      oneSent = trackRecords(time, level, p, raf, oneSent);
    }

    // let all references to Index go, to reduce retained size LOOK
    records.clear();

    return v;
  }

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

  public void showRecord(int recnum, Formatter f) {
    if ((recnum < 0) || (recnum > recordTracker.length - 1)) {
      f.format("%d out of range [0,%d]%n", recnum, recordTracker.length - 1);
      return;
    }
    GridRecord gr = recordTracker[recnum];

    if (hasEnsemble()) {
      // recnum = ens * (ntimes * nlevels) + (time * nlevels) + level
      int ens = recnum / (nlevels * ntimes);
      int tmp = recnum - ens *(nlevels * ntimes);
      int time = tmp / nlevels;
      int level = tmp % nlevels;
      f.format("recnum=%d (record hash=%d) ens=%d time=%s(%d) level=%f(%d)%n", recnum, gr.hashCode(), ens, tcs.getCoord(time), time, vc.getCoord(level), level);
    }  else {
      int time = recnum / nlevels;
      int level = recnum % nlevels;
      f.format("recnum=%d (record hash=%d) time=%s(%d) level=%f(%d)%n", recnum, gr.hashCode(), tcs.getCoord(time), time, vc.getCoord(level), level);
    }
  }

  /**
   * Dump out the missing data
   * @param f write to this
   */
  public void showMissing(Formatter f) {
    //System.out.println("  " +name+" ntimes (across)= "+ ntimes+" nlevs (down)= "+ nlevels+":");
    int count = 0, total = 0;
    f.format("  %s%n", name);
    for (int j = 0; j < nlevels; j++) {
      f.format("   ");
      for (int i = 0; i < ntimes; i++) {
        boolean missing = recordTracker[i * nlevels + j] == null;
        f.format("%s", missing ? "-" : "X");
        if (missing) count++;
        total++;
      }
      f.format("%n");
    }
    f.format("  MISSING= %d / %d for %s%n",count,total, name);
  }

  /**
   * Dump out the missing data as a summary
   *
   * @param f write to this
   * @return number of missing levels
   */
  public int showMissingSummary(Formatter f) {
    int count = 0;
    int total = recordTracker.length;

    for (int i = 0; i < total; i++) {
      if (recordTracker[i] == null)
        count++;
    }

    f.format("  MISSING= %d / %d for %s%n", count, total, name);
    return count;
  }

  /**
   * Find the grid record for the time and level indices
   * Canonical ordering is ens, time, level
   * @param ens   ensemble index
   * @param time  time index
   * @param level level index
   * @return the record or null
   */
  public GridRecord findRecord(int ens, int time, int level) {
    if (hasEnsemble()) {
      return recordTracker[ens * (ntimes * nlevels) + (time * nlevels) + level];
    } else {
      return recordTracker[time * nlevels + level];
    }
  }

  /**
   * Check for equality
   *
   * @param oo object in question
   * @return true if they are equal
   */
  public boolean equals(Object oo) {
    if (this == oo) {
      return true;
    }
    if (!(oo instanceof GridVariable)) {
      return false;
    }
    return hashCode() == oo.hashCode();
  }

  /**
   * Get the name
   *
   * @return the name
   */
  public String getName() {
    return name;
  }

  /**
   * Override Object.hashCode() to implement equals.
   *
   * @return equals;
   */
  public int hashCode() {
    if (hashCode == 0) {
      int result = 17;
      result = 37 * result + name.hashCode();
      result += 37 * result + firstRecord.getLevelType1();
      result += 37 * result + hcs.getID().hashCode();
      hashCode = result;
    }
    return hashCode;
  }

  /**
   * hash code
   */
  private volatile int hashCode = 0;

  @Override
  public String toString() {
    return vname == null ? name : vname;
  }

  /**
   * Dump this variable
   *
   * @return the variable
   */
  public String dump() {
    DateFormatter formatter = new DateFormatter();
    Formatter sbuff = new Formatter();
    sbuff.format("%s %d %n", name, records.size());
    for (GridRecord record : records) {
      sbuff.format(" level = %d %f", record.getLevelType1(), record.getLevel1());
      if (null != record.getValidTime())
        sbuff.format(" time = %s", formatter.toDateTimeString(record.getValidTime()));
      sbuff.format("%n");
    }
    return sbuff.toString();
  }

  /**
   * Make a long name for the variable
   *
   * @return long variable name
   */
  protected String makeLongName() {
    Formatter f = new Formatter();
    GridParameter param = lookup.getParameter(firstRecord);
    if (param == null) return null;

    f.format("%s", param.getDescription());

    String levelName = makeLevelName(firstRecord, lookup);
    if (levelName.length() != 0)
      f.format(" @ %s", levelName);

    return f.toString();
  }

  /**
   * Make the level name
   *
   * @param gr     grid record
   * @param lookup lookup table
   * @return name for the level
   */
  public String makeLevelName(GridRecord gr, GridTableLookup lookup) {
    return lookup.getLevelName(gr);
  }

  public String makeIntervalName() {
    // get information from timeCoord
    if (tcs.getConstantInterval() < 0)
      return " Mixed Intervals";
    else
      return tcs.getConstantInterval() + " " + tcs.getTimeUnit() + " Intervals";
  }

}


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

package ucar.nc2.dt;

import java.util.ArrayList;
import java.util.List;
import java.io.IOException;

import ucar.nc2.dataset.DatasetUrl;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.constants.FeatureType;
import ucar.nc2.dt.radial.*;

/**
 * Manager of factories for TypedDatasets.
 * NOTE: use ucar.nc2.ft.FeatureDatasetFactoryManager instead, starting with 4.0
 *
 * @deprecated
 * @author caron
 */
public class TypedDatasetFactory {

  static private List<Factory> transformList = new ArrayList<Factory>();
  static private boolean userMode = false;

  // search in the order added
  static {
    registerFactory(FeatureType.POINT, ucar.nc2.dt.point.UnidataPointObsDataset.class);
    registerFactory(FeatureType.STATION, ucar.nc2.dt.point.UnidataStationObsDataset.class);
    registerFactory(FeatureType.STATION, ucar.nc2.dt.point.UnidataStationObsMultidimDataset.class);
    //registerFactory(FeatureType.POINT, ucar.nc2.dt.point.DapperDataset.class);
    //registerFactory(FeatureType.STATION, ucar.nc2.dt.point.SequenceObsDataset.class);
    registerFactory(FeatureType.STATION, ucar.nc2.dt.point.UnidataStationObsDataset2.class);
    registerFactory(FeatureType.STATION, ucar.nc2.dt.point.NdbcDataset.class);
    registerFactory(FeatureType.STATION, ucar.nc2.dt.point.MadisStationObsDataset.class);
    registerFactory(FeatureType.STATION, ucar.nc2.dt.point.OldUnidataStationObsDataset.class);
    registerFactory(FeatureType.POINT, ucar.nc2.dt.point.OldUnidataPointObsDataset.class);

    registerFactory(FeatureType.RADIAL, NsslRadialAdapter.class);
    registerFactory(FeatureType.RADIAL, Dorade2RadialAdapter.class);
    registerFactory(FeatureType.RADIAL, Nexrad2RadialAdapter.class);
    registerFactory(FeatureType.RADIAL, NidsRadialAdapter.class);
    registerFactory(FeatureType.RADIAL, UF2RadialAdapter.class);
    registerFactory(FeatureType.RADIAL, CFRadialAdapter.class);

    registerFactory(FeatureType.TRAJECTORY, ucar.nc2.dt.trajectory.RafTrajectoryObsDataset.class);
    registerFactory(FeatureType.TRAJECTORY, ucar.nc2.dt.trajectory.UnidataTrajectoryObsDataset.class);
    registerFactory(FeatureType.TRAJECTORY, ucar.nc2.dt.trajectory.SimpleTrajectoryObsDataset.class);
    registerFactory(FeatureType.TRAJECTORY, ucar.nc2.dt.trajectory.Float10TrajectoryObsDataset.class);
    registerFactory(FeatureType.TRAJECTORY, ucar.nc2.dt.trajectory.ZebraClassTrajectoryObsDataset.class);
    registerFactory(FeatureType.TRAJECTORY, ucar.nc2.dt.trajectory.ARMTrajectoryObsDataset.class);

    // further calls to registerFactory are by the user
    userMode = true;
  }

   /**
    * Register a class that implements a TypedDatasetFactoryIF.
    * @param className name of class that implements TypedDatasetFactoryIF.
    * @param datatype  scientific data type
    * @throws ClassNotFoundException if loading error
    */
   static public void registerFactory( FeatureType datatype, String className) throws ClassNotFoundException {
     Class c = Class.forName( className);
     registerFactory( datatype, c);
   }

   /**
    * Register a class that implements a TypedDatasetFactoryIF.
    * @param datatype scientific data type
    * @param c class that implements TypedDatasetFactoryIF.
    */
  static public void registerFactory( FeatureType datatype, Class c) {
    if (!(TypedDatasetFactoryIF.class.isAssignableFrom( c)))
      throw new IllegalArgumentException("Class "+c.getName()+" must implement TypedDatasetFactoryIF");

    // fail fast - check newInstance works
    Object instance;
    try {
      instance = c.newInstance();
    } catch (InstantiationException e) {
      throw new IllegalArgumentException("CoordTransBuilderIF Class "+c.getName()+" cannot instantiate, probably need default Constructor");
    } catch (IllegalAccessException e) {
      throw new IllegalArgumentException("CoordTransBuilderIF Class "+c.getName()+" is not accessible");
    }

    // user stuff gets put at top
    if (userMode)
      transformList.add( 0, new Factory( datatype, c, (TypedDatasetFactoryIF) instance));
    else
      transformList.add( new Factory( datatype, c, (TypedDatasetFactoryIF)instance));

  }

  static private class Factory {
    FeatureType datatype;
    Class c;
    TypedDatasetFactoryIF instance;

    Factory(FeatureType datatype, Class c, TypedDatasetFactoryIF instance) {
      this.datatype = datatype;
      this.c = c;
      this.instance = instance;
    }
  }

  /**
   * Open a dataset as a TypedDataset.
   *
   * @param datatype open this kind of Typed Dataset; may be null, which means search all factories.
   *   If datatype is not null, only return correct TypedDataset (eg PointObsDataset for DataType.POINT).
   * @param location URL or file location of the dataset
   * @param task user may cancel
   * @param errlog place errors here, may not be null
   * @return a subclass of TypedDataset
   * @throws java.io.IOException on io error
   */
  static public TypedDataset open( FeatureType datatype, String location, ucar.nc2.util.CancelTask task, StringBuilder errlog) throws IOException {
    DatasetUrl durl = DatasetUrl.findDatasetUrl(location);
    NetcdfDataset ncd = NetcdfDataset.acquireDataset(durl, true, task);
    return open( datatype, ncd, task, errlog);
  }

  /**
   * Open a dataset as a TypedDataset.
   *
   * @param datatype open this kind of Typed Dataset; may be null, which means search all factories.
   *   If datatype is not null, only return correct TypedDataset (eg PointObsDataset for DataType.POINT).
   * @param ncd  the NetcdfDataset to wrap in a TypedDataset
   * @param task user may cancel
   * @param errlog place errors here, may not be null
   * @return a subclass of TypedDataset, or null if cant find
   * @throws java.io.IOException on io error
   */
  static public TypedDataset open( FeatureType datatype, NetcdfDataset ncd, ucar.nc2.util.CancelTask task, StringBuilder errlog) throws IOException {

    // look for a Factory that claims this dataset
    Class useClass = null;
    for (Factory fac : transformList) {
      if ((datatype != null) && (datatype != fac.datatype)) continue;

      if (fac.instance.isMine(ncd)) {
        useClass = fac.c;
        break;
      }
    }

    // Factory not found
    if (null == useClass) {

      // POINT is also a STATION
      if (datatype == FeatureType.POINT) {
        return open( FeatureType.STATION, ncd, task, errlog);
      }

      // if explicitly requested, give em a GridDataset even if no Grids
      if (datatype == FeatureType.GRID) {
        return null; // new ucar.nc2.dt.grid.GridDataset( ncd);   LOOK have to copy old  GridDataset to archive
      }

      if (null == datatype) {
        // if no datatype was requested, give em a GridDataset only if some Grids are found.
        ucar.nc2.dt.grid.GridDataset gds = new ucar.nc2.dt.grid.GridDataset( ncd);
        if (gds.getGrids().size() > 0)
          return null; // gds;    LOOK have to copy old  GridDataset to archive
      }

      errlog.append("**Failed to find Datatype Factory for= ").append(ncd.getLocation()).append(" datatype= ").append(datatype).append("\n");
      return null;
    }

    // get a new instance of the Factory class, for thread safety
    TypedDatasetFactoryIF builder = null;
    try {
      builder = (TypedDatasetFactoryIF) useClass.newInstance();
    } catch (InstantiationException e) {
      errlog.append(e.getMessage()).append("\n");
    } catch (IllegalAccessException e) {
      errlog.append(e.getMessage()).append("\n");
    }
    if (null == builder) {
      errlog.append("**Error on TypedDatasetFactory object from class= ").append(useClass.getName()).append("\n");
      return null;
    }

    return builder.open( ncd, task, errlog);
  }

}


package ucar.nc2.ogc.waterml;

import net.opengis.waterml.x20.DefaultTVPMeasurementMetadataDocument;
import net.opengis.waterml.x20.TVPDefaultMetadataPropertyType;
import ucar.nc2.VariableSimpleIF;

/**
 * Created by cwardgar on 2014/03/06.
 */
public abstract class NcTVPDefaultMetadataPropertyType {
    // wml2:Collection/wml2:observationMember/om:OM_Observation/om:result/wml2:MeasurementTimeseries/
    //         wml2:defaultPointMetadata
    public static TVPDefaultMetadataPropertyType initDefaultPointMetadata(
            TVPDefaultMetadataPropertyType defaultPointMetadata, VariableSimpleIF dataVar) {
        // wml2:DefaultTVPMeasurementMetadata
        DefaultTVPMeasurementMetadataDocument defaultTVPMeasurementMetadataDoc =
                DefaultTVPMeasurementMetadataDocument.Factory.newInstance();
        NcTVPMeasurementMetadataType.initDefaultTVPMeasurementMetadata(
                defaultTVPMeasurementMetadataDoc.addNewDefaultTVPMeasurementMetadata(), dataVar);
        defaultPointMetadata.set(defaultTVPMeasurementMetadataDoc);

        return defaultPointMetadata;
    }

    private NcTVPDefaultMetadataPropertyType() { }
}


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

package opendap.dap;

import java.io.*;

/**
 * Holds a OPeNDAP Byte value.
 *
 * @author jehamby
 * @version $Revision: 15901 $
 * @see BaseType
 */
public class DByte extends DPrimitive implements ClientIO {
    /**
     * Constructs a new DByte.
     */
    public DByte() {
        super();
    }

    /**
     * Constructs a new DByte with name n.
     *
     * @param n the name of the variable.
     */
    public DByte(String n) {
        super(n);
    }

    /**
     * The value of this DByte.
     */
    private byte val;

    /**
     * Get the current value as a byte.
     *
     * @return the current value.
     */
    public final byte getValue() {
        return val;
    }

    /**
     * Set the current value.
     *
     * @param newVal the new value.
     */
    public final void setValue(byte newVal) {
        val = newVal;
    }

    /**
     * Constructs a new BytePrimitiveVector.
     *
     * @return a new BytePrimitiveVector.
     */
    public PrimitiveVector newPrimitiveVector() {
        return new BytePrimitiveVector(this);
    }

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

    /**
     * Prints the value of the variable, with its declaration.  This
     * function is primarily intended for debugging OPeNDAP applications and
     * text-based clients such as geturl.
     *
     * @param os           the PrintWriter on which to print the value.
     * @param space        this value is passed to the printDecl method,
     *                     and controls the leading spaces of the output.
     * @param print_decl_p a boolean value controlling whether the
     *                     variable declaration is printed as well as the value.
     * @see BaseType#printVal(PrintWriter, String, boolean)
     */
    public void printVal(PrintWriter os, String space, boolean print_decl_p) {
        if (print_decl_p) {
            printDecl(os, space, false);
            os.println(" = " + (((int) val) & 0xFF) + ";");
        } else
            // convert to unsigned and print
            os.print(((int) val) & 0xFF);
    }

    /**
     * Reads data from a DataInputStream. This method is only used
     * on the client side of the OPeNDAP client/server connection.
     *
     * @param source   a DataInputStream to read from.
     * @param sv       the ServerVersion returned by the server.
     * @param statusUI the StatusUI object to use for GUI updates
     *                 and user cancellation notification (may be null).
     * @throws EOFException if EOF is found before the variable is completely
     *                      deserialized.
     * @throws IOException  thrown on any other InputStream exception.
     * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    public synchronized void deserialize(DataInputStream source,
                                         ServerVersion sv,
                                         StatusUI statusUI)
            throws IOException, EOFException {
        // throw away first three bytes (padding)
        byte unused;
        for (int i = 0; i < 3; i++) {
            unused = source.readByte();
        }
        // read fourth byte
        val = source.readByte();

        if (statusUI != null)
            statusUI.incrementByteCount(4);
    }

    /**
     * Writes data to a DataOutputStream. This method is used
     * primarily 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.
     */
    public void externalize(DataOutputStream sink) throws IOException {
        // print three null bytes (padding)
        for (int i = 0; i < 3; i++) {
            sink.writeByte(0);
        }
        sink.writeByte(val);
    }
}


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

import ucar.ma2.*;
import ucar.nc2.constants.AxisType;
import ucar.nc2.time.CalendarDate;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.util.Indent;
import ucar.nc2.util.NamedAnything;
import ucar.nc2.util.NamedObject;
import ucar.nc2.util.Optional;
import ucar.unidata.util.Format;

import javax.annotation.concurrent.Immutable;
import java.util.ArrayList;
import java.util.Formatter;
import java.util.Iterator;
import java.util.List;

/**
 * Coverage CoordAxis 1D case
 *
 * @author caron
 * @since 7/15/2015
 */
@Immutable
public class CoverageCoordAxis1D extends CoverageCoordAxis { // implements Iterable<Object> {

  // does this really describe all subset possibilities? what about RangeScatter, composite ??
  protected final Range range;            // for subset, tracks the indexes in the original
  protected final RangeComposite crange;

  public CoverageCoordAxis1D(CoverageCoordAxisBuilder builder) {
    super(builder);

    if (axisType == null && builder.dependenceType == DependenceType.independent)
      throw new IllegalArgumentException("independent axis must have type");

    // make sure range has axisType as the name
    String rangeName = (axisType != null) ? axisType.toString() : null;
    if (builder.range != null) {
      this.range = (rangeName != null) ? builder.range.setName(rangeName) : builder.range;
    } else {
      this.range = Range.make(rangeName, getNcoords());
    }
    this.crange = builder.crange;
  }

  @Override
  public RangeIterator getRangeIterator() {
    return crange != null ? crange : range;
  }

  @Override
  public Range getRange() {
    return range;
  }

  @Override
  public void toString(Formatter f, Indent indent) {
    super.toString(f, indent);
    f.format("%s range=%s isSubset=%s", indent, range, isSubset());
    f.format("%n");
  }

  @Override
  public String getSummary() {
    if (axisType != AxisType.RunTime)
      return super.getSummary();

    if (ncoords < 7) {
      Formatter f = new Formatter();
      for (int i = 0; i < ncoords; i++) {
        CalendarDate cd = makeDate(getCoordMidpoint(i));
        if (i > 0) f.format(", ");
        f.format("%s", cd);
      }
      return f.toString();
    }

    Formatter f = new Formatter();
    CalendarDate start = makeDate(getStartValue());
    f.format("start=%s", start);
    CalendarDate end = makeDate(getEndValue());
    f.format(", end=%s", end);
    f.format(" (npts=%d spacing=%s)", getNcoords(), getSpacing());

    return f.toString();
  }

  ///////////////////////////////////////////////////////////////////
  // Spacing

  public boolean isAscending() {
    loadValuesIfNeeded();
    switch (spacing) {
      case regularInterval:
      case regularPoint:
        return getResolution() > 0;

      case irregularPoint:
        return values[0] <= values[ncoords - 1];

      case contiguousInterval:
        return values[0] <= values[ncoords];

      case discontiguousInterval:
        return values[0] <= values[2 * ncoords - 1];
    }
    throw new IllegalStateException("unknown spacing" + spacing);
  }

  public double getCoordMidpoint(int index) {
    if (index < 0 || index >= getNcoords())
      throw new IllegalArgumentException("Index out of range=" + index);
    loadValuesIfNeeded();

    switch (spacing) {
      case regularPoint:
        return startValue + index * getResolution();

      case irregularPoint:
        return values[index];

      case regularInterval:
        return startValue + (index + .5) * getResolution();

      case contiguousInterval:
      case discontiguousInterval:
        return (getCoordEdge1(index) + getCoordEdge2(index)) / 2;
    }
    throw new IllegalStateException("Unknown spacing=" + spacing);
  }

  public double getCoordEdge1(int index) {
    if (index < 0 || index >= getNcoords())
      throw new IllegalArgumentException("Index out of range=" + index);
    loadValuesIfNeeded();

    switch (spacing) {
      case regularPoint:
        return startValue + (index - .5) * getResolution();

      case regularInterval:
        return startValue + index * getResolution();

      case irregularPoint:
        if (index > 0)
          return (values[index - 1] + values[index]) / 2;
        else
          return values[0] - (values[1] - values[0]) / 2;

      case contiguousInterval:
        return values[index];

      case discontiguousInterval:
        return values[2 * index];
    }
    throw new IllegalStateException("Unknown spacing=" + spacing);
  }

  public double getCoordEdge2(int index) {
    if (index < 0 || index >= getNcoords())
      throw new IllegalArgumentException("Index out of range=" + index);
    loadValuesIfNeeded();

    switch (spacing) {
      case regularPoint:
        if (index < 0 || index >= ncoords) throw new IllegalArgumentException("Index out of range " + index);
        return startValue + (index + .5) * getResolution();

      case regularInterval:
        return startValue + (index+1) * getResolution();

      case irregularPoint:
        if (index < ncoords - 1)
          return (values[index] + values[index + 1]) / 2;
        else
          return values[index] + (values[index] - values[index - 1]) / 2;

      case contiguousInterval:
        return values[index + 1];

      case discontiguousInterval:
        return values[2 * index + 1];
    }
    throw new IllegalStateException("Unknown spacing=" + spacing);
  }

  public double getCoordEdgeFirst() {
    return getCoordEdge1(0);
  }

  public double getCoordEdgeLast() {
    return getCoordEdge2(ncoords - 1);
  }

  @Override
  public Array getCoordsAsArray() {
    Array result;
    switch (dependenceType) {
      case scalar:
        result = Array.factory(getDataType(), new int[0]);
        break;
      default:
        result = Array.factory(getDataType(), new int[]{ncoords});
        break;
    }

    for (int i = 0; i < ncoords; i++)
      result.setDouble(i, getCoordMidpoint(i));
    return result;
  }

  @Override
  public Array getCoordBoundsAsArray() {
    Array result = Array.factory(getDataType(), new int[]{ncoords, 2});

    int count = 0;
    for (int i = 0; i < ncoords; i++) {
      result.setDouble(count++, getCoordEdge1(i));
      result.setDouble(count++, getCoordEdge2(i));
    }
    return result;
  }

  @Override
  public Optional<CoverageCoordAxis> subset(double minValue, double maxValue, int stride) throws InvalidRangeException {
    CoordAxisHelper helper = new CoordAxisHelper(this);
    Optional<CoverageCoordAxisBuilder> buildero = helper.subset(minValue, maxValue, stride);
    return !buildero.isPresent() ? Optional.empty(buildero.getErrorMessage()) : Optional.of(new CoverageCoordAxis1D(buildero.get()));
  }

  // CalendarDate, double[2], or Double
  public Object getCoordObject(int index) {
    if (axisType == AxisType.RunTime)
      return makeDate(getCoordMidpoint(index));
    if (isInterval())
      return new double[]{getCoordEdge1(index), getCoordEdge2(index)};
    return getCoordMidpoint(index);
  }

  public List<NamedObject> getCoordValueNames() {
    loadValuesIfNeeded();
    if (timeHelper != null)
      return timeHelper.getCoordValueNames(this);

    List<NamedObject> result = new ArrayList<>();
    for (int i = 0; i < ncoords; i++) {
      Object value = null;
      switch (spacing) {
        case regularPoint:
        case irregularPoint:
          value = Format.d(getCoordMidpoint(i), 3);
          break;

        case regularInterval:
        case contiguousInterval:
        case discontiguousInterval:
          value = new CoordInterval(getCoordEdge1(i), getCoordEdge2(i), 3);
          break;
      }
      result.add(new NamedAnything(value, value + " " + getUnits()));
    }

    return result;
  }

  @Override
  public CoverageCoordAxis copy() {
    return new CoverageCoordAxis1D(new CoverageCoordAxisBuilder(this));
  }

  @Override
  public Optional<CoverageCoordAxis> subset(SubsetParams params) {
    Optional<CoverageCoordAxisBuilder> buildero = subsetBuilder(params);
    return !buildero.isPresent() ? Optional.empty(buildero.getErrorMessage()) : Optional.of(new CoverageCoordAxis1D(buildero.get()));
  }

  // only for longitude, only for regular (do we need a subclass for longitude 1D coords ??
  public Optional<CoverageCoordAxis> subsetByIntervals(List<MAMath.MinMax> lonIntvs, int stride) {
    if (axisType != AxisType.Lon)
      return Optional.empty("subsetByIntervals only for longitude");
    if (!isRegular())
      return Optional.empty("subsetByIntervals only for regular longitude");

    CoordAxisHelper helper = new CoordAxisHelper(this);

    double start = Double.NaN;
    boolean first = true;
    List<RangeIterator> ranges = new ArrayList<>();
    for (MAMath.MinMax lonIntv : lonIntvs) {
      if (first) start = lonIntv.min;
      first = false;

      Optional<RangeIterator> opt = helper.makeRange(lonIntv.min, lonIntv.max, stride);
      if (!opt.isPresent())
        return Optional.empty(opt.getErrorMessage());
      ranges.add(opt.get());
    }

    try {
      RangeComposite compositeRange = new RangeComposite(AxisType.Lon.toString(), ranges);
      int npts = compositeRange.length();
      double end = start + npts * resolution;

      CoverageCoordAxisBuilder builder = new CoverageCoordAxisBuilder(this); // copy
      builder.subset(npts, start, end, resolution, null);
      builder.setRange(null);
      builder.setCompositeRange(compositeRange);

      return Optional.of(new CoverageCoordAxis1D(builder));
    } catch (InvalidRangeException e) {
      return Optional.empty(e.getMessage());
    }
  }

  public Optional<CoverageCoordAxis> subsetByIndex(Range range) {
    try {
      CoordAxisHelper helper = new CoordAxisHelper(this);
      CoverageCoordAxisBuilder builder = helper.subsetByIndex(range);
      return Optional.of(new CoverageCoordAxis1D(builder));
    } catch (InvalidRangeException e) {
      return Optional.empty(e.getMessage());
    }
  }

  // LOOK  incomplete handling of subsetting params
  protected Optional<CoverageCoordAxisBuilder> subsetBuilder(SubsetParams params) {
    if (params == null)
      return Optional.of(new CoverageCoordAxisBuilder(this));

    CoordAxisHelper helper = new CoordAxisHelper(this);

    switch (getAxisType()) {
      case GeoZ:
      case Pressure:
      case Height:
        Double dval = params.getVertCoord();
        if (dval != null)
          return Optional.of(helper.subsetClosest(dval));
        // use midpoint of interval LOOK may not always be unique
        double[] intv = params.getVertCoordIntv();
        if (intv != null)
          return Optional.of(helper.subsetClosest((intv[0]+intv[1])/2));

        double[] vertRange = params.getVertRange(); // used by WCS
        if (vertRange != null)
          return helper.subset(vertRange[0], vertRange[1], 1);

        // default is all
        break;

      case Ensemble:
        Double eval = params.getDouble(SubsetParams.ensCoord);
        if (eval != null) {
          return Optional.of(helper.subsetClosest(eval));
        }
        // default is all
        break;

      // x,y get seperately subsetted
      case GeoX:
      case GeoY:
      case Lat:
      case Lon:
        throw new IllegalArgumentException();
        // return null; // LOOK heres a case where null is "correct"

      case Time:
        if (params.isTrue(SubsetParams.timePresent))
          return Optional.of(helper.subsetLatest());

        CalendarDate date = (CalendarDate) params.get(SubsetParams.time);
        if (date != null)
          return Optional.of(helper.subsetClosest(date));

        Integer stride = (Integer) params.get(SubsetParams.timeStride);
        if (stride == null || stride < 0) stride = 1;

        CalendarDateRange dateRange = (CalendarDateRange) params.get(SubsetParams.timeRange);
        if (dateRange != null)
          return helper.subset(dateRange, stride);

        // If no time range or time point, a timeOffset can be used to specify the time point.
        /* CalendarDate timeOffsetDate = params.getTimeOffsetDate();
        if (timeOffsetDate != null) {
          return Optional.of(helper.subsetClosest(timeOffsetDate));
        } */

        // A time offset or time offset interval starts from the rundate of the offset
        Double timeOffset = params.getTimeOffset();
        CalendarDate runtime = params.getRunTime();
        if (timeOffset != null) {
          if (runtime != null) {
            date = makeDateInTimeUnits(runtime, timeOffset);
            return Optional.of(helper.subsetClosest(date));
          } else {
            return Optional.of(helper.subsetClosest(timeOffset));
          }
        }

        // If a time interval is sent, search for match.
        double[] timeOffsetIntv = params.getTimeOffsetIntv();
        if (timeOffsetIntv != null && runtime != null) {
          // double midOffset = (timeOffsetIntv[0] + timeOffsetIntv[1]) / 2;
          CalendarDate[] dateIntv = new CalendarDate[2];
          dateIntv[0] = makeDateInTimeUnits(runtime, timeOffsetIntv[0]);
          dateIntv[1] = makeDateInTimeUnits(runtime, timeOffsetIntv[1]);
          return Optional.of(helper.subsetClosest(dateIntv));
        }

        if (stride != 1)
          try {
            return Optional.of(helper.subsetByIndex(getRange().setStride(stride)));
          } catch (InvalidRangeException e) {
            return Optional.empty(e.getMessage());
          }

        // default is all
        break;

      case RunTime:
        CalendarDate rundate = (CalendarDate) params.get(SubsetParams.runtime);
        if (rundate != null)
          return Optional.of(helper.subsetClosest(rundate));

/*        CalendarDateRange rundateRange = (CalendarDateRange) params.get(SubsetParams.runtimeRange);
        if (rundateRange != null)
          return helper.subset(rundateRange, 1); */

        if (params.isTrue(SubsetParams.runtimeAll))
          break;

        // default is latest
        return Optional.of(helper.subsetLatest());

      case TimeOffset:
        Double oval = params.getDouble(SubsetParams.timeOffset);
        if (oval != null) {
          return Optional.of(helper.subsetClosest(oval));
        }

        // If a time interval is sent, search for match.
        timeOffsetIntv = params.getTimeOffsetIntv();
        if (timeOffsetIntv != null) {
          return Optional.of(helper.subsetClosest((timeOffsetIntv[0]+timeOffsetIntv[1])/2));
        }

        if (params.isTrue(SubsetParams.timeOffsetFirst)) {
          try {
            return Optional.of(helper.subsetByIndex(new Range(1)));
          } catch (InvalidRangeException e) {
            return Optional.empty(e.getMessage());
          }
        }
        // default is all
        break;
    }

    // otherwise return copy the original axis
    return Optional.of(new CoverageCoordAxisBuilder(this));
  }

  @Override
  public Optional<CoverageCoordAxis> subsetDependent(CoverageCoordAxis1D dependsOn) {
    CoverageCoordAxisBuilder builder;
    try {
      builder = new CoordAxisHelper(this).subsetByIndex(dependsOn.getRange()); // LOOK Other possible subsets?
    } catch (InvalidRangeException e) {
      return Optional.empty(e.getMessage());
    }
    return Optional.of(new CoverageCoordAxis1D(builder));
  }

 // @Override
  public Iterator<Object> iterator() {
    return new MyIterator();
  }

  // Look what about intervals ??
  private class MyIterator implements java.util.Iterator<Object> {
    private int current = 0;
    private int ncoords = getNcoords();

    public boolean hasNext() {
      return current < ncoords;
    }

    public Object next() {
      return getCoordMidpoint(current++);
    }
  }

}


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

package opendap.dap;

import java.io.*;

/**
 * A vector of ints.
 *
 * @author jehamby
 * @version $Revision: 15901 $
 * @see PrimitiveVector
 */
public class Int32PrimitiveVector extends PrimitiveVector
{
    /**
     * the array of int values.
     */
    private int vals[];

    /**
     * Constructs a new Int32PrimitiveVector.
     *
     * @param var the template BaseType to use.
     */
    public Int32PrimitiveVector(BaseType var) {
        super(var);
    }

    /**
     * Returns the number of elements in the array.
     *
     * @return the number of elements in the array.
     */
    public int getLength() {
        return vals.length;
    }

    /**
     * Sets the number of elements in the array.  Allocates a new primitive
     * array of the desired size.  Note that if this is called multiple times,
     * the old array and its contents will be lost.
     *

     * Only called inside of deserialize method or in derived
     * classes on server.
     *
     * @param len the number of elements in the array.
     */
    public void setLength(int len) {
        vals = new int[len];
    }

    /**
     * Return the i'th value as an int.
     *
     * @param i the index of the value to return.
     * @return the i'th value.
     */
    public final int getValue(int i) {
        return vals[i];
    }

    /**
     * Set the i'th value of the array.
     *
     * @param i      the index of the value to set.
     * @param newVal the new value.
     */
    public final void setValue(int i, int newVal) {
        vals[i] = newVal;
    }

    /**
     * Prints the value of all variables in this vector.  This
     * method is primarily intended for debugging OPeNDAP applications and
     * text-based clients such as geturl.
     *
     * @param os    the PrintWriter on which to print the value.
     * @param space this value is passed to the printDecl method,
     *              and controls the leading spaces of the output.
     * @see BaseType#printVal(PrintWriter, String, boolean)
     */
    public void printVal(PrintWriter os, String space) {
        int len = vals.length;
        for (int i = 0; i < len - 1; i++) {
            os.print(vals[i]);
            os.print(", ");
        }
        // print last value, if any, without trailing comma
        if (len > 0)
            os.print(vals[len - 1]);
    }

    /**
     * Prints the value of a single variable in this vector.
     * method is used by DArray's printVal method.
     *
     * @param os    the PrintWriter on which to print the value.
     * @param index the index of the variable to print.
     * @see DArray#printVal(PrintWriter, String, boolean)
     */
    public void printSingleVal(PrintWriter os, int index) {
        os.print(vals[index]);
    }

    /**
     * Reads data from a DataInputStream. This method is only used
     * on the client side of the OPeNDAP client/server connection.
     *
     * @param source   a DataInputStream to read from.
     * @param sv       The ServerVersion returned by the server.
     *                 (used by DSequence to determine which protocol version was
     *                 used).
     * @param statusUI The StatusUI object to use for GUI updates
     *                 and user cancellation notification (may be null).
     * @throws DataReadException when invalid data is read, or if the user
     *                           cancels the download.
     * @throws EOFException      if EOF is found before the variable is completely
     *                           deserialized.
     * @throws IOException       thrown on any other InputStream exception.
     * @see ClientIO#deserialize(DataInputStream, ServerVersion, StatusUI)
     */
    public synchronized void deserialize(DataInputStream source,
                                         ServerVersion sv,
                                         StatusUI statusUI)
            throws IOException, EOFException, DataReadException {
        for (int i = 0; i < vals.length; i++) {
            vals[i] = source.readInt();
            if (statusUI != null) {
                statusUI.incrementByteCount(4);
                if (statusUI.userCancelled())
                    throw new DataReadException("User cancelled");
            }
        }
    }

    /**
     * Writes data to a DataOutputStream. This method is used
     * primarily 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.
     */
    public void externalize(DataOutputStream sink) throws IOException {
        for (int i = 0; i < vals.length; i++) {
            sink.writeInt(vals[i]);
        }
    }

    /**
     * Write a subset of the data to a DataOutputStream.
     *
     * @param sink    a DataOutputStream to write to.
     * @param start  starting index (i=start)
     * @param stop   ending index (i<=stop)
     * @param stride index stride (i+=stride)
     * @throws IOException thrown on any OutputStream exception.
     */
    public void externalize(DataOutputStream sink, int start, int stop, int stride) throws IOException {
        for (int i = start; i <= stop; i += stride)
            sink.writeInt(vals[i]);
    }

    /**
     * Returns (a reference to) the internal storage for this PrimitiveVector
     * object.
     *
WARNING:

     * Because this method breaks encapsulation rules the user must beware!
     * If we (the OPeNDAP prgramming team) choose to change the internal
     * representation(s) of these types your code will probably break.
     *

     * This method is provided as an optimization to eliminate massive
     * copying of data.
     *
     * @return The internal array of ints.
     */
    public Object getInternalStorage() {
        return (vals);
    }

    /**
     * Set the internal storage for PrimitiveVector.
     *
WARNING:

     * Because this method breaks encapsulation rules the user must beware!
     * If we (the OPeNDAP prgramming team) choose to change the internal
     * representation(s) of these types your code will probably break.
     *

     * This method is provided as an optimization to eliminate massive
     * copying of data.
     */
    public void setInternalStorage(Object o) {
        vals = (int []) o;
    }

    /**
     * Create a new primitive vector using a subset of the data.
     *
     * @param start  starting index (i=start)
     * @param stop   ending index (i<=stop)
     * @param stride index stride (i+=stride)
     * @return new primitive vector, of type Int32PrimitiveVector.
     */
    public PrimitiveVector subset(int start, int stop, int stride) {
        Int32PrimitiveVector n = new Int32PrimitiveVector(getTemplate());
        stride = Math.max(stride, 1);
        stop = Math.max(start, stop);
        int length = 1 + (stop - start) / stride;
        n.setLength(length);

        int count = 0;
        for (int i = start; i <= stop; i += stride) {
            n.setValue(count, vals[i]);
            count++;
        }
        return n;
    }
/**
   * Prints the value of the variable, with its declaration.  This
   * function is primarily intended for debugging OPeNDAP applications and
   * text-based clients such as geturl.
   *
   * @param os           the PrintWriter on which to print the value.
   * @param space        this value is passed to the printDecl method,
   *                     and controls the leading spaces of the output.
   * @param print_decl_p a boolean value controlling whether the
   *                     variable declaration is printed as well as the value.
   */
   public void printVal(PrintWriter os, String space,
                                boolean print_decl_p) {}

    /**
     * Returns a clone of this Int32PrimitiveVector.
     * See DAPNode.cloneDag()
     *
     * @param map track previously cloned nodes
     * @return a clone of this Int32PrimitiveVector.
     */
    public DAPNode cloneDAG(CloneMap map)
        throws CloneNotSupportedException
    {
        Int32PrimitiveVector v = (Int32PrimitiveVector) super.cloneDAG(map);
        if (vals != null) {
            v.vals = new int[vals.length];
            System.arraycopy(vals, 0, v.vals, 0, vals.length);
        }
        return v;
    }

}


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

package ucar.nc2.iosp;

import java.io.IOException;

/**
 * Common header for netcdf3, netcdf4, hdf5, hdf4.
 *
 * @author dmh
 */

public class NCheader
{
    //////////////////////////////////////////////////
    // Constants

    // Constants for check_file_type
    static private final int MAGIC_NUMBER_LEN = 8;
    static private final long MAXHEADERPOS = 50000; // header's gotta be within this range

    static public final byte[] H5HEAD = {(byte) 0x89, 'H', 'D', 'F', '\r', '\n', (byte) 0x1a, '\n'};
    static public final byte[] H4HEAD = {(byte) 0x0e, (byte) 0x03, (byte) 0x13, (byte) 0x01};
    static public final byte[] CDF1HEAD = {(byte) 'C', (byte) 'D', (byte) 'F', (byte) 0x01};
    static public final byte[] CDF2HEAD = {(byte) 'C', (byte) 'D', (byte) 'F', (byte) 0x02};
    static public final byte[] CDF5HEAD = {(byte) 'C', (byte) 'D', (byte) 'F', (byte) 0x05};

    // These should match the constants in netcdf-c/include/netcdf.h
    static public final int NC_FORMAT_NETCDF3 = (1);
    static public final int NC_FORMAT_64BIT_OFFSET = (2);
    static public final int NC_FORMAT_NETCDF4 = (3);
    static public final int NC_FORMAT_64BIT_DATA = (5);

    // Extensions
    static public final int NC_FORMAT_HDF4 = (0x7005);

    // Aliases
    static public final int NC_FORMAT_CLASSIC = (NC_FORMAT_NETCDF3);
    static public final int NC_FORMAT_64BIT = (NC_FORMAT_64BIT_OFFSET);
    static public final int NC_FORMAT_CDF5 = (NC_FORMAT_64BIT_DATA);
    static public final int NC_FORMAT_HDF5 = (NC_FORMAT_NETCDF4);

    //////////////////////////////////////////////////
    // Static Methods

    /**
     * Figure out what kind of netcdf-related file we have.
     * Constraint: leave raf read pointer to point just after
     * the magic number.
     *
     * @param raf to test type
     * @return
     * @throws IOException
     */
    static public int
    checkFileType(ucar.unidata.io.RandomAccessFile raf)
            throws IOException
    {
        int format = 0;

        byte[] magic = new byte[MAGIC_NUMBER_LEN];

        // If this is not an HDF5 file, then the magic number is at
        // position 0; If it is an HDF5 file, then we need to search
        // forward for it.

        // Look for the relevant leading tag
        raf.seek(0);
        if(raf.readBytes(magic, 0, MAGIC_NUMBER_LEN) < MAGIC_NUMBER_LEN)
            return 0; // unknown
        // Some version of CDF
        int hdrlen = 0;
        hdrlen = CDF1HEAD.length; // all CDF headers are assumed to be same length
        format = 0;
        if(memequal(CDF1HEAD, magic, CDF1HEAD.length))
            format = NC_FORMAT_CLASSIC;
        else if(memequal(CDF2HEAD, magic, CDF2HEAD.length))
            format = NC_FORMAT_64BIT_OFFSET;
        else if(memequal(CDF5HEAD, magic, CDF5HEAD.length))
            format = NC_FORMAT_CDF5;
        else if(memequal(H4HEAD, magic, H4HEAD.length))
            format = NC_FORMAT_HDF4;
        if(format != 0) {
            raf.seek(hdrlen);
            return format;
        }
        // For HDF5, we need to search forward
        format = 0;
        long filePos = 0;
        long size = raf.length();
        while((filePos < size - 8) && (filePos < MAXHEADERPOS)) {
            boolean match;
            raf.seek(filePos);
            if(raf.readBytes(magic, 0, MAGIC_NUMBER_LEN) < MAGIC_NUMBER_LEN)
                return 0; // unknown
            // Test for HDF5
            if(memequal(H5HEAD, magic, H5HEAD.length)) {
                format = NC_FORMAT_HDF5;
                break;
            }
            filePos = (filePos == 0) ? 512 : 2 * filePos;
        }
        if(format != 0)
            raf.seek(filePos + H5HEAD.length);
        return format;
    }

    /**
     * Not quite memcmp
     *
     * @param b1
     * @param b2
     * @param len
     * @return
     */
    static boolean
    memequal(byte[] b1, byte[] b2, int len)
    {
        if(b1 == b2) return true;
        if(b1 == null || b2 == null) return false;
        if(b1.length < len || b2.length < len) return false;
        for(int i = 0; i < len; i++) {
            if(b1[i] != b2[i]) return false;
        }
        return true;
    }

    static public String
    formatName(int format)
    {
        switch (format) {
        case NCheader.NC_FORMAT_NETCDF3:
            return "netcdf-3";
        case NCheader.NC_FORMAT_64BIT_OFFSET:
            return "netcdf-3 64bit-offset";
        case NCheader.NC_FORMAT_NETCDF4:
            return "netcdf-4";
        case NCheader.NC_FORMAT_64BIT_DATA:
            return "netcdf-5";
        case NCheader.NC_FORMAT_HDF4:
            return "HDF-4";
        default:
            break;
        }
        return null;
    }
}


/////////////////////////////////////////////////////////////////////////////
// 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.ascii;

import java.io.PrintWriter;
import java.util.Enumeration;

import opendap.dap.*;

/**
 */
public class asciiArray extends DArray implements toASCII {

    private static boolean _Debug = false;

    /**
     * Constructs a new asciiArray.
     */
    public asciiArray() {
        this(null);
    }

    /**
     * Constructs a new asciiArray with name n.
     *
     * @param n the name of the variable.
     */
    public asciiArray(String n) {
        super(n);
    }

    /**
     * Returns a string representation of the variables value. This
     * is really foreshadowing functionality for Server types, but
     * as it may come in useful for clients it is added here. Simple
     * types (example: DFloat32) will return a single value. DConstuctor
     * and DVector types will be flattened. DStrings and DURL's will
     * have double quotes around them.
     *
     * @param addName is a flag indicating if the variable name should
     *                appear at the begining of the returned string.
     */
    public void toASCII(PrintWriter pw,
                        boolean addName,
                        String rootName,
                        boolean newLine) {

        if (_Debug) {
            System.out.println("asciiArray.toASCII(" + addName + ",'" + rootName + "')  getName(): " + getEncodedName());
            System.out.println("  PrimitiveVector size = " + getPrimitiveVector().getLength());
        }

        if (addName)
            pw.print("\n");

        int dims = numDimensions();
        int shape[] = new int[dims];
        int i = 0;

        for (Enumeration e = getDimensions(); e.hasMoreElements();) {
            DArrayDimension d = (DArrayDimension) e.nextElement();
            shape[i++] = d.getSize();
        }

        if (newLine)
            pw.print("\n");

    }

    public String toASCIIFlatName(String rootName) {

        StringBuilder s = new StringBuilder();
        if (rootName != null) {
            s.append(rootName);
            s.append(".");
            s.append(getEncodedName());
        } else {
            s.append(getEncodedName());
        }

        String s2 = "";
        PrimitiveVector pv = getPrimitiveVector();
        if (pv instanceof BaseTypePrimitiveVector) {
            BaseType bt = ((BaseTypePrimitiveVector) pv).getValue(0);
            if (bt instanceof DString) {
                int dims = numDimensions();
                int i = 0;
                for (Enumeration e = getDimensions(); e.hasMoreElements();) {
                    DArrayDimension d = (DArrayDimension) e.nextElement();
                    s.append("[");
                    s.append(d.getSize());
                    s.append("]");
                }
                s2 = s.toString();
            } else {
                s2 = ((toASCII) bt).toASCIIFlatName(s.toString());
            }
        } else {
            int dims = numDimensions();
            int i = 0;
            for (Enumeration e = getDimensions(); e.hasMoreElements();) {
                DArrayDimension d = (DArrayDimension) e.nextElement();
                s.append("[");
                s.append(d.getSize());
                s.append("]");
            }
            s2 = s.toString();
        }
        return (s2);
    }

    public String toASCIIAddRootName(PrintWriter pw, boolean addName, String rootName) {

        if (addName) {
            rootName = toASCIIFlatName(rootName);
            pw.print(rootName);
        }
        return (rootName);

    }

    /**
     * Print an array. This is a private member function.
     *
     * @param os     is the stream used for writing
     * @param index  is the index of VEC to start printing
     * @param dims   is the number of dimensions in the array
     * @param shape  holds the size of the dimensions of the array.
     * @param offset holds the current offset into the shape array.
     * @return the number of elements written.
     */
    private int asciiArray(PrintWriter os, boolean addName, String label, int index, int dims, int shape[],
                           int offset) {

        //os.println("\n\n");
        //os.println("\tdims:   " + dims);
        //os.println("\toffset: " + offset);
        //os.println("\tshape["+offset+"]: " + shape[offset]);
        //os.println("\tindex: " + index);
        //os.println("\n");

        if (dims == 1) {

            if (addName)
                os.print(label);

            for (int i = 0; i < shape[offset]; i++) {

                PrimitiveVector pv = getPrimitiveVector();

                if (pv instanceof BaseTypePrimitiveVector) {

                    BaseType bt = ((BaseTypePrimitiveVector) pv).getValue(index++);

                    if (i > 0) {
                        if (bt instanceof DString)
                            os.print(", ");
                        else
                            os.println("");
                    }
                    ((toASCII) bt).toASCII(os, false, null, false);

                } else {

                    if (i > 0)
                        os.print(", ");
                    pv.printSingleVal(os, index++);
                }

            }
            if (addName) os.print("\n");

            return index;
        } else {
            for (int i = 0; i < shape[offset]; i++) {
                StringBuilder s = new StringBuilder();
                s.append(label);
                s.append("[");
                s.append(i);
                s.append("]");
                if ((dims - 1) == 1)
                    s.append(", ");
                index = asciiArray(os, addName, s.toString(), index, dims - 1, shape, offset + 1);
            }
            return index;
        }
    }

}


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

package dap4.dap4lib;

import dap4.core.data.ChecksumMode;
import dap4.core.data.DSP;
import dap4.core.data.DataCursor;
import dap4.core.dmr.DapAttribute;
import dap4.core.dmr.DapDataset;
import dap4.core.dmr.DapNode;
import dap4.core.dmr.DapVariable;
import dap4.core.dmr.parser.DOM4Parser;
import dap4.core.dmr.parser.Dap4Parser;
import dap4.core.util.DapContext;
import dap4.core.util.DapException;
import dap4.core.util.DapUtil;
import org.xml.sax.SAXException;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Provide a superclass for DSPs.
 */

abstract public class AbstractDSP implements DSP
{
    static public boolean TESTING = false; /* Turned on by test programs */

    //////////////////////////////////////////////////
    // constants

    static protected final boolean DEBUG = false;
    static protected final boolean PARSEDEBUG = false;

    static public final boolean USEDOM = false;

    static protected final String DAPVERSION = "4.0";
    static protected final String DMRVERSION = "1.0";
    static protected final String DMRNS = "http://xml.opendap.org/ns/DAP/4.0#";

    // Define reserved attributes
    static public final String UCARTAGVLEN = "_edu.ucar.isvlen";
    static public final String UCARTAGOPAQUE = "_edu.ucar.opaque.size";
    static public final String UCARTAGORIGTYPE = "_edu.ucar.orig.type";
    static public final String UCARTAGUNLIMITED = "_edu.ucar.isunlimited";

    protected DapContext context = null;
    protected DapDataset dmr = null;
    protected String location = null;
    private ByteOrder order = null;
    private ChecksumMode checksummode = ChecksumMode.DAP;

    protected Map<DapVariable, DataCursor> variables = new HashMap<>();
    protected DataCursor rootcursor = null;

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

    public AbstractDSP()  /* must have a parameterless constructor */
    {
    }

    //////////////////////////////////////////////////
    // DSP Interface

    // Subclass defined

    /**
     * "open" a reference to a data source and return the DSP wrapper.
     *
     * @param location - Object that defines the data source
     * @return = wrapping dsp
     * @throws DapException
     */
    @Override
    abstract public AbstractDSP open(String location) throws DapException;

    /**
     * @throws IOException
     */
    abstract public void close() throws IOException;

    //////////////////////////////////////////////////
    // Implemented

    @Override
    public DataCursor
    getVariableData(DapVariable var) throws DapException
    {
        return this.variables.get(var);
    }

    @Override
    public DapContext getContext()
    {
        return this.context;
    }

    @Override
    public String
    getLocation()
    {
        return this.location;
    }

    @Override
    public AbstractDSP
    setLocation(String loc)
    {
        this.location = loc;
        return this;
    }

    @Override
    public DapDataset getDMR()
    {
        return this.dmr;
    }

    // DSP Extensions

    @Override
    public void setContext(DapContext context)
    {
        this.context = context;
        // Extract some things from the context
        Object o = this.context.get(Dap4Util.DAP4ENDIANTAG);
        if(o != null)
            setOrder((ByteOrder) o);
        o = this.context.get(Dap4Util.DAP4CSUMTAG);
        if(o != null)
            setChecksumMode(ChecksumMode.modeFor(o.toString()));
    }

    public void
    setDMR(DapDataset dmr)
    {
        this.dmr = dmr;
        if(getDMR() != null) {
            // Add some canonical attributes to the  <Dataset>
            getDMR().setDataset(getDMR());
            getDMR().setDapVersion(DAPVERSION);
            getDMR().setDMRVersion(DMRVERSION);
            getDMR().setNS(DMRNS);
        }
    }

    protected void
    setDataset(DapDataset dataset)
            throws DapException
    {
        this.dmr = dataset;
    }

    public ByteOrder
    getOrder()
    {
        return this.order;
    }

    public AbstractDSP
    setOrder(ByteOrder order)
    {
        this.order = order;
        return this;
    }

    public ChecksumMode
    getChecksumMode()
    {
        return this.checksummode;
    }

    public AbstractDSP
    setChecksumMode(ChecksumMode mode)
    {
        if(mode != null)
            this.checksummode = mode;
        return this;
    }

    public void
    addVariableData(DapVariable var, DataCursor cursor)
    {
        this.variables.put(var, cursor);
    }

    //////////////////////////////////////////////////
    // Utilities

    /**
     * It is common to want to parse a DMR text to a DapDataset,
     * so provide this utility.
     *
     * @param document the dmr to parse
     * @return the parsed dmr
     * @throws DapException on parse errors
     */

    protected DapDataset
    parseDMR(String document)
            throws DapException
    {
        // Parse the dmr
        Dap4Parser parser;
        //if(USEDOM)
        parser = new DOM4Parser(null);
        //else
        //    parser = new DOM4Parser(new DefaultDMRFactory());
        if(PARSEDEBUG)
            parser.setDebugLevel(1);
        try {
            if(!parser.parse(document))
                throw new DapException("DMR Parse failed");
        } catch (SAXException se) {
            throw new DapException(se);
        }
        if(parser.getErrorResponse() != null)
            throw new DapException("Error Response Document not supported");
        DapDataset result = parser.getDMR();
        processAttributes(result);
        return result;
    }

    /**
     * Walk the dataset tree and remove selected attributes
     * such as _Unsigned
     *
     * @param dataset
     */
    protected void
    processAttributes(DapDataset dataset)
            throws DapException
    {
        List<DapNode> nodes = dataset.getNodeList();
        for(DapNode node : nodes) {
            switch (node.getSort()) {
            case GROUP:
            case DATASET:
            case VARIABLE:
                Map<String, DapAttribute> attrs = node.getAttributes();
                if(attrs.size() > 0) {
                    List<DapAttribute> suppressed = new ArrayList<>();
                    for(DapAttribute dattr : attrs.values()) {
                        if(suppress(dattr.getShortName()))
                            suppressed.add(dattr);
                    }
                    for(DapAttribute dattr : suppressed) {
                        node.removeAttribute(dattr);
                    }
                }
                break;
            default:
                break; /*ignore*/
            }
        }
        // Try to extract the byte order
        getEndianAttribute(dataset);
    }

    /**
     * Some attributes that are added by the NetcdfDataset
     * need to be kept out of the DMR. This function
     * defines that set.
     *
     * @param attrname A non-escaped attribute name to be tested for suppression
     * @return true if the attribute should be suppressed, false otherwise.
     */

    protected boolean suppress(String attrname)
    {
        if(attrname.startsWith("_Coord")) return true;
        if(attrname.equals("_Unsigned"))
            return true;
        return false;
    }

    void
    getEndianAttribute(DapDataset dataset)
    {
        DapAttribute a = dataset.findAttribute(DapUtil.LITTLEENDIANATTRNAME);
        if(a == null)
            this.order = (ByteOrder.LITTLE_ENDIAN);
        else {
            Object v = a.getValues();
            int len = java.lang.reflect.Array.getLength(v);
            if(len == 0)
                this.order = (ByteOrder.nativeOrder());
            else {
                String onezero = java.lang.reflect.Array.get(v,0).toString();
                int islittle = 1;
                try {
                    islittle = Integer.parseInt(onezero);
                } catch (NumberFormatException e) {
                    islittle = 1;
                }
                if(islittle == 0)
                    this.order = (ByteOrder.BIG_ENDIAN);
                else
                    this.order = (ByteOrder.LITTLE_ENDIAN);
            }
        }
    }

    static public String
    printDMR(DapDataset dmr)
    {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        DMRPrinter printer = new DMRPrinter(dmr, pw);
        try {
            printer.print();
            pw.close();
            sw.close();
        } catch (IOException e) {
        }
        return sw.toString();
    }

}


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

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import java.text.SimpleDateFormat;
import java.text.ParseException;

/**
 * Convenience routines for parsing a String to produce a Date.
 *
 * @author edavis
 * @since Nov 29, 2005 4:53:46 PM
 */
public class DateFromString {
  static private final Logger logger = LoggerFactory.getLogger(DateFromString.class);

  /**
   * Parse the given date string (starting at the first numeric character)
   * using the given date format string (as described in
   * java.text.SimpleDateFormat) and return a Date.
   *
   * @param dateString the String to be parsed
   * @param dateFormatString the date format String
   * @return the Date that was parsed.
   */
  public static Date getDateUsingSimpleDateFormat( String dateString, String dateFormatString )
  {
    // Determine first numeric character in dateString and drop proceeding characters.
    int smallestIndex = dateString.length();
    if ( smallestIndex == 0 ) return null;
    for ( int i = 0; i < 10; i++ )
    {
      int curIndex = dateString.indexOf( String.valueOf( i ) );
      if ( curIndex != -1 && smallestIndex > curIndex )
        smallestIndex = curIndex;
    }

    return getDateUsingCompleteDateFormatWithOffset( dateString, dateFormatString, smallestIndex );
  }

  /**
   * Parse the given date string, starting at a position given by the offset of the demark character in the dateFormatString.
   * The rest of the dateFormatString is the date format string (as described in java.text.SimpleDateFormat).
   * <pre> Example:
   *   dateString =        wrfout_d01_2006-07-06_080000.nc
   *   dateFormatString = wrfout_d01_#yyyy-MM-dd_HHmm
   * </pre>
   * This simply counts over "wrfout_d01_" number of chars in dateString, then applies the remaining dateFormatString.
   *
   * @param dateString the String to be parsed
   * @param dateFormatString the date format String
   * @param demark the demarkation character
   * @return the Date that was parsed.
   */
  public static Date getDateUsingDemarkatedCount( String dateString, String dateFormatString, char demark )
  {
    // the position of the demark char is where to start parsing the dateString
    int pos1 = dateFormatString.indexOf( demark);

    // the rest of the dateFormatString is the SimpleDateFormat
    dateFormatString = dateFormatString.substring( pos1+1);

    return getDateUsingCompleteDateFormatWithOffset( dateString, dateFormatString, pos1 );
  }

  /**
   * Parse the given date string (between the demarcation characters)
   * using the given date format string (as described in
   * java.text.SimpleDateFormat) and return a Date.
   * <pre>
   * Example:
   *  dateString =  /data/anything/2006070611/wrfout_d01_2006-07-06_080000.nc
   *  dateFormatString =                    #wrfout_d01_#yyyy-MM-dd_HHmm
   *  would extract the date 2006-07-06T08:00
   *
   *  dateString =  /data/anything/2006070611/wrfout_d01_2006-07-06_080000.nc
   *  dateFormatString =          yyyyMMddHH#/wrfout_d01_#
   *  would extract the date 2006-07-06T11:00
   * </pre>
   *
   * @param dateString the String to be parsed
   * @param dateFormatString the date format String
   * @param demark the demarkation character
   * @return the Date that was parsed.
   */
  public static Date getDateUsingDemarkatedMatch( String dateString, String dateFormatString, char demark )
  {
    // extract the match string
    int pos1 = dateFormatString.indexOf( demark);
    int pos2 = dateFormatString.indexOf( demark, pos1+1);
    if ((pos1 < 0) || (pos2 < 0)) {
      logger.error("Must delineate Date between 2 '#' chars, dateFormatString = '"+ dateFormatString+"'", new Throwable());
      return null;
    }
    String match = dateFormatString.substring(pos1+1, pos2);

    int pos3 = dateString.indexOf(match);
    if (pos3 < 0) return null;

    if (pos1 > 0) {  // pos1 > 0, date is before the match: "yyyyMMddHH#/wrfout_d01_#"
      dateFormatString = dateFormatString.substring(0, pos1);
      dateString = dateString.substring(pos3-dateFormatString.length(), pos3);

    }  else {        // pos1 == 0, date is after the match: "#wrfout_d01_#yyyy-MM-dd_HHmm"
      dateFormatString = dateFormatString.substring(pos2+1);
      dateString = dateString.substring(pos3+match.length());
    }

    // any leading or trailing "." in the dateFormatString means trim
    int posDot1 = 0;
    while (dateFormatString.charAt(posDot1) == '.')
      posDot1++;
    int posDot2 = dateFormatString.length();
    while (dateFormatString.charAt(posDot2-1) == '.')
      posDot2--;
    if (posDot1 != 0 || posDot2 != dateFormatString.length()) {
      dateFormatString = dateFormatString.substring(posDot1, posDot2);
      dateString = dateString.substring(posDot1, posDot2);
    }

    return getDateUsingCompleteDateFormatWithOffset( dateString, dateFormatString, 0 );
  }

  public static Date getDateUsingDemarkatedMatchOld( String dateString, String dateFormatString, char demark )
  {
    // extract the match string
    int pos1 = dateFormatString.indexOf( demark);
    int pos2 = dateFormatString.indexOf( demark, pos1+1);
    if ((pos1 < 0) || (pos2 < 0)) {
      logger.error("Must delineate Date between 2 '#' chars, dateFormatString = '"+ dateFormatString+"'", new Throwable());
      return null;
    }
    String match = dateFormatString.substring(pos1+1, pos2);

    int pos3 = dateString.indexOf(match);
    if (pos3 < 0) return null;

    if (pos1 > 0) {  // pos1 > 0, date is before the match: "yyyyMMddHH#/wrfout_d01_#"
      dateFormatString = dateFormatString.substring(0, pos1);
      dateString = dateString.substring(pos3-dateFormatString.length(), pos3);

    }  else {        // pos1 == 0, date is after the match: "#wrfout_d01_#yyyy-MM-dd_HHmm"
      dateFormatString = dateFormatString.substring(pos2+1);
      dateString = dateString.substring(pos3+match.length());
    }

    return getDateUsingCompleteDateFormatWithOffset( dateString, dateFormatString, 0 );
  }

  public static Double getHourUsingDemarkatedMatch( String hourString, String formatString, char demark )
  {
    // extract the match string
    int pos1 = formatString.indexOf( demark);
    int pos2 = formatString.indexOf( demark, pos1+1);
    if ((pos1 < 0) || (pos2 < 0)) return null;
    String match = formatString.substring(pos1+1, pos2);

    // where does it live in the hour string ?
    int pos3 = hourString.indexOf(match);
    if (pos3 < 0) return null;

    // for now, just match the number of chars
    if (pos1 > 0) {
      hourString = hourString.substring(pos3-pos1, pos3);
    }  else {
      int len = formatString.length() - pos2 - 1;
      int start = pos3 + match.length();
      hourString = hourString.substring(start, start+len);
    }

    return Double.valueOf( hourString);
  }

  /**
   * Parse the given date string using the given date format string (as
   * described in java.text.SimpleDateFormat) and return a Date.
   *
   * @param dateString the String to be parsed
   * @param dateFormatString the date format String
   * @return the Date that was parsed.
   */
  public static Date getDateUsingCompleteDateFormat( String dateString, String dateFormatString )
  {
    return getDateUsingCompleteDateFormatWithOffset( dateString, dateFormatString, 0 );
  }

  /**
   * Parse the given date string (starting at the given startIndex)  using the
   * given date format string (as described in java.text.SimpleDateFormat) and
   * return a Date. Assumes TimeZone is GMT.
   *
   * @param dateString the String to be parsed
   * @param dateFormatString the date format String
   * @param startIndex the index at which to start parsing the date string
   * @return the Date that was parsed.
   */
  public static Date getDateUsingCompleteDateFormatWithOffset( String dateString, String dateFormatString, int startIndex )
  {
    try {
      SimpleDateFormat dateFormat = new SimpleDateFormat( dateFormatString, Locale.US );
      dateFormat.setTimeZone( TimeZone.getTimeZone( "GMT" ) );

      // We have to cut off the dateString, so that it doesnt grab extra characters.
      // eg  new SimpleDateFormat("yyyyMMdd_HH").parse("20061129_06") -> 2006-12-24T00:00:00Z (WRONG!)
      String s;
      if (startIndex + dateFormatString.length() <= dateString.length())
        s = dateString.substring( startIndex, startIndex + dateFormatString.length());
      else
        s = dateString;

      Date result = dateFormat.parse( s );
      if (result == null)
        throw new RuntimeException("SimpleDateFormat bad ="+dateFormatString+" working on ="+s);
      return result;

    } catch (ParseException e) {
      throw new RuntimeException("SimpleDateFormat = "+dateFormatString+" fails on "+ dateString+ " ParseException:"+e.getMessage());
    } catch (IllegalArgumentException e) {
      throw new RuntimeException("SimpleDateFormat = "+dateFormatString+" fails on "+ dateString+ " IllegalArgumentException:"+e.getMessage());
    }
  }

  /**
   * Use regular expression capture group replacement to construct a date
   * string and return the Date that is obtained by parseing the constructed
   * date string using the date format string "yyyy-MM-dd'T'HH:mm".
   *
   * The date string is constructed by matching the given dateString against
   * the given regular expression matchPattern and then using the capture
   * groups from the match to replace the capture group references, i.e., "$n", where n is an
   * integer.
   *
   * @param dateString the String to be parsed
   * @param matchPattern the regular expression String on which to match.
   * @param substitutionPattern the String to use in the capture group replacement.
   * @return the calculated Date
   */
  public static Date getDateUsingRegExp( String dateString,
                                         String matchPattern,
                                         String substitutionPattern )
  {
    String dateFormatString = "yyyy-MM-dd'T'HH:mm";
    return getDateUsingRegExpAndDateFormat( dateString,
                                            matchPattern,
                                            substitutionPattern,
                                            dateFormatString );
  }

  /**
   * The same as getDateUsingRegExp() except the date format string to be used
   * must be specified.
   *
   * @param dateString the String to be parsed
   * @param matchPattern the regular expression String on which to match.
   * @param substitutionPattern the String to use in the capture group replacement.
   * @param dateFormatString the date format string to use in the parsing of the date string.
   * @return the calculated Date
   */
  public static Date getDateUsingRegExpAndDateFormat( String dateString,
                                                      String matchPattern,
                                                      String substitutionPattern,
                                                      String dateFormatString )
  {
    // Match the given date string against the regular expression.
    java.util.regex.Pattern pattern = java.util.regex.Pattern.compile( matchPattern );
    java.util.regex.Matcher matcher = pattern.matcher( dateString );
    if ( ! matcher.matches() )
    {
      return null;
    }

    // Build date string to use with date format string by
    // substituting the capture groups into the substitution pattern.
    StringBuffer dateStringFormatted = new StringBuffer();
    matcher.appendReplacement( dateStringFormatted, substitutionPattern );
    if ( dateStringFormatted.length() == 0 )
    {
      return null;
    }

    return getDateUsingCompleteDateFormat( dateStringFormatted.toString(), dateFormatString );
  }

}